@gajae-code/coding-agent 0.5.0 → 0.5.2

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 (194) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +1 -1
  3. package/dist/types/async/job-manager.d.ts +26 -0
  4. package/dist/types/cli/args.d.ts +1 -0
  5. package/dist/types/cli/list-models.d.ts +6 -0
  6. package/dist/types/cli/setup-cli.d.ts +8 -1
  7. package/dist/types/commands/gc.d.ts +26 -0
  8. package/dist/types/commands/setup.d.ts +7 -0
  9. package/dist/types/config/file-lock-gc.d.ts +5 -0
  10. package/dist/types/config/file-lock.d.ts +29 -0
  11. package/dist/types/config/model-registry.d.ts +4 -0
  12. package/dist/types/config/models-config-schema.d.ts +5 -0
  13. package/dist/types/config/settings-schema.d.ts +62 -0
  14. package/dist/types/coordinator/contract.d.ts +1 -1
  15. package/dist/types/defaults/gjc/extensions/grok-build/index.d.ts +1 -0
  16. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/index.d.ts +1 -0
  17. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +25 -0
  18. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +27 -0
  19. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.d.ts +8 -0
  20. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.d.ts +5 -0
  21. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +10 -0
  22. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.d.ts +2 -0
  23. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +2 -0
  24. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.d.ts +38 -0
  25. package/dist/types/defaults/gjc-grok-cli.d.ts +5 -0
  26. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  27. package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +35 -0
  28. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +103 -0
  29. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -0
  30. package/dist/types/gjc-runtime/deep-interview-state.d.ts +112 -0
  31. package/dist/types/gjc-runtime/gc-render.d.ts +6 -0
  32. package/dist/types/gjc-runtime/gc-runtime.d.ts +134 -0
  33. package/dist/types/gjc-runtime/ledger-event-renderer.d.ts +68 -0
  34. package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
  35. package/dist/types/gjc-runtime/team-gc.d.ts +7 -0
  36. package/dist/types/gjc-runtime/team-runtime.d.ts +5 -0
  37. package/dist/types/gjc-runtime/tmux-common.d.ts +11 -0
  38. package/dist/types/gjc-runtime/tmux-gc.d.ts +7 -0
  39. package/dist/types/gjc-runtime/tmux-sessions.d.ts +13 -0
  40. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +10 -0
  41. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
  42. package/dist/types/harness-control-plane/gc-adapter.d.ts +3 -0
  43. package/dist/types/harness-control-plane/owner.d.ts +7 -0
  44. package/dist/types/harness-control-plane/storage.d.ts +20 -0
  45. package/dist/types/modes/components/hook-selector.d.ts +7 -1
  46. package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
  47. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  48. package/dist/types/modes/interactive-mode.d.ts +1 -1
  49. package/dist/types/modes/rpc/rpc-mode.d.ts +72 -2
  50. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +13 -0
  51. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
  52. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +2 -0
  53. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
  54. package/dist/types/modes/theme/defaults/index.d.ts +302 -0
  55. package/dist/types/modes/theme/theme.d.ts +1 -0
  56. package/dist/types/modes/types.d.ts +1 -1
  57. package/dist/types/session/agent-session.d.ts +1 -1
  58. package/dist/types/session/blob-store.d.ts +39 -3
  59. package/dist/types/session/history-storage.d.ts +2 -2
  60. package/dist/types/session/session-manager.d.ts +10 -1
  61. package/dist/types/setup/credential-import.d.ts +79 -0
  62. package/dist/types/skill-state/workflow-hud.d.ts +14 -0
  63. package/dist/types/task/executor.d.ts +1 -0
  64. package/dist/types/task/render.d.ts +1 -1
  65. package/dist/types/tools/ask.d.ts +15 -1
  66. package/dist/types/tools/subagent-render.d.ts +7 -1
  67. package/dist/types/tools/subagent.d.ts +27 -0
  68. package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
  69. package/dist/types/web/search/index.d.ts +4 -4
  70. package/dist/types/web/search/provider.d.ts +16 -20
  71. package/dist/types/web/search/providers/base.d.ts +2 -1
  72. package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
  73. package/dist/types/web/search/types.d.ts +14 -2
  74. package/package.json +7 -7
  75. package/scripts/build-binary.ts +7 -0
  76. package/src/async/job-manager.ts +52 -0
  77. package/src/cli/args.ts +5 -0
  78. package/src/cli/auth-broker-cli.ts +1 -0
  79. package/src/cli/fast-help.ts +2 -0
  80. package/src/cli/list-models.ts +13 -1
  81. package/src/cli/setup-cli.ts +138 -3
  82. package/src/cli.ts +1 -0
  83. package/src/commands/gc.ts +22 -0
  84. package/src/commands/harness.ts +7 -3
  85. package/src/commands/setup.ts +5 -1
  86. package/src/commands/ultragoal.ts +3 -1
  87. package/src/config/file-lock-gc.ts +193 -0
  88. package/src/config/file-lock.ts +66 -10
  89. package/src/config/model-profile-activation.ts +15 -3
  90. package/src/config/model-profiles.ts +39 -30
  91. package/src/config/model-registry.ts +21 -1
  92. package/src/config/models-config-schema.ts +1 -0
  93. package/src/config/settings-schema.ts +62 -0
  94. package/src/coordinator/contract.ts +1 -0
  95. package/src/coordinator-mcp/server.ts +459 -3
  96. package/src/defaults/gjc/agent.models.grok-cli.yml +36 -0
  97. package/src/defaults/gjc/extensions/grok-build/index.ts +1 -0
  98. package/src/defaults/gjc/extensions/grok-build/package.json +7 -0
  99. package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +39 -0
  100. package/src/defaults/gjc/extensions/grok-cli-vendor/package.json +8 -0
  101. package/src/defaults/gjc/extensions/grok-cli-vendor/src/index.ts +1 -0
  102. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +155 -0
  103. package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +361 -0
  104. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.ts +57 -0
  105. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +99 -0
  106. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +50 -0
  107. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.ts +56 -0
  108. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +36 -0
  109. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.ts +44 -0
  110. package/src/defaults/gjc/skills/deep-interview/SKILL.md +131 -113
  111. package/src/defaults/gjc/skills/deep-interview/lateral-review-panel.md +49 -0
  112. package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
  113. package/src/defaults/gjc-defaults.ts +7 -0
  114. package/src/defaults/gjc-grok-cli.ts +22 -0
  115. package/src/extensibility/extensions/index.ts +1 -0
  116. package/src/extensibility/extensions/prefix-command-bridge.ts +128 -0
  117. package/src/gjc-runtime/deep-interview-recorder.ts +457 -0
  118. package/src/gjc-runtime/deep-interview-runtime.ts +18 -26
  119. package/src/gjc-runtime/deep-interview-state.ts +324 -0
  120. package/src/gjc-runtime/gc-render.ts +70 -0
  121. package/src/gjc-runtime/gc-runtime.ts +403 -0
  122. package/src/gjc-runtime/launch-tmux.ts +3 -4
  123. package/src/gjc-runtime/ledger-event-renderer.ts +164 -0
  124. package/src/gjc-runtime/ralplan-runtime.ts +232 -19
  125. package/src/gjc-runtime/state-renderer.ts +12 -3
  126. package/src/gjc-runtime/state-runtime.ts +48 -30
  127. package/src/gjc-runtime/state-writer.ts +254 -7
  128. package/src/gjc-runtime/team-gc.ts +49 -0
  129. package/src/gjc-runtime/team-runtime.ts +179 -2
  130. package/src/gjc-runtime/tmux-common.ts +14 -0
  131. package/src/gjc-runtime/tmux-gc.ts +177 -0
  132. package/src/gjc-runtime/tmux-sessions.ts +49 -1
  133. package/src/gjc-runtime/ultragoal-guard.ts +155 -0
  134. package/src/gjc-runtime/ultragoal-runtime.ts +1239 -31
  135. package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
  136. package/src/gjc-runtime/workflow-manifest.ts +12 -0
  137. package/src/harness-control-plane/gc-adapter.ts +184 -0
  138. package/src/harness-control-plane/owner.ts +14 -2
  139. package/src/harness-control-plane/rpc-adapter.ts +1 -1
  140. package/src/harness-control-plane/storage.ts +70 -0
  141. package/src/hooks/skill-state.ts +121 -2
  142. package/src/internal-urls/docs-index.generated.ts +22 -12
  143. package/src/lsp/defaults.json +1 -0
  144. package/src/main.ts +18 -3
  145. package/src/modes/acp/acp-agent.ts +4 -2
  146. package/src/modes/bridge/bridge-mode.ts +2 -1
  147. package/src/modes/components/history-search.ts +5 -2
  148. package/src/modes/components/hook-selector.ts +19 -0
  149. package/src/modes/components/model-selector.ts +51 -8
  150. package/src/modes/components/provider-onboarding-selector.ts +6 -1
  151. package/src/modes/components/status-line/segments.ts +1 -1
  152. package/src/modes/controllers/command-controller.ts +25 -6
  153. package/src/modes/controllers/extension-ui-controller.ts +3 -0
  154. package/src/modes/controllers/selector-controller.ts +81 -1
  155. package/src/modes/interactive-mode.ts +11 -1
  156. package/src/modes/rpc/rpc-mode.ts +266 -34
  157. package/src/modes/shared/agent-wire/command-dispatch.ts +281 -261
  158. package/src/modes/shared/agent-wire/deep-interview-gate.ts +30 -1
  159. package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
  160. package/src/modes/shared/agent-wire/session-registry.ts +109 -0
  161. package/src/modes/shared/agent-wire/unattended-action-policy.ts +24 -0
  162. package/src/modes/shared/agent-wire/unattended-run-controller.ts +23 -3
  163. package/src/modes/shared/agent-wire/unattended-session.ts +32 -2
  164. package/src/modes/theme/defaults/claude-code.json +100 -0
  165. package/src/modes/theme/defaults/codex.json +100 -0
  166. package/src/modes/theme/defaults/index.ts +6 -0
  167. package/src/modes/theme/defaults/opencode.json +102 -0
  168. package/src/modes/theme/theme.ts +2 -2
  169. package/src/modes/types.ts +1 -1
  170. package/src/prompts/agents/executor.md +5 -2
  171. package/src/sdk.ts +29 -4
  172. package/src/session/agent-session.ts +99 -19
  173. package/src/session/blob-store.ts +59 -3
  174. package/src/session/history-storage.ts +32 -11
  175. package/src/session/session-manager.ts +72 -20
  176. package/src/setup/credential-import.ts +429 -0
  177. package/src/setup/hermes/templates/operator-instructions.v1.md +7 -1
  178. package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
  179. package/src/skill-state/workflow-hud.ts +106 -10
  180. package/src/slash-commands/builtin-registry.ts +3 -2
  181. package/src/task/executor.ts +16 -1
  182. package/src/task/render.ts +18 -7
  183. package/src/tools/ask.ts +59 -2
  184. package/src/tools/cron.ts +1 -1
  185. package/src/tools/job.ts +3 -2
  186. package/src/tools/monitor.ts +36 -1
  187. package/src/tools/subagent-render.ts +128 -29
  188. package/src/tools/subagent.ts +173 -9
  189. package/src/tools/ultragoal-ask-guard.ts +39 -0
  190. package/src/web/search/index.ts +25 -25
  191. package/src/web/search/provider.ts +178 -87
  192. package/src/web/search/providers/base.ts +2 -1
  193. package/src/web/search/providers/openai-compatible.ts +151 -0
  194. package/src/web/search/types.ts +47 -22
@@ -0,0 +1,177 @@
1
+ /**
2
+ * GC adapter for gjc-tagged tmux sessions. Stale iff `@gjc-project` path is gone
3
+ * OR `@gjc-branch` has no live git worktree. Removal is a spec-authorized
4
+ * destructive `kill-session`, gated by exact-target re-read + revalidation.
5
+ */
6
+
7
+ import * as fs from "node:fs";
8
+
9
+ import { worktree } from "../utils/git";
10
+ import type { GcCollectResult, GcContext, GcPruneOutcome, GcRecord, GcStoreAdapter } from "./gc-runtime";
11
+ import { GJC_TMUX_PROFILE_VALUE } from "./tmux-common";
12
+ import {
13
+ type GjcTmuxSessionStatus,
14
+ type GjcTmuxSessionsForGc,
15
+ listTmuxSessionsForGc,
16
+ readTmuxSessionTagsForGc,
17
+ removeGjcTmuxSession,
18
+ } from "./tmux-sessions";
19
+
20
+ const STORE = "tmux_sessions" as const;
21
+ const TOCTOU_SKIP = "tmux_revalidation_failed_or_became_live";
22
+
23
+ function pathExists(path: string): boolean {
24
+ try {
25
+ return fs.existsSync(path);
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ function detail(project?: string, branch?: string): string | undefined {
32
+ const parts = [];
33
+ if (project) parts.push(`project=${project}`);
34
+ if (branch) parts.push(`branch=${branch}`);
35
+ return parts.length > 0 ? parts.join(" ") : undefined;
36
+ }
37
+
38
+ function unclassifiedRecord(id: string, reason: string, project?: string, branch?: string): GcRecord {
39
+ return {
40
+ store: STORE,
41
+ id,
42
+ path: project,
43
+ root: project,
44
+ pid_status: "none",
45
+ status: "unclassified",
46
+ stale: false,
47
+ removable: false,
48
+ action: "none",
49
+ reason,
50
+ detail: detail(project, branch),
51
+ };
52
+ }
53
+
54
+ function branchMatches(candidate: string | undefined, branch: string): boolean {
55
+ if (!candidate) return false;
56
+ const branchNames = new Set([
57
+ branch,
58
+ branch.startsWith("refs/heads/") ? branch.slice("refs/heads/".length) : `refs/heads/${branch}`,
59
+ ]);
60
+ return branchNames.has(candidate);
61
+ }
62
+
63
+ async function hasLiveWorktreeForBranch(project: string, branch: string): Promise<boolean> {
64
+ const entries = await worktree.list(project);
65
+ return entries.some(entry => branchMatches(entry.branch, branch));
66
+ }
67
+
68
+ async function classifyTaggedSession(session: GjcTmuxSessionStatus): Promise<GcRecord> {
69
+ const { name, project, branch } = session;
70
+ if (!project || !branch) return unclassifiedRecord(name, "missing_project_or_branch_tag", project, branch);
71
+ if (!pathExists(project)) {
72
+ return {
73
+ store: STORE,
74
+ id: name,
75
+ path: project,
76
+ root: project,
77
+ pid_status: "none",
78
+ status: "stale",
79
+ stale: true,
80
+ removable: true,
81
+ action: "none",
82
+ reason: "project_missing",
83
+ detail: detail(project, branch),
84
+ };
85
+ }
86
+ if (!(await hasLiveWorktreeForBranch(project, branch))) {
87
+ return {
88
+ store: STORE,
89
+ id: name,
90
+ path: project,
91
+ root: project,
92
+ pid_status: "none",
93
+ status: "stale",
94
+ stale: true,
95
+ removable: true,
96
+ action: "none",
97
+ reason: "branch_no_worktree",
98
+ detail: detail(project, branch),
99
+ };
100
+ }
101
+ return {
102
+ store: STORE,
103
+ id: name,
104
+ path: project,
105
+ root: project,
106
+ pid_status: "none",
107
+ status: "live",
108
+ stale: false,
109
+ removable: false,
110
+ action: "none",
111
+ reason: "project_and_branch_worktree_present",
112
+ detail: detail(project, branch),
113
+ };
114
+ }
115
+
116
+ async function revalidateRemovable(name: string, env: NodeJS.ProcessEnv): Promise<boolean> {
117
+ const tags = readTmuxSessionTagsForGc(name, env);
118
+ if (tags.profile !== GJC_TMUX_PROFILE_VALUE || !tags.project || !tags.branch) return false;
119
+ if (!pathExists(tags.project)) return true;
120
+ return !(await hasLiveWorktreeForBranch(tags.project, tags.branch));
121
+ }
122
+
123
+ export const tmuxSessionsGcAdapter: GcStoreAdapter = {
124
+ store: STORE,
125
+ async collect(ctx: GcContext): Promise<GcCollectResult> {
126
+ const records: GcRecord[] = [];
127
+ const errors: GcCollectResult["errors"] = [];
128
+ let sessions: GjcTmuxSessionsForGc;
129
+ try {
130
+ sessions = listTmuxSessionsForGc(ctx.env);
131
+ } catch (error) {
132
+ return {
133
+ records,
134
+ errors: [
135
+ {
136
+ store: STORE,
137
+ scope: "list_sessions",
138
+ message: error instanceof Error ? error.message : String(error),
139
+ },
140
+ ],
141
+ };
142
+ }
143
+
144
+ for (const session of sessions.tagged) {
145
+ try {
146
+ records.push(await classifyTaggedSession(session));
147
+ } catch (error) {
148
+ errors.push({
149
+ store: STORE,
150
+ scope: session.name,
151
+ message: error instanceof Error ? error.message : String(error),
152
+ });
153
+ records.push(unclassifiedRecord(session.name, "worktree_list_failed", session.project, session.branch));
154
+ }
155
+ }
156
+
157
+ for (const name of sessions.untagged) {
158
+ records.push(unclassifiedRecord(name, "untagged_tmux_session"));
159
+ }
160
+
161
+ return { records, errors };
162
+ },
163
+ async prune(record: GcRecord, ctx: GcContext): Promise<GcPruneOutcome> {
164
+ if (record.store !== STORE || record.status !== "stale" || !record.removable) {
165
+ return { removed: false, skipped: "not_removable_tmux_session" };
166
+ }
167
+ try {
168
+ if (!(await revalidateRemovable(record.id, ctx.env))) {
169
+ return { removed: false, skipped: TOCTOU_SKIP };
170
+ }
171
+ removeGjcTmuxSession(record.id, ctx.env);
172
+ return { removed: true };
173
+ } catch (error) {
174
+ return { removed: false, error: error instanceof Error ? error.message : String(error) };
175
+ }
176
+ },
177
+ };
@@ -1,4 +1,5 @@
1
1
  import {
2
+ buildGjcTmuxExactOptionTarget,
2
3
  buildGjcTmuxProfileCommands,
3
4
  buildGjcTmuxSessionName,
4
5
  buildGjcTmuxUntaggedSessionError,
@@ -23,6 +24,17 @@ export interface GjcTmuxSessionStatus {
23
24
  project?: string;
24
25
  }
25
26
 
27
+ export interface GjcTmuxSessionTagsForGc {
28
+ profile?: string;
29
+ project?: string;
30
+ branch?: string;
31
+ }
32
+
33
+ export interface GjcTmuxSessionsForGc {
34
+ tagged: GjcTmuxSessionStatus[];
35
+ untagged: string[];
36
+ }
37
+
26
38
  function runTmux(args: string[], env: NodeJS.ProcessEnv = process.env): string {
27
39
  const tmuxCommand = resolveGjcTmuxCommand(env);
28
40
  const result = Bun.spawnSync([tmuxCommand, ...args], { stdout: "pipe", stderr: "pipe", env });
@@ -107,6 +119,16 @@ export function listGjcTmuxSessions(env: NodeJS.ProcessEnv = process.env): GjcTm
107
119
  .sort((a, b) => a.name.localeCompare(b.name));
108
120
  }
109
121
 
122
+ /** @internal */
123
+ export function listTmuxSessionsForGc(env: NodeJS.ProcessEnv = process.env): GjcTmuxSessionsForGc {
124
+ const tagged = listGjcTmuxSessions(env);
125
+ const taggedNames = new Set(tagged.map(session => session.name));
126
+ const untagged = listRawTmuxSessionNames(env)
127
+ .filter(name => !taggedNames.has(name))
128
+ .sort((a, b) => a.localeCompare(b));
129
+ return { tagged, untagged };
130
+ }
131
+
110
132
  export function findGjcTmuxSessionByBranch(
111
133
  branch: string,
112
134
  env: NodeJS.ProcessEnv = process.env,
@@ -148,7 +170,33 @@ export function createGjcTmuxSession(env: NodeJS.ProcessEnv = process.env): GjcT
148
170
  }
149
171
 
150
172
  function readProfileForExactTarget(sessionName: string, env: NodeJS.ProcessEnv): string {
151
- return runTmux(["show-options", "-qv", "-t", `=${sessionName}`, GJC_TMUX_PROFILE_OPTION], env).trim();
173
+ return runTmux(
174
+ ["show-options", "-qv", "-t", buildGjcTmuxExactOptionTarget(sessionName), GJC_TMUX_PROFILE_OPTION],
175
+ env,
176
+ ).trim();
177
+ }
178
+
179
+ function readExactOptionForGc(sessionName: string, option: string, env: NodeJS.ProcessEnv): string | undefined {
180
+ try {
181
+ return (
182
+ runTmux(["show-options", "-qv", "-t", buildGjcTmuxExactOptionTarget(sessionName), option], env).trim() ||
183
+ undefined
184
+ );
185
+ } catch {
186
+ return undefined;
187
+ }
188
+ }
189
+
190
+ /** @internal */
191
+ export function readTmuxSessionTagsForGc(
192
+ sessionName: string,
193
+ env: NodeJS.ProcessEnv = process.env,
194
+ ): GjcTmuxSessionTagsForGc {
195
+ return {
196
+ profile: readExactOptionForGc(sessionName, GJC_TMUX_PROFILE_OPTION, env),
197
+ project: readExactOptionForGc(sessionName, GJC_TMUX_PROJECT_OPTION, env),
198
+ branch: readExactOptionForGc(sessionName, GJC_TMUX_BRANCH_OPTION, env),
199
+ };
152
200
  }
153
201
 
154
202
  export function removeGjcTmuxSession(sessionName: string, env: NodeJS.ProcessEnv = process.env): GjcTmuxSessionStatus {
@@ -32,6 +32,16 @@ export interface UltragoalGuardDiagnostic {
32
32
  goalId?: string;
33
33
  }
34
34
 
35
+ export interface UltragoalAskBlockDiagnostic {
36
+ active: boolean;
37
+ reason: string;
38
+ source: "absent" | "durable_state" | "durable_state_unreadable" | "ledger" | "goals_json";
39
+ goalsPath?: string;
40
+ ledgerPath?: string;
41
+ goalIds?: string[];
42
+ message: string;
43
+ }
44
+
35
45
  export interface CurrentGoalLike {
36
46
  objective: string;
37
47
  status?: string;
@@ -70,6 +80,48 @@ async function hasDurableUltragoalState(cwd: string): Promise<boolean> {
70
80
  }
71
81
  }
72
82
 
83
+ function isEnoent(error: unknown): boolean {
84
+ return (
85
+ typeof error === "object" && error !== null && "code" in error && (error as { code?: unknown }).code === "ENOENT"
86
+ );
87
+ }
88
+
89
+ function activeAskDiagnostic(input: {
90
+ reason: string;
91
+ source: UltragoalAskBlockDiagnostic["source"];
92
+ goalsPath?: string;
93
+ ledgerPath?: string;
94
+ goalIds?: string[];
95
+ }): UltragoalAskBlockDiagnostic {
96
+ return {
97
+ active: true,
98
+ reason: input.reason,
99
+ source: input.source,
100
+ goalsPath: input.goalsPath,
101
+ ledgerPath: input.ledgerPath,
102
+ goalIds: input.goalIds,
103
+ message: `${input.reason} Use \`gjc ultragoal record-review-blockers\` instead of asking the user.`,
104
+ };
105
+ }
106
+
107
+ function inactiveAskDiagnostic(input: {
108
+ reason: string;
109
+ source: UltragoalAskBlockDiagnostic["source"];
110
+ goalsPath?: string;
111
+ ledgerPath?: string;
112
+ goalIds?: string[];
113
+ }): UltragoalAskBlockDiagnostic {
114
+ return {
115
+ active: false,
116
+ reason: input.reason,
117
+ source: input.source,
118
+ goalsPath: input.goalsPath,
119
+ ledgerPath: input.ledgerPath,
120
+ goalIds: input.goalIds,
121
+ message: input.reason,
122
+ };
123
+ }
124
+
73
125
  function requiredGoals(plan: UltragoalPlan): UltragoalGoal[] {
74
126
  return plan.goals.filter(goal => goal.status !== "superseded");
75
127
  }
@@ -278,6 +330,109 @@ export async function readUltragoalVerificationState(input: {
278
330
  return receiptDiagnostic;
279
331
  }
280
332
 
333
+ export async function isUltragoalAskBlocked(cwd: string): Promise<UltragoalAskBlockDiagnostic> {
334
+ const paths = getUltragoalPaths(cwd);
335
+ try {
336
+ await fs.stat(paths.dir);
337
+ } catch (error) {
338
+ if (isEnoent(error)) {
339
+ return inactiveAskDiagnostic({
340
+ reason: "No durable .gjc/ultragoal state exists.",
341
+ source: "absent",
342
+ goalsPath: paths.goalsPath,
343
+ ledgerPath: paths.ledgerPath,
344
+ });
345
+ }
346
+ return activeAskDiagnostic({
347
+ reason: `Durable .gjc/ultragoal state is present but unreadable: ${error instanceof Error ? error.message : String(error)}`,
348
+ source: "durable_state_unreadable",
349
+ goalsPath: paths.goalsPath,
350
+ ledgerPath: paths.ledgerPath,
351
+ });
352
+ }
353
+
354
+ let plan: UltragoalPlan | null;
355
+ let ledger: UltragoalLedgerEvent[];
356
+ try {
357
+ plan = await readUltragoalPlan(cwd);
358
+ ledger = await readUltragoalLedger(cwd);
359
+ } catch (error) {
360
+ return activeAskDiagnostic({
361
+ reason: `Unable to read durable Ultragoal state: ${error instanceof Error ? error.message : String(error)}`,
362
+ source: "durable_state_unreadable",
363
+ goalsPath: paths.goalsPath,
364
+ ledgerPath: paths.ledgerPath,
365
+ });
366
+ }
367
+ if (!plan) {
368
+ return activeAskDiagnostic({
369
+ reason: "Durable .gjc/ultragoal state exists but goals.json is missing or empty.",
370
+ source: "durable_state_unreadable",
371
+ goalsPath: paths.goalsPath,
372
+ ledgerPath: paths.ledgerPath,
373
+ });
374
+ }
375
+
376
+ if (plan.goals.some(goal => goal.status === "review_blocked")) {
377
+ const goalIds = plan.goals.filter(goal => goal.status === "review_blocked").map(goal => goal.id);
378
+ return activeAskDiagnostic({
379
+ reason: `Ultragoal has recorded review blockers: ${goalIds.join(", ")}.`,
380
+ source: "goals_json",
381
+ goalsPath: paths.goalsPath,
382
+ ledgerPath: paths.ledgerPath,
383
+ goalIds,
384
+ });
385
+ }
386
+
387
+ const runState = getUltragoalRunCompletionState(plan);
388
+ if (runState.incompleteGoals.length > 0) {
389
+ const goalIds = runState.incompleteGoals.map(goal => goal.id);
390
+ return activeAskDiagnostic({
391
+ reason: `Ultragoal has incomplete required goals: ${goalIds.join(", ")}.`,
392
+ source: "goals_json",
393
+ goalsPath: paths.goalsPath,
394
+ ledgerPath: paths.ledgerPath,
395
+ goalIds,
396
+ });
397
+ }
398
+
399
+ const finalReceiptGoal = [...requiredGoals(plan)]
400
+ .reverse()
401
+ .find(goal => goal.completionVerification?.receiptKind === "final-aggregate");
402
+ if (!finalReceiptGoal) {
403
+ return activeAskDiagnostic({
404
+ reason: "Ultragoal aggregate completion is missing a final aggregate receipt.",
405
+ source: "durable_state",
406
+ goalsPath: paths.goalsPath,
407
+ ledgerPath: paths.ledgerPath,
408
+ goalIds: requiredGoals(plan).map(goal => goal.id),
409
+ });
410
+ }
411
+
412
+ const diagnostic = validateCompletionReceipt({
413
+ plan,
414
+ ledger,
415
+ goal: finalReceiptGoal,
416
+ receiptKind: "final-aggregate",
417
+ });
418
+ if (diagnostic.state !== "active_verified_complete") {
419
+ return activeAskDiagnostic({
420
+ reason: diagnostic.message,
421
+ source: diagnostic.state === "active_dirty_quality_gate" ? "ledger" : "durable_state",
422
+ goalsPath: paths.goalsPath,
423
+ ledgerPath: paths.ledgerPath,
424
+ goalIds: diagnostic.goalId ? [diagnostic.goalId] : undefined,
425
+ });
426
+ }
427
+ return inactiveAskDiagnostic({
428
+ reason: "Ultragoal run is verified complete.",
429
+ source: "durable_state",
430
+ goalsPath: paths.goalsPath,
431
+ ledgerPath: paths.ledgerPath,
432
+ goalIds: [finalReceiptGoal.id],
433
+ });
434
+ }
435
+
281
436
  export async function assertCanCompleteCurrentGoal(input: {
282
437
  cwd: string;
283
438
  currentGoal?: CurrentGoalLike | null;