@gajae-code/coding-agent 0.12.0 → 0.12.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 (223) hide show
  1. package/CHANGELOG.md +82 -1
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/config/settings-schema.d.ts +54 -0
  4. package/dist/types/exa/mcp-client.d.ts +10 -1
  5. package/dist/types/exec/bash-executor.d.ts +45 -12
  6. package/dist/types/extensibility/extensions/types.d.ts +16 -2
  7. package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
  8. package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
  9. package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
  10. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
  11. package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
  12. package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
  13. package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
  14. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  15. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +18 -0
  16. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  17. package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
  18. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  19. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
  20. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
  21. package/dist/types/modes/acp/acp-agent.d.ts +2 -19
  22. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  23. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  24. package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
  25. package/dist/types/modes/components/settings-selector.d.ts +9 -0
  26. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  27. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  28. package/dist/types/reminders/star-reminder.d.ts +3 -13
  29. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  30. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  31. package/dist/types/sdk/broker/transport.d.ts +2 -0
  32. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  33. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
  34. package/dist/types/sdk/bus/config.d.ts +6 -2
  35. package/dist/types/sdk/bus/index.d.ts +29 -0
  36. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
  37. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  38. package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
  39. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  40. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  41. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  42. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  43. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
  44. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  45. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  46. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  47. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  48. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  49. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  50. package/dist/types/sdk/prompt-status.d.ts +23 -3
  51. package/dist/types/sdk/session.d.ts +6 -0
  52. package/dist/types/session/agent-session.d.ts +38 -2
  53. package/dist/types/session/artifacts.d.ts +8 -0
  54. package/dist/types/session/blob-store.d.ts +57 -4
  55. package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
  56. package/dist/types/session/session-manager.d.ts +23 -6
  57. package/dist/types/session/session-storage.d.ts +4 -0
  58. package/dist/types/session/streaming-output.d.ts +36 -7
  59. package/dist/types/setup/credential-import.d.ts +12 -0
  60. package/dist/types/skill-state/active-state.d.ts +12 -0
  61. package/dist/types/skill-state/workflow-hud.d.ts +6 -0
  62. package/dist/types/task/executor.d.ts +3 -2
  63. package/dist/types/task/index.d.ts +3 -1
  64. package/dist/types/task/provider-retry-status.d.ts +8 -0
  65. package/dist/types/task/receipt.d.ts +7 -1
  66. package/dist/types/task/types.d.ts +23 -0
  67. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  68. package/dist/types/tools/bash-interactive.d.ts +4 -1
  69. package/dist/types/tools/bash.d.ts +3 -1
  70. package/dist/types/tools/image-gen.d.ts +4 -0
  71. package/dist/types/tools/index.d.ts +4 -0
  72. package/dist/types/tools/output-meta.d.ts +23 -2
  73. package/dist/types/tools/path-utils.d.ts +1 -0
  74. package/dist/types/tools/read.d.ts +0 -6
  75. package/dist/types/tools/review.d.ts +5 -7
  76. package/dist/types/tools/subagent.d.ts +2 -0
  77. package/dist/types/utils/gh.d.ts +13 -0
  78. package/dist/types/utils/image-resize.d.ts +1 -0
  79. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  80. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  81. package/dist/types/web/search/providers/xai.d.ts +2 -0
  82. package/package.json +10 -8
  83. package/scripts/acp-conformance-agent.ts +218 -0
  84. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  85. package/scripts/generate-sdk-operation-inventory.ts +3 -0
  86. package/scripts/resident-memory-bench.ts +1341 -0
  87. package/scripts/run-acp-conformance.ts +285 -0
  88. package/src/async/job-manager.ts +78 -20
  89. package/src/cli/fast-help.ts +1 -1
  90. package/src/cli/plugin-cli.ts +176 -18
  91. package/src/commands/team.ts +19 -1
  92. package/src/config/file-lock.ts +11 -2
  93. package/src/config/settings-schema.ts +51 -0
  94. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  95. package/src/coordinator-mcp/server.ts +5 -12
  96. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
  97. package/src/defaults/gjc/skills/ralplan/SKILL.md +19 -16
  98. package/src/defaults/gjc/skills/team/SKILL.md +2 -0
  99. package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -24
  100. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  101. package/src/defaults/gjc-defaults.ts +4 -4
  102. package/src/exa/mcp-client.ts +12 -3
  103. package/src/exec/bash-executor.ts +144 -27
  104. package/src/extensibility/extensions/runner.ts +15 -0
  105. package/src/extensibility/extensions/types.ts +18 -1
  106. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  107. package/src/extensibility/gjc-plugins/index.ts +19 -2
  108. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  109. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  110. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  111. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  112. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  113. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  114. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  115. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  116. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  117. package/src/extensibility/gjc-plugins/types.ts +141 -0
  118. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  119. package/src/gjc-runtime/launch-tmux.ts +25 -6
  120. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  121. package/src/gjc-runtime/ralplan-runtime.ts +411 -17
  122. package/src/gjc-runtime/team-launch.ts +8 -0
  123. package/src/gjc-runtime/team-runtime.ts +140 -32
  124. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
  126. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  127. package/src/gjc-runtime/ultragoal-runtime.ts +401 -611
  128. package/src/gjc-runtime/workflow-manifest.generated.json +8 -99
  129. package/src/gjc-runtime/workflow-manifest.ts +4 -68
  130. package/src/internal-urls/docs-index.generated.ts +15 -15
  131. package/src/modes/DESIGN.md +51 -0
  132. package/src/modes/acp/acp-agent.ts +298 -186
  133. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  134. package/src/modes/components/notifications-settings-editor.ts +12 -1
  135. package/src/modes/components/oauth-selector.ts +68 -30
  136. package/src/modes/components/session-observer-overlay.ts +305 -86
  137. package/src/modes/components/settings-selector.ts +56 -5
  138. package/src/modes/components/status-line/gh.ts +16 -0
  139. package/src/modes/components/tool-status-header.ts +3 -14
  140. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  141. package/src/modes/controllers/command-controller.ts +4 -0
  142. package/src/modes/controllers/extension-ui-controller.ts +7 -0
  143. package/src/modes/controllers/selector-controller.ts +45 -0
  144. package/src/modes/runtime-init.ts +3 -0
  145. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  146. package/src/prompts/system/eager-todo.md +8 -4
  147. package/src/prompts/system/system-prompt.md +11 -4
  148. package/src/prompts/tools/bash.md +5 -5
  149. package/src/prompts/tools/search-tool-bm25.md +5 -0
  150. package/src/prompts/tools/task.md +1 -1
  151. package/src/reminders/star-reminder.ts +4 -42
  152. package/src/runtime-mcp/manager.ts +19 -4
  153. package/src/sdk/acp/adapter.ts +22 -6
  154. package/src/sdk/broker/transport.ts +35 -3
  155. package/src/sdk/bus/attachment-registry.ts +5 -1
  156. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  157. package/src/sdk/bus/chat-daemon-control.ts +6 -3
  158. package/src/sdk/bus/config.ts +18 -2
  159. package/src/sdk/bus/index.ts +661 -126
  160. package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
  161. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  162. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  163. package/src/sdk/bus/reconciliation-store.ts +95 -16
  164. package/src/sdk/bus/rich-draft.ts +1 -1
  165. package/src/sdk/bus/rich-render.ts +2 -2
  166. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  167. package/src/sdk/bus/telegram-cli.ts +33 -8
  168. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  169. package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
  170. package/src/sdk/bus/telegram-daemon.ts +916 -23
  171. package/src/sdk/bus/telegram-reference.ts +40 -3
  172. package/src/sdk/bus/threaded-render.ts +48 -3
  173. package/src/sdk/bus/topic-registry.ts +20 -0
  174. package/src/sdk/host/query/revision-store.ts +36 -2
  175. package/src/sdk/host/reverse-leases.ts +44 -7
  176. package/src/sdk/prompt-status.ts +16 -3
  177. package/src/sdk/protocol/operation-inventory.generated.json +22 -0
  178. package/src/sdk/session.ts +88 -9
  179. package/src/session/agent-session.ts +822 -172
  180. package/src/session/artifacts.ts +22 -0
  181. package/src/session/auth-broker-config.ts +10 -3
  182. package/src/session/blob-store.ts +915 -24
  183. package/src/session/internal/managed-session-scope.ts +44 -6
  184. package/src/session/internal/managed-session-storage.ts +48 -6
  185. package/src/session/session-manager.ts +1614 -435
  186. package/src/session/session-storage.ts +9 -1
  187. package/src/session/streaming-output.ts +238 -68
  188. package/src/setup/credential-import.ts +75 -13
  189. package/src/skill-state/active-state.ts +32 -0
  190. package/src/skill-state/workflow-hud.ts +17 -0
  191. package/src/skill-state/workflow-mutation-guard.ts +496 -2
  192. package/src/task/executor.ts +122 -39
  193. package/src/task/index.ts +23 -14
  194. package/src/task/provider-retry-status.ts +16 -0
  195. package/src/task/receipt.ts +23 -7
  196. package/src/task/render.ts +6 -2
  197. package/src/task/types.ts +108 -0
  198. package/src/tools/ask-answer-registry.ts +12 -6
  199. package/src/tools/ask.ts +86 -55
  200. package/src/tools/ast-edit.ts +1 -0
  201. package/src/tools/ast-grep.ts +1 -0
  202. package/src/tools/bash-interactive.ts +8 -3
  203. package/src/tools/bash.ts +503 -60
  204. package/src/tools/find.ts +1 -0
  205. package/src/tools/image-gen.ts +29 -5
  206. package/src/tools/index.ts +4 -0
  207. package/src/tools/output-meta.ts +108 -14
  208. package/src/tools/path-utils.ts +3 -2
  209. package/src/tools/read.ts +18 -0
  210. package/src/tools/review.ts +5 -7
  211. package/src/tools/search.ts +1 -0
  212. package/src/tools/subagent-render.ts +6 -2
  213. package/src/tools/subagent.ts +6 -0
  214. package/src/tools/telegram-send.ts +6 -3
  215. package/src/utils/gh.ts +39 -0
  216. package/src/utils/image-resize.ts +3 -0
  217. package/src/web/search/providers/anthropic.ts +4 -4
  218. package/src/web/search/providers/kimi.ts +15 -5
  219. package/src/web/search/providers/searxng.ts +47 -4
  220. package/src/web/search/providers/xai.ts +7 -2
  221. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  222. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  223. package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
@@ -25,7 +25,9 @@
25
25
  * Reference: https://docs.searxng.org/dev/search_api.html
26
26
  */
27
27
 
28
+ import * as path from "node:path";
28
29
  import type { AuthStorage } from "@gajae-code/ai";
30
+ import { $credentialEnv, parseEnvFile } from "@gajae-code/utils";
29
31
 
30
32
  import { settings } from "../../../config/settings";
31
33
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
@@ -74,6 +76,47 @@ interface SearXNGAuth {
74
76
  }
75
77
 
76
78
  /** Find SearXNG endpoint from settings or environment. */
79
+ /**
80
+ * SearXNG environment value, from trusted sources only, preserving an
81
+ * intentionally empty setting.
82
+ *
83
+ * These four names decide where search requests go and what credential they
84
+ * carry, and `$env` merges the caller's `cwd/.env` into `process.env`, so a
85
+ * repository could otherwise redirect the search and hand over the basic-auth
86
+ * material.
87
+ *
88
+ * `$credentialEnv` is the resolver for that boundary, but it collapses an empty
89
+ * value to `undefined` — and SearXNG basic auth treats "set but empty" as
90
+ * meaningful (`alice:` and `:s3cret` are both valid). So an empty value is
91
+ * recovered explicitly, and only when the project `.env` is not what set it.
92
+ */
93
+ function trustedSearxngEnv(name: string): string | null {
94
+ const trusted = $credentialEnv(name);
95
+ if (trusted !== undefined) return trusted;
96
+
97
+ const raw = process.env[name];
98
+ if (raw === undefined || raw.trim().length > 0) return null;
99
+ // Empty-but-set. Honour it unless the project `.env` is the source.
100
+ const projectValue = parseEnvFile(path.join(process.cwd(), ".env"))[name];
101
+ if (projectValue !== undefined && projectValue.trim().length === 0) return null;
102
+ return "";
103
+ }
104
+
105
+ /** Test seam: the SearXNG endpoint and auth material as resolved from trusted env. */
106
+ export function resolveSearxngConfigForTest(): {
107
+ endpoint: string | null;
108
+ token: string | null;
109
+ basicUsername: string | null;
110
+ basicPassword: string | null;
111
+ } {
112
+ return {
113
+ endpoint: findEndpoint(),
114
+ token: findToken(),
115
+ basicUsername: findBasicUsername(),
116
+ basicPassword: findBasicPassword(),
117
+ };
118
+ }
119
+
77
120
  function findEndpoint(): string | null {
78
121
  try {
79
122
  const endpoint = settings.get("searxng.endpoint");
@@ -81,7 +124,7 @@ function findEndpoint(): string | null {
81
124
  } catch {
82
125
  // Settings not initialized yet
83
126
  }
84
- return process.env.SEARXNG_ENDPOINT ?? null;
127
+ return trustedSearxngEnv("SEARXNG_ENDPOINT");
85
128
  }
86
129
 
87
130
  /** Find SearXNG bearer token from settings or environment. */
@@ -92,7 +135,7 @@ function findToken(): string | null {
92
135
  } catch {
93
136
  // Settings not initialized yet
94
137
  }
95
- return process.env.SEARXNG_TOKEN ?? null;
138
+ return trustedSearxngEnv("SEARXNG_TOKEN");
96
139
  }
97
140
 
98
141
  /** Find SearXNG Basic auth username from settings or environment. */
@@ -103,7 +146,7 @@ function findBasicUsername(): string | null {
103
146
  } catch {
104
147
  // Settings not initialized yet
105
148
  }
106
- return process.env.SEARXNG_BASIC_USERNAME ?? null;
149
+ return trustedSearxngEnv("SEARXNG_BASIC_USERNAME");
107
150
  }
108
151
 
109
152
  /** Find SearXNG Basic auth password from settings or environment. */
@@ -114,7 +157,7 @@ function findBasicPassword(): string | null {
114
157
  } catch {
115
158
  // Settings not initialized yet
116
159
  }
117
- return process.env.SEARXNG_BASIC_PASSWORD ?? null;
160
+ return trustedSearxngEnv("SEARXNG_BASIC_PASSWORD");
118
161
  }
119
162
 
120
163
  /** Build the RFC 7617 Basic auth credential using UTF-8 bytes. */
@@ -5,7 +5,7 @@
5
5
  * Endpoint: POST https://api.x.ai/v1/responses
6
6
  */
7
7
  import type { AuthStorage } from "@gajae-code/ai";
8
- import { $env } from "@gajae-code/utils";
8
+ import { $credentialEnv, $env } from "@gajae-code/utils";
9
9
  import type { SearchCitation, SearchResponse, SearchSource, SearchUsage } from "../../../web/search/types";
10
10
  import { SearchProviderError } from "../../../web/search/types";
11
11
  import type { SearchParams } from "./base";
@@ -71,8 +71,13 @@ function getModel(): string {
71
71
  return asTrimmed($env.PI_XAI_WEB_SEARCH_MODEL) ?? asTrimmed($env.XAI_WEB_SEARCH_MODEL) ?? DEFAULT_MODEL;
72
72
  }
73
73
 
74
+ /** Test seam: the xAI search base URL as resolved from trusted env. */
75
+ export function resolveXaiSearchBaseUrlForTest(): string {
76
+ return getBaseUrl();
77
+ }
78
+
74
79
  function getBaseUrl(): string {
75
- return asTrimmed($env.XAI_SEARCH_BASE_URL) ?? DEFAULT_BASE_URL;
80
+ return asTrimmed($credentialEnv("XAI_SEARCH_BASE_URL")) ?? DEFAULT_BASE_URL;
76
81
  }
77
82
 
78
83
  function responsesEndpoint(): string {
@@ -1,23 +0,0 @@
1
- import { type JsonObject, type UltragoalGoal, type UltragoalPipelineOverlapReceipt, type UltragoalPlan } from "./ultragoal-runtime";
2
- export declare function requirePipelineStartable(plan: UltragoalPlan, prior: UltragoalGoal, next: UltragoalGoal): void;
3
- export declare function startUltragoalPipelineOverlap(input: {
4
- cwd: string;
5
- priorGoalId: string;
6
- nextGoalId: string;
7
- reviewHandles: JsonObject | JsonObject[];
8
- qaHandles: JsonObject | JsonObject[];
9
- implementationHandle: JsonObject;
10
- }): Promise<UltragoalPipelineOverlapReceipt>;
11
- export declare function joinUltragoalPipelineOverlap(input: {
12
- cwd: string;
13
- overlapId: string;
14
- reviewResult: JsonObject;
15
- qaResult: JsonObject;
16
- }): Promise<UltragoalPipelineOverlapReceipt>;
17
- export declare function rebaselineUltragoalPipelineOverlap(input: {
18
- cwd: string;
19
- overlapId: string;
20
- goalId: string;
21
- evidence: string;
22
- targetState: JsonObject;
23
- }): Promise<UltragoalPipelineOverlapReceipt>;
@@ -1,30 +0,0 @@
1
- # Ultragoal Pipeline & Validation-Batch Contracts Fragment
2
-
3
- Internal Ultragoal sub-skill fragment (`kind: "skill-fragment"`, parent skill `ultragoal`, installed at `skill-fragments/ultragoal/pipeline-validation-contracts.md`). The Ultragoal leader loads it on demand before operating a pipeline overlap or checkpointing a validation-batch member; it is never user-facing, not slash-command discoverable, and never resolvable through `skill://`. The runtime enforces every rule below verbatim and fails closed.
4
-
5
- ## Runtime-backed pipeline overlap lifecycle
6
-
7
- Use the lifecycle commands exactly when runtime metadata proves safety:
8
-
9
- ```sh
10
- gjc ultragoal start-pipeline-overlap --prior-goal-id G001 --next-goal-id G002 --review-handles-json '<json>' --qa-handles-json '<json>' --implementation-handle-json '<json>' --json
11
- gjc ultragoal join-pipeline-overlap --overlap-id <id> --review-result-json '<json>' --qa-result-json '<json>' --json
12
- gjc ultragoal rebaseline-pipeline-overlap --overlap-id <id> --goal-id G002 --evidence "<evidence>" --target-state-json '<json>' --json
13
- ```
14
-
15
- Runtime-backed pipelining is deliberately narrow:
16
-
17
- - At most one eligible next goal may overlap the current goal's review/QA join window.
18
- - G(N) remains active until `join-pipeline-overlap` records a clean join; do not checkpoint G(N) complete before clean join evidence exists.
19
- - `start-pipeline-overlap` must fail closed for missing metadata, one-sided independence, shared target files/surfaces, stale metadata hashes, missing handles, another open overlap, or per-story mode.
20
- - `join-pipeline-overlap` must fail closed for missing lane evidence or unresolved blockers. Continue G(N+1) only when structured blocker footprints are disjoint from G(N+1) targets; otherwise quarantine and re-baseline with `rebaseline-pipeline-overlap` before G(N+1) can complete.
21
- - Complete checkpoints must fail closed for open overlaps, missing clean joins, stale metadata, quarantined next goals, shared or unattributable change-set paths, and any missing pipeline evidence.
22
- - After a crash or lost live handles, the leader reruns review/QA lanes and joins with replacement evidence when metadata hashes still match; otherwise quarantine and re-baseline. Ultragoal must not auto-start G(N+2) during recovery.
23
-
24
- ## Validation-batch checkpoint contract
25
-
26
- - **Non-final members** checkpoint `complete` with a single top-level `deferredToBatch` quality gate (kind `validation-batch-deferred`): targeted verification, ai-slop-cleaner pass, and a rerun iteration, plus a cumulative-since-base change set. A `deferredToBatch` gate must NOT contain `architectReview`, `executorQa`, or `validationBatchClose` — deferring never manufactures fake review approvals.
27
- - **The final member** (`finalGoalId`) checkpoints `complete` with the normal full strict gate PLUS a top-level `validationBatchClose` proof that covers all member IDs, member metadata hashes, member receipt/checkpoint-ledger-event IDs, per-member change-set hashes, and union change-set coverage. The final close only starts once every non-final member is already `complete` with a structurally fresh deferred receipt (out-of-order close is rejected).
28
- - Close state is append-only proof: it lives in the final member's checkpoint receipt and matching `goal_checkpointed` ledger row only. Never stamp `closedReceiptId`/`closedAt` or any close-state field onto member goals, and never append a separate close ledger event.
29
- - Change sets are cumulative-since-base: each member's `changeSet.paths` is the whole-worktree diff vs base (`cumulativeFromBase: true`), `memberGoalId` is a label not a per-path attribution, and `unionChangeSet.paths` carries no per-goal attribution.
30
- - Batch invalidation is fail-closed: steering mutations that would invalidate a batch are rejected while any member holds a fresh deferred receipt.
@@ -1,306 +0,0 @@
1
- import * as crypto from "node:crypto";
2
- import {
3
- appendLedger,
4
- collectPipelineBlockerFootprints,
5
- currentUltragoalSessionId,
6
- getUltragoalPaths,
7
- handleIdsFromValue,
8
- hashPipelineMetadata,
9
- isSubstantiveEvidence,
10
- type JsonObject,
11
- nonEmptyString,
12
- openPipelineOverlap,
13
- pipelinePeer,
14
- readUltragoalLedger,
15
- readUltragoalPlan,
16
- requireCoveredHandles,
17
- requireFreshPipelineMetadata,
18
- requireJsonObjectOrArrayValue,
19
- requireJsonObjectValue,
20
- requireNonEmptyPipelineTargets,
21
- resultHandleIds,
22
- stringArray,
23
- targetsAreDisjoint,
24
- targetsOverlap,
25
- type UltragoalGoal,
26
- type UltragoalLedgerEvent,
27
- type UltragoalPipelineLedgerEventName,
28
- type UltragoalPipelineOverlapReceipt,
29
- type UltragoalPipelineOverlapState,
30
- type UltragoalPlan,
31
- validateValidationBatchPipelineExclusion,
32
- writePlan,
33
- } from "./ultragoal-runtime";
34
-
35
- export function requirePipelineStartable(plan: UltragoalPlan, prior: UltragoalGoal, next: UltragoalGoal): void {
36
- if (plan.gjcGoalMode !== "aggregate")
37
- throw new Error("pipeline overlap is supported only for aggregate ultragoal mode");
38
- if (openPipelineOverlap(plan))
39
- throw new Error("Cannot start pipeline overlap because another overlap is already open");
40
- if (prior.status !== "active")
41
- throw new Error(`Prior goal ${prior.id} must be active before pipeline overlap starts`);
42
- if (next.status !== "pending" && next.status !== "failed")
43
- throw new Error(`Next goal ${next.id} must be pending or retryable failed`);
44
- validateValidationBatchPipelineExclusion(prior);
45
- validateValidationBatchPipelineExclusion(next);
46
- if (prior.validationBatch || next.validationBatch)
47
- throw new Error("pipeline overlap cannot start for validation batch goals");
48
- const priorMetadata = requireFreshPipelineMetadata(prior);
49
- const nextMetadata = requireFreshPipelineMetadata(next);
50
- if (!priorMetadata.eligible || !nextMetadata.eligible)
51
- throw new Error("pipeline overlap requires eligible original-plan metadata on both goals");
52
- if (!priorMetadata.independentOf.includes(next.id) || !nextMetadata.independentOf.includes(prior.id)) {
53
- throw new Error("pipeline overlap requires symmetric original independence");
54
- }
55
- if (!targetsAreDisjoint(priorMetadata.targets, nextMetadata.targets))
56
- throw new Error("pipeline overlap requires disjoint files and surfaces");
57
- }
58
-
59
- function pipelineEventRefs(prior: UltragoalGoal, next: UltragoalGoal): JsonObject {
60
- return {
61
- priorMetadataHash: prior.pipelineMetadata?.metadataHash,
62
- nextMetadataHash: next.pipelineMetadata?.metadataHash,
63
- priorTargets: prior.pipelineMetadata?.targets,
64
- nextTargets: next.pipelineMetadata?.targets,
65
- };
66
- }
67
-
68
- function pipelineReceipt(
69
- cwd: string,
70
- event: string,
71
- overlapId: string,
72
- prior: UltragoalGoal,
73
- next: UltragoalGoal,
74
- ): UltragoalPipelineOverlapReceipt {
75
- const paths = getUltragoalPaths(cwd, currentUltragoalSessionId(cwd));
76
- return {
77
- ok: true,
78
- event,
79
- overlap_id: overlapId,
80
- prior_goal_id: prior.id,
81
- next_goal_id: next.id,
82
- status: next.pipelineMetadata?.overlap,
83
- next_goal_status: next.status,
84
- goals_path: paths.goalsPath,
85
- ledger_path: paths.ledgerPath,
86
- };
87
- }
88
-
89
- export async function startUltragoalPipelineOverlap(input: {
90
- cwd: string;
91
- priorGoalId: string;
92
- nextGoalId: string;
93
- reviewHandles: JsonObject | JsonObject[];
94
- qaHandles: JsonObject | JsonObject[];
95
- implementationHandle: JsonObject;
96
- }): Promise<UltragoalPipelineOverlapReceipt> {
97
- const plan = await readUltragoalPlan(input.cwd);
98
- if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
99
- const prior = plan.goals.find(goal => goal.id === input.priorGoalId);
100
- const next = plan.goals.find(goal => goal.id === input.nextGoalId);
101
- if (!prior || !next) throw new Error("start-pipeline-overlap requires existing prior and next goal ids");
102
- const reviewHandles = requireJsonObjectOrArrayValue(input.reviewHandles, "review handles");
103
- const qaHandles = requireJsonObjectOrArrayValue(input.qaHandles, "QA handles");
104
- requireJsonObjectValue(input.implementationHandle, "implementation handle");
105
- requirePipelineStartable(plan, prior, next);
106
- const now = new Date().toISOString();
107
- const overlapId = `pipeline-${crypto.randomUUID()}`;
108
- const priorMetadata = requireFreshPipelineMetadata(prior);
109
- const nextMetadata = requireFreshPipelineMetadata(next);
110
- const priorOpenMetadata = {
111
- ...priorMetadata,
112
- overlap: "open" as const,
113
- overlapId,
114
- priorGoalId: prior.id,
115
- nextGoalId: next.id,
116
- };
117
- const nextOpenMetadata = {
118
- ...nextMetadata,
119
- overlap: "open" as const,
120
- overlapId,
121
- priorGoalId: prior.id,
122
- nextGoalId: next.id,
123
- };
124
- prior.pipelineMetadata = { ...priorOpenMetadata, metadataHash: hashPipelineMetadata(priorOpenMetadata) };
125
- next.pipelineMetadata = { ...nextOpenMetadata, metadataHash: hashPipelineMetadata(nextOpenMetadata) };
126
- prior.updatedAt = now;
127
- next.updatedAt = now;
128
- next.status = "active";
129
- next.startedAt = next.startedAt ?? now;
130
- plan.updatedAt = now;
131
- await writePlan(input.cwd, plan);
132
- const refs = pipelineEventRefs(prior, next);
133
- const expectedReviewHandleIds = handleIdsFromValue(reviewHandles, "review");
134
- const expectedQaHandleIds = handleIdsFromValue(qaHandles, "QA");
135
- await appendLedger(input.cwd, {
136
- event: "pipeline_overlap_started",
137
- eventId: crypto.randomUUID(),
138
- timestamp: now,
139
- schemaVersion: 1,
140
- overlapId,
141
- priorGoalId: prior.id,
142
- nextGoalId: next.id,
143
- reviewHandles,
144
- reviewHandleIds: expectedReviewHandleIds,
145
- qaHandles,
146
- qaHandleIds: expectedQaHandleIds,
147
- implementationHandle: input.implementationHandle,
148
- ...refs,
149
- });
150
- await appendLedger(input.cwd, { event: "goal_started", goalId: next.id, pipelineOverlapId: overlapId });
151
- return pipelineReceipt(input.cwd, "pipeline_overlap_started", overlapId, prior, next);
152
- }
153
-
154
- function resultStatus(value: JsonObject, fieldName: string): string {
155
- const status = nonEmptyString(value.status) ?? nonEmptyString(value.verdict) ?? nonEmptyString(value.result);
156
- if (!status) throw new Error(`${fieldName} requires status, verdict, or result`);
157
- return status.toLowerCase();
158
- }
159
-
160
- function requireCleanPipelineResult(result: JsonObject, expectedHandleIds: readonly string[], fieldName: string): void {
161
- const status = resultStatus(result, fieldName);
162
- if (!["passed", "pass", "approved", "clear"].includes(status)) throw new Error(`${fieldName} did not pass`);
163
- const evidence = nonEmptyString(result.evidence);
164
- if (!evidence || !isSubstantiveEvidence(evidence)) throw new Error(`${fieldName} requires substantive evidence`);
165
- requireCoveredHandles(expectedHandleIds, resultHandleIds(result, fieldName), fieldName);
166
- if (collectPipelineBlockerFootprints(result, fieldName).length > 0)
167
- throw new Error(`${fieldName} cannot clean-join with blockers`);
168
- }
169
-
170
- function pipelineStartEventHandleIds(
171
- ledger: UltragoalLedgerEvent[],
172
- overlapId: string,
173
- ): { review: string[]; qa: string[] } {
174
- const event = ledger.find(row => row.event === "pipeline_overlap_started" && row.overlapId === overlapId) as
175
- | JsonObject
176
- | undefined;
177
- if (!event) throw new Error(`No pipeline_overlap_started event found for ${overlapId}`);
178
- const review =
179
- stringArray(event.reviewHandleIds) ??
180
- handleIdsFromValue(requireJsonObjectOrArrayValue(event.reviewHandles, "review handles"), "review");
181
- const qa =
182
- stringArray(event.qaHandleIds) ??
183
- handleIdsFromValue(requireJsonObjectOrArrayValue(event.qaHandles, "QA handles"), "QA");
184
- return { review, qa };
185
- }
186
-
187
- export async function joinUltragoalPipelineOverlap(input: {
188
- cwd: string;
189
- overlapId: string;
190
- reviewResult: JsonObject;
191
- qaResult: JsonObject;
192
- }): Promise<UltragoalPipelineOverlapReceipt> {
193
- const plan = await readUltragoalPlan(input.cwd);
194
- if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
195
- const overlap = openPipelineOverlap(plan);
196
- if (!overlap || overlap.overlapId !== input.overlapId)
197
- throw new Error(`No open pipeline overlap found for ${input.overlapId}`);
198
- const { prior, next, overlapId } = overlap;
199
- const nextMetadata = requireFreshPipelineMetadata(next);
200
- const ledger = await readUltragoalLedger(input.cwd);
201
- const expectedHandles = pipelineStartEventHandleIds(ledger, overlapId);
202
- const reviewBlockers = collectPipelineBlockerFootprints(input.reviewResult, "review result");
203
- const qaBlockers = collectPipelineBlockerFootprints(input.qaResult, "QA result");
204
- const blockerFootprints = [...reviewBlockers, ...qaBlockers];
205
- let state: UltragoalPipelineOverlapState;
206
- let event: UltragoalPipelineLedgerEventName;
207
- if (blockerFootprints.length === 0) {
208
- try {
209
- requireCleanPipelineResult(input.reviewResult, expectedHandles.review, "review result");
210
- requireCleanPipelineResult(input.qaResult, expectedHandles.qa, "QA result");
211
- state = "joined_clean";
212
- event = "pipeline_overlap_joined";
213
- } catch {
214
- state = "quarantine_required";
215
- event = "pipeline_overlap_quarantined";
216
- }
217
- } else if (blockerFootprints.every(footprint => !targetsOverlap(footprint, nextMetadata.targets))) {
218
- state = "blocked_disjoint_continue";
219
- event = "pipeline_overlap_joined";
220
- } else {
221
- state = "quarantine_required";
222
- event = "pipeline_overlap_quarantined";
223
- }
224
- const now = new Date().toISOString();
225
- for (const goal of [prior, next]) {
226
- const metadata = requireFreshPipelineMetadata(goal);
227
- const joinedMetadata = { ...metadata, overlap: state, blockerFootprints };
228
- goal.pipelineMetadata = { ...joinedMetadata, metadataHash: hashPipelineMetadata(joinedMetadata) };
229
- goal.updatedAt = now;
230
- }
231
- if (state === "quarantine_required") next.status = "blocked";
232
- plan.updatedAt = now;
233
- await writePlan(input.cwd, plan);
234
- await appendLedger(input.cwd, {
235
- event,
236
- eventId: crypto.randomUUID(),
237
- timestamp: now,
238
- schemaVersion: 1,
239
- overlapId,
240
- priorGoalId: prior.id,
241
- nextGoalId: next.id,
242
- status: state,
243
- reviewResult: input.reviewResult,
244
- qaResult: input.qaResult,
245
- blockerFootprints,
246
- ...pipelineEventRefs(prior, next),
247
- });
248
- return pipelineReceipt(input.cwd, event, overlapId, prior, next);
249
- }
250
-
251
- export async function rebaselineUltragoalPipelineOverlap(input: {
252
- cwd: string;
253
- overlapId: string;
254
- goalId: string;
255
- evidence: string;
256
- targetState: JsonObject;
257
- }): Promise<UltragoalPipelineOverlapReceipt> {
258
- const plan = await readUltragoalPlan(input.cwd);
259
- if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
260
- const goal = plan.goals.find(item => item.id === input.goalId);
261
- if (!goal) throw new Error(`No ultragoal goal found for ${input.goalId}.`);
262
- const evidence = input.evidence.trim();
263
- if (!isSubstantiveEvidence(evidence)) throw new Error("rebaseline-pipeline-overlap requires substantive evidence");
264
- const targetState = requireNonEmptyPipelineTargets(input.targetState, "target state");
265
- const metadata = requireFreshPipelineMetadata(goal);
266
- if (metadata.overlap !== "quarantine_required" || metadata.overlapId !== input.overlapId) {
267
- throw new Error(`Goal ${goal.id} is not quarantined for overlap ${input.overlapId}`);
268
- }
269
- for (const footprint of metadata.blockerFootprints ?? []) {
270
- if (targetsOverlap(footprint, targetState))
271
- throw new Error("rebaseline-pipeline-overlap target state overlaps unresolved blocker footprints");
272
- }
273
- const now = new Date().toISOString();
274
- const rebaselinedMetadata = { ...metadata, overlap: "rebaseline_complete" as const, targets: targetState };
275
- goal.pipelineMetadata = { ...rebaselinedMetadata, metadataHash: hashPipelineMetadata(rebaselinedMetadata) };
276
- goal.status = "active";
277
- goal.evidence = evidence;
278
- goal.updatedAt = now;
279
- plan.updatedAt = now;
280
- await writePlan(input.cwd, plan);
281
- const peer = pipelinePeer(plan, metadata);
282
- await appendLedger(input.cwd, {
283
- event: "pipeline_overlap_rebaselined",
284
- eventId: crypto.randomUUID(),
285
- timestamp: now,
286
- schemaVersion: 1,
287
- overlapId: input.overlapId,
288
- priorGoalId: metadata.priorGoalId ?? peer?.id ?? "",
289
- nextGoalId: metadata.nextGoalId ?? goal.id,
290
- goalId: goal.id,
291
- evidence,
292
- targetState,
293
- metadataHash: goal.pipelineMetadata.metadataHash,
294
- });
295
- const paths = getUltragoalPaths(input.cwd, currentUltragoalSessionId(input.cwd));
296
- return {
297
- ok: true,
298
- event: "pipeline_overlap_rebaselined",
299
- overlap_id: input.overlapId,
300
- prior_goal_id: metadata.priorGoalId ?? peer?.id ?? "",
301
- goal_id: goal.id,
302
- status: goal.pipelineMetadata.overlap,
303
- goals_path: paths.goalsPath,
304
- ledger_path: paths.ledgerPath,
305
- };
306
- }