@gajae-code/coding-agent 0.6.3 → 0.6.5
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 +50 -0
- package/README.md +73 -1
- package/dist/types/cli/migrate-cli.d.ts +20 -0
- package/dist/types/commands/migrate.d.ts +33 -0
- package/dist/types/config/keybindings.d.ts +4 -0
- package/dist/types/config/settings-schema.d.ts +27 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -2
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/gjc-runtime/session-layout.d.ts +59 -0
- package/dist/types/gjc-runtime/session-resolution.d.ts +47 -0
- package/dist/types/gjc-runtime/state-graph.d.ts +1 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +5 -4
- package/dist/types/gjc-runtime/state-schema.d.ts +2 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +36 -7
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +2 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +7 -4
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +1 -1
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +1 -1
- package/dist/types/harness-control-plane/storage.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +12 -4
- package/dist/types/migrate/action-planner.d.ts +11 -0
- package/dist/types/migrate/adapters/claude-code.d.ts +2 -0
- package/dist/types/migrate/adapters/codex.d.ts +5 -0
- package/dist/types/migrate/adapters/index.d.ts +45 -0
- package/dist/types/migrate/adapters/opencode.d.ts +2 -0
- package/dist/types/migrate/executor.d.ts +2 -0
- package/dist/types/migrate/mcp-mapper.d.ts +20 -0
- package/dist/types/migrate/report.d.ts +18 -0
- package/dist/types/migrate/skill-normalizer.d.ts +27 -0
- package/dist/types/migrate/types.d.ts +126 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +1 -1
- package/dist/types/research-plan/index.d.ts +1 -0
- package/dist/types/research-plan/ledger.d.ts +33 -0
- package/dist/types/rlm/artifacts.d.ts +1 -1
- package/dist/types/runtime-mcp/config-writer.d.ts +26 -0
- package/dist/types/skill-state/active-state.d.ts +6 -11
- package/dist/types/skill-state/canonical-skills.d.ts +3 -0
- package/dist/types/skill-state/workflow-hud.d.ts +2 -0
- package/dist/types/task/spawn-gate.d.ts +1 -10
- package/package.json +7 -7
- package/src/cli/migrate-cli.ts +106 -0
- package/src/cli/setup-cli.ts +14 -1
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +2 -2
- package/src/commands/launch.ts +1 -1
- package/src/commands/migrate.ts +46 -0
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +7 -3
- package/src/config/model-registry.ts +9 -2
- package/src/config/model-resolver.ts +13 -2
- package/src/config/settings-schema.ts +17 -0
- package/src/coordinator-mcp/policy.ts +10 -2
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +0 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +28 -24
- package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
- package/src/defaults/gjc/skills/team/SKILL.md +51 -47
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +17 -13
- package/src/exec/bash-executor.ts +3 -1
- package/src/extensibility/custom-commands/loader.ts +0 -7
- package/src/extensibility/gjc-plugins/injection.ts +23 -4
- package/src/extensibility/gjc-plugins/state.ts +16 -1
- package/src/gjc-runtime/deep-interview-recorder.ts +43 -18
- package/src/gjc-runtime/deep-interview-runtime.ts +49 -23
- package/src/gjc-runtime/goal-mode-request.ts +26 -11
- package/src/gjc-runtime/launch-tmux.ts +68 -15
- package/src/gjc-runtime/ralplan-runtime.ts +79 -50
- package/src/gjc-runtime/session-layout.ts +180 -0
- package/src/gjc-runtime/session-resolution.ts +217 -0
- package/src/gjc-runtime/state-graph.ts +1 -2
- package/src/gjc-runtime/state-migrations.ts +1 -0
- package/src/gjc-runtime/state-runtime.ts +230 -121
- package/src/gjc-runtime/state-schema.ts +2 -0
- package/src/gjc-runtime/state-writer.ts +289 -41
- package/src/gjc-runtime/team-runtime.ts +43 -19
- package/src/gjc-runtime/tmux-sessions.ts +43 -2
- package/src/gjc-runtime/ultragoal-guard.ts +45 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +121 -41
- package/src/gjc-runtime/workflow-command-ref.ts +1 -2
- package/src/gjc-runtime/workflow-manifest.ts +1 -2
- package/src/harness-control-plane/storage.ts +14 -4
- package/src/hooks/native-skill-hook.ts +38 -12
- package/src/hooks/skill-state.ts +178 -83
- package/src/internal-urls/docs-index.generated.ts +9 -6
- package/src/migrate/action-planner.ts +318 -0
- package/src/migrate/adapters/claude-code.ts +39 -0
- package/src/migrate/adapters/codex.ts +70 -0
- package/src/migrate/adapters/index.ts +277 -0
- package/src/migrate/adapters/opencode.ts +52 -0
- package/src/migrate/executor.ts +81 -0
- package/src/migrate/mcp-mapper.ts +152 -0
- package/src/migrate/report.ts +104 -0
- package/src/migrate/skill-normalizer.ts +80 -0
- package/src/migrate/types.ts +163 -0
- package/src/modes/bridge/bridge-mode.ts +2 -2
- package/src/modes/components/custom-editor.ts +30 -20
- package/src/modes/components/welcome.ts +42 -9
- package/src/modes/controllers/input-controller.ts +21 -3
- package/src/modes/interactive-mode.ts +22 -1
- package/src/modes/prompt-action-autocomplete.ts +11 -1
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/shared/agent-wire/unattended-audit.ts +3 -2
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/task.md +1 -2
- package/src/research-plan/index.ts +1 -0
- package/src/research-plan/ledger.ts +177 -0
- package/src/rlm/artifacts.ts +12 -3
- package/src/rlm/index.ts +7 -0
- package/src/runtime-mcp/config-writer.ts +46 -0
- package/src/session/agent-session.ts +15 -21
- package/src/session/session-manager.ts +19 -2
- package/src/setup/hermes/templates/operator-instructions.v1.md +8 -0
- package/src/setup/hermes-setup.ts +1 -1
- package/src/skill-state/active-state.ts +72 -108
- package/src/skill-state/canonical-skills.ts +4 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +28 -109
- package/src/skill-state/workflow-hud.ts +4 -2
- package/src/skill-state/workflow-state-contract.ts +3 -3
- package/src/slash-commands/builtin-registry.ts +8 -4
- package/src/system-prompt.ts +11 -9
- package/src/task/agents.ts +1 -22
- package/src/task/index.ts +1 -41
- package/src/task/spawn-gate.ts +1 -38
- package/src/task/types.ts +1 -1
- package/src/tools/ask.ts +34 -12
- package/src/tools/computer.ts +58 -4
- package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +0 -10
- package/src/extensibility/custom-commands/bundled/review/index.ts +0 -456
- package/src/prompts/agents/explore.md +0 -58
- package/src/prompts/agents/plan.md +0 -49
- package/src/prompts/agents/reviewer.md +0 -141
- package/src/prompts/agents/task.md +0 -16
- package/src/prompts/review-request.md +0 -70
|
@@ -114,7 +114,13 @@ function runListSessions(format: string, env: NodeJS.ProcessEnv = process.env):
|
|
|
114
114
|
output = runTmux(["list-sessions", "-F", format], env);
|
|
115
115
|
} catch (error) {
|
|
116
116
|
const message = error instanceof Error ? error.message : String(error);
|
|
117
|
-
if (
|
|
117
|
+
if (
|
|
118
|
+
message.includes("no server running") ||
|
|
119
|
+
message.includes("failed to connect to server") ||
|
|
120
|
+
message.includes("error connecting to")
|
|
121
|
+
) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
118
124
|
throw error;
|
|
119
125
|
}
|
|
120
126
|
return output
|
|
@@ -137,6 +143,8 @@ function listRawTmuxSessionNames(env: NodeJS.ProcessEnv = process.env): string[]
|
|
|
137
143
|
export function listGjcTmuxSessions(env: NodeJS.ProcessEnv = process.env): GjcTmuxSessionStatus[] {
|
|
138
144
|
return listSessionLines(env)
|
|
139
145
|
.map(parseSessionLine)
|
|
146
|
+
.filter((session): session is GjcTmuxSessionStatus => session != null)
|
|
147
|
+
.map(session => hydrateSessionFromExactOptions(session, env))
|
|
140
148
|
.filter((session): session is GjcTmuxSessionStatus => session?.profile === GJC_TMUX_PROFILE_VALUE)
|
|
141
149
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
142
150
|
}
|
|
@@ -145,7 +153,8 @@ export function listGjcTmuxSessions(env: NodeJS.ProcessEnv = process.env): GjcTm
|
|
|
145
153
|
export function listTmuxSessionsForGc(env: NodeJS.ProcessEnv = process.env): GjcTmuxSessionsForGc {
|
|
146
154
|
const sessions = listSessionLines(env)
|
|
147
155
|
.map(parseSessionLine)
|
|
148
|
-
.filter((session): session is GjcTmuxSessionStatus => session != null)
|
|
156
|
+
.filter((session): session is GjcTmuxSessionStatus => session != null)
|
|
157
|
+
.map(session => hydrateSessionFromExactOptions(session, env));
|
|
149
158
|
const tagged = sessions
|
|
150
159
|
.filter(session => session.profile === GJC_TMUX_PROFILE_VALUE)
|
|
151
160
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -179,6 +188,22 @@ export function findGjcTmuxSessionByBranch(
|
|
|
179
188
|
);
|
|
180
189
|
}
|
|
181
190
|
|
|
191
|
+
export function findGjcTmuxSessionByName(
|
|
192
|
+
sessionName: string,
|
|
193
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
194
|
+
): GjcTmuxSessionStatus | undefined {
|
|
195
|
+
return listGjcTmuxSessions(env).find(session => session.name === sessionName);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function findGjcTmuxSessionByScope(
|
|
199
|
+
project: string,
|
|
200
|
+
branch: string | null | undefined,
|
|
201
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
202
|
+
): GjcTmuxSessionStatus | undefined {
|
|
203
|
+
return listGjcTmuxSessions(env).find(
|
|
204
|
+
session => session.project === project && (branch ? session.branch === branch : session.branch === undefined),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
182
207
|
export function statusGjcTmuxSession(sessionName: string, env: NodeJS.ProcessEnv = process.env): GjcTmuxSessionStatus {
|
|
183
208
|
const session = listGjcTmuxSessions(env).find(candidate => candidate.name === sessionName);
|
|
184
209
|
if (session) return session;
|
|
@@ -227,6 +252,22 @@ function readExactOptionForGc(sessionName: string, option: string, env: NodeJS.P
|
|
|
227
252
|
}
|
|
228
253
|
}
|
|
229
254
|
|
|
255
|
+
function hydrateSessionFromExactOptions(session: GjcTmuxSessionStatus, env: NodeJS.ProcessEnv): GjcTmuxSessionStatus {
|
|
256
|
+
if (session.profile === GJC_TMUX_PROFILE_VALUE) return session;
|
|
257
|
+
const profile = readExactOptionForGc(session.name, GJC_TMUX_PROFILE_OPTION, env);
|
|
258
|
+
if (profile !== GJC_TMUX_PROFILE_VALUE) return session;
|
|
259
|
+
return {
|
|
260
|
+
...session,
|
|
261
|
+
profile,
|
|
262
|
+
branch: session.branch ?? readExactOptionForGc(session.name, GJC_TMUX_BRANCH_OPTION, env),
|
|
263
|
+
branchSlug: session.branchSlug ?? readExactOptionForGc(session.name, GJC_TMUX_BRANCH_SLUG_OPTION, env),
|
|
264
|
+
project: session.project ?? readExactOptionForGc(session.name, GJC_TMUX_PROJECT_OPTION, env),
|
|
265
|
+
sessionId: session.sessionId ?? readExactOptionForGc(session.name, GJC_TMUX_SESSION_ID_OPTION, env),
|
|
266
|
+
sessionStateFile:
|
|
267
|
+
session.sessionStateFile ?? readExactOptionForGc(session.name, GJC_TMUX_SESSION_STATE_FILE_OPTION, env),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
230
271
|
/** @internal */
|
|
231
272
|
export function readTmuxSessionTagsForGc(
|
|
232
273
|
sessionName: string,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
|
|
3
|
+
import { resolveGjcSessionForRead, SessionResolutionError } from "./session-resolution";
|
|
3
4
|
import {
|
|
4
5
|
computeUltragoalPlanGeneration,
|
|
5
6
|
getUltragoalPaths,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
type UltragoalCompletionVerification,
|
|
11
12
|
type UltragoalGoal,
|
|
12
13
|
type UltragoalLedgerEvent,
|
|
14
|
+
type UltragoalPaths,
|
|
13
15
|
type UltragoalPlan,
|
|
14
16
|
type UltragoalReceiptKind,
|
|
15
17
|
} from "./ultragoal-runtime";
|
|
@@ -63,9 +65,30 @@ function isKnownUltragoalObjective(currentObjective: string): boolean {
|
|
|
63
65
|
);
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
async function ultragoalReadPaths(cwd: string): Promise<UltragoalPaths> {
|
|
69
|
+
const envSessionId = process.env.GJC_SESSION_ID?.trim();
|
|
70
|
+
if (envSessionId) return getUltragoalPaths(cwd, envSessionId);
|
|
71
|
+
try {
|
|
72
|
+
const session = await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID });
|
|
73
|
+
return getUltragoalPaths(cwd, session.gjcSessionId);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error instanceof SessionResolutionError && error.code === "no_session") {
|
|
76
|
+
return getUltragoalPaths(cwd, null);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
66
82
|
async function hasDurableUltragoalState(cwd: string): Promise<boolean> {
|
|
83
|
+
let paths: UltragoalPaths;
|
|
84
|
+
try {
|
|
85
|
+
paths = await ultragoalReadPaths(cwd);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (error instanceof SessionResolutionError) return true;
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
67
90
|
try {
|
|
68
|
-
await fs.stat(
|
|
91
|
+
await fs.stat(paths.dir);
|
|
69
92
|
return true;
|
|
70
93
|
} catch (error) {
|
|
71
94
|
if (
|
|
@@ -308,6 +331,18 @@ export async function readUltragoalVerificationState(input: {
|
|
|
308
331
|
}
|
|
309
332
|
const receiptTarget = findReceiptGoal(plan, currentObjective);
|
|
310
333
|
if (!receiptTarget) {
|
|
334
|
+
// When earlier required goals are already complete but later ones remain, name the
|
|
335
|
+
// specific blocking goals (a final-aggregate receipt cannot exist yet anyway). Only
|
|
336
|
+
// fall back to the generic missing-receipt message when no progress has been verified.
|
|
337
|
+
const completedRequired = requiredGoals(plan).filter(goal => goal.status === "complete");
|
|
338
|
+
if (completedRequired.length > 0 && runState.incompleteGoals.length > 0) {
|
|
339
|
+
return {
|
|
340
|
+
state: "active_missing_final_receipt",
|
|
341
|
+
message: `Ultragoal still has incomplete required goals: ${runState.incompleteGoals
|
|
342
|
+
.map(goal => goal.id)
|
|
343
|
+
.join(", ")}. Run \`gjc ultragoal complete-goals\` to continue.`,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
311
346
|
return {
|
|
312
347
|
state: "active_missing_final_receipt",
|
|
313
348
|
message: "Ultragoal aggregate completion requires a fresh final aggregate receipt.",
|
|
@@ -331,7 +366,15 @@ export async function readUltragoalVerificationState(input: {
|
|
|
331
366
|
}
|
|
332
367
|
|
|
333
368
|
export async function isUltragoalAskBlocked(cwd: string): Promise<UltragoalAskBlockDiagnostic> {
|
|
334
|
-
|
|
369
|
+
let paths: UltragoalPaths;
|
|
370
|
+
try {
|
|
371
|
+
paths = await ultragoalReadPaths(cwd);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
return activeAskDiagnostic({
|
|
374
|
+
reason: `Unable to resolve durable Ultragoal state: ${error instanceof Error ? error.message : String(error)}`,
|
|
375
|
+
source: "durable_state_unreadable",
|
|
376
|
+
});
|
|
377
|
+
}
|
|
335
378
|
try {
|
|
336
379
|
await fs.stat(paths.dir);
|
|
337
380
|
} catch (error) {
|
|
@@ -6,10 +6,11 @@ import type { WorkflowHudSummary } from "../skill-state/active-state";
|
|
|
6
6
|
import { buildUltragoalHudSummary as buildWorkflowUltragoalHudSummary } from "../skill-state/workflow-hud";
|
|
7
7
|
import { renderCliWriteReceipt } from "./cli-write-receipt";
|
|
8
8
|
import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
|
|
9
|
-
import {
|
|
9
|
+
import { gjcRoot, sessionUltragoalDir } from "./session-layout";
|
|
10
|
+
import { resolveGjcSessionForRead, resolveGjcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
|
|
10
11
|
import { renderUltragoalStatusMarkdown } from "./state-renderer";
|
|
11
12
|
import { reconcileWorkflowSkillState } from "./state-runtime";
|
|
12
|
-
import { appendJsonl, writeArtifact,
|
|
13
|
+
import { appendJsonl, persistedStateRevision, writeArtifact, writeGuardedJsonAtomic } from "./state-writer";
|
|
13
14
|
|
|
14
15
|
export type UltragoalGjcGoalMode = "aggregate" | "per-story";
|
|
15
16
|
export type UltragoalGoalStatus =
|
|
@@ -44,6 +45,7 @@ export interface UltragoalPlan {
|
|
|
44
45
|
goals: UltragoalGoal[];
|
|
45
46
|
createdAt: string;
|
|
46
47
|
updatedAt: string;
|
|
48
|
+
[key: string]: unknown;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export type UltragoalReceiptKind = "per-goal" | "final-aggregate";
|
|
@@ -107,6 +109,10 @@ interface JsonObject {
|
|
|
107
109
|
[key: string]: unknown;
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
function currentUltragoalSessionId(cwd: string): string {
|
|
113
|
+
return resolveGjcSessionForWrite(cwd, { envSessionId: process.env.GJC_SESSION_ID }).gjcSessionId;
|
|
114
|
+
}
|
|
115
|
+
|
|
110
116
|
const TERMINAL_OR_SKIPPED_STATUSES = new Set<UltragoalGoalStatus>(["complete", "superseded"]);
|
|
111
117
|
const CLEAN_ARCHITECT_STATUS = "CLEAR";
|
|
112
118
|
const APPROVE_RECOMMENDATION = "APPROVE";
|
|
@@ -162,8 +168,9 @@ export function hashStructuredValue(value: unknown): string {
|
|
|
162
168
|
.digest("hex");
|
|
163
169
|
}
|
|
164
170
|
|
|
165
|
-
export function getUltragoalPaths(cwd: string): UltragoalPaths {
|
|
166
|
-
const
|
|
171
|
+
export function getUltragoalPaths(cwd: string, sessionId?: string | null): UltragoalPaths {
|
|
172
|
+
const explicitSessionId = sessionId?.trim() || process.env.GJC_SESSION_ID?.trim();
|
|
173
|
+
const dir = explicitSessionId ? sessionUltragoalDir(cwd, explicitSessionId) : path.join(gjcRoot(cwd), "ultragoal");
|
|
167
174
|
return {
|
|
168
175
|
dir,
|
|
169
176
|
briefPath: path.join(dir, "brief.md"),
|
|
@@ -178,8 +185,10 @@ function isEnoent(error: unknown): boolean {
|
|
|
178
185
|
);
|
|
179
186
|
}
|
|
180
187
|
|
|
181
|
-
async function appendLedger(cwd: string, event: JsonObject): Promise<UltragoalLedgerEvent> {
|
|
182
|
-
const
|
|
188
|
+
async function appendLedger(cwd: string, event: JsonObject, sessionId?: string | null): Promise<UltragoalLedgerEvent> {
|
|
189
|
+
const resolvedSessionId =
|
|
190
|
+
sessionId?.trim() || resolveGjcSessionForWrite(cwd, { envSessionId: process.env.GJC_SESSION_ID }).gjcSessionId;
|
|
191
|
+
const paths = getUltragoalPaths(cwd, resolvedSessionId);
|
|
183
192
|
const entry: UltragoalLedgerEvent = {
|
|
184
193
|
eventId: typeof event.eventId === "string" ? event.eventId : crypto.randomUUID(),
|
|
185
194
|
...event,
|
|
@@ -187,14 +196,18 @@ async function appendLedger(cwd: string, event: JsonObject): Promise<UltragoalLe
|
|
|
187
196
|
};
|
|
188
197
|
await appendJsonl(paths.ledgerPath, entry, {
|
|
189
198
|
cwd,
|
|
190
|
-
audit: { category: "ledger", verb: "append", owner: "gjc-runtime" },
|
|
199
|
+
audit: { category: "ledger", verb: "append", owner: "gjc-runtime", sessionId: resolvedSessionId },
|
|
191
200
|
});
|
|
201
|
+
await writeSessionActivityMarker(cwd, resolvedSessionId, { writer: "ultragoal-runtime", path: paths.ledgerPath });
|
|
192
202
|
return entry;
|
|
193
203
|
}
|
|
194
204
|
|
|
195
|
-
export async function readUltragoalLedger(cwd: string): Promise<UltragoalLedgerEvent[]> {
|
|
205
|
+
export async function readUltragoalLedger(cwd: string, sessionId?: string | null): Promise<UltragoalLedgerEvent[]> {
|
|
206
|
+
const resolvedSessionId =
|
|
207
|
+
sessionId?.trim() ||
|
|
208
|
+
(await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
|
|
196
209
|
try {
|
|
197
|
-
const raw = await Bun.file(getUltragoalPaths(cwd).ledgerPath).text();
|
|
210
|
+
const raw = await Bun.file(getUltragoalPaths(cwd, resolvedSessionId).ledgerPath).text();
|
|
198
211
|
return raw
|
|
199
212
|
.split(/\r?\n/)
|
|
200
213
|
.map(line => line.trim())
|
|
@@ -206,16 +219,21 @@ export async function readUltragoalLedger(cwd: string): Promise<UltragoalLedgerE
|
|
|
206
219
|
}
|
|
207
220
|
}
|
|
208
221
|
|
|
209
|
-
async function writePlan(cwd: string, plan: UltragoalPlan): Promise<void> {
|
|
210
|
-
const
|
|
222
|
+
async function writePlan(cwd: string, plan: UltragoalPlan, sessionId?: string | null): Promise<void> {
|
|
223
|
+
const resolvedSessionId =
|
|
224
|
+
sessionId?.trim() || resolveGjcSessionForWrite(cwd, { envSessionId: process.env.GJC_SESSION_ID }).gjcSessionId;
|
|
225
|
+
const paths = getUltragoalPaths(cwd, resolvedSessionId);
|
|
211
226
|
await writeArtifact(paths.briefPath, `${plan.brief.trim()}\n`, {
|
|
212
227
|
cwd,
|
|
213
|
-
audit: { category: "artifact", verb: "write", owner: "gjc-runtime" },
|
|
228
|
+
audit: { category: "artifact", verb: "write", owner: "gjc-runtime", sessionId: resolvedSessionId },
|
|
214
229
|
});
|
|
215
|
-
await
|
|
230
|
+
await writeGuardedJsonAtomic(paths.goalsPath, plan, {
|
|
216
231
|
cwd,
|
|
217
|
-
|
|
232
|
+
policy: "source",
|
|
233
|
+
expectedRevision: typeof plan.state_revision === "number" ? persistedStateRevision(plan) : undefined,
|
|
234
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", sessionId: resolvedSessionId },
|
|
218
235
|
});
|
|
236
|
+
await writeSessionActivityMarker(cwd, resolvedSessionId, { writer: "ultragoal-runtime", path: paths.goalsPath });
|
|
219
237
|
}
|
|
220
238
|
|
|
221
239
|
function requiredUltragoalGoals(plan: UltragoalPlan): UltragoalGoal[] {
|
|
@@ -426,6 +444,7 @@ function normalizePlan(raw: unknown): UltragoalPlan {
|
|
|
426
444
|
const objective = nonEmptyString(goalRecord.objective) ?? title;
|
|
427
445
|
const goalCreatedAt = nonEmptyString(goalRecord.createdAt) ?? createdAt;
|
|
428
446
|
return {
|
|
447
|
+
...goalRecord,
|
|
429
448
|
id,
|
|
430
449
|
title,
|
|
431
450
|
objective,
|
|
@@ -459,12 +478,18 @@ function normalizePlan(raw: unknown): UltragoalPlan {
|
|
|
459
478
|
goals,
|
|
460
479
|
createdAt,
|
|
461
480
|
updatedAt,
|
|
481
|
+
...(typeof record.state_revision === "number" && Number.isFinite(record.state_revision)
|
|
482
|
+
? { state_revision: record.state_revision }
|
|
483
|
+
: {}),
|
|
462
484
|
};
|
|
463
485
|
}
|
|
464
486
|
|
|
465
|
-
export async function readUltragoalPlan(cwd: string): Promise<UltragoalPlan | null> {
|
|
487
|
+
export async function readUltragoalPlan(cwd: string, sessionId?: string | null): Promise<UltragoalPlan | null> {
|
|
488
|
+
const resolvedSessionId =
|
|
489
|
+
sessionId?.trim() ||
|
|
490
|
+
(await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
|
|
466
491
|
try {
|
|
467
|
-
return normalizePlan(await Bun.file(getUltragoalPaths(cwd).goalsPath).json());
|
|
492
|
+
return normalizePlan(await Bun.file(getUltragoalPaths(cwd, resolvedSessionId).goalsPath).json());
|
|
468
493
|
} catch (error) {
|
|
469
494
|
if (isEnoent(error)) return null;
|
|
470
495
|
throw error;
|
|
@@ -483,9 +508,12 @@ function emptyCounts(): Record<UltragoalGoalStatus, number> {
|
|
|
483
508
|
};
|
|
484
509
|
}
|
|
485
510
|
|
|
486
|
-
export async function getUltragoalStatus(cwd: string): Promise<UltragoalStatusSummary> {
|
|
487
|
-
const
|
|
488
|
-
|
|
511
|
+
export async function getUltragoalStatus(cwd: string, sessionId?: string | null): Promise<UltragoalStatusSummary> {
|
|
512
|
+
const resolvedSessionId =
|
|
513
|
+
sessionId?.trim() ||
|
|
514
|
+
(await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
|
|
515
|
+
const paths = getUltragoalPaths(cwd, resolvedSessionId);
|
|
516
|
+
const plan = await readUltragoalPlan(cwd, resolvedSessionId);
|
|
489
517
|
const counts = emptyCounts();
|
|
490
518
|
if (!plan) return { exists: false, status: "missing", paths, counts, goals: [] };
|
|
491
519
|
for (const goal of plan.goals) counts[goal.status] += 1;
|
|
@@ -577,6 +605,7 @@ export async function createUltragoalPlan(input: {
|
|
|
577
605
|
cwd: string;
|
|
578
606
|
brief: string;
|
|
579
607
|
gjcGoalMode?: UltragoalGjcGoalMode;
|
|
608
|
+
sessionId?: string | null;
|
|
580
609
|
}): Promise<UltragoalPlan> {
|
|
581
610
|
const brief = input.brief.trim();
|
|
582
611
|
if (!brief) throw new Error("ultragoal brief is required");
|
|
@@ -601,8 +630,8 @@ export async function createUltragoalPlan(input: {
|
|
|
601
630
|
createdAt: now,
|
|
602
631
|
updatedAt: now,
|
|
603
632
|
};
|
|
604
|
-
await writePlan(input.cwd, plan);
|
|
605
|
-
await appendLedger(input.cwd, { event: "plan_created", goalIds: plan.goals.map(goal => goal.id) });
|
|
633
|
+
await writePlan(input.cwd, plan, input.sessionId);
|
|
634
|
+
await appendLedger(input.cwd, { event: "plan_created", goalIds: plan.goals.map(goal => goal.id) }, input.sessionId);
|
|
606
635
|
return plan;
|
|
607
636
|
}
|
|
608
637
|
|
|
@@ -639,12 +668,16 @@ export function getUltragoalRunCompletionState(
|
|
|
639
668
|
};
|
|
640
669
|
}
|
|
641
670
|
|
|
642
|
-
export async function startNextUltragoalGoal(input: {
|
|
671
|
+
export async function startNextUltragoalGoal(input: {
|
|
672
|
+
cwd: string;
|
|
673
|
+
retryFailed?: boolean;
|
|
674
|
+
sessionId?: string | null;
|
|
675
|
+
}): Promise<{
|
|
643
676
|
plan: UltragoalPlan;
|
|
644
677
|
goal?: UltragoalGoal;
|
|
645
678
|
allComplete: boolean;
|
|
646
679
|
}> {
|
|
647
|
-
const plan = await readUltragoalPlan(input.cwd);
|
|
680
|
+
const plan = await readUltragoalPlan(input.cwd, input.sessionId);
|
|
648
681
|
if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
|
|
649
682
|
const goal = chooseNextGoal(plan, input.retryFailed === true);
|
|
650
683
|
if (!goal) return { plan, allComplete: getUltragoalRunCompletionState(plan).allComplete };
|
|
@@ -654,8 +687,8 @@ export async function startNextUltragoalGoal(input: { cwd: string; retryFailed?:
|
|
|
654
687
|
goal.startedAt = goal.startedAt ?? now;
|
|
655
688
|
goal.updatedAt = now;
|
|
656
689
|
plan.updatedAt = now;
|
|
657
|
-
await writePlan(input.cwd, plan);
|
|
658
|
-
await appendLedger(input.cwd, { event: "goal_started", goalId: goal.id });
|
|
690
|
+
await writePlan(input.cwd, plan, input.sessionId);
|
|
691
|
+
await appendLedger(input.cwd, { event: "goal_started", goalId: goal.id }, input.sessionId);
|
|
659
692
|
}
|
|
660
693
|
return { plan, goal, allComplete: false };
|
|
661
694
|
}
|
|
@@ -2350,6 +2383,8 @@ export async function checkpointUltragoalGoal(input: {
|
|
|
2350
2383
|
if (input.status === "complete") goal.completedAt = now;
|
|
2351
2384
|
plan.updatedAt = now;
|
|
2352
2385
|
await writePlan(input.cwd, plan);
|
|
2386
|
+
const persistedPlan = await readUltragoalPlan(input.cwd);
|
|
2387
|
+
if (persistedPlan?.state_revision !== undefined) plan.state_revision = persistedPlan.state_revision;
|
|
2353
2388
|
await appendLedger(input.cwd, {
|
|
2354
2389
|
eventId: pendingCheckpointEventId,
|
|
2355
2390
|
event: "goal_checkpointed",
|
|
@@ -2799,6 +2834,8 @@ export async function recordUltragoalReviewBlockers(input: {
|
|
|
2799
2834
|
evidence: input.evidence,
|
|
2800
2835
|
gjcGoalJson: input.gjcGoalJson,
|
|
2801
2836
|
});
|
|
2837
|
+
const persistedPlan = await readUltragoalPlan(input.cwd);
|
|
2838
|
+
if (persistedPlan?.state_revision !== undefined) plan.state_revision = persistedPlan.state_revision;
|
|
2802
2839
|
const now = new Date().toISOString();
|
|
2803
2840
|
const nextId = `G${String(plan.goals.length + 1).padStart(3, "0")}`;
|
|
2804
2841
|
plan.goals.push({
|
|
@@ -3329,7 +3366,7 @@ function renderCompleteHandoff(
|
|
|
3329
3366
|
goal_id: result.goal?.id,
|
|
3330
3367
|
goal_status: result.goal?.status,
|
|
3331
3368
|
gjc_objective: result.plan.gjcObjective,
|
|
3332
|
-
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
3369
|
+
goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
|
|
3333
3370
|
});
|
|
3334
3371
|
}
|
|
3335
3372
|
if (result.allComplete) return "ultragoal complete all=true\n";
|
|
@@ -3353,7 +3390,7 @@ function renderCheckpointContinuation(
|
|
|
3353
3390
|
ok: true,
|
|
3354
3391
|
goal_id: result.checkpointedGoal.id,
|
|
3355
3392
|
status,
|
|
3356
|
-
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
3393
|
+
goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
|
|
3357
3394
|
completion_receipt_kind: result.checkpointedGoal.completionVerification?.receiptKind,
|
|
3358
3395
|
quality_gate_hash: result.checkpointedGoal.completionVerification?.qualityGateHash,
|
|
3359
3396
|
all_complete: result.allComplete,
|
|
@@ -3407,7 +3444,12 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
|
|
|
3407
3444
|
return {
|
|
3408
3445
|
kind,
|
|
3409
3446
|
message: "Accepted add_subgoal steering.\n",
|
|
3410
|
-
receipt: {
|
|
3447
|
+
receipt: {
|
|
3448
|
+
ok: true,
|
|
3449
|
+
kind,
|
|
3450
|
+
goal_id: result.goalId,
|
|
3451
|
+
goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
|
|
3452
|
+
},
|
|
3411
3453
|
};
|
|
3412
3454
|
}
|
|
3413
3455
|
case "split_subgoal": {
|
|
@@ -3427,7 +3469,7 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
|
|
|
3427
3469
|
kind,
|
|
3428
3470
|
goal_id: result.goalId,
|
|
3429
3471
|
replacement_goal_ids: result.replacementGoalIds,
|
|
3430
|
-
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
3472
|
+
goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
|
|
3431
3473
|
},
|
|
3432
3474
|
};
|
|
3433
3475
|
}
|
|
@@ -3446,7 +3488,7 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
|
|
|
3446
3488
|
ok: true,
|
|
3447
3489
|
kind,
|
|
3448
3490
|
pending_goal_ids: result.pendingGoalIds,
|
|
3449
|
-
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
3491
|
+
goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
|
|
3450
3492
|
},
|
|
3451
3493
|
};
|
|
3452
3494
|
}
|
|
@@ -3468,7 +3510,7 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
|
|
|
3468
3510
|
kind,
|
|
3469
3511
|
goal_id: result.goalId,
|
|
3470
3512
|
changed_fields: result.changedFields,
|
|
3471
|
-
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
3513
|
+
goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
|
|
3472
3514
|
},
|
|
3473
3515
|
};
|
|
3474
3516
|
}
|
|
@@ -3477,7 +3519,11 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
|
|
|
3477
3519
|
return {
|
|
3478
3520
|
kind,
|
|
3479
3521
|
message: "Accepted annotate_ledger steering.\n",
|
|
3480
|
-
receipt: {
|
|
3522
|
+
receipt: {
|
|
3523
|
+
ok: true,
|
|
3524
|
+
kind,
|
|
3525
|
+
ledger_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).ledgerPath,
|
|
3526
|
+
},
|
|
3481
3527
|
};
|
|
3482
3528
|
}
|
|
3483
3529
|
case "mark_blocked_superseded": {
|
|
@@ -3496,7 +3542,7 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
|
|
|
3496
3542
|
kind,
|
|
3497
3543
|
goal_id: result.goalId,
|
|
3498
3544
|
no_replacement_required: true,
|
|
3499
|
-
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
3545
|
+
goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
|
|
3500
3546
|
},
|
|
3501
3547
|
};
|
|
3502
3548
|
}
|
|
@@ -3517,6 +3563,7 @@ async function executeUltragoalSteeringCommand(args: readonly string[], cwd: str
|
|
|
3517
3563
|
}
|
|
3518
3564
|
|
|
3519
3565
|
async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<UltragoalCommandResult> {
|
|
3566
|
+
const sessionId = currentUltragoalSessionId(cwd);
|
|
3520
3567
|
const help = renderUltragoalHelp(args);
|
|
3521
3568
|
if (help) return { status: 0, stdout: help };
|
|
3522
3569
|
try {
|
|
@@ -3524,7 +3571,7 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
|
|
|
3524
3571
|
const json = hasFlag(args, "--json");
|
|
3525
3572
|
switch (command) {
|
|
3526
3573
|
case "status":
|
|
3527
|
-
return { status: 0, stdout: renderStatus(await getUltragoalStatus(cwd), json) };
|
|
3574
|
+
return { status: 0, stdout: renderStatus(await getUltragoalStatus(cwd, sessionId), json) };
|
|
3528
3575
|
case "create":
|
|
3529
3576
|
case "create-goals": {
|
|
3530
3577
|
const mode = flagValue(args, "--gjc-goal-mode") === "per-story" ? "per-story" : "aggregate";
|
|
@@ -3537,9 +3584,9 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
|
|
|
3537
3584
|
ok: true,
|
|
3538
3585
|
goals_count: plan.goals.length,
|
|
3539
3586
|
goal_ids: plan.goals.map(goal => goal.id),
|
|
3540
|
-
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
3587
|
+
goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
|
|
3541
3588
|
})
|
|
3542
|
-
: `Created ultragoal plan with ${plan.goals.length} goal${plan.goals.length === 1 ? "" : "s"} at ${getUltragoalPaths(cwd).goalsPath}.\n`,
|
|
3589
|
+
: `Created ultragoal plan with ${plan.goals.length} goal${plan.goals.length === 1 ? "" : "s"} at ${getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath}.\n`,
|
|
3543
3590
|
};
|
|
3544
3591
|
}
|
|
3545
3592
|
case "complete-goals":
|
|
@@ -3598,7 +3645,11 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
|
|
|
3598
3645
|
return {
|
|
3599
3646
|
status: 0,
|
|
3600
3647
|
stdout: json
|
|
3601
|
-
? renderCliWriteReceipt({
|
|
3648
|
+
? renderCliWriteReceipt({
|
|
3649
|
+
ok: true,
|
|
3650
|
+
goal_id: goal?.id,
|
|
3651
|
+
goals_path: getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath,
|
|
3652
|
+
})
|
|
3602
3653
|
: "Recorded review blockers.\n",
|
|
3603
3654
|
};
|
|
3604
3655
|
}
|
|
@@ -3632,9 +3683,9 @@ const RECONCILE_COMMANDS = new Set([
|
|
|
3632
3683
|
* beyond that reconcile-failure audit event.
|
|
3633
3684
|
*/
|
|
3634
3685
|
async function reconcileUltragoalState(cwd: string): Promise<void> {
|
|
3635
|
-
const sessionId =
|
|
3686
|
+
const sessionId = currentUltragoalSessionId(cwd);
|
|
3636
3687
|
try {
|
|
3637
|
-
const summary = await getUltragoalStatus(cwd);
|
|
3688
|
+
const summary = await getUltragoalStatus(cwd, sessionId);
|
|
3638
3689
|
const status = summary.status;
|
|
3639
3690
|
const active = summary.exists && status !== "complete";
|
|
3640
3691
|
const payload: Record<string, unknown> = {
|
|
@@ -3653,15 +3704,44 @@ async function reconcileUltragoalState(cwd: string): Promise<void> {
|
|
|
3653
3704
|
const ledgerText = await Bun.file(summary.paths.ledgerPath)
|
|
3654
3705
|
.text()
|
|
3655
3706
|
.catch(() => "");
|
|
3656
|
-
const latestLedger =
|
|
3707
|
+
const latestLedger = ledgerText
|
|
3708
|
+
.split(/\r?\n/)
|
|
3709
|
+
.map(line => line.trim())
|
|
3710
|
+
.filter(Boolean)
|
|
3711
|
+
.toReversed()
|
|
3712
|
+
.map(line => {
|
|
3713
|
+
try {
|
|
3714
|
+
const row = JSON.parse(line) as Record<string, unknown>;
|
|
3715
|
+
const event =
|
|
3716
|
+
typeof row.event === "string" ? row.event : typeof row.type === "string" ? row.type : undefined;
|
|
3717
|
+
return event ? { ...row, event } : undefined;
|
|
3718
|
+
} catch {
|
|
3719
|
+
return undefined;
|
|
3720
|
+
}
|
|
3721
|
+
})
|
|
3722
|
+
.find((row): row is Record<string, unknown> & { event: string } => Boolean(row));
|
|
3657
3723
|
if (latestLedger) {
|
|
3658
3724
|
payload.latestLedgerEvent = {
|
|
3659
3725
|
event: latestLedger.event,
|
|
3660
3726
|
...(latestLedger.goalId ? { goalId: latestLedger.goalId } : {}),
|
|
3661
3727
|
...(latestLedger.timestamp ? { timestamp: latestLedger.timestamp } : {}),
|
|
3728
|
+
...(typeof latestLedger.kind === "string" ? { kind: latestLedger.kind } : {}),
|
|
3729
|
+
...(typeof latestLedger.evidence === "string" ? { evidence: latestLedger.evidence } : {}),
|
|
3662
3730
|
};
|
|
3663
3731
|
}
|
|
3664
|
-
|
|
3732
|
+
const sourceRevision = Math.max(
|
|
3733
|
+
persistedStateRevision(await readUltragoalPlan(cwd, sessionId)),
|
|
3734
|
+
ledgerText.split(/\r?\n/).filter(line => line.trim().length > 0).length,
|
|
3735
|
+
);
|
|
3736
|
+
await reconcileWorkflowSkillState({
|
|
3737
|
+
cwd,
|
|
3738
|
+
mode: "ultragoal",
|
|
3739
|
+
sessionId,
|
|
3740
|
+
active,
|
|
3741
|
+
phase: status,
|
|
3742
|
+
payload,
|
|
3743
|
+
...(sourceRevision > 0 ? { sourceRevision } : {}),
|
|
3744
|
+
});
|
|
3665
3745
|
} catch (error) {
|
|
3666
3746
|
const message = error instanceof Error ? error.message : String(error);
|
|
3667
3747
|
process.stderr.write(`ultragoal state reconciliation failed: ${message}\n`);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { CANONICAL_GJC_WORKFLOW_SKILLS } from "../skill-state/active-state";
|
|
1
|
+
import { CANONICAL_GJC_WORKFLOW_SKILLS, type CanonicalGjcWorkflowSkill } from "../skill-state/canonical-skills";
|
|
3
2
|
|
|
4
3
|
export type CommandRefVisibility = "public" | "hidden" | "planned";
|
|
5
4
|
export type CommandRefIncludeWhen = "implemented-only" | "planned";
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* hand-edited.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type
|
|
8
|
-
import { CANONICAL_GJC_WORKFLOW_SKILLS } from "../skill-state/active-state";
|
|
7
|
+
import { CANONICAL_GJC_WORKFLOW_SKILLS, type CanonicalGjcWorkflowSkill } from "../skill-state/canonical-skills";
|
|
9
8
|
import { initialPhaseForSkill } from "../skill-state/initial-phase";
|
|
10
9
|
|
|
11
10
|
export interface WorkflowState {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Session-scoped storage for the harness control plane.
|
|
3
3
|
*
|
|
4
|
-
* Layout (under the harness state root, default `<cwd>/.gjc/state/harness`):
|
|
4
|
+
* Layout (under the harness state root, default `<cwd>/.gjc/_session-{sessionid}/state/harness`):
|
|
5
5
|
* sessions/<encoded-id>/state.json lifecycle + handle (atomic)
|
|
6
6
|
* sessions/<encoded-id>/lease.json owner lease (M3)
|
|
7
7
|
* sessions/<encoded-id>/events.jsonl owner-only severity envelopes
|
|
@@ -18,6 +18,7 @@ import * as fsSync from "node:fs";
|
|
|
18
18
|
import * as fs from "node:fs/promises";
|
|
19
19
|
import * as os from "node:os";
|
|
20
20
|
import * as path from "node:path";
|
|
21
|
+
import { harnessStateRoot } from "../gjc-runtime/session-layout";
|
|
21
22
|
import { appendReceiptToConfiguredSpool } from "./receipt-spool";
|
|
22
23
|
import type { ReceiptEnvelope } from "./receipts";
|
|
23
24
|
import type { EventEnvelope, ReceiptFamily, SessionState } from "./types";
|
|
@@ -232,13 +233,22 @@ export class StorageError extends Error {
|
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
/** Resolve the harness state root from explicit value, env, or cwd default. */
|
|
236
|
-
export function resolveHarnessRoot(opts?: {
|
|
236
|
+
/** Resolve the harness state root from explicit value, env, or cwd/session default. */
|
|
237
|
+
export function resolveHarnessRoot(opts?: {
|
|
238
|
+
root?: string;
|
|
239
|
+
cwd?: string;
|
|
240
|
+
env?: NodeJS.ProcessEnv;
|
|
241
|
+
gjcSessionId?: string;
|
|
242
|
+
}): string {
|
|
237
243
|
const env = opts?.env ?? process.env;
|
|
238
244
|
if (opts?.root) return path.resolve(opts.root);
|
|
239
245
|
const fromEnv = env.GJC_HARNESS_STATE_ROOT;
|
|
240
246
|
if (fromEnv?.trim()) return path.resolve(fromEnv.trim());
|
|
241
|
-
|
|
247
|
+
const gjcSessionId = opts?.gjcSessionId ?? env.GJC_SESSION_ID?.trim();
|
|
248
|
+
if (!gjcSessionId) {
|
|
249
|
+
throw new StorageError("GJC session id is required for default harness state root", "missing_gjc_session_id");
|
|
250
|
+
}
|
|
251
|
+
return harnessStateRoot(opts?.cwd ?? process.cwd(), gjcSessionId);
|
|
242
252
|
}
|
|
243
253
|
|
|
244
254
|
export function assertSafeSessionId(id: string): void {
|