@gajae-code/coding-agent 0.6.4 → 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.
Files changed (120) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/types/cli/migrate-cli.d.ts +20 -0
  3. package/dist/types/commands/migrate.d.ts +33 -0
  4. package/dist/types/config/keybindings.d.ts +4 -0
  5. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +2 -0
  6. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -2
  7. package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
  8. package/dist/types/gjc-runtime/session-layout.d.ts +59 -0
  9. package/dist/types/gjc-runtime/session-resolution.d.ts +47 -0
  10. package/dist/types/gjc-runtime/state-graph.d.ts +1 -1
  11. package/dist/types/gjc-runtime/state-runtime.d.ts +5 -4
  12. package/dist/types/gjc-runtime/state-schema.d.ts +2 -0
  13. package/dist/types/gjc-runtime/state-writer.d.ts +36 -7
  14. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +7 -4
  15. package/dist/types/gjc-runtime/workflow-command-ref.d.ts +1 -1
  16. package/dist/types/gjc-runtime/workflow-manifest.d.ts +1 -1
  17. package/dist/types/harness-control-plane/storage.d.ts +2 -1
  18. package/dist/types/hooks/skill-state.d.ts +12 -4
  19. package/dist/types/migrate/action-planner.d.ts +11 -0
  20. package/dist/types/migrate/adapters/claude-code.d.ts +2 -0
  21. package/dist/types/migrate/adapters/codex.d.ts +5 -0
  22. package/dist/types/migrate/adapters/index.d.ts +45 -0
  23. package/dist/types/migrate/adapters/opencode.d.ts +2 -0
  24. package/dist/types/migrate/executor.d.ts +2 -0
  25. package/dist/types/migrate/mcp-mapper.d.ts +20 -0
  26. package/dist/types/migrate/report.d.ts +18 -0
  27. package/dist/types/migrate/skill-normalizer.d.ts +27 -0
  28. package/dist/types/migrate/types.d.ts +126 -0
  29. package/dist/types/modes/components/custom-editor.d.ts +1 -1
  30. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +1 -1
  31. package/dist/types/research-plan/index.d.ts +1 -0
  32. package/dist/types/research-plan/ledger.d.ts +33 -0
  33. package/dist/types/rlm/artifacts.d.ts +1 -1
  34. package/dist/types/runtime-mcp/config-writer.d.ts +26 -0
  35. package/dist/types/skill-state/active-state.d.ts +6 -11
  36. package/dist/types/skill-state/canonical-skills.d.ts +3 -0
  37. package/dist/types/skill-state/workflow-hud.d.ts +2 -0
  38. package/dist/types/task/spawn-gate.d.ts +1 -10
  39. package/package.json +7 -7
  40. package/src/cli/migrate-cli.ts +106 -0
  41. package/src/cli.ts +1 -0
  42. package/src/commands/deep-interview.ts +2 -2
  43. package/src/commands/migrate.ts +46 -0
  44. package/src/commands/state.ts +2 -1
  45. package/src/commands/team.ts +7 -3
  46. package/src/coordinator-mcp/policy.ts +10 -2
  47. package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +0 -1
  48. package/src/defaults/gjc/skills/deep-interview/SKILL.md +28 -24
  49. package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
  50. package/src/defaults/gjc/skills/team/SKILL.md +51 -47
  51. package/src/defaults/gjc/skills/ultragoal/SKILL.md +17 -13
  52. package/src/extensibility/custom-commands/loader.ts +0 -7
  53. package/src/extensibility/gjc-plugins/injection.ts +23 -4
  54. package/src/extensibility/gjc-plugins/state.ts +16 -1
  55. package/src/gjc-runtime/deep-interview-recorder.ts +43 -18
  56. package/src/gjc-runtime/deep-interview-runtime.ts +49 -23
  57. package/src/gjc-runtime/goal-mode-request.ts +26 -11
  58. package/src/gjc-runtime/launch-tmux.ts +6 -1
  59. package/src/gjc-runtime/ralplan-runtime.ts +79 -50
  60. package/src/gjc-runtime/session-layout.ts +180 -0
  61. package/src/gjc-runtime/session-resolution.ts +217 -0
  62. package/src/gjc-runtime/state-graph.ts +1 -2
  63. package/src/gjc-runtime/state-migrations.ts +1 -0
  64. package/src/gjc-runtime/state-runtime.ts +230 -121
  65. package/src/gjc-runtime/state-schema.ts +2 -0
  66. package/src/gjc-runtime/state-writer.ts +289 -41
  67. package/src/gjc-runtime/team-runtime.ts +43 -19
  68. package/src/gjc-runtime/tmux-sessions.ts +7 -1
  69. package/src/gjc-runtime/ultragoal-guard.ts +45 -2
  70. package/src/gjc-runtime/ultragoal-runtime.ts +121 -41
  71. package/src/gjc-runtime/workflow-command-ref.ts +1 -2
  72. package/src/gjc-runtime/workflow-manifest.ts +1 -2
  73. package/src/harness-control-plane/storage.ts +14 -4
  74. package/src/hooks/native-skill-hook.ts +38 -12
  75. package/src/hooks/skill-state.ts +178 -83
  76. package/src/internal-urls/docs-index.generated.ts +6 -4
  77. package/src/migrate/action-planner.ts +318 -0
  78. package/src/migrate/adapters/claude-code.ts +39 -0
  79. package/src/migrate/adapters/codex.ts +70 -0
  80. package/src/migrate/adapters/index.ts +277 -0
  81. package/src/migrate/adapters/opencode.ts +52 -0
  82. package/src/migrate/executor.ts +81 -0
  83. package/src/migrate/mcp-mapper.ts +152 -0
  84. package/src/migrate/report.ts +104 -0
  85. package/src/migrate/skill-normalizer.ts +80 -0
  86. package/src/migrate/types.ts +163 -0
  87. package/src/modes/bridge/bridge-mode.ts +2 -2
  88. package/src/modes/components/custom-editor.ts +30 -20
  89. package/src/modes/rpc/rpc-mode.ts +2 -2
  90. package/src/modes/shared/agent-wire/unattended-audit.ts +3 -2
  91. package/src/prompts/agents/init.md +1 -1
  92. package/src/prompts/system/plan-mode-active.md +1 -1
  93. package/src/prompts/tools/ast-grep.md +1 -1
  94. package/src/prompts/tools/search.md +1 -1
  95. package/src/prompts/tools/task.md +1 -2
  96. package/src/research-plan/index.ts +1 -0
  97. package/src/research-plan/ledger.ts +177 -0
  98. package/src/rlm/artifacts.ts +12 -3
  99. package/src/rlm/index.ts +7 -0
  100. package/src/runtime-mcp/config-writer.ts +46 -0
  101. package/src/session/agent-session.ts +15 -21
  102. package/src/setup/hermes-setup.ts +1 -1
  103. package/src/skill-state/active-state.ts +72 -108
  104. package/src/skill-state/canonical-skills.ts +4 -0
  105. package/src/skill-state/deep-interview-mutation-guard.ts +28 -109
  106. package/src/skill-state/workflow-hud.ts +4 -2
  107. package/src/skill-state/workflow-state-contract.ts +3 -3
  108. package/src/task/agents.ts +1 -22
  109. package/src/task/index.ts +1 -41
  110. package/src/task/spawn-gate.ts +1 -38
  111. package/src/task/types.ts +1 -1
  112. package/src/tools/ask.ts +34 -12
  113. package/src/tools/computer.ts +58 -4
  114. package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +0 -10
  115. package/src/extensibility/custom-commands/bundled/review/index.ts +0 -456
  116. package/src/prompts/agents/explore.md +0 -58
  117. package/src/prompts/agents/plan.md +0 -49
  118. package/src/prompts/agents/reviewer.md +0 -141
  119. package/src/prompts/agents/task.md +0 -16
  120. package/src/prompts/review-request.md +0 -70
@@ -1,3 +1,16 @@
1
+ import { resolveGjcSessionForRead, SessionResolutionError } from "../../gjc-runtime/session-resolution";
2
+
3
+ async function resolveBoundarySessionId(cwd: string, sessionId?: string): Promise<string | undefined> {
4
+ const normalizedSessionId = sessionId?.trim();
5
+ if (normalizedSessionId) return normalizedSessionId;
6
+ try {
7
+ return (await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
8
+ } catch (error) {
9
+ if (error instanceof SessionResolutionError && error.code === "no_session") return undefined;
10
+ throw error;
11
+ }
12
+ }
13
+
1
14
  import { readVisibleSkillActiveState } from "../../skill-state/active-state";
2
15
  import { initialPhaseForSkill } from "../../skill-state/initial-phase";
3
16
  import { readActiveSubskillsForParent } from "./state";
@@ -35,7 +48,8 @@ export async function resolveCurrentPhaseForParent(input: {
35
48
  const explicitPhase = input.explicitPhase?.trim();
36
49
  if (explicitPhase) return explicitPhase;
37
50
 
38
- const state = await readVisibleSkillActiveState(input.cwd, input.sessionId);
51
+ const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
52
+ const state = resolvedSessionId ? await readVisibleSkillActiveState(input.cwd, resolvedSessionId) : null;
39
53
  const persistedPhase = state?.active_skills?.find(entry => entry.skill === input.parent)?.phase?.trim();
40
54
  if (persistedPhase) return persistedPhase;
41
55
 
@@ -54,9 +68,10 @@ export async function buildSubskillInjection(input: {
54
68
  activation?: LoadedSubskillActivation;
55
69
  currentPhase?: string;
56
70
  }): Promise<{ block: string; details?: LoadedSubskillActivation } | null> {
71
+ const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
57
72
  const resolvedPhase = await resolveCurrentPhaseForParent({
58
73
  cwd: input.cwd,
59
- sessionId: input.sessionId,
74
+ sessionId: resolvedSessionId,
60
75
  parent: input.skillName,
61
76
  explicitPhase: input.currentPhase,
62
77
  });
@@ -67,9 +82,11 @@ export async function buildSubskillInjection(input: {
67
82
  return { block: wrapSubskillBlock(directActivation, body), details: directActivation };
68
83
  }
69
84
 
85
+ if (!resolvedSessionId) return null;
86
+
70
87
  const [entry] = await readActiveSubskillsForParent({
71
88
  cwd: input.cwd,
72
- sessionId: input.sessionId,
89
+ sessionId: resolvedSessionId,
73
90
  parent: input.skillName,
74
91
  phase: resolvedPhase,
75
92
  });
@@ -96,9 +113,11 @@ export async function buildAgentSubskillInjection(input: {
96
113
  }): Promise<string> {
97
114
  if (!(GJC_SUBSKILL_PARENT_AGENTS as readonly string[]).includes(input.agentName)) return "";
98
115
 
116
+ const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
117
+ if (!resolvedSessionId) return "";
99
118
  const entries = await readActiveSubskillsForParent({
100
119
  cwd: input.cwd,
101
- sessionId: input.sessionId,
120
+ sessionId: resolvedSessionId,
102
121
  parent: input.agentName,
103
122
  phase: "prompt",
104
123
  });
@@ -1,3 +1,16 @@
1
+ import { resolveGjcSessionForRead, SessionResolutionError } from "../../gjc-runtime/session-resolution";
2
+
3
+ async function resolveBoundarySessionId(cwd: string, sessionId?: string): Promise<string | undefined> {
4
+ const normalizedSessionId = sessionId?.trim();
5
+ if (normalizedSessionId) return normalizedSessionId;
6
+ try {
7
+ return (await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
8
+ } catch (error) {
9
+ if (error instanceof SessionResolutionError && error.code === "no_session") return undefined;
10
+ throw error;
11
+ }
12
+ }
13
+
1
14
  import type { ActiveSubskillEntry } from "../../skill-state/active-state";
2
15
  import { readVisibleSkillActiveState } from "../../skill-state/active-state";
3
16
  import type { LoadedSubskillActivation } from "./types";
@@ -21,7 +34,9 @@ export async function readActiveSubskillsForParent(input: {
21
34
  parent: string;
22
35
  phase: string;
23
36
  }): Promise<ActiveSubskillEntry[]> {
24
- const state = await readVisibleSkillActiveState(input.cwd, input.sessionId);
37
+ const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
38
+ if (!resolvedSessionId) return [];
39
+ const state = await readVisibleSkillActiveState(input.cwd, resolvedSessionId);
25
40
  const parent = input.parent.trim();
26
41
  const phase = input.phase.trim();
27
42
  if (!state || !parent || !phase) return [];
@@ -11,7 +11,8 @@ import {
11
11
  normalizeDeepInterviewEnvelope,
12
12
  questionHash,
13
13
  } from "./deep-interview-state";
14
- import { readExistingStateForMutation, writeWorkflowEnvelopeAtomic } from "./state-writer";
14
+ import { writeSessionActivityMarker } from "./session-resolution";
15
+ import { readExistingStateForMutation, writeGuardedWorkflowEnvelopeAtomic } from "./state-writer";
15
16
 
16
17
  export * from "./deep-interview-state";
17
18
 
@@ -298,6 +299,12 @@ async function readEnvelope(statePath: string): Promise<DeepInterviewStateEnvelo
298
299
  return ensureDeepInterviewStateShape(undefined);
299
300
  }
300
301
 
302
+ function existingStateRevision(value: unknown): number | undefined {
303
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
304
+ const revision = (value as Record<string, unknown>).state_revision;
305
+ return typeof revision === "number" && Number.isFinite(revision) ? revision : 0;
306
+ }
307
+
301
308
  function interviewIdOf(envelope: DeepInterviewStateEnvelope): string | undefined {
302
309
  const inner = (envelope.state ?? {}) as Record<string, unknown>;
303
310
  return typeof inner.interview_id === "string" ? inner.interview_id : undefined;
@@ -310,6 +317,7 @@ async function persistEnvelope(
310
317
  sessionId: string | undefined,
311
318
  command: string,
312
319
  ): Promise<void> {
320
+ if (!sessionId) throw new Error("deep-interview recorder requires a session id");
313
321
  const now = new Date().toISOString();
314
322
  const payload: Record<string, unknown> = { ...normalizeDeepInterviewEnvelope(envelope), updated_at: now };
315
323
  // Guarantee RequiredOnWriteEnvelopeSchema fields for the fresh/absent fallback;
@@ -318,11 +326,14 @@ async function persistEnvelope(
318
326
  payload.version ??= WORKFLOW_STATE_VERSION;
319
327
  payload.active ??= true;
320
328
  payload.current_phase ??= "interviewing";
321
- await writeWorkflowEnvelopeAtomic(statePath, payload, {
329
+ await writeGuardedWorkflowEnvelopeAtomic(statePath, payload, {
322
330
  cwd,
331
+ policy: "source",
332
+ expectedRevision: existingStateRevision(envelope),
323
333
  receipt: { cwd, skill: "deep-interview", owner: "gjc-runtime", command, sessionId, nowIso: now },
324
- audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
334
+ audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "deep-interview", sessionId },
325
335
  });
336
+ await writeSessionActivityMarker(cwd, sessionId, { writer: "deep-interview-recorder", path: statePath });
326
337
  }
327
338
 
328
339
  /**
@@ -335,20 +346,17 @@ async function syncRecorderHud(
335
346
  envelope: DeepInterviewStateEnvelope,
336
347
  sessionId: string | undefined,
337
348
  ): Promise<void> {
338
- try {
339
- const phase = typeof envelope.current_phase === "string" ? envelope.current_phase : "interviewing";
340
- await syncSkillActiveState({
341
- cwd,
342
- skill: "deep-interview",
343
- active: phase !== "complete",
344
- phase,
345
- sessionId,
346
- source: "gjc-runtime-deep-interview-recorder",
347
- hud: deriveDeepInterviewHud(envelope as Record<string, unknown>, { phase }),
348
- });
349
- } catch {
350
- // HUD sync is best-effort cache maintenance and must not change record semantics.
351
- }
349
+ const phase = typeof envelope.current_phase === "string" ? envelope.current_phase : "interviewing";
350
+ await syncSkillActiveState({
351
+ cwd,
352
+ skill: "deep-interview",
353
+ active: phase !== "complete",
354
+ phase,
355
+ sessionId,
356
+ source: "gjc-runtime-deep-interview-recorder",
357
+ hud: deriveDeepInterviewHud(envelope as Record<string, unknown>, { phase }),
358
+ sourceRevision: existingStateRevision(envelope),
359
+ });
352
360
  }
353
361
 
354
362
  /**
@@ -360,6 +368,19 @@ async function repairRecorderHudFromPersisted(
360
368
  cwd: string,
361
369
  statePath: string,
362
370
  sessionId: string | undefined,
371
+ ): Promise<void> {
372
+ try {
373
+ await syncDeepInterviewRecorderHud(cwd, statePath, sessionId);
374
+ } catch {
375
+ // HUD sync is best-effort cache maintenance and must not change record semantics.
376
+ }
377
+ }
378
+
379
+ /** Refresh the best-effort HUD cache from persisted deep-interview state. */
380
+ export async function syncDeepInterviewRecorderHud(
381
+ cwd: string,
382
+ statePath: string,
383
+ sessionId: string | undefined,
363
384
  ): Promise<void> {
364
385
  const read = await readExistingStateForMutation(statePath);
365
386
  if (read.kind !== "valid") return;
@@ -384,7 +405,11 @@ export async function appendOrMergeDeepInterviewRound(
384
405
  }
385
406
  (envelope.state as Record<string, unknown>).rounds = result.rounds;
386
407
  await persistEnvelope(cwd, statePath, envelope, options.sessionId, "gjc deep-interview record-answer");
387
- await syncRecorderHud(cwd, envelope, options.sessionId);
408
+ try {
409
+ await syncRecorderHud(cwd, envelope, options.sessionId);
410
+ } catch {
411
+ // HUD sync is best-effort cache maintenance and must not change record semantics.
412
+ }
388
413
  return { action: result.action, record: result.record };
389
414
  }
390
415
 
@@ -7,6 +7,8 @@ import { deriveDeepInterviewHud } from "../skill-state/workflow-hud";
7
7
  import { WORKFLOW_STATE_VERSION } from "../skill-state/workflow-state-contract";
8
8
  import { normalizeDeepInterviewEnvelope } from "./deep-interview-state";
9
9
  import { runNativeRalplanCommand } from "./ralplan-runtime";
10
+ import { modeStatePath, sessionSpecsDir } from "./session-layout";
11
+ import { resolveGjcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
10
12
  import { runNativeStateCommand } from "./state-runtime";
11
13
  import { appendJsonl, readExistingStateForMutation, writeArtifact, writeWorkflowEnvelopeAtomic } from "./state-writer";
12
14
 
@@ -76,10 +78,6 @@ function assertSafePathComponent(value: string, label: string): void {
76
78
  }
77
79
  }
78
80
 
79
- function encodeSessionSegment(value: string): string {
80
- return encodeURIComponent(value).replaceAll(".", "%2E");
81
- }
82
-
83
81
  function defaultSpecSlug(now: Date = new Date()): string {
84
82
  const yyyy = now.getUTCFullYear().toString().padStart(4, "0");
85
83
  const mm = (now.getUTCMonth() + 1).toString().padStart(2, "0");
@@ -89,14 +87,10 @@ function defaultSpecSlug(now: Date = new Date()): string {
89
87
  return `${yyyy}-${mm}-${dd}-${hh}${min}-${randomBytes(2).toString("hex")}`;
90
88
  }
91
89
 
92
- function stateDirFor(cwd: string, sessionId: string | undefined): string {
93
- return sessionId
94
- ? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(sessionId))
95
- : path.join(cwd, ".gjc", "state");
96
- }
97
-
98
- export function deepInterviewStatePath(cwd: string, sessionId: string | undefined): string {
99
- return path.join(stateDirFor(cwd, sessionId), "deep-interview-state.json");
90
+ export function deepInterviewStatePath(cwd: string, sessionId?: string): string {
91
+ const resolvedSessionId = sessionId?.trim() || process.env.GJC_SESSION_ID?.trim();
92
+ if (!resolvedSessionId) throw new Error("deep-interview state path requires a session id");
93
+ return modeStatePath(cwd, resolvedSessionId, "deep-interview");
100
94
  }
101
95
 
102
96
  async function resolveSpecContent(rawSpec: string, cwd: string): Promise<string> {
@@ -117,7 +111,7 @@ interface ResolvedDeepInterviewArgs {
117
111
  resolution: DeepInterviewResolution;
118
112
  threshold: number;
119
113
  thresholdSource: string;
120
- sessionId?: string;
114
+ sessionId: string;
121
115
  idea: string;
122
116
  language?: DeepInterviewLanguagePreference;
123
117
  json: boolean;
@@ -134,7 +128,7 @@ export interface ResolvedDeepInterviewSpecWriteArgs {
134
128
  stage: "final";
135
129
  slug: string;
136
130
  spec: string;
137
- sessionId?: string;
131
+ sessionId: string;
138
132
  json: boolean;
139
133
  deliberate: boolean;
140
134
  handoff?: "ralplan";
@@ -277,8 +271,12 @@ async function resolveSpecWriteArgs(args: readonly string[], cwd: string): Promi
277
271
  throw new DeepInterviewCommandError(2, "--spec is required for deep-interview --write");
278
272
  }
279
273
 
280
- const sessionId = flagValue(args, "--session-id")?.trim() || undefined;
281
- if (sessionId) assertSafePathComponent(sessionId, "session-id");
274
+ const session = resolveGjcSessionForWrite(cwd, {
275
+ flagValue: flagValue(args, "--session-id"),
276
+ envSessionId: process.env.GJC_SESSION_ID,
277
+ });
278
+ const sessionId = session.gjcSessionId;
279
+ assertSafePathComponent(sessionId, "session-id");
282
280
 
283
281
  const rawHandoff = flagValue(args, "--handoff")?.trim() || undefined;
284
282
  if (rawHandoff && rawHandoff !== "ralplan") {
@@ -324,8 +322,12 @@ async function resolveSpecWriteArgs(args: readonly string[], cwd: string): Promi
324
322
  }
325
323
 
326
324
  async function resolveDeepInterviewArgs(args: readonly string[], cwd: string): Promise<ResolvedDeepInterviewArgs> {
327
- const sessionId = flagValue(args, "--session-id")?.trim() || undefined;
328
- if (sessionId) assertSafePathComponent(sessionId, "session-id");
325
+ const session = resolveGjcSessionForWrite(cwd, {
326
+ flagValue: flagValue(args, "--session-id"),
327
+ envSessionId: process.env.GJC_SESSION_ID,
328
+ });
329
+ const sessionId = session.gjcSessionId;
330
+ assertSafePathComponent(sessionId, "session-id");
329
331
 
330
332
  const explicitResolutions = (["quick", "standard", "deep"] as const).filter(name => hasFlag(args, `--${name}`));
331
333
  if (explicitResolutions.length > 1) {
@@ -402,19 +404,34 @@ export async function persistDeepInterviewSpec(
402
404
  }
403
405
  const existing = existingRead.kind === "valid" ? existingRead.value : {};
404
406
 
405
- const specPath = path.join(cwd, ".gjc", "specs", `deep-interview-${resolved.slug}.md`);
407
+ const specPath = path.join(sessionSpecsDir(cwd, resolved.sessionId), `deep-interview-${resolved.slug}.md`);
406
408
  const content = resolved.spec.endsWith("\n") ? resolved.spec : `${resolved.spec}\n`;
407
409
  await writeArtifact(specPath, content, {
408
410
  cwd,
409
- audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
411
+ audit: {
412
+ category: "artifact",
413
+ verb: "write",
414
+ owner: "gjc-runtime",
415
+ skill: "deep-interview",
416
+ sessionId: resolved.sessionId,
417
+ },
410
418
  });
411
419
 
412
420
  const sha256 = createHash("sha256").update(content).digest("hex");
413
421
  const createdAt = new Date().toISOString();
414
422
  await appendJsonl(
415
- path.join(cwd, ".gjc", "specs", "deep-interview-index.jsonl"),
423
+ path.join(sessionSpecsDir(cwd, resolved.sessionId), "deep-interview-index.jsonl"),
416
424
  { slug: resolved.slug, stage: resolved.stage, path: specPath, created_at: createdAt, sha256 },
417
- { cwd, audit: { category: "ledger", verb: "append", owner: "gjc-runtime", skill: "deep-interview" } },
425
+ {
426
+ cwd,
427
+ audit: {
428
+ category: "ledger",
429
+ verb: "append",
430
+ owner: "gjc-runtime",
431
+ skill: "deep-interview",
432
+ sessionId: resolved.sessionId,
433
+ },
434
+ },
418
435
  );
419
436
 
420
437
  const payload = normalizeDeepInterviewEnvelope({
@@ -446,9 +463,11 @@ export async function persistDeepInterviewSpec(
446
463
  verb: "write",
447
464
  owner: "gjc-runtime",
448
465
  skill: "deep-interview",
466
+ sessionId: resolved.sessionId,
449
467
  forced: resolved.force,
450
468
  },
451
469
  });
470
+ await writeSessionActivityMarker(cwd, resolved.sessionId, { writer: "deep-interview-runtime", path: statePath });
452
471
  await syncDeepInterviewHud({
453
472
  cwd,
454
473
  sessionId: resolved.sessionId,
@@ -503,8 +522,15 @@ async function seedDeepInterviewState(cwd: string, resolved: ResolvedDeepIntervi
503
522
  sessionId: resolved.sessionId,
504
523
  nowIso: now,
505
524
  },
506
- audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
525
+ audit: {
526
+ category: "state",
527
+ verb: "write",
528
+ owner: "gjc-runtime",
529
+ skill: "deep-interview",
530
+ sessionId: resolved.sessionId,
531
+ },
507
532
  });
533
+ await writeSessionActivityMarker(cwd, resolved.sessionId, { writer: "deep-interview-runtime", path: statePath });
508
534
  await syncDeepInterviewHud({ cwd, sessionId: resolved.sessionId, payload, phase: "interviewing" });
509
535
  return statePath;
510
536
  }
@@ -8,6 +8,8 @@ import {
8
8
  type ModeChangeEntry,
9
9
  type SessionEntry,
10
10
  } from "../session/session-manager";
11
+ import { sessionStateDir, sessionUltragoalDir } from "./session-layout";
12
+ import { resolveGjcSessionForRead, resolveGjcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
11
13
  import { removeFileAudited, writeJsonAtomic } from "./state-writer";
12
14
 
13
15
  export const GJC_SESSION_FILE_ENV = "GJC_SESSION_FILE";
@@ -48,12 +50,12 @@ function isEnoent(error: unknown): boolean {
48
50
  );
49
51
  }
50
52
 
51
- function requestPath(cwd: string): string {
52
- return path.join(cwd, ".gjc", "state", "goal-mode-request.json");
53
+ function requestPath(cwd: string, sessionId: string): string {
54
+ return path.join(sessionStateDir(cwd, sessionId), "goal-mode-request.json");
53
55
  }
54
56
 
55
- function ultragoalGoalsPath(cwd: string): string {
56
- return path.join(cwd, ".gjc", "ultragoal", "goals.json");
57
+ function ultragoalGoalsPath(cwd: string, sessionId: string): string {
58
+ return path.join(sessionUltragoalDir(cwd, sessionId), "goals.json");
57
59
  }
58
60
 
59
61
  function isCreateGoalsArg(value: string): boolean {
@@ -65,8 +67,14 @@ export function isUltragoalCreateGoalsInvocation(args: readonly string[]): boole
65
67
  return command !== undefined && isCreateGoalsArg(command);
66
68
  }
67
69
 
68
- export async function readUltragoalGjcObjective(cwd: string): Promise<{ objective: string; goalsPath: string }> {
69
- const goalsPath = ultragoalGoalsPath(cwd);
70
+ export async function readUltragoalGjcObjective(
71
+ cwd: string,
72
+ sessionId?: string | null,
73
+ ): Promise<{ objective: string; goalsPath: string }> {
74
+ const session = sessionId?.trim()
75
+ ? { gjcSessionId: sessionId.trim() }
76
+ : await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID });
77
+ const goalsPath = ultragoalGoalsPath(cwd, session.gjcSessionId);
70
78
  try {
71
79
  const plan = (await Bun.file(goalsPath).json()) as UltragoalPlanShape;
72
80
  const objective = typeof plan.gjcObjective === "string" ? plan.gjcObjective.trim() : "";
@@ -87,7 +95,10 @@ export async function writePendingGoalModeRequest(input: {
87
95
  }): Promise<PendingGoalModeRequest> {
88
96
  const objective = input.objective.trim();
89
97
  if (!objective) throw new Error("goal objective is required");
90
- const sessionId = input.sessionId?.trim();
98
+ const resolvedSessionId =
99
+ input.sessionId?.trim() ||
100
+ resolveGjcSessionForWrite(input.cwd, { envSessionId: process.env.GJC_SESSION_ID }).gjcSessionId;
101
+ const sessionId = resolvedSessionId;
91
102
  const request: PendingGoalModeRequest = {
92
103
  version: REQUEST_VERSION,
93
104
  kind: "goal_mode_request",
@@ -97,11 +108,12 @@ export async function writePendingGoalModeRequest(input: {
97
108
  goalsPath: input.goalsPath,
98
109
  ...(sessionId ? { sessionId } : {}),
99
110
  };
100
- const filePath = requestPath(input.cwd);
111
+ const filePath = requestPath(input.cwd, sessionId);
101
112
  await writeJsonAtomic(filePath, request, {
102
113
  cwd: input.cwd,
103
- audit: { category: "state", verb: "write", owner: "gjc-runtime" },
114
+ audit: { category: "state", verb: "write", owner: "gjc-runtime", sessionId },
104
115
  });
116
+ await writeSessionActivityMarker(input.cwd, sessionId, { writer: "goal-mode-request", path: filePath });
105
117
  return request;
106
118
  }
107
119
 
@@ -175,7 +187,10 @@ export async function consumePendingGoalModeRequest(
175
187
  cwd: string,
176
188
  currentSessionId?: string | null,
177
189
  ): Promise<PendingGoalModeRequest | null> {
178
- const filePath = requestPath(cwd);
190
+ const session = currentSessionId?.trim()
191
+ ? { gjcSessionId: currentSessionId.trim() }
192
+ : await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID });
193
+ const filePath = requestPath(cwd, session.gjcSessionId);
179
194
  let raw: unknown;
180
195
  try {
181
196
  raw = await Bun.file(filePath).json();
@@ -203,7 +218,7 @@ export async function consumePendingGoalModeRequest(
203
218
  }
204
219
  await removeFileAudited(filePath, {
205
220
  cwd,
206
- audit: { category: "prune", verb: "remove", owner: "gjc-runtime" },
221
+ audit: { category: "prune", verb: "remove", owner: "gjc-runtime", sessionId: session.gjcSessionId },
207
222
  }).catch(error => {
208
223
  if (!isEnoent(error)) throw error;
209
224
  });
@@ -2,6 +2,7 @@ import { Buffer } from "node:buffer";
2
2
  import * as path from "node:path";
3
3
  import { safeStderrWrite } from "@gajae-code/utils";
4
4
  import type { Args } from "../cli/args";
5
+ import { tmuxRuntimeSessionPath } from "./session-layout";
5
6
  import { GJC_COORDINATOR_SESSION_ID_ENV, GJC_COORDINATOR_SESSION_STATE_FILE_ENV } from "./session-state-sidecar";
6
7
  import {
7
8
  buildGjcTmuxProfileCommands,
@@ -360,9 +361,13 @@ export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaun
360
361
  const sessionName = buildGjcTmuxSessionName(env, { branch });
361
362
  const tmuxCommand = resolveGjcTmuxCommand(env);
362
363
  const sessionId = env[GJC_COORDINATOR_SESSION_ID_ENV]?.trim() || sessionName;
364
+ // The session ROOT is keyed by the active GJC session (GJC_SESSION_ID), NOT the
365
+ // coordinator/tmux identity. Fall back to the coordinator id only for standalone
366
+ // tmux launches with no GJC session context.
367
+ const gjcSessionId = env.GJC_SESSION_ID?.trim() || sessionId;
363
368
  const sessionStateFile =
364
369
  env[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]?.trim() ||
365
- path.join(cwd, ".gjc", "runtime", "tmux-sessions", `${buildGjcTmuxSessionSlug(sessionName)}.json`);
370
+ tmuxRuntimeSessionPath(cwd, gjcSessionId, buildGjcTmuxSessionSlug(sessionName));
366
371
  const tmuxAvailable = context.tmuxAvailable ?? Bun.which(tmuxCommand) !== null;
367
372
  if (!tmuxAvailable) return undefined;
368
373
  const existingSessionName =