@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.
- package/CHANGELOG.md +36 -0
- package/README.md +1 -1
- package/dist/types/async/job-manager.d.ts +26 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/list-models.d.ts +6 -0
- package/dist/types/cli/setup-cli.d.ts +8 -1
- package/dist/types/commands/gc.d.ts +26 -0
- package/dist/types/commands/setup.d.ts +7 -0
- package/dist/types/config/file-lock-gc.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +29 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +62 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/defaults/gjc/extensions/grok-build/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +25 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +27 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.d.ts +8 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.d.ts +5 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +10 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.d.ts +38 -0
- package/dist/types/defaults/gjc-grok-cli.d.ts +5 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +35 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +103 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +112 -0
- package/dist/types/gjc-runtime/gc-render.d.ts +6 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +134 -0
- package/dist/types/gjc-runtime/ledger-event-renderer.d.ts +68 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
- package/dist/types/gjc-runtime/team-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +11 -0
- package/dist/types/gjc-runtime/tmux-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +13 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
- package/dist/types/harness-control-plane/gc-adapter.d.ts +3 -0
- package/dist/types/harness-control-plane/owner.d.ts +7 -0
- package/dist/types/harness-control-plane/storage.d.ts +20 -0
- package/dist/types/modes/components/hook-selector.d.ts +7 -1
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +72 -2
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +13 -0
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/theme/defaults/index.d.ts +302 -0
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/blob-store.d.ts +39 -3
- package/dist/types/session/history-storage.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +10 -1
- package/dist/types/setup/credential-import.d.ts +79 -0
- package/dist/types/skill-state/workflow-hud.d.ts +14 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/render.d.ts +1 -1
- package/dist/types/tools/ask.d.ts +15 -1
- package/dist/types/tools/subagent-render.d.ts +7 -1
- package/dist/types/tools/subagent.d.ts +27 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
- package/dist/types/web/search/index.d.ts +4 -4
- package/dist/types/web/search/provider.d.ts +16 -20
- package/dist/types/web/search/providers/base.d.ts +2 -1
- package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +14 -2
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +52 -0
- package/src/cli/args.ts +5 -0
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/fast-help.ts +2 -0
- package/src/cli/list-models.ts +13 -1
- package/src/cli/setup-cli.ts +138 -3
- package/src/cli.ts +1 -0
- package/src/commands/gc.ts +22 -0
- package/src/commands/harness.ts +7 -3
- package/src/commands/setup.ts +5 -1
- package/src/commands/ultragoal.ts +3 -1
- package/src/config/file-lock-gc.ts +193 -0
- package/src/config/file-lock.ts +66 -10
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +39 -30
- package/src/config/model-registry.ts +21 -1
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +62 -0
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/server.ts +459 -3
- package/src/defaults/gjc/agent.models.grok-cli.yml +36 -0
- package/src/defaults/gjc/extensions/grok-build/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-build/package.json +7 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +39 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/package.json +8 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +155 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +361 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.ts +57 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +99 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +50 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.ts +56 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +36 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.ts +44 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +131 -113
- package/src/defaults/gjc/skills/deep-interview/lateral-review-panel.md +49 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
- package/src/defaults/gjc-defaults.ts +7 -0
- package/src/defaults/gjc-grok-cli.ts +22 -0
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +128 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +457 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +18 -26
- package/src/gjc-runtime/deep-interview-state.ts +324 -0
- package/src/gjc-runtime/gc-render.ts +70 -0
- package/src/gjc-runtime/gc-runtime.ts +403 -0
- package/src/gjc-runtime/launch-tmux.ts +3 -4
- package/src/gjc-runtime/ledger-event-renderer.ts +164 -0
- package/src/gjc-runtime/ralplan-runtime.ts +232 -19
- package/src/gjc-runtime/state-renderer.ts +12 -3
- package/src/gjc-runtime/state-runtime.ts +48 -30
- package/src/gjc-runtime/state-writer.ts +254 -7
- package/src/gjc-runtime/team-gc.ts +49 -0
- package/src/gjc-runtime/team-runtime.ts +179 -2
- package/src/gjc-runtime/tmux-common.ts +14 -0
- package/src/gjc-runtime/tmux-gc.ts +177 -0
- package/src/gjc-runtime/tmux-sessions.ts +49 -1
- package/src/gjc-runtime/ultragoal-guard.ts +155 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +1239 -31
- package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
- package/src/gjc-runtime/workflow-manifest.ts +12 -0
- package/src/harness-control-plane/gc-adapter.ts +184 -0
- package/src/harness-control-plane/owner.ts +14 -2
- package/src/harness-control-plane/rpc-adapter.ts +1 -1
- package/src/harness-control-plane/storage.ts +70 -0
- package/src/hooks/skill-state.ts +121 -2
- package/src/internal-urls/docs-index.generated.ts +22 -12
- package/src/lsp/defaults.json +1 -0
- package/src/main.ts +18 -3
- package/src/modes/acp/acp-agent.ts +4 -2
- package/src/modes/bridge/bridge-mode.ts +2 -1
- package/src/modes/components/history-search.ts +5 -2
- package/src/modes/components/hook-selector.ts +19 -0
- package/src/modes/components/model-selector.ts +51 -8
- package/src/modes/components/provider-onboarding-selector.ts +6 -1
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/controllers/command-controller.ts +25 -6
- package/src/modes/controllers/extension-ui-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +81 -1
- package/src/modes/interactive-mode.ts +11 -1
- package/src/modes/rpc/rpc-mode.ts +266 -34
- package/src/modes/shared/agent-wire/command-dispatch.ts +281 -261
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +30 -1
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
- package/src/modes/shared/agent-wire/session-registry.ts +109 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +24 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +23 -3
- package/src/modes/shared/agent-wire/unattended-session.ts +32 -2
- package/src/modes/theme/defaults/claude-code.json +100 -0
- package/src/modes/theme/defaults/codex.json +100 -0
- package/src/modes/theme/defaults/index.ts +6 -0
- package/src/modes/theme/defaults/opencode.json +102 -0
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +1 -1
- package/src/prompts/agents/executor.md +5 -2
- package/src/sdk.ts +29 -4
- package/src/session/agent-session.ts +99 -19
- package/src/session/blob-store.ts +59 -3
- package/src/session/history-storage.ts +32 -11
- package/src/session/session-manager.ts +72 -20
- package/src/setup/credential-import.ts +429 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +7 -1
- package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
- package/src/skill-state/workflow-hud.ts +106 -10
- package/src/slash-commands/builtin-registry.ts +3 -2
- package/src/task/executor.ts +16 -1
- package/src/task/render.ts +18 -7
- package/src/tools/ask.ts +59 -2
- package/src/tools/cron.ts +1 -1
- package/src/tools/job.ts +3 -2
- package/src/tools/monitor.ts +36 -1
- package/src/tools/subagent-render.ts +128 -29
- package/src/tools/subagent.ts +173 -9
- package/src/tools/ultragoal-ask-guard.ts +39 -0
- package/src/web/search/index.ts +25 -25
- package/src/web/search/provider.ts +178 -87
- package/src/web/search/providers/base.ts +2 -1
- package/src/web/search/providers/openai-compatible.ts +151 -0
- 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(
|
|
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;
|