@gajae-code/coding-agent 0.11.11 → 0.12.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 (182) hide show
  1. package/CHANGELOG.md +49 -3
  2. package/dist/types/cli/read-cli.d.ts +1 -0
  3. package/dist/types/commands/deep-interview.d.ts +9 -0
  4. package/dist/types/commands/harness.d.ts +22 -0
  5. package/dist/types/commands/read.d.ts +6 -0
  6. package/dist/types/config/model-registry.d.ts +4 -0
  7. package/dist/types/config/provider-auth-health.d.ts +14 -0
  8. package/dist/types/config/provider-ranking.d.ts +59 -0
  9. package/dist/types/config/settings-schema.d.ts +29 -1
  10. package/dist/types/config/settings.d.ts +2 -0
  11. package/dist/types/extensibility/extensions/types.d.ts +22 -2
  12. package/dist/types/extensibility/shared-events.d.ts +6 -0
  13. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
  14. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
  15. package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
  16. package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
  17. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  18. package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
  19. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +52 -1
  20. package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
  21. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  22. package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
  23. package/dist/types/gjc-runtime/team-store.d.ts +12 -0
  24. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
  25. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +7 -0
  26. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
  27. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
  28. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  29. package/dist/types/main.d.ts +1 -1
  30. package/dist/types/memories/index.d.ts +2 -0
  31. package/dist/types/modes/acp/acp-agent.d.ts +8 -0
  32. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  33. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  34. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  35. package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
  36. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  37. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  38. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  39. package/dist/types/modes/interactive-mode.d.ts +2 -0
  40. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  41. package/dist/types/modes/types.d.ts +2 -0
  42. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  43. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  44. package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
  45. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  46. package/dist/types/sdk/bus/index.d.ts +15 -1
  47. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
  48. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
  49. package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
  50. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  51. package/dist/types/sdk/host/host.d.ts +2 -0
  52. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  53. package/dist/types/session/agent-session.d.ts +44 -10
  54. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  55. package/dist/types/session/session-manager.d.ts +25 -13
  56. package/dist/types/session/streaming-output.d.ts +60 -5
  57. package/dist/types/skill-state/active-state.d.ts +0 -4
  58. package/dist/types/skill-state/workflow-hud.d.ts +3 -0
  59. package/dist/types/tools/browser/launch.d.ts +7 -0
  60. package/dist/types/tools/fetch.d.ts +17 -1
  61. package/dist/types/tools/output-meta.d.ts +21 -1
  62. package/dist/types/tools/read-internals.d.ts +12 -0
  63. package/dist/types/tools/read.d.ts +45 -1
  64. package/dist/types/tools/resource-gc.d.ts +4 -0
  65. package/dist/types/tools/tool-result.d.ts +7 -1
  66. package/package.json +9 -13
  67. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  68. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  69. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  70. package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
  71. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  72. package/src/capability/index.ts +8 -0
  73. package/src/cli/read-cli.ts +2 -1
  74. package/src/cli.ts +1 -0
  75. package/src/commands/deep-interview.ts +29 -9
  76. package/src/commands/harness.ts +35 -12
  77. package/src/commands/read.ts +11 -2
  78. package/src/commands/team.ts +1 -1
  79. package/src/config/model-registry.ts +21 -2
  80. package/src/config/provider-auth-health.ts +42 -0
  81. package/src/config/provider-ranking.ts +119 -0
  82. package/src/config/settings-schema.ts +23 -1
  83. package/src/config/settings.ts +246 -108
  84. package/src/defaults/gjc/skills/deep-interview/SKILL.md +229 -82
  85. package/src/defaults/gjc/skills/ralplan/SKILL.md +58 -25
  86. package/src/defaults/gjc/skills/team/SKILL.md +1 -1
  87. package/src/defaults/gjc/skills/ultragoal/SKILL.md +25 -15
  88. package/src/eval/py/executor.ts +7 -2
  89. package/src/extensibility/extensions/types.ts +28 -4
  90. package/src/extensibility/shared-events.ts +5 -0
  91. package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
  92. package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
  93. package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
  94. package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
  95. package/src/gjc-runtime/deep-interview-state.ts +12 -943
  96. package/src/gjc-runtime/gc-render.ts +1 -0
  97. package/src/gjc-runtime/gc-runtime.ts +11 -1
  98. package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
  99. package/src/gjc-runtime/ralplan-runtime.ts +742 -85
  100. package/src/gjc-runtime/state-runtime.ts +2 -1
  101. package/src/gjc-runtime/state-writer.ts +70 -222
  102. package/src/gjc-runtime/team-launch.ts +34 -5
  103. package/src/gjc-runtime/team-runtime.ts +970 -33
  104. package/src/gjc-runtime/team-store.ts +32 -0
  105. package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
  106. package/src/gjc-runtime/team-workers.ts +1 -0
  107. package/src/gjc-runtime/tmux-owner-isolation.ts +18 -0
  108. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
  109. package/src/gjc-runtime/ultragoal-runtime.ts +13 -7
  110. package/src/gjc-runtime/workflow-manifest.generated.json +97 -350
  111. package/src/gjc-runtime/workflow-manifest.ts +34 -179
  112. package/src/hooks/native-skill-hook.ts +17 -4
  113. package/src/hooks/skill-state.ts +1 -23
  114. package/src/internal-urls/docs-index.generated.ts +8 -9
  115. package/src/internal-urls/local-root-gc.ts +127 -0
  116. package/src/main.ts +9 -5
  117. package/src/memories/index.ts +10 -0
  118. package/src/modes/DESIGN.md +47 -0
  119. package/src/modes/acp/acp-agent.ts +43 -7
  120. package/src/modes/acp/acp-event-mapper.ts +14 -7
  121. package/src/modes/acp/acp-mode.ts +39 -3
  122. package/src/modes/components/assistant-message.ts +123 -22
  123. package/src/modes/components/irc-sidebar.ts +176 -62
  124. package/src/modes/components/model-selector.ts +61 -4
  125. package/src/modes/components/oauth-selector.ts +42 -12
  126. package/src/modes/components/read-tool-group.ts +82 -14
  127. package/src/modes/components/session-observer-overlay.ts +187 -32
  128. package/src/modes/components/settings-selector.ts +60 -18
  129. package/src/modes/components/tool-execution.ts +138 -27
  130. package/src/modes/controllers/event-controller.ts +109 -27
  131. package/src/modes/controllers/extension-ui-controller.ts +19 -2
  132. package/src/modes/controllers/input-controller.ts +18 -3
  133. package/src/modes/controllers/selector-controller.ts +83 -37
  134. package/src/modes/interactive-mode.ts +62 -3
  135. package/src/modes/irc-observation-ledger.ts +71 -15
  136. package/src/modes/runtime-init.ts +19 -2
  137. package/src/modes/types.ts +2 -0
  138. package/src/modes/utils/ui-helpers.ts +94 -1
  139. package/src/prompts/agents/architect.md +9 -1
  140. package/src/prompts/agents/critic.md +9 -0
  141. package/src/prompts/system/system-prompt.md +0 -1
  142. package/src/prompts/tools/read.md +4 -2
  143. package/src/runtime-mcp/smithery-auth.ts +17 -2
  144. package/src/runtime-mcp/smithery-connect.ts +11 -1
  145. package/src/sdk/broker/lifecycle.ts +25 -6
  146. package/src/sdk/bus/control-drain-lease.ts +104 -0
  147. package/src/sdk/bus/index.ts +341 -38
  148. package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
  149. package/src/sdk/bus/prompt-reconciliation.ts +2 -1
  150. package/src/sdk/bus/reconciliation-store.ts +224 -0
  151. package/src/sdk/host/control/dispatch.ts +5 -1
  152. package/src/sdk/host/control/operations.ts +1 -1
  153. package/src/sdk/host/host.ts +23 -1
  154. package/src/sdk/host/query/handlers.ts +55 -0
  155. package/src/sdk/mcp/server.ts +46 -18
  156. package/src/sdk/protocol/operation-inventory.generated.json +29 -0
  157. package/src/sdk/protocol/operation-registry.ts +7 -2
  158. package/src/sdk/session.ts +13 -1
  159. package/src/session/agent-session.ts +466 -50
  160. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  161. package/src/session/session-manager.ts +764 -17
  162. package/src/session/streaming-output.ts +262 -43
  163. package/src/setup/provider-onboarding.ts +19 -4
  164. package/src/skill-state/active-state.ts +0 -10
  165. package/src/skill-state/workflow-hud.ts +25 -2
  166. package/src/skill-state/workflow-mutation-guard.ts +65 -7
  167. package/src/tools/ask.ts +89 -69
  168. package/src/tools/browser/launch.ts +41 -6
  169. package/src/tools/fetch.ts +114 -23
  170. package/src/tools/output-meta.ts +176 -9
  171. package/src/tools/path-utils.ts +5 -1
  172. package/src/tools/read-internals.ts +44 -0
  173. package/src/tools/read.ts +1542 -310
  174. package/src/tools/resource-gc.ts +182 -18
  175. package/src/tools/tool-result.ts +14 -1
  176. package/src/web/search/providers/codex.ts +1 -1
  177. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
  178. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
  179. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
  180. package/src/gjc-runtime/deep-interview-draft.ts +0 -843
  181. package/src/gjc-runtime/deep-interview-payload.ts +0 -262
  182. package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
@@ -857,7 +857,8 @@ function buildHudForMode(
857
857
  : typeof payload.mode === "string"
858
858
  ? (payload.mode as string)
859
859
  : undefined;
860
- const verdict = typeof payload.verdict === "string" ? (payload.verdict as string) : undefined;
860
+ const rawVerdict = payload.last_review_verdict ?? payload.verdict;
861
+ const verdict = typeof rawVerdict === "string" ? rawVerdict : undefined;
861
862
  const iteration = typeof payload.iteration === "number" ? (payload.iteration as number) : undefined;
862
863
  const pendingApproval = payload.pending_approval === true || stage === "final";
863
864
  return buildRalplanHudSummary({
@@ -11,7 +11,6 @@ import {
11
11
  type WorkflowStateMutationOwner,
12
12
  type WorkflowStateReceipt,
13
13
  } from "../skill-state/workflow-state-contract";
14
- import { validateDeepInterviewV1Envelope } from "./deep-interview-state";
15
14
  import {
16
15
  activeEntryPath as layoutActiveEntryPath,
17
16
  activeSnapshotPath as layoutActiveSnapshotPath,
@@ -78,47 +77,6 @@ export interface WorkflowEnvelopeIntegrityMismatch {
78
77
  expected: string;
79
78
  actual: string;
80
79
  }
81
- export type WorkflowReceiptVerification =
82
- | "legacy"
83
- | "native-valid"
84
- | "receipt-malformed"
85
- | "receipt-missing"
86
- | "checksum-mismatch";
87
-
88
- export interface GuardedWorkflowEnvelopeTransformOptions extends StateWriterOptions {
89
- expectedRevision: number;
90
- /** Additional native schema validation applied to both current and candidate envelopes. */
91
- validate?: (value: Record<string, unknown>, legacy: boolean) => void;
92
- transform: (
93
- current: Record<string, unknown>,
94
- context: { revision: number; legacy: boolean },
95
- ) =>
96
- | { kind: "noop" }
97
- | { kind: "write"; value: Record<string, unknown> }
98
- | Promise<{ kind: "noop" } | { kind: "write"; value: Record<string, unknown> }>;
99
- }
100
-
101
- export interface GuardedWorkflowEnvelopeWarning {
102
- code: "DI_POST_COMMIT_AUDIT_FAILED";
103
- message: string;
104
- }
105
-
106
- export type GuardedWorkflowEnvelopeTransformResult =
107
- | { path: string; written: false; reason: "noop"; revision: number; warnings: [] }
108
- | {
109
- path: string;
110
- written: true;
111
- revision: number;
112
- stamped: Record<string, unknown>;
113
- warnings: GuardedWorkflowEnvelopeWarning[];
114
- };
115
-
116
- export class GuardedWorkflowEnvelopeError extends Error {
117
- constructor(public readonly code: string) {
118
- super(code);
119
- this.name = "GuardedWorkflowEnvelopeError";
120
- }
121
- }
122
80
 
123
81
  export interface WorkflowTransactionJournal {
124
82
  version: 1;
@@ -138,7 +96,6 @@ export interface GuardedStateWriterOptions extends StateWriterOptions {
138
96
  policy: StateWritePolicy;
139
97
  expectedRevision?: number;
140
98
  sourceRevision?: number;
141
- lockHeld?: boolean;
142
99
  }
143
100
 
144
101
  export type GuardedWriteResult =
@@ -150,13 +107,17 @@ export interface StateWriterOptions {
150
107
  receipt?: StateWriterReceiptContext;
151
108
  audit?: StateWriterAuditContext;
152
109
  sourceRevision?: number;
153
- orderingRevision?: number;
154
110
  /**
155
111
  * Cross-process lock tuning for read-modify-write paths that route through
156
112
  * `withWorkflowStateLock` / `updateJsonAtomic`. Omit for the hardened
157
113
  * `withFileLock` defaults.
158
114
  */
159
115
  lock?: FileLockOptions;
116
+ /**
117
+ * Caller already holds the workflow state lock for this target path (via
118
+ * `withWorkflowStateLock`). Skip re-acquisition to avoid self-deadlock.
119
+ */
120
+ lockHeld?: boolean;
160
121
  }
161
122
 
162
123
  export class StateWriteConflictError extends Error {
@@ -298,49 +259,6 @@ export function workflowEnvelopeContentSha256(value: unknown): string {
298
259
  .update(JSON.stringify(canonicalizeJson(withoutReceiptChecksum(value))))
299
260
  .digest("hex");
300
261
  }
301
- export function isNativeDeepInterviewV1(value: Record<string, unknown>): boolean {
302
- if (value.skill !== "deep-interview") return false;
303
- return value.schema_version === 1 || (isPlainObject(value.state) && value.state.schema_version === 1);
304
- }
305
-
306
- export function verifyWorkflowEnvelopeReceiptValue(value: unknown, filePath: string): WorkflowReceiptVerification {
307
- if (!isPlainObject(value)) return "receipt-malformed";
308
- const native = isNativeDeepInterviewV1(value);
309
- if (!Object.hasOwn(value, "receipt")) return native ? "receipt-missing" : "legacy";
310
- if (!isPlainObject(value.receipt) || !isPlainObject(value.receipt.content_sha256)) return "receipt-malformed";
311
-
312
- const receipt = value.receipt as Record<string, unknown>;
313
- const checksum = receipt.content_sha256 as Record<string, unknown>;
314
- if (
315
- typeof value.skill !== "string" ||
316
- receipt.version !== 1 ||
317
- receipt.skill !== value.skill ||
318
- (receipt.skill !== "deep-interview" &&
319
- receipt.skill !== "ralplan" &&
320
- receipt.skill !== "ultragoal" &&
321
- receipt.skill !== "team") ||
322
- (receipt.owner !== "gjc-state-cli" && receipt.owner !== "gjc-runtime" && receipt.owner !== "gjc-hook") ||
323
- typeof receipt.command !== "string" ||
324
- typeof receipt.state_path !== "string" ||
325
- typeof receipt.storage_path !== "string" ||
326
- typeof receipt.mutated_at !== "string" ||
327
- !Number.isFinite(Date.parse(receipt.mutated_at)) ||
328
- typeof receipt.fresh_until !== "string" ||
329
- !Number.isFinite(Date.parse(receipt.fresh_until)) ||
330
- (receipt.status !== "fresh" && receipt.status !== "stale") ||
331
- typeof receipt.mutation_id !== "string" ||
332
- checksum.algorithm !== "sha256" ||
333
- typeof checksum.value !== "string" ||
334
- !/^[0-9a-f]{64}$/.test(checksum.value) ||
335
- typeof checksum.covered_path !== "string" ||
336
- path.resolve(checksum.covered_path) !== path.resolve(filePath) ||
337
- typeof checksum.computed_at !== "string" ||
338
- !Number.isFinite(Date.parse(checksum.computed_at))
339
- ) {
340
- return "receipt-malformed";
341
- }
342
- return workflowEnvelopeContentSha256(value) === checksum.value ? "native-valid" : "checksum-mismatch";
343
- }
344
262
 
345
263
  export function stampWorkflowEnvelopeChecksum<T>(value: T, filePath: string, computedAt = new Date().toISOString()): T {
346
264
  if (!value || typeof value !== "object" || Array.isArray(value)) return value;
@@ -507,11 +425,6 @@ function persistedSourceRevision(value: unknown): number {
507
425
  const revision = value.source_state_revision;
508
426
  return typeof revision === "number" && Number.isFinite(revision) ? revision : persistedStateRevision(value);
509
427
  }
510
- function persistedCommittedModeRevision(value: unknown): number {
511
- if (!isPlainObject(value)) return 0;
512
- const revision = value.committed_mode_state_revision;
513
- return typeof revision === "number" && Number.isFinite(revision) ? revision : 0;
514
- }
515
428
 
516
429
  function withoutCandidateRevision(value: unknown): unknown {
517
430
  if (!isPlainObject(value)) return value;
@@ -620,12 +533,7 @@ async function writeGuardedResolvedJsonAtomic(
620
533
 
621
534
  const incomingSourceRevision =
622
535
  options.sourceRevision ?? (isPlainObject(value) ? persistedStateRevision(value) : 0);
623
- if (
624
- current !== undefined &&
625
- (options.orderingRevision !== undefined
626
- ? options.orderingRevision <= persistedCommittedModeRevision(current)
627
- : incomingSourceRevision <= persistedSourceRevision(current))
628
- ) {
536
+ if (current !== undefined && incomingSourceRevision <= persistedSourceRevision(current)) {
629
537
  return { path: filePath, written: false, reason: "stale-skip", revision: currentRevision };
630
538
  }
631
539
  const next = stampStateRevision(
@@ -659,7 +567,6 @@ export async function writeGuardedWorkflowEnvelopeAtomic(
659
567
  const write = async (): Promise<GuardedWriteResult> => {
660
568
  const current = await readJsonIfPresentTolerant(filePath);
661
569
  const currentRevision = persistedStateRevision(current);
662
-
663
570
  if (options.policy === "source") {
664
571
  if (options.expectedRevision !== undefined && options.expectedRevision !== currentRevision) {
665
572
  throw new StateWriteConflictError(filePath, options.expectedRevision, currentRevision);
@@ -683,15 +590,9 @@ export async function writeGuardedWorkflowEnvelopeAtomic(
683
590
  await maybeAudit(filePath, options);
684
591
  return { path: filePath, written: true, revision: currentRevision + 1, stamped: next };
685
592
  }
686
-
687
593
  const incomingSourceRevision =
688
594
  options.sourceRevision ?? (isPlainObject(value) ? persistedStateRevision(value) : 0);
689
- if (
690
- current !== undefined &&
691
- (options.orderingRevision !== undefined
692
- ? options.orderingRevision <= persistedCommittedModeRevision(current)
693
- : incomingSourceRevision <= persistedSourceRevision(current))
694
- ) {
595
+ if (current !== undefined && incomingSourceRevision <= persistedSourceRevision(current)) {
695
596
  return { path: filePath, written: false, reason: "stale-skip", revision: currentRevision };
696
597
  }
697
598
  const next = stampWorkflowEnvelopeRevisionAndChecksum(
@@ -715,67 +616,6 @@ export async function writeGuardedWorkflowEnvelopeAtomic(
715
616
  };
716
617
  return options.lockHeld ? write() : lockResolvedWorkflowTarget(filePath, write, options.lock);
717
618
  }
718
- export async function transformGuardedWorkflowEnvelopeAtomic(
719
- targetPath: string,
720
- options: GuardedWorkflowEnvelopeTransformOptions,
721
- ): Promise<GuardedWorkflowEnvelopeTransformResult> {
722
- const filePath = resolveGjcTarget(targetPath, cwdForOptions(options));
723
- return lockResolvedWorkflowTarget(
724
- filePath,
725
- async () => {
726
- const read = await readExistingStateForMutation(filePath);
727
- if (read.kind === "absent") throw new GuardedWorkflowEnvelopeError("DI_STATE_ABSENT");
728
- if (read.kind === "corrupt") throw new GuardedWorkflowEnvelopeError("DI_STATE_CORRUPT");
729
- const receipt = verifyWorkflowEnvelopeReceiptValue(read.value, filePath);
730
- if (receipt === "receipt-malformed") throw new GuardedWorkflowEnvelopeError("DI_RECEIPT_MALFORMED");
731
- if (receipt === "receipt-missing") throw new GuardedWorkflowEnvelopeError("DI_RECEIPT_MISSING");
732
- if (receipt === "checksum-mismatch") throw new GuardedWorkflowEnvelopeError("DI_RECEIPT_CHECKSUM_MISMATCH");
733
- const legacy = receipt === "legacy";
734
- try {
735
- if (isNativeDeepInterviewV1(read.value)) validateDeepInterviewV1Envelope(read.value);
736
- options.validate?.(read.value, legacy);
737
- } catch {
738
- throw new GuardedWorkflowEnvelopeError("DI_STATE_SCHEMA_INVALID");
739
- }
740
- const phase = read.value.current_phase;
741
- if (phase === "complete" || phase === "handoff" || read.value.active === false) {
742
- throw new GuardedWorkflowEnvelopeError("DI_PHASE_NOT_REPAIRABLE");
743
- }
744
- const revision = persistedStateRevision(read.value);
745
- if (revision !== options.expectedRevision) throw new GuardedWorkflowEnvelopeError("DI_REVISION_CONFLICT");
746
- const transformed = await options.transform(read.value, { revision, legacy });
747
- if (transformed.kind === "noop")
748
- return { path: filePath, written: false, reason: "noop", revision, warnings: [] };
749
- try {
750
- if (isNativeDeepInterviewV1(transformed.value)) validateDeepInterviewV1Envelope(transformed.value);
751
- options.validate?.(transformed.value, false);
752
- } catch {
753
- throw new GuardedWorkflowEnvelopeError("DI_STATE_SCHEMA_INVALID");
754
- }
755
- const stamped = stampWorkflowEnvelopeRevisionAndChecksum(
756
- transformed.value,
757
- filePath,
758
- revision + 1,
759
- undefined,
760
- options,
761
- ) as Record<string, unknown>;
762
- const parsed = RequiredOnWriteEnvelopeSchema.safeParse(stamped);
763
- if (!parsed.success) throw new GuardedWorkflowEnvelopeError("DI_STATE_SCHEMA_INVALID");
764
- await atomicWrite(filePath, jsonText(stamped));
765
- const warnings: GuardedWorkflowEnvelopeWarning[] = [];
766
- try {
767
- await maybeAudit(filePath, options);
768
- } catch (error) {
769
- warnings.push({
770
- code: "DI_POST_COMMIT_AUDIT_FAILED",
771
- message: error instanceof Error ? error.message : String(error),
772
- });
773
- }
774
- return { path: filePath, written: true, revision: revision + 1, stamped, warnings };
775
- },
776
- options.lock,
777
- );
778
- }
779
619
 
780
620
  export async function writeJsonAtomic(
781
621
  targetPath: string,
@@ -845,63 +685,71 @@ export async function writeWorkflowEnvelopeAtomic(
845
685
  options?: StateWriterOptions,
846
686
  ): Promise<string> {
847
687
  const filePath = resolveGjcTarget(targetPath, cwdForOptions(options));
848
- const withReceipt = withWorkflowReceipt(value, buildReceipt(options));
849
- const stamped = stampWorkflowEnvelopeChecksum(withReceipt, filePath);
850
- const parsed = RequiredOnWriteEnvelopeSchema.safeParse(stamped);
851
- if (!parsed.success) {
852
- throw new Error(
853
- `Refusing to write invalid workflow state envelope to ${filePath}: ${parsed.error.issues
854
- .map(issue => `${issue.path.join(".") || "<root>"}: ${issue.message}`)
855
- .join("; ")}`,
856
- );
857
- }
858
- // #658: internal runtime writers (ralplan/ultragoal/deep-interview/team) persist
859
- // envelopes directly, bypassing the `gjc state` CLI transition gate (`isValidTransition`,
860
- // historically the sole call site in state-runtime.ts). Re-assert that gate on every
861
- // sanctioned envelope write so internal writes cannot persist invalid state-machine phase
862
- // transitions silently. Forced writes (`gjc state ... --force`, reconcile repairs) carry
863
- // `audit.forced` and bypass, mirroring the CLI's `use --force to bypass`.
864
- //
865
- // The gate governs ACTIVE workflow progression only. Deactivation/teardown writes
866
- // (`active: false`, e.g. `gjc state clear`, which persists the universal `complete`
867
- // sentinel that is not a per-skill manifest state) leave the transition graph and are
868
- // intentionally exempt.
869
- if (options?.audit?.forced !== true && parsed.data.active === true) {
870
- const toPhase = parsed.data.current_phase.trim();
871
- if (toPhase) {
872
- // Lazy import: workflow-manifest dereferences CANONICAL_GJC_WORKFLOW_SKILLS at
873
- // module load, and active-state -> state-writer -> workflow-manifest -> active-state
874
- // is a load-time cycle. Importing at call time (after init) avoids the TDZ.
875
- const { isKnownWorkflowState, isValidTransition } = await import("./workflow-manifest");
876
- const skill = parsed.data.skill;
877
- // Structural invariant (hard): a `current_phase` absent from the skill's manifest is
878
- // never a legitimate internal write, matching the CLI/reconcile unknown-phase gate.
879
- if (!isKnownWorkflowState(skill, toPhase)) {
880
- throw new Error(
881
- `Refusing to write unknown ${skill} phase "${toPhase}" to ${filePath}: not a known ${skill} manifest state (forced writes bypass via audit.forced)`,
882
- );
883
- }
884
- // Transition invariant (#658, diagnostic-only safety net): resolve the prior phase
885
- // (caller-supplied `audit.fromPhase`, else the active persisted envelope on disk) and
886
- // flag edges the manifest does not define. Intentionally NON-blocking and audit-only
887
- // — the CLI path already hard-fails invalid edges before reaching here, and legitimate
888
- // internal repairs / ralplan short-mode stage skips move between valid states without a
889
- // direct manifest edge. It records an `invalid_transition_detected` audit entry (no
890
- // stderr) so such transitions are non-silent without breaking those flows.
891
- const fromPhase = (options?.audit?.fromPhase ?? (await readPersistedPhase(filePath)))?.trim();
892
- if (
893
- fromPhase &&
894
- fromPhase !== toPhase &&
895
- isKnownWorkflowState(skill, fromPhase) &&
896
- !isValidTransition(skill, fromPhase, toPhase)
897
- ) {
898
- await recordInvalidWorkflowTransition({ filePath, skill, fromPhase, toPhase, options });
688
+ const write = async (): Promise<string> => {
689
+ const withReceipt = withWorkflowReceipt(value, buildReceipt(options));
690
+ const stamped = stampWorkflowEnvelopeChecksum(withReceipt, filePath);
691
+ const parsed = RequiredOnWriteEnvelopeSchema.safeParse(stamped);
692
+ if (!parsed.success) {
693
+ throw new Error(
694
+ `Refusing to write invalid workflow state envelope to ${filePath}: ${parsed.error.issues
695
+ .map(issue => `${issue.path.join(".") || "<root>"}: ${issue.message}`)
696
+ .join("; ")}`,
697
+ );
698
+ }
699
+ // #658: internal runtime writers (ralplan/ultragoal/deep-interview/team) persist
700
+ // envelopes directly, bypassing the `gjc state` CLI transition gate (`isValidTransition`,
701
+ // historically the sole call site in state-runtime.ts). Re-assert that gate on every
702
+ // sanctioned envelope write so internal writes cannot persist invalid state-machine phase
703
+ // transitions silently. Forced writes (`gjc state ... --force`, reconcile repairs) carry
704
+ // `audit.forced` and bypass, mirroring the CLI's `use --force to bypass`.
705
+ //
706
+ // The gate governs ACTIVE workflow progression only. Deactivation/teardown writes
707
+ // (`active: false`, e.g. `gjc state clear`, which persists the universal `complete`
708
+ // sentinel that is not a per-skill manifest state) leave the transition graph and are
709
+ // intentionally exempt.
710
+ if (options?.audit?.forced !== true && parsed.data.active === true) {
711
+ const toPhase = parsed.data.current_phase.trim();
712
+ if (toPhase) {
713
+ // Lazy import: workflow-manifest dereferences CANONICAL_GJC_WORKFLOW_SKILLS at
714
+ // module load, and active-state -> state-writer -> workflow-manifest -> active-state
715
+ // is a load-time cycle. Importing at call time (after init) avoids the TDZ.
716
+ const { isKnownWorkflowState, isValidTransition } = await import("./workflow-manifest");
717
+ const skill = parsed.data.skill;
718
+ // Structural invariant (hard): a `current_phase` absent from the skill's manifest is
719
+ // never a legitimate internal write, matching the CLI/reconcile unknown-phase gate.
720
+ if (!isKnownWorkflowState(skill, toPhase)) {
721
+ throw new Error(
722
+ `Refusing to write unknown ${skill} phase "${toPhase}" to ${filePath}: not a known ${skill} manifest state (forced writes bypass via audit.forced)`,
723
+ );
724
+ }
725
+ // Transition invariant (#658, diagnostic-only safety net): resolve the prior phase
726
+ // (caller-supplied `audit.fromPhase`, else the active persisted envelope on disk) and
727
+ // flag edges the manifest does not define. Intentionally NON-blocking and audit-only
728
+ // — the CLI path already hard-fails invalid edges before reaching here, and legitimate
729
+ // internal repairs / ralplan short-mode stage skips move between valid states without a
730
+ // direct manifest edge. It records an `invalid_transition_detected` audit entry (no
731
+ // stderr) so such transitions are non-silent without breaking those flows.
732
+ const fromPhase = (options?.audit?.fromPhase ?? (await readPersistedPhase(filePath)))?.trim();
733
+ if (
734
+ fromPhase &&
735
+ fromPhase !== toPhase &&
736
+ isKnownWorkflowState(skill, fromPhase) &&
737
+ !isValidTransition(skill, fromPhase, toPhase)
738
+ ) {
739
+ await recordInvalidWorkflowTransition({ filePath, skill, fromPhase, toPhase, options });
740
+ }
899
741
  }
900
742
  }
901
- }
902
- await atomicWrite(filePath, jsonText(stamped));
903
- await maybeAudit(filePath, options);
904
- return filePath;
743
+ await atomicWrite(filePath, jsonText(stamped));
744
+ await maybeAudit(filePath, options);
745
+ return filePath;
746
+ };
747
+ // Serialize with every other writer of the same state path. Without this, a
748
+ // revision-preserving envelope write (seed/spec persistence) can interleave
749
+ // inside a staged apply's check-then-write window and be silently overwritten
750
+ // (#3387 architect finding 1). Callers already inside `withWorkflowStateLock`
751
+ // for this path pass `lockHeld: true`.
752
+ return options?.lockHeld ? write() : lockResolvedWorkflowTarget(filePath, write, options?.lock);
905
753
  }
906
754
 
907
755
  export async function writeTextAtomic(targetPath: string, text: string, options?: StateWriterOptions): Promise<string> {
@@ -12,6 +12,7 @@ import type {
12
12
  GjcTeamWorkerLifecycle,
13
13
  GjcTeamWorktreeMode,
14
14
  } from "./team-runtime";
15
+ import { createInitialGjcTeamWorkerMemoryGuardLedger, workerMemoryGuardLedgerPath } from "./team-worker-memory-guard";
15
16
 
16
17
  /** Launch-specific option wiring kept separate from runtime dispatch. */
17
18
  export function withTeamLaunchTransport(
@@ -34,6 +35,7 @@ export function buildWorkerCommand(
34
35
  config: GjcTeamConfig,
35
36
  worker: GjcTeamWorker,
36
37
  platform: NodeJS.Platform = process.platform,
38
+ promptOverride?: string,
37
39
  ): string {
38
40
  const quote = platform === "win32" ? powershellQuote : shellQuote;
39
41
  const envAssignment = (key: string, value: string): string =>
@@ -41,7 +43,8 @@ export function buildWorkerCommand(
41
43
  const workspace = worker.worktree_path
42
44
  ? `Worker worktree: ${worker.worktree_path}.`
43
45
  : `Worker cwd: ${config.leader.cwd}.`;
44
- const prompt =
46
+ const initialPrompt =
47
+ promptOverride ??
45
48
  [
46
49
  `You are ${worker.id} in gjc team ${config.team_name}.`,
47
50
  `Team state root: ${config.state_root}.`,
@@ -50,8 +53,9 @@ export function buildWorkerCommand(
50
53
  "Before implementation, claim your worker-owned task and treat the claimed task record as the source of truth. Do not implement directly from the broad team brief.",
51
54
  `Before claiming work, send startup ACK: gjc team api worker-startup-ack --input '{"team_name":"${config.team_name}","worker_id":"${worker.id}","protocol_version":"1"}' --json.`,
52
55
  "Use gjc team api update-worker-status to report task-local activity, then claim-task/transition-task-status with this worker id; keep heartbeat current during long work, record completion_evidence (summary plus a passed command or verified inspection/artifact item) before completed, and do not mutate leader-owned goal state.",
53
- ]
54
- .join("\n")
56
+ ].join("\n");
57
+ const prompt =
58
+ initialPrompt
55
59
  .replace(/[\uFEFF\u200B]/g, "")
56
60
  .replace(/\r?\n+/g, " ")
57
61
  .trim() || `Worker ${worker.id} ready.`;
@@ -66,6 +70,10 @@ export function buildWorkerCommand(
66
70
  envAssignment("GJC_TEAM_DISPLAY_NAME", config.display_name),
67
71
  envAssignment(SPAWN_PROVENANCE_ENV, config.leader.session_id.trim() || config.team_name),
68
72
  ...(worker.worktree_path ? [envAssignment("GJC_TEAM_WORKTREE_PATH", worker.worktree_path)] : []),
73
+ envAssignment(
74
+ "GJC_TEAM_WORKER_MEMORY_GUARD_PATH",
75
+ workerMemoryGuardLedgerPath(path.join(config.state_root, config.team_name), worker.id),
76
+ ),
69
77
  ];
70
78
  const joined = envLines.join(" ");
71
79
  const clearInheritedSession = config.gjc_session_id
@@ -146,6 +154,7 @@ async function initializeStateDirs(
146
154
  runtime: GjcTeamLaunchRuntime,
147
155
  dir: string,
148
156
  workers: GjcTeamWorker[],
157
+ platform: NodeJS.Platform,
149
158
  ): Promise<void> {
150
159
  await fs.mkdir(path.join(dir, "mailbox"), { recursive: true });
151
160
  for (const worker of workers) {
@@ -167,6 +176,14 @@ async function initializeStateDirs(
167
176
  turn_count: 0,
168
177
  alive: true,
169
178
  });
179
+ await runtime.writeJson(
180
+ workerMemoryGuardLedgerPath(dir, worker.id),
181
+ createInitialGjcTeamWorkerMemoryGuardLedger({
182
+ workerId: worker.id,
183
+ platform,
184
+ now: runtime.now(),
185
+ }),
186
+ );
170
187
  }
171
188
  await fs.mkdir(runtime.mailboxDirPath(dir, "leader-fixed"), { recursive: true });
172
189
  await runtime.writeJson(runtime.mailboxPath(dir, "leader-fixed"), { messages: [] });
@@ -217,6 +234,18 @@ export async function startGjcTeamLaunch(
217
234
  await runtime.rollbackCreatedWorktrees(workers);
218
235
  throw error;
219
236
  }
237
+ const tasksByOwner = new Map<string, string[]>();
238
+ for (const task of initialTasks) {
239
+ const owner = task.owner?.trim();
240
+ if (!owner) continue;
241
+ const assigned = tasksByOwner.get(owner) ?? [];
242
+ assigned.push(task.id);
243
+ tasksByOwner.set(owner, assigned);
244
+ }
245
+ const workersWithAssignments = workers.map(worker => ({
246
+ ...worker,
247
+ assigned_tasks: tasksByOwner.get(worker.id) ?? worker.assigned_tasks,
248
+ }));
220
249
  const config: GjcTeamConfig = {
221
250
  team_name: teamName,
222
251
  display_name: displayName,
@@ -242,11 +271,11 @@ export async function startGjcTeamLaunch(
242
271
  },
243
272
  leader_cwd: cwd,
244
273
  team_state_root: stateRoot,
245
- workers,
274
+ workers: workersWithAssignments,
246
275
  created_at: createdAt,
247
276
  updated_at: createdAt,
248
277
  };
249
- await initializeStateDirs(runtime, dir, config.workers);
278
+ await initializeStateDirs(runtime, dir, config.workers, platform);
250
279
  await runtime.writeJson(path.join(dir, "config.json"), config);
251
280
  await runtime.writeJson(path.join(dir, "manifest.v2.json"), {
252
281
  version: 2,