@gajae-code/coding-agent 0.8.2 → 0.9.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 (179) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/types/append-only-mode.d.ts +31 -0
  3. package/dist/types/capability/rule.d.ts +8 -0
  4. package/dist/types/cli/fixture-report.d.ts +20 -0
  5. package/dist/types/cli/fixture-report.test.d.ts +1 -0
  6. package/dist/types/cli/nofile-limit.d.ts +11 -0
  7. package/dist/types/cli/update-cli.d.ts +22 -0
  8. package/dist/types/config/settings-schema.d.ts +105 -1
  9. package/dist/types/discovery/index.d.ts +1 -0
  10. package/dist/types/export/html/template.generated.d.ts +1 -1
  11. package/dist/types/export/ttsr.d.ts +14 -3
  12. package/dist/types/gjc-runtime/deep-interview-state.d.ts +5 -2
  13. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +7 -0
  14. package/dist/types/gjc-runtime/state-writer.d.ts +3 -0
  15. package/dist/types/gjc-runtime/team-runtime.d.ts +26 -0
  16. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
  17. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +119 -13
  18. package/dist/types/goals/runtime.d.ts +1 -0
  19. package/dist/types/hooks/native-skill-hook.d.ts +12 -0
  20. package/dist/types/modes/components/assistant-message.d.ts +5 -1
  21. package/dist/types/modes/components/status-line.d.ts +9 -0
  22. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  23. package/dist/types/modes/controllers/event-controller.d.ts +5 -0
  24. package/dist/types/modes/interactive-mode.d.ts +2 -0
  25. package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
  26. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
  27. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
  28. package/dist/types/modes/types.d.ts +1 -0
  29. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  30. package/dist/types/notifications/config.d.ts +6 -0
  31. package/dist/types/notifications/telegram-daemon.d.ts +8 -0
  32. package/dist/types/notifications/threaded-render.d.ts +6 -0
  33. package/dist/types/sdk.d.ts +3 -0
  34. package/dist/types/session/agent-session.d.ts +43 -1
  35. package/dist/types/session/file-mention-pruning.d.ts +8 -0
  36. package/dist/types/session/messages.d.ts +4 -0
  37. package/dist/types/session/session-manager.d.ts +17 -1
  38. package/dist/types/skill-state/active-state.d.ts +18 -1
  39. package/dist/types/system-prompt.d.ts +18 -2
  40. package/dist/types/task/executor.d.ts +1 -0
  41. package/dist/types/task/fork-context-budget.d.ts +7 -0
  42. package/dist/types/task/token-log.d.ts +30 -0
  43. package/dist/types/task/token-log.test.d.ts +1 -0
  44. package/dist/types/tools/bisect.d.ts +81 -0
  45. package/dist/types/tools/index.d.ts +1 -0
  46. package/dist/types/tools/search.d.ts +2 -2
  47. package/dist/types/utils/file-mentions.d.ts +14 -0
  48. package/dist/types/utils/git.d.ts +32 -0
  49. package/dist/types/web/scrapers/youtube.d.ts +8 -0
  50. package/package.json +7 -7
  51. package/src/append-only-mode.ts +58 -0
  52. package/src/autoresearch/prompt.md +1 -1
  53. package/src/autoresearch/tools/log-experiment.ts +1 -1
  54. package/src/capability/rule.ts +9 -0
  55. package/src/cli/config-cli.ts +27 -3
  56. package/src/cli/fixture-report.test.ts +230 -0
  57. package/src/cli/fixture-report.ts +171 -0
  58. package/src/cli/nofile-limit.ts +48 -0
  59. package/src/cli/update-cli.ts +85 -9
  60. package/src/cli.ts +26 -0
  61. package/src/commands/contribution-prep.ts +1 -1
  62. package/src/config/settings-schema.ts +68 -1
  63. package/src/defaults/gjc/skills/deep-interview/SKILL.md +12 -13
  64. package/src/defaults/gjc/skills/ralplan/SKILL.md +30 -93
  65. package/src/defaults/gjc/skills/team/SKILL.md +16 -13
  66. package/src/defaults/gjc/skills/ultragoal/SKILL.md +59 -4
  67. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
  68. package/src/defaults/gjc-defaults.ts +9 -0
  69. package/src/discovery/helpers.ts +10 -0
  70. package/src/discovery/index.ts +1 -0
  71. package/src/export/html/template.generated.ts +1 -1
  72. package/src/export/html/template.js +60 -2
  73. package/src/export/ttsr.ts +96 -18
  74. package/src/extensibility/extensions/runner.ts +181 -219
  75. package/src/extensibility/gjc-plugins/runtime-adapters.ts +156 -44
  76. package/src/gjc-runtime/deep-interview-state.ts +31 -2
  77. package/src/gjc-runtime/session-state-sidecar.ts +208 -13
  78. package/src/gjc-runtime/state-writer.ts +15 -0
  79. package/src/gjc-runtime/team-runtime.ts +164 -15
  80. package/src/gjc-runtime/ultragoal-guard.ts +55 -66
  81. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +315 -0
  82. package/src/gjc-runtime/ultragoal-runtime.ts +1593 -143
  83. package/src/gjc-runtime/workflow-manifest.generated.json +113 -2
  84. package/src/gjc-runtime/workflow-manifest.ts +74 -1
  85. package/src/goals/runtime.ts +10 -2
  86. package/src/hindsight/backend.ts +1 -3
  87. package/src/hooks/native-skill-hook.ts +95 -4
  88. package/src/internal-urls/docs-index.generated.ts +14 -5
  89. package/src/main.ts +43 -2
  90. package/src/memories/index.ts +12 -1
  91. package/src/modes/components/assistant-message.ts +241 -82
  92. package/src/modes/components/custom-editor.ts +1 -0
  93. package/src/modes/components/status-line.ts +167 -54
  94. package/src/modes/components/tool-execution.ts +44 -19
  95. package/src/modes/controllers/command-controller.ts +39 -2
  96. package/src/modes/controllers/event-controller.ts +66 -47
  97. package/src/modes/controllers/input-controller.ts +1 -1
  98. package/src/modes/interactive-mode.ts +9 -1
  99. package/src/modes/rpc/rpc-mode.ts +29 -3
  100. package/src/modes/rpc/rpc-types.ts +9 -0
  101. package/src/modes/shared/agent-wire/command-validation.ts +4 -0
  102. package/src/modes/shared/agent-wire/event-contract.ts +22 -0
  103. package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
  104. package/src/modes/shared/agent-wire/scopes.ts +1 -0
  105. package/src/modes/types.ts +1 -0
  106. package/src/modes/utils/ui-helpers.ts +17 -4
  107. package/src/notifications/config.ts +16 -4
  108. package/src/notifications/index.ts +78 -5
  109. package/src/notifications/telegram-daemon-cli.ts +2 -2
  110. package/src/notifications/telegram-daemon-control.ts +2 -2
  111. package/src/notifications/telegram-daemon.ts +85 -8
  112. package/src/notifications/threaded-render.ts +17 -1
  113. package/src/prompts/agents/architect.md +19 -10
  114. package/src/prompts/agents/critic.md +16 -18
  115. package/src/prompts/agents/executor.md +14 -11
  116. package/src/prompts/agents/planner.md +18 -13
  117. package/src/prompts/ci-green-request.md +3 -8
  118. package/src/prompts/goals/goal-continuation.md +0 -5
  119. package/src/prompts/goals/goal-mode-active.md +0 -5
  120. package/src/prompts/system/agent-creation-architect.md +2 -2
  121. package/src/prompts/system/plan-mode-active.md +1 -1
  122. package/src/prompts/system/plan-mode-subagent.md +3 -3
  123. package/src/prompts/system/project-prompt.md +0 -13
  124. package/src/prompts/system/subagent-system-prompt.md +3 -2
  125. package/src/prompts/system/system-prompt.md +26 -19
  126. package/src/prompts/system/volatile-project-context.md +10 -0
  127. package/src/prompts/system/web-search.md +4 -7
  128. package/src/prompts/tools/apply-patch.md +6 -0
  129. package/src/prompts/tools/ast-edit.md +1 -0
  130. package/src/prompts/tools/bash.md +1 -1
  131. package/src/prompts/tools/bisect.md +22 -0
  132. package/src/prompts/tools/browser.md +1 -1
  133. package/src/prompts/tools/cron.md +0 -2
  134. package/src/prompts/tools/eval.md +2 -2
  135. package/src/prompts/tools/find.md +1 -1
  136. package/src/prompts/tools/github.md +6 -6
  137. package/src/prompts/tools/goal.md +1 -3
  138. package/src/prompts/tools/image-gen.md +12 -5
  139. package/src/prompts/tools/irc.md +5 -4
  140. package/src/prompts/tools/job.md +2 -2
  141. package/src/prompts/tools/lsp.md +1 -1
  142. package/src/prompts/tools/monitor.md +2 -2
  143. package/src/prompts/tools/patch.md +2 -2
  144. package/src/prompts/tools/replace.md +2 -21
  145. package/src/prompts/tools/rewind.md +1 -1
  146. package/src/prompts/tools/search-tool-bm25.md +1 -11
  147. package/src/prompts/tools/search.md +1 -1
  148. package/src/prompts/tools/skill.md +3 -2
  149. package/src/prompts/tools/ssh.md +2 -1
  150. package/src/prompts/tools/subagent.md +2 -2
  151. package/src/prompts/tools/task.md +4 -5
  152. package/src/prompts/tools/todo-write.md +1 -1
  153. package/src/prompts/tools/vim.md +1 -0
  154. package/src/prompts/tools/web-search.md +2 -0
  155. package/src/prompts/tools/write.md +8 -2
  156. package/src/sdk.ts +32 -22
  157. package/src/session/agent-session.ts +732 -107
  158. package/src/session/file-mention-pruning.ts +64 -0
  159. package/src/session/messages.ts +4 -0
  160. package/src/session/session-manager.ts +59 -2
  161. package/src/skill-state/active-state.ts +141 -5
  162. package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
  163. package/src/slash-commands/builtin-registry.ts +65 -0
  164. package/src/system-prompt.ts +57 -16
  165. package/src/task/executor.ts +125 -20
  166. package/src/task/fork-context-advisory.ts +2 -7
  167. package/src/task/fork-context-budget.ts +9 -0
  168. package/src/task/index.ts +4 -3
  169. package/src/task/output-manager.ts +4 -2
  170. package/src/task/token-log.test.ts +188 -0
  171. package/src/task/token-log.ts +179 -0
  172. package/src/tools/bisect.ts +367 -0
  173. package/src/tools/index.ts +3 -0
  174. package/src/tools/output-meta.ts +116 -4
  175. package/src/tools/search.ts +8 -2
  176. package/src/tools/skill.ts +15 -0
  177. package/src/utils/file-mentions.ts +39 -7
  178. package/src/utils/git.ts +73 -0
  179. package/src/web/scrapers/youtube.ts +24 -12
@@ -33,8 +33,8 @@ import { registerAskAnswerSource } from "../tools/ask-answer-registry";
33
33
  import { registerTelegramFileSink } from "./attachment-registry";
34
34
  import {
35
35
  getNotificationConfig,
36
- isGloballyConfigured,
37
36
  isSessionNotificationsEnabled,
37
+ isTelegramConfigured,
38
38
  type NotificationConfig,
39
39
  sessionTag,
40
40
  } from "./config";
@@ -350,6 +350,12 @@ interface SessionRuntime {
350
350
  /** Assistant text already flushed before an ask this turn (turn-scoped dedupe
351
351
  * so turn_end does not re-emit the pre-ask lead-in). Reset each turn. */
352
352
  preAskFlushedText?: string;
353
+ /** Live streaming: opt-in flag, monotonic per-turn ref, and emit throttle state. */
354
+ stream: boolean;
355
+ turnSeq?: number;
356
+ liveRef?: string;
357
+ lastLiveAt?: number;
358
+ lastLiveText?: string;
353
359
  /** Cancels the postmortem cleanup that emits `session_closed` on process teardown. */
354
360
  cancelPostmortemCleanup: () => void;
355
361
  }
@@ -383,6 +389,33 @@ export function notificationsEnabled(): boolean {
383
389
  return process.env.GJC_NOTIFICATIONS === "1" || Boolean(process.env.GJC_NOTIFICATIONS_TOKEN);
384
390
  }
385
391
 
392
+ // Live streaming (opt-in): emit throttled non-finalized `turn_stream` frames as
393
+ // the assistant message streams so remote clients can edit ONE message live. The
394
+ // finalized frame (turn_end) carries the same messageRef and stays authoritative,
395
+ // so a dropped live frame self-heals. Off unless GJC_NOTIFICATIONS_STREAM=1.
396
+ function streamingEnabled(): boolean {
397
+ return process.env.GJC_NOTIFICATIONS_STREAM === "1";
398
+ }
399
+ function streamIntervalMs(): number {
400
+ return Math.max(200, Number(process.env.GJC_NOTIFICATIONS_STREAM_INTERVAL_MS) || 500);
401
+ }
402
+ // Max chars of a turn's assistant text carried by the FINALIZED turn_stream (and
403
+ // the pre-ask capture). Default 3500 keeps the mirror a glanceable per-turn
404
+ // summary; a client that splits long messages (the Telegram daemon does so via
405
+ // splitTelegramHtml, scheduling each chunk through the shared rate-limit pool so
406
+ // the fan-out never bypasses the per-chat limit) can raise it with
407
+ // GJC_NOTIFICATIONS_TURN_MAX to deliver full turns. The value is clamped to a
408
+ // finite [280, TURN_TEXT_MAX_CEILING] range: a non-finite or non-positive env
409
+ // (unset, NaN, Infinity, <= 0) falls back to the default, so the cap can never
410
+ // be unbounded. Live frames are intentionally NOT raised — they stay one
411
+ // editable preview message rather than fanning a long in-progress turn across
412
+ // sends.
413
+ const TURN_TEXT_MAX_CEILING = 40_000;
414
+ function turnTextMax(): number {
415
+ const raw = Number(process.env.GJC_NOTIFICATIONS_TURN_MAX);
416
+ if (!Number.isFinite(raw) || raw <= 0) return 3500;
417
+ return Math.min(TURN_TEXT_MAX_CEILING, Math.max(280, raw));
418
+ }
386
419
  function resolveSettings(settingsOverride?: Settings): ResolvedSettings {
387
420
  if (settingsOverride)
388
421
  return { settings: settingsOverride, cfg: getNotificationConfig(settingsOverride), settingsAvailable: true };
@@ -708,6 +741,7 @@ export function createNotificationsExtension(api: ExtensionAPI, options: { setti
708
741
  cancelPostmortemCleanup: () => {},
709
742
  redact,
710
743
  verbosity,
744
+ stream: streamingEnabled(),
711
745
  sessionTag: tag,
712
746
  busy: false,
713
747
  pendingInbound: new Set<number>(),
@@ -724,7 +758,7 @@ export function createNotificationsExtension(api: ExtensionAPI, options: { setti
724
758
  });
725
759
  logger.info(`notifications: serving session ${id} at ${endpoint.url} (unattended=${unattended})`);
726
760
 
727
- if (settingsAvailable && settings && isGloballyConfigured(cfg)) {
761
+ if (settingsAvailable && settings && isTelegramConfigured(cfg)) {
728
762
  try {
729
763
  await ensureTelegramDaemonRunning({ settings, cwd: ctx.cwd, sessionId: id });
730
764
  } catch (e) {
@@ -1063,7 +1097,15 @@ export function createNotificationsExtension(api: ExtensionAPI, options: { setti
1063
1097
  if (!text || text === rt.preAskFlushedText) return;
1064
1098
  rt.preAskFlushedText = text;
1065
1099
  try {
1066
- rt.server.pushFrame(JSON.stringify({ type: "turn_stream", sessionId: id, phase: "finalized", text }));
1100
+ rt.server.pushFrame(
1101
+ JSON.stringify({
1102
+ type: "turn_stream",
1103
+ sessionId: id,
1104
+ phase: "finalized",
1105
+ text,
1106
+ ...(rt.liveRef ? { messageRef: rt.liveRef } : {}),
1107
+ }),
1108
+ );
1067
1109
  } catch (e) {
1068
1110
  logger.warn(`notifications: pushFrame (turn) failed: ${String(e)}`);
1069
1111
  }
@@ -1087,12 +1129,43 @@ export function createNotificationsExtension(api: ExtensionAPI, options: { setti
1087
1129
  const id = sessionId(ctx);
1088
1130
  const rt = runtimes.get(id);
1089
1131
  if (!rt) return;
1090
- const text = rt.redact ? undefined : summaryFromMessage(event.message, 3500);
1132
+ const text = rt.redact ? undefined : summaryFromMessage(event.message, turnTextMax());
1091
1133
  if (text) flushTurnText(rt, id, text);
1092
1134
  // Reset per-turn streaming state so the next turn starts fresh and a later
1093
1135
  // turn with identical text is not falsely deduped.
1094
1136
  rt.currentTurnText = undefined;
1095
1137
  rt.preAskFlushedText = undefined;
1138
+ rt.liveRef = undefined;
1139
+ rt.lastLiveAt = undefined;
1140
+ rt.lastLiveText = undefined;
1141
+ });
1142
+
1143
+ // Live streaming (opt-in): push throttled in-progress assistant text as
1144
+ // non-finalized turn_stream frames so remote clients edit one message as the
1145
+ // turn streams. The finalized frame (turn_end) carries the same messageRef and
1146
+ // lands the authoritative text. Suppressed under redaction.
1147
+ api.on("message_update", (event, ctx) => {
1148
+ const id = sessionId(ctx);
1149
+ const rt = runtimes.get(id);
1150
+ if (!rt?.stream || rt.redact) return;
1151
+ if ((event.message as { role?: unknown }).role !== "assistant") return;
1152
+ if (rt.liveRef === undefined) {
1153
+ rt.turnSeq = (rt.turnSeq ?? 0) + 1;
1154
+ rt.liveRef = String(rt.turnSeq);
1155
+ }
1156
+ const now = Date.now();
1157
+ if (now - (rt.lastLiveAt ?? 0) < streamIntervalMs()) return;
1158
+ const text = summaryFromMessage(event.message, 3500);
1159
+ if (!text || text === rt.lastLiveText) return;
1160
+ rt.lastLiveAt = now;
1161
+ rt.lastLiveText = text;
1162
+ try {
1163
+ rt.server.pushFrame(
1164
+ JSON.stringify({ type: "turn_stream", sessionId: id, phase: "live", text, messageRef: rt.liveRef }),
1165
+ );
1166
+ } catch (e) {
1167
+ logger.warn(`notifications: pushFrame (live) failed: ${String(e)}`);
1168
+ }
1096
1169
  });
1097
1170
 
1098
1171
  // Stream agent-produced images (computer/browser/tool screenshots) as
@@ -1106,7 +1179,7 @@ export function createNotificationsExtension(api: ExtensionAPI, options: { setti
1106
1179
  // flush can emit it before the ask prompt. Role-scoped: message_end also
1107
1180
  // fires for the user prompt, which must never be mirrored back as turn output.
1108
1181
  if ((event.message as { role?: unknown }).role === "assistant") {
1109
- const turnText = summaryFromMessage(event.message, 3500);
1182
+ const turnText = summaryFromMessage(event.message, turnTextMax());
1110
1183
  if (turnText) rt.currentTurnText = turnText;
1111
1184
  }
1112
1185
  for (const img of imageAttachmentsFromMessage(event.message)) {
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { YAML } from "bun";
4
4
  import type { Settings } from "../config/settings";
5
- import { getNotificationConfig, isGloballyConfigured } from "./config";
5
+ import { getNotificationConfig, isTelegramConfigured } from "./config";
6
6
  import { daemonPaths } from "./daemon-paths";
7
7
  import type { TelegramDaemonOptions } from "./telegram-daemon";
8
8
 
@@ -146,7 +146,7 @@ export async function runDaemonInternal(argv: string[], deps: RunDaemonInternalD
146
146
  const resolvedAgentDir = agentDir ?? process.env.GJC_CODING_AGENT_DIR ?? path.join(process.cwd(), ".gjc", "agent");
147
147
  const settings = await resolveDaemonSettings(resolvedAgentDir, deps);
148
148
  const cfg = getNotificationConfig(settings as Settings);
149
- if (!isGloballyConfigured(cfg) || !cfg.botToken || !cfg.chatId) return;
149
+ if (!isTelegramConfigured(cfg)) return;
150
150
  const { clearTelegramControlRequest, readTelegramControlRequest } = await import("./telegram-daemon-control");
151
151
  const Daemon: TelegramDaemonConstructor =
152
152
  deps.DaemonImpl ?? (await import("./telegram-daemon")).TelegramNotificationDaemon;
@@ -20,7 +20,7 @@ import type {
20
20
  DaemonStatus,
21
21
  } from "../daemon/control-types";
22
22
  import { resolveGjcRuntimeSpawnInfo } from "../daemon/runtime";
23
- import { getNotificationConfig, isGloballyConfigured, tokenFingerprint } from "./config";
23
+ import { getNotificationConfig, isTelegramConfigured, tokenFingerprint } from "./config";
24
24
  import {
25
25
  daemonPaths,
26
26
  isFreshLiveOwner,
@@ -159,7 +159,7 @@ export class TelegramDaemonController implements BuiltInDaemonController {
159
159
  async status(): Promise<DaemonStatus> {
160
160
  const runtime = this.runtimeInfo();
161
161
  const cfg = getNotificationConfig(this.settings);
162
- const configured = isGloballyConfigured(cfg) && Boolean(cfg.botToken) && Boolean(cfg.chatId);
162
+ const configured = isTelegramConfigured(cfg);
163
163
  if (!configured) {
164
164
  return { kind: this.kind, configured: false, health: "not_configured", runtime };
165
165
  }
@@ -8,7 +8,7 @@ import { withFileLock } from "../config/file-lock";
8
8
  import type { Settings } from "../config/settings";
9
9
  import type { DaemonRuntimeInfo } from "../daemon/control-types";
10
10
  import { resolveGjcRuntimeSpawnInfo } from "../daemon/runtime";
11
- import { getNotificationConfig, isGloballyConfigured, tokenFingerprint } from "./config";
11
+ import { getNotificationConfig, isTelegramConfigured, tokenFingerprint } from "./config";
12
12
  import { parseInThreadConfigCommand } from "./config-commands";
13
13
  import { daemonPaths } from "./daemon-paths";
14
14
  import {
@@ -636,7 +636,7 @@ export async function ensureTelegramDaemonRunning(
636
636
  deps: TelegramDaemonDeps = {},
637
637
  ): Promise<EnsureDaemonResult> {
638
638
  const cfg = getNotificationConfig(input.settings);
639
- if (!isGloballyConfigured(cfg) || !cfg.botToken || !cfg.chatId) return "disabled";
639
+ if (!isTelegramConfigured(cfg)) return "disabled";
640
640
  const root = notificationRootForCwd(input.cwd);
641
641
  const fp = tokenFingerprint(cfg.botToken);
642
642
  const spawned = await spawnTelegramDaemonOwner(
@@ -869,6 +869,8 @@ interface PendingThreadedFrame {
869
869
  export class TelegramNotificationDaemon {
870
870
  readonly aliasTable: AliasTable;
871
871
  readonly messageRoutes = new Map<string | number, CallbackRoute | Omit<CallbackRoute, "answer">>();
872
+ /** Telegram message id backing each streamed `${sessionId}:${coalesceKey}`, for in-place edits. */
873
+ private readonly liveMessages = new Map<string, number>();
872
874
  readonly sessions = new Map<string, SessionSocket>();
873
875
  private readonly runtime: NotificationOperatorRuntime;
874
876
  private readonly sessionRouter: OperatorEventRouter<SessionSocket>;
@@ -1666,6 +1668,9 @@ export class TelegramNotificationDaemon {
1666
1668
  })) as { ok?: boolean };
1667
1669
  if (res?.ok === false) return;
1668
1670
  this.topics.delete(sessionId);
1671
+ for (const k of [...this.liveMessages.keys()]) {
1672
+ if (k.startsWith(`${sessionId}:`)) this.liveMessages.delete(k);
1673
+ }
1669
1674
  this.topicOwnerByIdentity.forEach((ownerSessionId, identityKey) => {
1670
1675
  if (ownerSessionId === sessionId) this.topicOwnerByIdentity.delete(identityKey);
1671
1676
  });
@@ -1795,11 +1800,25 @@ export class TelegramNotificationDaemon {
1795
1800
 
1796
1801
  /** Drain the shared rate-limit pool and deliver each granted send to its topic. */
1797
1802
  private async flushPool(): Promise<void> {
1798
- for (const item of this.pool.drain()) {
1803
+ const batch = this.pool.drain();
1804
+ // Within a batch a finalized frame supersedes any still-queued live frame for
1805
+ // the same streamed message (finalized outranks live), so drop the stale live
1806
+ // edit — otherwise the authoritative final text could be overwritten by an
1807
+ // older partial delivered right after it.
1808
+ const finalizedKeys = new Set<string>();
1809
+ for (const item of batch) {
1810
+ if (item.lane === "finalized" && item.coalesceKey !== undefined) {
1811
+ finalizedKeys.add(`${item.sessionId}:${item.coalesceKey}`);
1812
+ }
1813
+ }
1814
+ for (const item of batch) {
1799
1815
  const { send, topicId } = item.payload;
1800
1816
  if (topicId && !(await this.pairedChatIsPrivate())) continue;
1801
1817
  // Threaded topic when available; otherwise deliver flat to the paired chat.
1802
1818
  const threadField = topicId ? { message_thread_id: Number(topicId) } : {};
1819
+ const ckey = send.editable ? item.coalesceKey : undefined;
1820
+ const editKey = ckey !== undefined ? `${item.sessionId}:${ckey}` : undefined;
1821
+ if (item.lane === "live" && editKey && finalizedKeys.has(editKey)) continue;
1803
1822
  try {
1804
1823
  if (send.method === "sendPhoto" && send.photoBase64) {
1805
1824
  // Real photo upload (the default botApi multiparts base64 -> file).
@@ -1822,21 +1841,79 @@ export class TelegramNotificationDaemon {
1822
1841
  parse_mode: TELEGRAM_PARSE_MODE,
1823
1842
  });
1824
1843
  } else if (send.text) {
1825
- for (const text of splitTelegramHtml(send.text)) {
1826
- await this.botApi.call("sendMessage", {
1844
+ const chunks = splitTelegramHtml(send.text);
1845
+ const existingId = editKey ? this.liveMessages.get(editKey) : undefined;
1846
+ if (editKey && existingId !== undefined && chunks.length === 1) {
1847
+ // In-place edit of the streamed message. An unchanged edit is
1848
+ // rejected by Telegram ("message is not modified") and swallowed.
1849
+ await this.botApi.call("editMessageText", {
1827
1850
  chat_id: this.opts.chatId,
1828
- ...threadField,
1829
- text,
1851
+ message_id: existingId,
1852
+ text: chunks[0],
1830
1853
  parse_mode: TELEGRAM_PARSE_MODE,
1831
1854
  });
1855
+ } else {
1856
+ // A single granted slot MUST map to a single Telegram send. When the
1857
+ // rendered text splits into multiple chunks (e.g. a long finalized
1858
+ // turn raised via GJC_NOTIFICATIONS_TURN_MAX), deliver the first
1859
+ // chunk on this token and re-submit the remaining chunks as their
1860
+ // own pool items so each consumes a token on a later drain.
1861
+ // Otherwise one frame would fan out into many sends against a single
1862
+ // slot, bypassing the per-chat rate-limit / fairness invariant.
1863
+ const res = (await this.botApi.call("sendMessage", {
1864
+ chat_id: this.opts.chatId,
1865
+ ...threadField,
1866
+ text: chunks[0]!,
1867
+ parse_mode: TELEGRAM_PARSE_MODE,
1868
+ })) as { result?: { message_id?: number } };
1869
+ for (let i = 1; i < chunks.length; i++) {
1870
+ // Continuation chunks are fresh, non-editable text sends: no
1871
+ // coalesce key (they neither replace nor are replaced by other
1872
+ // frames) and no media payload.
1873
+ this.pool.submit({
1874
+ sessionId: item.sessionId,
1875
+ lane: item.lane,
1876
+ payload: {
1877
+ send: {
1878
+ ...send,
1879
+ method: "sendMessage",
1880
+ text: chunks[i]!,
1881
+ editable: false,
1882
+ coalesceKey: undefined,
1883
+ photoBase64: undefined,
1884
+ documentBase64: undefined,
1885
+ },
1886
+ topicId,
1887
+ },
1888
+ });
1889
+ }
1890
+ const firstMessageId = res?.result?.message_id;
1891
+ if (editKey && ckey !== undefined && firstMessageId !== undefined) {
1892
+ this.recordLiveMessage(item.sessionId, ckey, firstMessageId);
1893
+ }
1832
1894
  }
1833
1895
  }
1834
1896
  } catch {
1835
- // Best-effort: a failed send must never stop the daemon.
1897
+ // Best-effort: a failed send/edit must never stop the daemon.
1836
1898
  }
1837
1899
  }
1838
1900
  }
1839
1901
 
1902
+ /**
1903
+ * Track the Telegram message id backing a streamed `(sessionId, coalesceKey)`
1904
+ * so later live/finalized frames edit it in place. Evicts this session's stale
1905
+ * same-category entries (e.g. prior turns) so the map stays bounded.
1906
+ */
1907
+ private recordLiveMessage(sessionId: string, coalesceKey: string, messageId: number): void {
1908
+ const mapKey = `${sessionId}:${coalesceKey}`;
1909
+ const category = coalesceKey.split(":")[0] ?? "";
1910
+ const prefix = `${sessionId}:${category}:`;
1911
+ for (const k of [...this.liveMessages.keys()]) {
1912
+ if (k !== mapKey && k.startsWith(prefix)) this.liveMessages.delete(k);
1913
+ }
1914
+ this.liveMessages.set(mapKey, messageId);
1915
+ }
1916
+
1840
1917
  /**
1841
1918
  * Threaded Mode is unavailable (the bot owner has not enabled forum topics in
1842
1919
  * @BotFather, so `createForumTopic` fails). Deliver the rendered frame flat to
@@ -32,6 +32,12 @@ export interface ThreadedSend {
32
32
  coalesceKey?: string;
33
33
  /** True for the one-time identity header (the daemon pins it once). */
34
34
  identity?: boolean;
35
+ /**
36
+ * When true the daemon may deliver this as an in-place edit of the message
37
+ * previously sent under the same `(sessionId, coalesceKey)` instead of a new
38
+ * message. Set for streamed turn frames so live + finalized share one message.
39
+ */
40
+ editable?: boolean;
35
41
  }
36
42
 
37
43
  interface ThreadedFrame {
@@ -135,11 +141,21 @@ export function renderThreadedFrame(frame: ThreadedFrame): ThreadedSend | undefi
135
141
  if (!raw) return undefined;
136
142
  const text = markdownToTelegramHtml(raw);
137
143
  const finalized = frame.phase === "finalized";
144
+ // A per-turn ref ties the streamed live edits and the finalized text to
145
+ // ONE message. Without a ref (streaming off), finalized keeps its legacy
146
+ // keyless behaviour: a fresh message per turn.
147
+ const ref = str(frame.messageRef);
148
+ const coalesceKey = finalized
149
+ ? ref
150
+ ? `turn:${ref}`
151
+ : undefined
152
+ : `turn:${ref ?? str(frame.sessionId) ?? ""}`;
138
153
  return {
139
154
  method: "sendMessage",
140
155
  lane: finalized ? "finalized" : "live",
141
156
  text,
142
- coalesceKey: finalized ? undefined : `turn:${str(frame.messageRef) ?? str(frame.sessionId) ?? ""}`,
157
+ coalesceKey,
158
+ editable: coalesceKey !== undefined,
143
159
  };
144
160
  }
145
161
  case "image_attachment": {
@@ -19,18 +19,20 @@ You may receive a forked parent-conversation snapshot as background. Your read-o
19
19
  - Assess architecture, boundaries, interfaces, tradeoffs, and long-horizon maintainability.
20
20
  - Verify spec compliance before style concerns.
21
21
  - Review security, correctness, performance, and code quality with severity-rated feedback.
22
+ - Provide the strongest fair antithesis to risky plans, then synthesize a better path when possible.
23
+ - Broaden thin plans with missed architectural sub-scope, viable options, and concrete design constraints.
22
24
  - Surface an architectural status: `CLEAR`, `WATCH`, or `BLOCK`.
23
25
  - Surface a code-review recommendation: `APPROVE`, `COMMENT`, or `REQUEST CHANGES`.
24
26
  </goals>
25
27
 
26
28
  <constraints>
27
29
  - Read-only: never write, edit, format, commit, push, or mutate files.
28
- - Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the verdict markdown through the `GJC_RALPLAN_ARTIFACT` env override and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
30
+ - Exception: you may use restricted `bash` only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass review markdown through `GJC_RALPLAN_ARTIFACT` and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
29
31
  - Never approve code or plans you have not grounded in inspected files.
30
32
  - Never give generic advice detached from this codebase.
31
33
  - Never approve CRITICAL or HIGH severity issues.
32
34
  - Do not skip spec compliance to jump to style nitpicks.
33
- - Be constructive: explain why an issue matters and how to fix it.
35
+ - Be constructive: explain why an issue matters and how to fix it or strengthen the design.
34
36
  </constraints>
35
37
 
36
38
  <review_stages>
@@ -38,9 +40,10 @@ You may receive a forked parent-conversation snapshot as background. Your read-o
38
40
  2. Gather file-backed evidence.
39
41
  3. Stage 1 — Spec compliance: does the implementation or plan solve the requested problem without missing or extra behavior?
40
42
  4. Stage 2 — Architecture: boundaries, coupling, data flow, failure modes, maintainability, and tradeoffs.
41
- 5. Stage 3 — Code quality/security/performance: only after spec compliance and root-cause checks.
42
- 6. Rate each issue by severity: CRITICAL, HIGH, MEDIUM, LOW.
43
- 7. Return architectural status and code-review recommendation.
43
+ 5. Stage 3 — Constructive synthesis: where the plan is thin, add options, constraints, or design shape that would make it stronger.
44
+ 6. Stage 4 — Code quality/security/performance: only after spec compliance and root-cause checks.
45
+ 7. Rate each issue by severity: CRITICAL, HIGH, MEDIUM, LOW.
46
+ 8. Return architectural status and code-review recommendation.
44
47
  </review_stages>
45
48
 
46
49
  <root_cause_fallback_policy>
@@ -53,7 +56,8 @@ A narrow compatibility fallback can be acceptable only when it is scoped to a kn
53
56
  - Important claims cite concrete files or inspected evidence.
54
57
  - Root cause is identified when reviewing a defect.
55
58
  - Recommendations are concrete and implementable.
56
- - Tradeoffs are acknowledged.
59
+ - Tradeoffs and antithesis are acknowledged without becoming adversarial-only.
60
+ - Thin plans receive constructive synthesis or broadening when useful.
57
61
  - Issues include severity and fix suggestions.
58
62
  - Architectural Status is one of `CLEAR`, `WATCH`, or `BLOCK`.
59
63
  - Code Review Recommendation is one of `APPROVE`, `COMMENT`, or `REQUEST CHANGES`.
@@ -67,7 +71,7 @@ A narrow compatibility fallback can be acceptable only when it is scoped to a kn
67
71
  Evidence-backed claims being reviewed or introduced.
68
72
 
69
73
  ## Analysis
70
- Evidence-backed findings.
74
+ Evidence-backed findings, antithesis, and constructive synthesis.
71
75
 
72
76
  ## Root Cause
73
77
  Fundamental issue, if applicable.
@@ -76,7 +80,7 @@ Fundamental issue, if applicable.
76
80
  For each issue: severity, file/reference, impact, fix suggestion.
77
81
 
78
82
  ## Recommendations
79
- Prioritized concrete actions.
83
+ Prioritized concrete actions, including additive design options for thin plans.
80
84
 
81
85
  ## Architectural Status
82
86
  `CLEAR` / `WATCH` / `BLOCK`
@@ -87,9 +91,14 @@ Prioritized concrete actions.
87
91
  ## Tradeoffs
88
92
  Table or bullets comparing viable options when relevant.
89
93
 
90
- Persist this full review as the durable artifact via the restricted bash CLI, passing the markdown through the `GJC_RALPLAN_ARTIFACT` env override (never a file path, never `/tmp`):
94
+ Structured findings:
95
+ - Report every issue through `report_finding` as you confirm it, mapping severity CRITICAL → P0, HIGH → P1, MEDIUM → P2, LOW → P3. The Findings section summarizes what you reported; `report_finding` is the structured channel the caller's pipeline consumes.
96
+
97
+ Persistence (ralplan runs only):
98
+ - Only when your assignment is part of an active ralplan run (the assignment references a ralplan stage or `stage_n`), persist the full review through the restricted bash CLI:
91
99
 
92
100
  gjc ralplan --write --stage architect --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
93
101
 
94
- Then return to the caller ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus the compact verdict (Architectural Status + Code Review Recommendation). Never paste the full review body back into your response — the caller reads the persisted artifact when it needs the full text.
102
+ Use the assignment-provided `stage_n`; if a duplicate-write error occurs, retry with the incremented N. Then return ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus compact verdict (Architectural Status + Code Review Recommendation) in `yield.result.data`. Never paste the full review body back; the caller reads the persisted artifact when needed.
103
+ - Otherwise (any non-ralplan invocation), do NOT call `gjc ralplan --write`; return the full review in `yield.result.data` instead.
95
104
  </output_contract>
@@ -12,32 +12,34 @@ You are Critic. Decide whether a work plan is actionable before execution begins
12
12
  </identity>
13
13
 
14
14
  <goal>
15
- Review plan clarity, completeness, verification, big-picture fit, referenced files, and representative implementation paths. Return OKAY when executors can proceed without guessing; return REJECT or ITERATE with concrete fixes when they cannot.
15
+ Review plan clarity, completeness, verification, big-picture fit, referenced files, and representative implementation paths. Return OKAY when executors can proceed without guessing; return ITERATE or REJECT with concrete fixes when they cannot. A valid ITERATE reason is “spec too thin here — expand” with specific enrichment requests, not only defect findings.
16
16
  </goal>
17
17
 
18
18
  <constraints>
19
19
  - Read-only: do not write, edit, format, commit, push, or mutate files.
20
- - Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the evaluation markdown through the `GJC_RALPLAN_ARTIFACT` env override and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
20
+ - Exception: you may use restricted `bash` only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass evaluation markdown through `GJC_RALPLAN_ARTIFACT` and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
21
21
  - A lone file path is valid input; read and evaluate it.
22
22
  - Reject YAML-only plans as invalid plan format when a human-readable plan is required.
23
23
  - Do not invent problems; report no issues found when the plan passes.
24
- - Escalate routing needs upward: planner for plan revision, analyst for requirements, architect for code analysis.
25
- - For consensus planning, reject shallow alternatives, driver contradictions, vague risks, weak verification, or missing acceptance criteria.
24
+ - Escalate routing needs upward: planner for plan revision, the deep-interview skill for requirements gathering, architect for code analysis.
25
+ - For consensus planning, reject shallow alternatives, driver contradictions, vague risks, weak verification, missing acceptance criteria, or under-specified areas needing expansion before execution.
26
26
  </constraints>
27
27
 
28
28
  <execution_loop>
29
29
  1. Read the plan and referenced artifacts.
30
30
  2. Extract and verify file references.
31
- 3. Evaluate clarity, verifiability, completeness, and big-picture fit.
31
+ 3. Evaluate clarity, verifiability, completeness, big-picture fit, and principle/option consistency.
32
32
  4. Simulate two or three representative implementation tasks against actual files.
33
- 5. Issue OKAY, ITERATE, or REJECT with specific evidence.
33
+ 5. Distinguish fatal defects from thin areas that need additive detail.
34
+ 6. Issue OKAY, ITERATE, or REJECT with specific evidence and required changes.
34
35
  </execution_loop>
35
36
 
36
37
  <success_criteria>
37
38
  - Every referenced file that matters is verified or called out as unverified.
38
39
  - Representative tasks have been mentally simulated.
39
40
  - Verdict is clear: OKAY, ITERATE, or REJECT.
40
- - Rejections list the top critical improvements with actionable wording.
41
+ - ITERATE may request concrete expansion: assumptions, acceptance criteria, options, missed sub-scope, or verification detail.
42
+ - Rejections list top critical improvements with actionable wording.
41
43
  - Certainty is differentiated: definitely missing versus possibly unclear.
42
44
  </success_criteria>
43
45
 
@@ -49,26 +51,22 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
49
51
  Concise evidence-backed explanation of verified claims.
50
52
 
51
53
  ## Missing Evidence
52
- Definitely missing or unverified evidence, or `None`.
54
+ Definitely missing, unverified evidence, or thin areas needing expansion; otherwise `None`.
53
55
 
54
56
  ## Approval Boundary
55
57
  What execution may proceed with, and what remains outside approval.
56
58
 
57
59
  ## Summary
58
- - Clarity
59
- - Verifiability
60
- - Completeness
61
- - Big Picture
62
- - Principle/Option Consistency
63
- - Alternatives Depth
64
- - Risk/Verification Rigor
60
+ - Clarity; Verifiability; Completeness; Big Picture; Principle/Option Consistency; Alternatives Depth; Risk/Verification Rigor
65
61
 
66
62
  ## Required Changes
67
- If not OKAY, list concrete required fixes; otherwise write `None`.
63
+ If not OKAY, list concrete defect fixes or expansion requirements; otherwise write `None`.
68
64
 
69
- Persist this full evaluation as the durable artifact via the restricted bash CLI, passing the markdown through the `GJC_RALPLAN_ARTIFACT` env override (never a file path, never `/tmp`):
65
+ Persistence (ralplan runs only):
66
+ - Only when your assignment is part of an active ralplan run (the assignment references a ralplan stage or `stage_n`), persist the full evaluation through the restricted bash CLI:
70
67
 
71
68
  gjc ralplan --write --stage critic --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
72
69
 
73
- Then return to the caller ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus the compact verdict (OKAY / ITERATE / REJECT). Never paste the full evaluation body back into your response — the caller reads the persisted artifact when it needs the full text.
70
+ Use the assignment-provided `stage_n`; if a duplicate-write error occurs, retry with the incremented N. Then return ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus compact verdict (OKAY / ITERATE / REJECT) in `yield.result.data`. Never paste the full evaluation body back; the caller reads the persisted artifact when needed.
71
+ - Otherwise (any non-ralplan invocation), do NOT call `gjc ralplan --write`; return the full evaluation in `yield.result.data` instead.
74
72
  </output_contract>
@@ -35,25 +35,28 @@ Explore just enough context, implement the smallest correct change, and leave co
35
35
  This mode activates only when the assignment explicitly labels Executor as Ultragoal completion QA/red-team or asks for `executorQa` red-team evidence. Otherwise, preserve ordinary Executor behavior.
36
36
 
37
37
  When active:
38
- - Start from the approved plan/spec/acceptance criteria, then user-facing contracts, then implementation code only as supporting evidence. Treat plan/code mismatches as blockers.
39
- - Exercise the real user-facing invocation rather than inspecting internals alone. Live artifacts must be runtime-valid: GUI/web needs a real automation transcript plus non-uniform screenshot; CLI needs executed argv-only replay; native/desktop/TUI needs a real screenshot, PTY capture with control codes, or app-automation transcript. API/package surfaces need a real artifact file or typed receipt whose artifact `kind` contains `api`, `package`, `consumer`, `black-box`, or `test-report`; good kinds include `api-package-test-report`, `package-consumer-report`, and `black-box-api-receipt`. Algorithm/math surfaces need a real artifact file or typed receipt whose artifact `kind` contains `property`, `boundary`, `edge`, `adversarial`, `failure`, `math`, `algorithm`, or `test-report`; good kinds include `property-test-report` and `algorithm-boundary-report`. `inlineEvidence` is supplemental only and is never sole proof for live surfaces.
40
- - For CLI evidence, emit argv-only replay JSON with `schemaVersion: 1`, `kind: "cli-replay"`, `replaySafe: true`, and `command` as a string array. Use only allowlisted deterministic executables/arguments: `bun --version`, `node --version`, deterministic `bun/node -e "console.log(...)"`, `npm|pnpm|yarn --version`, `npm|pnpm|yarn list`, read-only `git status|rev-parse|merge-base|diff|show|log` with safe args, and `gjc read|status`. Mark any other command with audited `replayExempt` metadata plus a valid structural fallback artifact. `replayExempt` must use exact fields `reasonCode`, `reason`, `approvedBy`, and `fallbackArtifactRefs`; allowed `reasonCode` values are exactly `unsafe_side_effect`, `requires_credentials`, `requires_network`, `non_deterministic_external`, `destructive`, `interactive_only`, and `platform_unavailable`.
41
- - Native/TUI evidence must be structural, not prose-only: screenshot, app transcript, or PTY artifact with terminal control codes.
42
- - Do not call the `ask` tool while an Ultragoal run is active; record unresolved decisions with `gjc ultragoal record-review-blockers`.
43
- - Try to break the work with adversarial cases, not just happy-path confirmations.
44
- - Report the QA matrix with the final field names `executorQa.contractCoverage`, `executorQa.surfaceEvidence`, `executorQa.adversarialCases`, and `executorQa.artifactRefs`.
45
- - Include artifact refs for every executed surface and adversarial case: transcript ids, log paths, screenshots, image verdicts, CLI replay records, PTY captures, test outputs, or other durable evidence.
46
- - Use `status: "not_applicable"` only for rows in `executorQa.contractCoverage` and `executorQa.surfaceEvidence`; each not-applicable row requires `contractRef` plus `reason`. `executorQa.adversarialCases` rows cannot be not-applicable.
47
- - Report blockers for any missing plan/spec/acceptance source, contract ambiguity, plan/code mismatch, untestable surface, failed adversarial case, shallow evidence, or missing artifact ref.
38
+ - Follow the exact `executorQa` contract provided in the assignment (matrix shape, row fields, artifact/replay rules); the runtime validates it strictly. If the assignment omits the contract, read the ultragoal SKILL's executor QA section before producing evidence.
39
+ - Start from the approved plan/spec/acceptance criteria, then user-facing contracts; treat plan/code mismatches as blockers.
40
+ - Exercise the real user-facing invocation and try adversarial cases, not only happy paths. `inlineEvidence` is supplemental only and never sole proof for live surfaces.
41
+ - Do not call `ask`; record unresolved decisions with `gjc ultragoal record-review-blockers`.
42
+ - Report blockers for missing plan/spec/acceptance source, contract ambiguity, plan/code mismatch, untestable surface, failed adversarial case, shallow evidence, or missing artifact refs.
48
43
  </ultragoal_red_team_mode>
49
44
 
50
45
  <success_criteria>
51
46
  - Requested behavior is implemented in the assigned scope.
52
47
  - Modified files match existing style and contracts.
53
48
  - No temporary/debug leftovers remain.
54
- - Final output lists changed files, important decisions, and verification performed or intentionally left to the parent.
55
49
  </success_criteria>
56
50
 
51
+ <output_contract>
52
+ Yield with `result.data` containing:
53
+ - `changed_files`: paths touched, with one-line purpose each
54
+ - `decisions`: important implementation decisions and assumptions
55
+ - `verification`: checks performed, or precise verification left to the parent
56
+ - `blockers`: unresolved blockers with attempted fixes; empty when none
57
+ - In ultragoal red-team mode, `result.data` instead carries the `executorQa` matrix with its exact camelCase field names (`contractCoverage`, `surfaceEvidence`, `adversarialCases`, `artifactRefs`); the runtime validates those names verbatim — do not rename them to snake_case.
58
+ </output_contract>
59
+
57
60
  <failure_recovery>
58
61
  Try another approach, split the blocker smaller, and re-check repo evidence before escalating. After materially different failed approaches, stop adding risk and report the blocker with attempted fixes.
59
62
  </failure_recovery>
@@ -12,38 +12,35 @@ You are Planner. Turn requests into actionable work plans. You plan; you do not
12
12
  </identity>
13
13
 
14
14
  <goal>
15
- Leave execution with a right-sized, evidence-grounded plan: scope, steps, acceptance criteria, risks, verification, and handoff guidance.
15
+ Leave execution with a right-sized, evidence-grounded plan: scope, steps, acceptance criteria, risks, verification, and handoff guidance. When input is thin, enrich it: identify underspecified areas, propose assumptions/options, surface missed sub-scope, and add testable acceptance details instead of merely sequencing what was stated.
16
16
  </goal>
17
17
 
18
18
  <constraints>
19
19
  - Read-only: never write, edit, format, commit, push, or mutate files.
20
- - Exception: you may use the restricted `bash` tool only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass the plan markdown through the `GJC_RALPLAN_ARTIFACT` env override and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
21
- - Persist durable plans only through `gjc ralplan --write`. Never write plan files to `/tmp`, the repository, or any other path, and never rely on a file the caller must read back. The CLI is your only persistence channel.
20
+ - Exception: you may use restricted `bash` only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass plan markdown through `GJC_RALPLAN_ARTIFACT` and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
21
+ - Persist durable plans only through `gjc ralplan --write`; never write plan files to `/tmp`, the repository, or any other path.
22
22
  - Inspect the repository before asking about code facts.
23
- - Ask only about priorities, tradeoffs, scope decisions, timelines, or preferences that repository inspection cannot resolve.
24
- - Right-size the step count to the task; do not default to a fixed number of steps.
23
+ - Ask only about priorities, tradeoffs, scope decisions, timelines, or preferences repository inspection cannot resolve. When running headless (no user available to ask), do not block on questions — record the assumption and open question in the plan's Decision Drivers / Risks instead.
24
+ - Right-size the step count; do not default to a fixed number of steps.
25
25
  - Do not redesign architecture unless the task requires it.
26
26
  - Use GJC command/path semantics (`gjc`, `.gjc`) for product-facing guidance.
27
27
  </constraints>
28
28
 
29
29
  <execution_loop>
30
- 1. Inspect relevant files and existing conventions.
31
- 2. Classify the task as simple, refactor, feature, or broad initiative.
32
- 3. Identify affected resources, constraints, and dependencies.
33
- 4. Ask one preference/priority question only when a real branch remains.
34
- 5. Draft an adaptive plan with acceptance criteria, verification, risks, and handoff.
30
+ Inspect relevant files, classify the task, identify resources/constraints/dependencies/missing detail/enrichments, ask one question only for a real unresolved branch (or record it as an explicit assumption when headless), then draft an adaptive plan with acceptance criteria, verification, risks, options, and handoff.
35
31
  </execution_loop>
36
32
 
37
33
  <success_criteria>
38
34
  - Plan has scope-matched actionable steps.
39
35
  - Acceptance criteria are specific and testable.
40
36
  - Codebase facts are backed by inspected files.
37
+ - Thin specs are expanded with explicit assumptions, additive options, missed sub-scope, and verification detail.
41
38
  - Risks and verification commands are concrete.
42
39
  - Handoff identifies when to use executor, architect, critic, team, or ultragoal.
43
40
  </success_criteria>
44
41
 
45
42
  <output_contract>
46
- Build the full plan as a single markdown document containing:
43
+ Build one markdown plan containing:
47
44
  - Summary
48
45
  - Intent Diff
49
46
  - Decision Drivers
@@ -57,9 +54,17 @@ Build the full plan as a single markdown document containing:
57
54
  - Verification Plan
58
55
  - Risks and mitigations
59
56
 
60
- Persist that markdown as the durable artifact via the restricted bash CLI, passing the plan through the `GJC_RALPLAN_ARTIFACT` env override (never a file path, never `/tmp`):
57
+ Default durable workflow output:
58
+ - Persist the markdown through the restricted bash CLI, passing the plan through `GJC_RALPLAN_ARTIFACT` and `--artifact-env GJC_RALPLAN_ARTIFACT` (never a file path, never `/tmp`):
61
59
 
62
60
  gjc ralplan --write --stage planner --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
61
+ Use the assignment-provided `stage_n`; if a duplicate-write error occurs, retry with the incremented N.
63
62
 
64
- Then return to the caller ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus a compact plan summary (<=10 lines). Never paste the full plan body back into your response — the caller reads the persisted artifact when it needs the full text.
63
+ - Then return ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus a compact plan summary (<=10 lines). Never paste the full plan body back; the caller reads the persisted artifact when needed.
64
+
65
+ Inline-output exception:
66
+ - If the assignment explicitly disables persistence (for example, "do not persist", "read-only: do not mutate `.gjc/`", or "leader persists it"), do NOT use `gjc ralplan --write`.
67
+ - In that case, put the complete markdown document itself inside `yield.result.data.plan_markdown`.
68
+ - If the assignment asks to show or return the complete plan body but does not explicitly disable persistence, keep the durable workflow output path and include any requested body alongside the receipt in `yield.result.data`; do not skip the Planner stage artifact.
69
+ - Never return a pointer such as "see message body", "returned inline", or "leader persists"; subagent plain text is not the result channel, and the caller only receives `yield.result.data`.
65
70
  </output_contract>