@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.
Files changed (140) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +73 -1
  3. package/dist/types/cli/migrate-cli.d.ts +20 -0
  4. package/dist/types/commands/migrate.d.ts +33 -0
  5. package/dist/types/config/keybindings.d.ts +4 -0
  6. package/dist/types/config/settings-schema.d.ts +27 -0
  7. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +2 -0
  8. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -2
  9. package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
  10. package/dist/types/gjc-runtime/session-layout.d.ts +59 -0
  11. package/dist/types/gjc-runtime/session-resolution.d.ts +47 -0
  12. package/dist/types/gjc-runtime/state-graph.d.ts +1 -1
  13. package/dist/types/gjc-runtime/state-runtime.d.ts +5 -4
  14. package/dist/types/gjc-runtime/state-schema.d.ts +2 -0
  15. package/dist/types/gjc-runtime/state-writer.d.ts +36 -7
  16. package/dist/types/gjc-runtime/tmux-sessions.d.ts +2 -0
  17. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +7 -4
  18. package/dist/types/gjc-runtime/workflow-command-ref.d.ts +1 -1
  19. package/dist/types/gjc-runtime/workflow-manifest.d.ts +1 -1
  20. package/dist/types/harness-control-plane/storage.d.ts +2 -1
  21. package/dist/types/hooks/skill-state.d.ts +12 -4
  22. package/dist/types/migrate/action-planner.d.ts +11 -0
  23. package/dist/types/migrate/adapters/claude-code.d.ts +2 -0
  24. package/dist/types/migrate/adapters/codex.d.ts +5 -0
  25. package/dist/types/migrate/adapters/index.d.ts +45 -0
  26. package/dist/types/migrate/adapters/opencode.d.ts +2 -0
  27. package/dist/types/migrate/executor.d.ts +2 -0
  28. package/dist/types/migrate/mcp-mapper.d.ts +20 -0
  29. package/dist/types/migrate/report.d.ts +18 -0
  30. package/dist/types/migrate/skill-normalizer.d.ts +27 -0
  31. package/dist/types/migrate/types.d.ts +126 -0
  32. package/dist/types/modes/components/custom-editor.d.ts +1 -1
  33. package/dist/types/modes/components/welcome.d.ts +3 -1
  34. package/dist/types/modes/interactive-mode.d.ts +3 -0
  35. package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -0
  36. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +1 -1
  37. package/dist/types/research-plan/index.d.ts +1 -0
  38. package/dist/types/research-plan/ledger.d.ts +33 -0
  39. package/dist/types/rlm/artifacts.d.ts +1 -1
  40. package/dist/types/runtime-mcp/config-writer.d.ts +26 -0
  41. package/dist/types/skill-state/active-state.d.ts +6 -11
  42. package/dist/types/skill-state/canonical-skills.d.ts +3 -0
  43. package/dist/types/skill-state/workflow-hud.d.ts +2 -0
  44. package/dist/types/task/spawn-gate.d.ts +1 -10
  45. package/package.json +7 -7
  46. package/src/cli/migrate-cli.ts +106 -0
  47. package/src/cli/setup-cli.ts +14 -1
  48. package/src/cli.ts +1 -0
  49. package/src/commands/deep-interview.ts +2 -2
  50. package/src/commands/launch.ts +1 -1
  51. package/src/commands/migrate.ts +46 -0
  52. package/src/commands/state.ts +2 -1
  53. package/src/commands/team.ts +7 -3
  54. package/src/config/model-registry.ts +9 -2
  55. package/src/config/model-resolver.ts +13 -2
  56. package/src/config/settings-schema.ts +17 -0
  57. package/src/coordinator-mcp/policy.ts +10 -2
  58. package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +0 -1
  59. package/src/defaults/gjc/skills/deep-interview/SKILL.md +28 -24
  60. package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
  61. package/src/defaults/gjc/skills/team/SKILL.md +51 -47
  62. package/src/defaults/gjc/skills/ultragoal/SKILL.md +17 -13
  63. package/src/exec/bash-executor.ts +3 -1
  64. package/src/extensibility/custom-commands/loader.ts +0 -7
  65. package/src/extensibility/gjc-plugins/injection.ts +23 -4
  66. package/src/extensibility/gjc-plugins/state.ts +16 -1
  67. package/src/gjc-runtime/deep-interview-recorder.ts +43 -18
  68. package/src/gjc-runtime/deep-interview-runtime.ts +49 -23
  69. package/src/gjc-runtime/goal-mode-request.ts +26 -11
  70. package/src/gjc-runtime/launch-tmux.ts +68 -15
  71. package/src/gjc-runtime/ralplan-runtime.ts +79 -50
  72. package/src/gjc-runtime/session-layout.ts +180 -0
  73. package/src/gjc-runtime/session-resolution.ts +217 -0
  74. package/src/gjc-runtime/state-graph.ts +1 -2
  75. package/src/gjc-runtime/state-migrations.ts +1 -0
  76. package/src/gjc-runtime/state-runtime.ts +230 -121
  77. package/src/gjc-runtime/state-schema.ts +2 -0
  78. package/src/gjc-runtime/state-writer.ts +289 -41
  79. package/src/gjc-runtime/team-runtime.ts +43 -19
  80. package/src/gjc-runtime/tmux-sessions.ts +43 -2
  81. package/src/gjc-runtime/ultragoal-guard.ts +45 -2
  82. package/src/gjc-runtime/ultragoal-runtime.ts +121 -41
  83. package/src/gjc-runtime/workflow-command-ref.ts +1 -2
  84. package/src/gjc-runtime/workflow-manifest.ts +1 -2
  85. package/src/harness-control-plane/storage.ts +14 -4
  86. package/src/hooks/native-skill-hook.ts +38 -12
  87. package/src/hooks/skill-state.ts +178 -83
  88. package/src/internal-urls/docs-index.generated.ts +9 -6
  89. package/src/migrate/action-planner.ts +318 -0
  90. package/src/migrate/adapters/claude-code.ts +39 -0
  91. package/src/migrate/adapters/codex.ts +70 -0
  92. package/src/migrate/adapters/index.ts +277 -0
  93. package/src/migrate/adapters/opencode.ts +52 -0
  94. package/src/migrate/executor.ts +81 -0
  95. package/src/migrate/mcp-mapper.ts +152 -0
  96. package/src/migrate/report.ts +104 -0
  97. package/src/migrate/skill-normalizer.ts +80 -0
  98. package/src/migrate/types.ts +163 -0
  99. package/src/modes/bridge/bridge-mode.ts +2 -2
  100. package/src/modes/components/custom-editor.ts +30 -20
  101. package/src/modes/components/welcome.ts +42 -9
  102. package/src/modes/controllers/input-controller.ts +21 -3
  103. package/src/modes/interactive-mode.ts +22 -1
  104. package/src/modes/prompt-action-autocomplete.ts +11 -1
  105. package/src/modes/rpc/rpc-mode.ts +2 -2
  106. package/src/modes/shared/agent-wire/unattended-audit.ts +3 -2
  107. package/src/prompts/agents/init.md +1 -1
  108. package/src/prompts/system/plan-mode-active.md +1 -1
  109. package/src/prompts/tools/ast-grep.md +1 -1
  110. package/src/prompts/tools/search.md +1 -1
  111. package/src/prompts/tools/task.md +1 -2
  112. package/src/research-plan/index.ts +1 -0
  113. package/src/research-plan/ledger.ts +177 -0
  114. package/src/rlm/artifacts.ts +12 -3
  115. package/src/rlm/index.ts +7 -0
  116. package/src/runtime-mcp/config-writer.ts +46 -0
  117. package/src/session/agent-session.ts +15 -21
  118. package/src/session/session-manager.ts +19 -2
  119. package/src/setup/hermes/templates/operator-instructions.v1.md +8 -0
  120. package/src/setup/hermes-setup.ts +1 -1
  121. package/src/skill-state/active-state.ts +72 -108
  122. package/src/skill-state/canonical-skills.ts +4 -0
  123. package/src/skill-state/deep-interview-mutation-guard.ts +28 -109
  124. package/src/skill-state/workflow-hud.ts +4 -2
  125. package/src/skill-state/workflow-state-contract.ts +3 -3
  126. package/src/slash-commands/builtin-registry.ts +8 -4
  127. package/src/system-prompt.ts +11 -9
  128. package/src/task/agents.ts +1 -22
  129. package/src/task/index.ts +1 -41
  130. package/src/task/spawn-gate.ts +1 -38
  131. package/src/task/types.ts +1 -1
  132. package/src/tools/ask.ts +34 -12
  133. package/src/tools/computer.ts +58 -4
  134. package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +0 -10
  135. package/src/extensibility/custom-commands/bundled/review/index.ts +0 -456
  136. package/src/prompts/agents/explore.md +0 -58
  137. package/src/prompts/agents/plan.md +0 -49
  138. package/src/prompts/agents/reviewer.md +0 -141
  139. package/src/prompts/agents/task.md +0 -16
  140. package/src/prompts/review-request.md +0 -70
@@ -27,6 +27,13 @@ import {
27
27
  } from "../skill-state/workflow-state-contract";
28
28
  import { renderCliWriteReceipt } from "./cli-write-receipt";
29
29
  import { mergeDeepInterviewEnvelope, normalizeDeepInterviewEnvelope } from "./deep-interview-state";
30
+ import { activeSnapshotPath, auditPath, modeStatePath, sessionStateDir } from "./session-layout";
31
+ import {
32
+ resolveGjcSessionForRead,
33
+ resolveGjcSessionForWrite,
34
+ SessionResolutionError,
35
+ writeSessionActivityMarker,
36
+ } from "./session-resolution";
30
37
  import { renderStateGraph, type StateGraphFormat } from "./state-graph";
31
38
  import { migrateAndPersistLegacyState, migrateWorkflowState } from "./state-migrations";
32
39
  import {
@@ -53,16 +60,16 @@ import {
53
60
  softDelete,
54
61
  updateWorkflowTransactionJournal,
55
62
  type WorkflowEnvelopeIntegrityMismatch,
56
- writeWorkflowEnvelopeAtomic,
63
+ writeGuardedWorkflowEnvelopeAtomic,
57
64
  } from "./state-writer";
58
65
  import { getSkillManifest, isKnownWorkflowState, isValidTransition, typedArgsFor } from "./workflow-manifest";
59
66
 
60
67
  /**
61
68
  * Native implementation of the `gjc state read|write|clear` command surface.
62
69
  *
63
- * Simple file-receipt operations against `.gjc/state/[sessions/<id>/]<mode>-state.json` and
64
- * `.gjc/state/[sessions/<id>/]skill-active-state.json`. This is the sanctioned CLI mediator for
65
- * the mutation-guarded `.gjc/state` ACL — agents call it instead of editing those files directly.
70
+ * Simple file-receipt operations against session-scoped state under
71
+ * `.gjc/_session-{id}/state/`. This is the sanctioned CLI mediator for
72
+ * mutation-guarded GJC state — agents call it instead of editing those files directly.
66
73
  */
67
74
 
68
75
  export interface StateCommandResult {
@@ -72,6 +79,7 @@ export interface StateCommandResult {
72
79
  }
73
80
 
74
81
  const SKILL_ACTIVE_STATE_FILE = "skill-active-state.json";
82
+ const TERMINAL_CLEAR_PHASES = new Set(["complete", "completed", "cancelled", "canceled", "failed"]);
75
83
  const PATH_COMPONENT_RE = /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$/;
76
84
  const KNOWN_MODES: readonly string[] = CANONICAL_GJC_WORKFLOW_SKILLS;
77
85
 
@@ -269,16 +277,22 @@ async function readInputJson(value: string | undefined, cwd: string): Promise<Re
269
277
 
270
278
  interface ResolvedSelectors {
271
279
  mode: CanonicalGjcWorkflowSkill | undefined;
272
- sessionId: string | undefined;
280
+ gjcSessionId: string;
273
281
  threadId: string | undefined;
274
282
  turnId: string | undefined;
275
283
  payload: Record<string, unknown> | undefined;
276
284
  }
277
285
 
286
+ // `clear` resolves like a read (explicit -> payload -> env -> latest-activity marker)
287
+ // per the spec: read/status/clear may fall back to the most-recent session. Commands
288
+ // that create or mutate new state roots still require an explicit/env session id.
289
+ const WRITE_SESSION_ACTIONS = new Set<ParsedInvocation["action"]>(["write", "handoff", "prune", "migrate"]);
290
+
278
291
  async function resolveSelectors(
279
292
  args: readonly string[],
280
293
  cwd: string,
281
294
  positionalSkill: string | undefined,
295
+ action: ParsedInvocation["action"],
282
296
  ): Promise<ResolvedSelectors> {
283
297
  const payload = await readInputJson(flagValue(args, "--input"), cwd);
284
298
 
@@ -297,41 +311,32 @@ async function resolveSelectors(
297
311
  }
298
312
  if (mode) assertKnownMode(mode);
299
313
 
300
- const sessionId = resolveSessionIdFromArgs(args, payload);
314
+ const sessionSources = {
315
+ flagValue: flagValue(args, "--session-id"),
316
+ payloadSessionId: payload?.session_id,
317
+ envSessionId: process.env.GJC_SESSION_ID,
318
+ };
319
+ const session = WRITE_SESSION_ACTIONS.has(action)
320
+ ? resolveGjcSessionForWrite(cwd, sessionSources)
321
+ : await resolveGjcSessionForRead(cwd, sessionSources);
301
322
 
302
323
  const threadId = flagValue(args, "--thread-id")?.trim() || undefined;
303
324
  if (threadId) assertSafePathComponent(threadId, "thread-id");
304
325
  const turnId = flagValue(args, "--turn-id")?.trim() || undefined;
305
326
  if (turnId) assertSafePathComponent(turnId, "turn-id");
306
327
 
307
- return { mode: mode as CanonicalGjcWorkflowSkill | undefined, sessionId, threadId, turnId, payload };
308
- }
309
-
310
- // Session-id resolution order: explicit --session-id flag, then payload
311
- // session_id, then GJC_SESSION_ID env var (set by AgentSession.sdk for
312
- // agent-initiated CLI invocations). The env-var default keeps shell
313
- // snippets in skill docs short while still routing state commands to the
314
- // caller's session-scoped state files.
315
- function resolveSessionIdFromArgs(
316
- args: readonly string[],
317
- payload: Record<string, unknown> | undefined,
318
- ): string | undefined {
319
- const explicitSessionId = flagValue(args, "--session-id");
320
- let sessionId = explicitSessionId !== undefined ? explicitSessionId.trim() || undefined : undefined;
321
- if (!sessionId && payload && typeof payload.session_id === "string") {
322
- sessionId = payload.session_id.trim() || undefined;
323
- }
324
- if (!sessionId && explicitSessionId === undefined) {
325
- const envSessionId = process.env.GJC_SESSION_ID?.trim();
326
- if (envSessionId) sessionId = envSessionId;
327
- }
328
- if (sessionId) assertSafePathComponent(sessionId, "session-id");
329
- return sessionId;
328
+ return {
329
+ mode: mode as CanonicalGjcWorkflowSkill | undefined,
330
+ gjcSessionId: session.gjcSessionId,
331
+ threadId,
332
+ turnId,
333
+ payload,
334
+ };
330
335
  }
331
336
 
332
337
  async function inferModeFromActiveState(
333
338
  cwd: string,
334
- sessionId: string | undefined,
339
+ sessionId: string,
335
340
  ): Promise<CanonicalGjcWorkflowSkill | undefined> {
336
341
  const state = await readVisibleSkillActiveState(cwd, sessionId);
337
342
  const entries = listActiveSkills(state);
@@ -341,22 +346,53 @@ async function inferModeFromActiveState(
341
346
  return canonical ?? undefined;
342
347
  }
343
348
 
344
- function encodeSessionSegment(value: string): string {
345
- return encodeURIComponent(value).replaceAll(".", "%2E");
349
+ function stateDirFor(cwd: string, sessionId: string): string {
350
+ return sessionStateDir(cwd, sessionId);
346
351
  }
347
352
 
348
- function stateDirFor(cwd: string, sessionId: string | undefined): string {
349
- const base = path.join(cwd, ".gjc", "state");
350
- if (!sessionId) return base;
351
- return path.join(base, "sessions", encodeSessionSegment(sessionId));
353
+ function modeStateFile(cwd: string, mode: string, sessionId: string): string {
354
+ return modeStatePath(cwd, sessionId, mode);
352
355
  }
353
356
 
354
- function modeStateFile(cwd: string, mode: string, sessionId: string | undefined): string {
355
- return path.join(stateDirFor(cwd, sessionId), `${mode}-state.json`);
357
+ function activeStateFile(cwd: string, sessionId: string): string {
358
+ return activeSnapshotPath(cwd, sessionId);
356
359
  }
357
360
 
358
- function activeStateFile(cwd: string, sessionId: string | undefined): string {
359
- return path.join(stateDirFor(cwd, sessionId), SKILL_ACTIVE_STATE_FILE);
361
+ function stateRelativePath(cwd: string, filePath: string): string {
362
+ return path.relative(cwd, filePath).split(path.sep).join(path.posix.sep);
363
+ }
364
+
365
+ async function touchStateActivityMarker(cwd: string, sessionId: string, filePath: string): Promise<void> {
366
+ await writeSessionActivityMarker(cwd, sessionId, {
367
+ writer: "state-runtime",
368
+ path: stateRelativePath(cwd, filePath),
369
+ });
370
+ }
371
+
372
+ async function readActivePhaseForSkill(
373
+ cwd: string,
374
+ sessionId: string,
375
+ mode: CanonicalGjcWorkflowSkill,
376
+ ): Promise<string | undefined> {
377
+ const state = await readVisibleSkillActiveState(cwd, sessionId);
378
+ const entries = listActiveSkills(state);
379
+ const entry = entries.find(item => item.skill === mode) ?? (state?.skill === mode ? state : undefined);
380
+ return isPlainObject(entry) && typeof entry.phase === "string" ? entry.phase.trim() || undefined : undefined;
381
+ }
382
+
383
+ async function describeStaleClearState(
384
+ cwd: string,
385
+ sessionId: string,
386
+ mode: CanonicalGjcWorkflowSkill,
387
+ existing: Record<string, unknown>,
388
+ ): Promise<string | undefined> {
389
+ const phase = typeof existing.current_phase === "string" ? existing.current_phase.trim() : undefined;
390
+ if (phase && TERMINAL_CLEAR_PHASES.has(phase)) return `mode-state is already terminal (${phase})`;
391
+ const activePhase = await readActivePhaseForSkill(cwd, sessionId, mode);
392
+ if (activePhase && phase && activePhase !== phase) {
393
+ return `active-state phase ${activePhase} differs from mode-state phase ${phase}`;
394
+ }
395
+ return undefined;
360
396
  }
361
397
 
362
398
  async function readJsonFile(filePath: string): Promise<Record<string, unknown> | null> {
@@ -446,7 +482,7 @@ function doctorProblem(
446
482
  : { type, path: pathValue, message, fixCommand };
447
483
  }
448
484
 
449
- function activeEntryDir(cwd: string, sessionId: string | undefined): string {
485
+ function activeEntryDir(cwd: string, sessionId: string): string {
450
486
  return path.join(stateDirFor(cwd, sessionId), "active");
451
487
  }
452
488
 
@@ -507,9 +543,9 @@ function pushPhaseDriftProblem(options: {
507
543
  async function collectDoctorSummary(
508
544
  cwd: string,
509
545
  skill: CanonicalGjcWorkflowSkill | undefined,
510
- sessionId: string | undefined,
546
+ sessionId: string,
511
547
  ): Promise<DoctorSummary> {
512
- const root = path.join(cwd, ".gjc", "state");
548
+ const root = sessionStateDir(cwd, sessionId);
513
549
  const skills = skill ? [skill] : [...CANONICAL_GJC_WORKFLOW_SKILLS];
514
550
  const problems: DoctorProblem[] = [];
515
551
  let filesScanned = 0;
@@ -583,7 +619,7 @@ async function collectDoctorSummary(
583
619
  }
584
620
  }
585
621
 
586
- const inspectActiveScope = async (scopeSessionId: string | undefined): Promise<void> => {
622
+ const inspectActiveScope = async (scopeSessionId: string): Promise<void> => {
587
623
  const snapshotPath = activeStateFile(cwd, scopeSessionId);
588
624
  const snapshot = await readRawJson(snapshotPath);
589
625
  if (snapshot.exists) filesScanned += 1;
@@ -624,7 +660,9 @@ async function collectDoctorSummary(
624
660
  }
625
661
  }
626
662
  if (isPlainObject(snapshot.value)) {
627
- const activeSkills = Array.isArray(snapshot.value.active_skills) ? snapshot.value.active_skills : [];
663
+ const activeSkills: unknown[] = Array.isArray(snapshot.value.active_skills)
664
+ ? snapshot.value.active_skills
665
+ : [];
628
666
  for (const entry of activeSkills) {
629
667
  const entrySkill = skillFromActiveValue(entry);
630
668
  if (!entrySkill) continue;
@@ -658,21 +696,6 @@ async function collectDoctorSummary(
658
696
  };
659
697
 
660
698
  await inspectActiveScope(sessionId);
661
- if (!sessionId) {
662
- const sessionsDir = path.join(root, "sessions");
663
- let sessions: string[] = [];
664
- try {
665
- const entries = await fs.readdir(sessionsDir, { withFileTypes: true });
666
- sessions = entries
667
- .filter(entry => entry.isDirectory())
668
- .map(entry => entry.name)
669
- .sort();
670
- } catch (error) {
671
- const err = error as NodeJS.ErrnoException;
672
- if (err.code !== "ENOENT") throw error;
673
- }
674
- for (const rawSession of sessions) await inspectActiveScope(decodeURIComponent(rawSession));
675
- }
676
699
 
677
700
  problems.sort(
678
701
  (a, b) =>
@@ -727,8 +750,16 @@ async function handleDoctor(
727
750
  const rawSkill = flagValue(args, "--skill")?.trim() || flagValue(args, "--mode")?.trim() || positionalSkill?.trim();
728
751
  if (rawSkill) assertKnownMode(rawSkill);
729
752
  const payload = await readInputJson(flagValue(args, "--input"), cwd);
730
- const sessionId = resolveSessionIdFromArgs(args, payload);
731
- const summary = await collectDoctorSummary(cwd, rawSkill as CanonicalGjcWorkflowSkill | undefined, sessionId);
753
+ const session = await resolveGjcSessionForRead(cwd, {
754
+ flagValue: flagValue(args, "--session-id"),
755
+ payloadSessionId: payload?.session_id,
756
+ envSessionId: process.env.GJC_SESSION_ID,
757
+ });
758
+ const summary = await collectDoctorSummary(
759
+ cwd,
760
+ rawSkill as CanonicalGjcWorkflowSkill | undefined,
761
+ session.gjcSessionId,
762
+ );
732
763
  return {
733
764
  status: summary.ok ? 0 : 1,
734
765
  stdout: hasFlag(args, "--json") ? `${JSON.stringify(summary, null, 2)}\n` : renderDoctorText(summary),
@@ -737,6 +768,7 @@ async function handleDoctor(
737
768
 
738
769
  async function warnAndAuditOutOfBandIfNeeded(
739
770
  cwd: string,
771
+ sessionId: string,
740
772
  filePath: string,
741
773
  skill: CanonicalGjcWorkflowSkill,
742
774
  options?: { mutationId?: string; forced?: boolean },
@@ -751,7 +783,7 @@ async function warnAndAuditOutOfBandIfNeeded(
751
783
  }
752
784
  if (!mismatch) return undefined;
753
785
  const message = `WARNING: workflow mode-state out-of-band edit detected for ${skill}: ${filePath} expected sha256 ${mismatch.expected} but found ${mismatch.actual}`;
754
- await appendAuditEntry(cwd, {
786
+ await appendAuditEntry(cwd, sessionId, {
755
787
  ts: new Date().toISOString(),
756
788
  skill,
757
789
  category: "state",
@@ -766,12 +798,19 @@ async function warnAndAuditOutOfBandIfNeeded(
766
798
  return message;
767
799
  }
768
800
 
801
+ function existingStateRevision(value: unknown): number | undefined {
802
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
803
+ const revision = (value as Record<string, unknown>).state_revision;
804
+ return typeof revision === "number" && Number.isFinite(revision) ? revision : 0;
805
+ }
806
+
769
807
  async function writeJsonAtomic(
770
808
  cwd: string,
771
809
  filePath: string,
772
810
  value: unknown,
773
811
  verb: "write" | "clear" | "handoff" | "reconcile" = "write",
774
812
  options?: {
813
+ sessionId: string;
775
814
  skill?: CanonicalGjcWorkflowSkill;
776
815
  mutationId?: string;
777
816
  force?: boolean;
@@ -781,7 +820,7 @@ async function writeJsonAtomic(
781
820
  },
782
821
  ): Promise<{ warning?: string; stamped: Record<string, unknown> }> {
783
822
  const warning = options?.skill
784
- ? await warnAndAuditOutOfBandIfNeeded(cwd, filePath, options.skill, {
823
+ ? await warnAndAuditOutOfBandIfNeeded(cwd, options.sessionId, filePath, options.skill, {
785
824
  mutationId: options.mutationId,
786
825
  forced: options.force ?? false,
787
826
  })
@@ -789,9 +828,15 @@ async function writeJsonAtomic(
789
828
  if (warning && !options?.force) {
790
829
  throw new StateCommandError(2, `${warning}; use --force to overwrite tampered mode-state`);
791
830
  }
792
- await writeWorkflowEnvelopeAtomic(filePath, value, {
831
+ // Authoritative CLI/runtime write. Stamp the next state_revision under the
832
+ // writer lock; do not enforce an optimistic `expectedRevision` here (tamper
833
+ // detection is handled by warnAndAuditOutOfBandIfNeeded above, and a forced
834
+ // write must succeed over corrupt/missing prior state).
835
+ await writeGuardedWorkflowEnvelopeAtomic(filePath, value, {
793
836
  cwd,
837
+ policy: "source",
794
838
  audit: {
839
+ sessionId: options?.sessionId ?? "",
795
840
  category: "state",
796
841
  verb,
797
842
  owner: options?.owner ?? "gjc-state-cli",
@@ -851,13 +896,14 @@ function parseSinceFlag(args: readonly string[]): string | undefined {
851
896
  async function readAuditWindow(
852
897
  cwd: string,
853
898
  args: readonly string[],
899
+ sessionId: string,
854
900
  ): Promise<{ entries: unknown[]; limit: number; since?: string; truncated: boolean }> {
855
901
  const limit = parseLimitFlag(args);
856
902
  const since = parseSinceFlag(args);
857
- const auditPath = path.join(cwd, ".gjc", "state", "audit.jsonl");
903
+ const auditFile = auditPath(cwd, sessionId);
858
904
  let raw = "";
859
905
  try {
860
- raw = await fs.readFile(auditPath, "utf-8");
906
+ raw = await fs.readFile(auditFile, "utf-8");
861
907
  } catch (error) {
862
908
  const err = error as NodeJS.ErrnoException;
863
909
  if (err.code !== "ENOENT") throw error;
@@ -965,6 +1011,14 @@ function buildHudForMode(
965
1011
  typeof (rawLedger as Record<string, unknown>).timestamp === "string"
966
1012
  ? ((rawLedger as Record<string, unknown>).timestamp as string)
967
1013
  : undefined,
1014
+ kind:
1015
+ typeof (rawLedger as Record<string, unknown>).kind === "string"
1016
+ ? ((rawLedger as Record<string, unknown>).kind as string)
1017
+ : undefined,
1018
+ evidence:
1019
+ typeof (rawLedger as Record<string, unknown>).evidence === "string"
1020
+ ? ((rawLedger as Record<string, unknown>).evidence as string)
1021
+ : undefined,
968
1022
  }
969
1023
  : undefined;
970
1024
  const status = typeof payload.status === "string" ? (payload.status as string) : (phase ?? "pending");
@@ -1014,7 +1068,7 @@ function buildHudForMode(
1014
1068
  async function syncWorkflowSkillState(options: {
1015
1069
  cwd: string;
1016
1070
  mode: CanonicalGjcWorkflowSkill;
1017
- sessionId: string | undefined;
1071
+ sessionId: string;
1018
1072
  threadId?: string;
1019
1073
  turnId?: string;
1020
1074
  active: boolean;
@@ -1034,6 +1088,7 @@ async function syncWorkflowSkillState(options: {
1034
1088
  source: "gjc-state-cli",
1035
1089
  hud: buildHudForMode(options.mode, options.payload),
1036
1090
  ...(options.receipt ? { receipt: options.receipt } : {}),
1091
+ sourceRevision: existingStateRevision(options.payload),
1037
1092
  });
1038
1093
  } catch {
1039
1094
  // HUD sync is best-effort and must not change command semantics.
@@ -1053,14 +1108,19 @@ async function syncWorkflowSkillState(options: {
1053
1108
  export async function reconcileWorkflowSkillState(options: {
1054
1109
  cwd: string;
1055
1110
  mode: CanonicalGjcWorkflowSkill;
1056
- sessionId: string | undefined;
1111
+ sessionId?: string;
1057
1112
  threadId?: string;
1058
1113
  turnId?: string;
1059
1114
  active: boolean;
1060
1115
  phase: string;
1061
1116
  payload: Record<string, unknown>;
1117
+ sourceRevision?: number;
1062
1118
  }): Promise<{ stateFile: string }> {
1063
- const { cwd, mode, sessionId, threadId, turnId, active, payload } = options;
1119
+ const { cwd, mode, threadId, turnId, active, payload } = options;
1120
+ const { gjcSessionId: sessionId } = resolveGjcSessionForWrite(cwd, {
1121
+ payloadSessionId: options.sessionId,
1122
+ envSessionId: process.env.GJC_SESSION_ID,
1123
+ });
1064
1124
  const filePath = modeStateFile(cwd, mode, sessionId);
1065
1125
  const existingRead = await readExistingStateForMutation(filePath);
1066
1126
  const existingPayload = existingRead.kind === "valid" ? existingRead.value : {};
@@ -1104,14 +1164,37 @@ export async function reconcileWorkflowSkillState(options: {
1104
1164
  const validation = validateWorkflowStateEnvelope(mode, merged);
1105
1165
  if (!validation.valid) throw new StateCommandError(2, validation.error ?? `invalid ${mode} state envelope`);
1106
1166
 
1107
- await writeJsonAtomic(cwd, filePath, merged, "reconcile", {
1108
- skill: mode,
1109
- mutationId,
1110
- force: true,
1111
- fromPhase,
1112
- toPhase: trimmedPhase,
1113
- owner: "gjc-runtime",
1167
+ if (existingRead.kind === "corrupt") await fs.rm(filePath, { force: true });
1168
+ await writeGuardedWorkflowEnvelopeAtomic(filePath, merged, {
1169
+ cwd,
1170
+ policy: "source",
1171
+ receipt: {
1172
+ cwd,
1173
+ skill: mode,
1174
+ owner: "gjc-runtime",
1175
+ command: `gjc ${mode} (reconcile)`,
1176
+ sessionId,
1177
+ nowIso: nowIsoStr,
1178
+ mutationId,
1179
+ verb: "reconcile",
1180
+ forced: true,
1181
+ fromPhase,
1182
+ toPhase: trimmedPhase,
1183
+ },
1184
+ audit: {
1185
+ category: "state",
1186
+ verb: "reconcile",
1187
+ owner: "gjc-runtime",
1188
+ sessionId,
1189
+ skill: mode,
1190
+ mutationId,
1191
+ forced: true,
1192
+ fromPhase,
1193
+ toPhase: trimmedPhase,
1194
+ },
1114
1195
  });
1196
+ const persisted = (await readJsonFile(filePath)) ?? {};
1197
+ const sourceRevision = options.sourceRevision ?? existingStateRevision(persisted);
1115
1198
 
1116
1199
  // Reconciliation drives the active-state/HUD update directly (not via the
1117
1200
  // best-effort syncWorkflowSkillState wrapper) so a failed HUD/active-state write
@@ -1128,7 +1211,9 @@ export async function reconcileWorkflowSkillState(options: {
1128
1211
  source: "gjc-runtime-reconcile",
1129
1212
  hud: buildHudForMode(mode, merged),
1130
1213
  receipt,
1214
+ sourceRevision,
1131
1215
  });
1216
+ await touchStateActivityMarker(cwd, sessionId, filePath);
1132
1217
  return { stateFile: filePath };
1133
1218
  }
1134
1219
  export async function readWorkflowStateJson(
@@ -1136,7 +1221,11 @@ export async function readWorkflowStateJson(
1136
1221
  skill: CanonicalGjcWorkflowSkill,
1137
1222
  sessionId?: string,
1138
1223
  ): Promise<Record<string, unknown>> {
1139
- return (await readJsonFile(modeStateFile(cwd, skill, sessionId))) ?? {};
1224
+ const session = await resolveGjcSessionForRead(cwd, {
1225
+ payloadSessionId: sessionId,
1226
+ envSessionId: process.env.GJC_SESSION_ID,
1227
+ });
1228
+ return (await readJsonFile(modeStateFile(cwd, skill, session.gjcSessionId))) ?? {};
1140
1229
  }
1141
1230
 
1142
1231
  async function handleRead(
@@ -1144,12 +1233,12 @@ async function handleRead(
1144
1233
  cwd: string,
1145
1234
  positionalSkill: string | undefined,
1146
1235
  ): Promise<StateCommandResult> {
1147
- const selectors = await resolveSelectors(args, cwd, positionalSkill);
1148
- const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.sessionId));
1236
+ const selectors = await resolveSelectors(args, cwd, positionalSkill, "read");
1237
+ const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.gjcSessionId));
1149
1238
  const fields = parseFieldsFlag(args);
1150
1239
  if (mode) {
1151
- const filePath = modeStateFile(cwd, mode, selectors.sessionId);
1152
- const existing = await readWorkflowStateJson(cwd, mode, selectors.sessionId);
1240
+ const filePath = modeStateFile(cwd, mode, selectors.gjcSessionId);
1241
+ const existing = await readWorkflowStateJson(cwd, mode, selectors.gjcSessionId);
1153
1242
  const envelope = { skill: mode, state: existing, storage_path: filePath };
1154
1243
  const manifest = getSkillManifest(mode);
1155
1244
  if (fields) {
@@ -1177,7 +1266,7 @@ async function handleRead(
1177
1266
  : renderStateMarkdown(mode, envelope, manifest),
1178
1267
  };
1179
1268
  }
1180
- const filePath = activeStateFile(cwd, selectors.sessionId);
1269
+ const filePath = activeStateFile(cwd, selectors.gjcSessionId);
1181
1270
  const existingRaw = await readJsonValue(filePath);
1182
1271
  const existing = isPlainObject(existingRaw) ? existingRaw : null;
1183
1272
  return { status: 0, stdout: `${JSON.stringify(existing ?? {}, null, 2)}\n` };
@@ -1188,16 +1277,16 @@ async function handleStatus(
1188
1277
  cwd: string,
1189
1278
  positionalSkill: string | undefined,
1190
1279
  ): Promise<StateCommandResult> {
1191
- const selectors = await resolveSelectors(args, cwd, positionalSkill);
1192
- const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.sessionId));
1280
+ const selectors = await resolveSelectors(args, cwd, positionalSkill, "read");
1281
+ const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.gjcSessionId));
1193
1282
  if (!mode) {
1194
1283
  throw new StateCommandError(
1195
1284
  2,
1196
- "gjc state status requires --mode <skill>, positional <skill>, input.skill, or an active workflow in .gjc/state/skill-active-state.json",
1285
+ "gjc state status requires --mode <skill>, positional <skill>, input.skill, or an active workflow in the current session active state",
1197
1286
  );
1198
1287
  }
1199
- const filePath = modeStateFile(cwd, mode, selectors.sessionId);
1200
- const existing = await readWorkflowStateJson(cwd, mode, selectors.sessionId);
1288
+ const filePath = modeStateFile(cwd, mode, selectors.gjcSessionId);
1289
+ const existing = await readWorkflowStateJson(cwd, mode, selectors.gjcSessionId);
1201
1290
  const summary = buildStateStatusSummary(
1202
1291
  mode,
1203
1292
  { skill: mode, state: existing, storage_path: filePath },
@@ -1215,14 +1304,14 @@ async function handleWrite(
1215
1304
  cwd: string,
1216
1305
  positionalSkill: string | undefined,
1217
1306
  ): Promise<StateCommandResult> {
1218
- const selectors = await resolveSelectors(args, cwd, positionalSkill);
1219
- const { sessionId, threadId, turnId, payload } = selectors;
1307
+ const selectors = await resolveSelectors(args, cwd, positionalSkill, "write");
1308
+ const { gjcSessionId: sessionId, threadId, turnId, payload } = selectors;
1220
1309
  if (!payload) throw new StateCommandError(2, "gjc state write requires --input '<json>'");
1221
1310
  const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, sessionId));
1222
1311
  if (!mode)
1223
1312
  throw new StateCommandError(
1224
1313
  2,
1225
- "gjc state write requires --mode <skill>, positional <skill>, input.skill, or an active workflow in .gjc/state/skill-active-state.json",
1314
+ "gjc state write requires --mode <skill>, positional <skill>, input.skill, or an active workflow in the current session active state",
1226
1315
  );
1227
1316
 
1228
1317
  const filePath = modeStateFile(cwd, mode, sessionId);
@@ -1311,6 +1400,7 @@ async function handleWrite(
1311
1400
  if (!validation.valid) throw new StateCommandError(2, validation.error ?? `invalid ${mode} state envelope`);
1312
1401
 
1313
1402
  const { warning: outOfBandWarning, stamped } = await writeJsonAtomic(cwd, filePath, merged, "write", {
1403
+ sessionId,
1314
1404
  skill: mode,
1315
1405
  mutationId,
1316
1406
  force: forced,
@@ -1322,6 +1412,7 @@ async function handleWrite(
1322
1412
  const phase = typeof merged.current_phase === "string" ? merged.current_phase : undefined;
1323
1413
  const active = merged.active !== false;
1324
1414
  await syncWorkflowSkillState({ cwd, mode, sessionId, threadId, turnId, active, phase, payload: merged, receipt });
1415
+ await touchStateActivityMarker(cwd, sessionId, filePath);
1325
1416
 
1326
1417
  return {
1327
1418
  status: 0,
@@ -1344,13 +1435,13 @@ async function handleClear(
1344
1435
  cwd: string,
1345
1436
  positionalSkill: string | undefined,
1346
1437
  ): Promise<StateCommandResult> {
1347
- const selectors = await resolveSelectors(args, cwd, positionalSkill);
1348
- const { sessionId, threadId, turnId } = selectors;
1438
+ const selectors = await resolveSelectors(args, cwd, positionalSkill, "clear");
1439
+ const { gjcSessionId: sessionId, threadId, turnId } = selectors;
1349
1440
  const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, sessionId));
1350
1441
  if (!mode)
1351
1442
  throw new StateCommandError(
1352
1443
  2,
1353
- "gjc state clear requires --mode <skill>, positional <skill>, input.skill, or an active workflow in .gjc/state/skill-active-state.json",
1444
+ "gjc state clear requires --mode <skill>, positional <skill>, input.skill, or an active workflow in the current session active state",
1354
1445
  );
1355
1446
 
1356
1447
  const filePath = modeStateFile(cwd, mode, sessionId);
@@ -1363,6 +1454,10 @@ async function handleClear(
1363
1454
  );
1364
1455
  }
1365
1456
  const existing = existingRead.kind === "valid" ? existingRead.value : {};
1457
+ const staleReason = await describeStaleClearState(cwd, sessionId, mode, existing);
1458
+ if (staleReason && !forced) {
1459
+ throw new StateCommandError(2, `existing state for ${mode} is stale (${staleReason}); use --force to clear`);
1460
+ }
1366
1461
  const clearedAt = nowIso();
1367
1462
  const cleared: Record<string, unknown> = {
1368
1463
  skill: mode,
@@ -1385,6 +1480,7 @@ async function handleClear(
1385
1480
  });
1386
1481
  cleared.receipt = receipt;
1387
1482
  const { warning: outOfBandWarning, stamped } = await writeJsonAtomic(cwd, filePath, cleared, "clear", {
1483
+ sessionId,
1388
1484
  skill: mode,
1389
1485
  mutationId,
1390
1486
  force: forced,
@@ -1403,6 +1499,7 @@ async function handleClear(
1403
1499
  phase: "complete",
1404
1500
  payload: cleared,
1405
1501
  });
1502
+ await touchStateActivityMarker(cwd, sessionId, filePath);
1406
1503
  return {
1407
1504
  status: 0,
1408
1505
  stdout: renderCliWriteReceipt({
@@ -1443,13 +1540,13 @@ async function handleHandoff(
1443
1540
  cwd: string,
1444
1541
  positionalSkill: string | undefined,
1445
1542
  ): Promise<StateCommandResult> {
1446
- const selectors = await resolveSelectors(args, cwd, positionalSkill);
1447
- const { sessionId, threadId, turnId } = selectors;
1543
+ const selectors = await resolveSelectors(args, cwd, positionalSkill, "handoff");
1544
+ const { gjcSessionId: sessionId, threadId, turnId } = selectors;
1448
1545
  const caller = selectors.mode ?? (await inferModeFromActiveState(cwd, sessionId));
1449
1546
  if (!caller) {
1450
1547
  throw new StateCommandError(
1451
1548
  2,
1452
- "gjc state handoff requires --mode <caller>, positional <caller>, input.skill, or an active workflow in .gjc/state/skill-active-state.json",
1549
+ "gjc state handoff requires --mode <caller>, positional <caller>, input.skill, or an active workflow in the current session active state",
1453
1550
  );
1454
1551
  }
1455
1552
  const calleeRaw = flagValue(args, "--to")?.trim();
@@ -1552,6 +1649,7 @@ async function handleHandoff(
1552
1649
 
1553
1650
  await beginWorkflowTransactionJournal({
1554
1651
  cwd,
1652
+ sessionId,
1555
1653
  mutationId,
1556
1654
  caller,
1557
1655
  callee,
@@ -1567,21 +1665,23 @@ async function handleHandoff(
1567
1665
  // only; corrupt JSON / IO failures propagate as non-zero CLI status.
1568
1666
  const force = hasFlag(args, "--force");
1569
1667
  const calleeWrite = await writeJsonAtomic(cwd, calleePath, mergedCalleeState, "handoff", {
1668
+ sessionId,
1570
1669
  skill: callee,
1571
1670
  mutationId,
1572
1671
  force,
1573
1672
  fromPhase: typeof existingCallee.current_phase === "string" ? existingCallee.current_phase : undefined,
1574
1673
  toPhase: calleeInitial,
1575
1674
  });
1576
- await updateWorkflowTransactionJournal(cwd, mutationId, { steps: ["callee-mode-state"] });
1675
+ await updateWorkflowTransactionJournal(cwd, sessionId, mutationId, { steps: ["callee-mode-state"] });
1577
1676
  const callerWrite = await writeJsonAtomic(cwd, callerPath, mergedCallerState, "handoff", {
1677
+ sessionId,
1578
1678
  skill: caller,
1579
1679
  mutationId,
1580
1680
  force,
1581
1681
  fromPhase: typeof existingCaller.current_phase === "string" ? existingCaller.current_phase : undefined,
1582
1682
  toPhase: "handoff",
1583
1683
  });
1584
- await updateWorkflowTransactionJournal(cwd, mutationId, {
1684
+ await updateWorkflowTransactionJournal(cwd, sessionId, mutationId, {
1585
1685
  steps: ["callee-mode-state", "caller-mode-state"],
1586
1686
  });
1587
1687
  const warnings = [calleeWrite.warning, callerWrite.warning].filter(
@@ -1626,10 +1726,11 @@ async function handleHandoff(
1626
1726
  receipt: calleeReceipt,
1627
1727
  },
1628
1728
  });
1629
- await updateWorkflowTransactionJournal(cwd, mutationId, {
1729
+ await updateWorkflowTransactionJournal(cwd, sessionId, mutationId, {
1630
1730
  steps: ["callee-mode-state", "caller-mode-state", "active-state"],
1631
1731
  });
1632
- await completeWorkflowTransactionJournal(cwd, mutationId);
1732
+ await completeWorkflowTransactionJournal(cwd, sessionId, mutationId);
1733
+ await touchStateActivityMarker(cwd, sessionId, callerPath);
1633
1734
 
1634
1735
  return {
1635
1736
  status: 0,
@@ -1668,7 +1769,7 @@ async function handleContract(
1668
1769
  cwd: string,
1669
1770
  positionalSkill: string | undefined,
1670
1771
  ): Promise<StateCommandResult> {
1671
- const { mode } = await resolveSelectors(args, cwd, positionalSkill);
1772
+ const { mode } = await resolveSelectors(args, cwd, positionalSkill, "read");
1672
1773
  if (!mode) {
1673
1774
  throw new StateCommandError(2, "gjc state contract requires --mode <skill>, positional <skill>, or input.skill");
1674
1775
  }
@@ -1723,11 +1824,7 @@ function categoryForStateRelativePath(relativePath: string): string | undefined
1723
1824
  if (normalized === "audit.jsonl") return undefined;
1724
1825
  if (normalized === SKILL_ACTIVE_STATE_FILE || normalized.endsWith(`/${SKILL_ACTIVE_STATE_FILE}`)) return undefined;
1725
1826
  if (normalized.startsWith("active/") || normalized.includes("/active/")) return undefined;
1726
- if (
1727
- /^[^/]+-state\.json$/.test(normalized) ||
1728
- (normalized.includes("/sessions/") && /\/[^/]+-state\.json$/.test(normalized))
1729
- )
1730
- return undefined;
1827
+ if (/^[^/]+-state\.json$/.test(normalized) || false) return undefined;
1731
1828
  if (normalized.startsWith("artifacts/") || normalized.includes("/artifacts/")) return "artifact";
1732
1829
  if (
1733
1830
  normalized.startsWith("logs/") ||
@@ -1753,9 +1850,10 @@ function categoryForStateRelativePath(relativePath: string): string | undefined
1753
1850
 
1754
1851
  async function collectRetentionCandidates(
1755
1852
  cwd: string,
1853
+ sessionId: string,
1756
1854
  skills: readonly CanonicalGjcWorkflowSkill[],
1757
1855
  ): Promise<RetentionCandidate[]> {
1758
- const stateRoot = path.join(cwd, ".gjc", "state");
1856
+ const stateRoot = sessionStateDir(cwd, sessionId);
1759
1857
  const policies = new Map<string, { keep?: number; maxAgeDays?: number }>();
1760
1858
  for (const skill of skills) {
1761
1859
  for (const policy of getSkillManifest(skill).retention) {
@@ -1836,7 +1934,11 @@ async function buildGcSummary(
1836
1934
  flagValue(args, "--skill")?.trim() || flagValue(args, "--mode")?.trim() || positionalSkill?.trim() || "all";
1837
1935
  if (rawSkill !== "all") assertKnownMode(rawSkill);
1838
1936
  const skills = rawSkill === "all" ? CANONICAL_GJC_WORKFLOW_SKILLS : [rawSkill as CanonicalGjcWorkflowSkill];
1839
- const eligible = selectRetentionEligible(await collectRetentionCandidates(cwd, skills));
1937
+ const session = await resolveGjcSessionForRead(cwd, {
1938
+ flagValue: flagValue(args, "--session-id"),
1939
+ envSessionId: process.env.GJC_SESSION_ID,
1940
+ });
1941
+ const eligible = selectRetentionEligible(await collectRetentionCandidates(cwd, session.gjcSessionId, skills));
1840
1942
  const counts: Record<string, number> = {};
1841
1943
  for (const candidate of eligible) counts[candidate.category] = (counts[candidate.category] ?? 0) + 1;
1842
1944
  const targets: GenericHardPruneTarget[] = eligible.map(candidate => ({
@@ -1850,6 +1952,7 @@ async function buildGcSummary(
1850
1952
  cwd,
1851
1953
  audit: {
1852
1954
  cwd,
1955
+ sessionId: session.gjcSessionId,
1853
1956
  skill: rawSkill,
1854
1957
  category: "prune",
1855
1958
  verb: "gc",
@@ -1861,7 +1964,7 @@ async function buildGcSummary(
1861
1964
  skill: rawSkill as CanonicalGjcWorkflowSkill | "all",
1862
1965
  dry_run: dryRun,
1863
1966
  eligible: eligible.map(candidate => candidate.relativePath),
1864
- pruned: pruned.map(filePath => path.relative(path.join(cwd, ".gjc", "state"), filePath)),
1967
+ pruned: pruned.map(filePath => path.relative(sessionStateDir(cwd, session.gjcSessionId), filePath)),
1865
1968
  counts,
1866
1969
  };
1867
1970
  }
@@ -1872,7 +1975,11 @@ async function handleGraph(
1872
1975
  positionalSkill: string | undefined,
1873
1976
  ): Promise<StateCommandResult> {
1874
1977
  if (hasFlag(args, "--history")) {
1875
- const history = await readAuditWindow(_cwd, args);
1978
+ const session = await resolveGjcSessionForRead(_cwd, {
1979
+ flagValue: flagValue(args, "--session-id"),
1980
+ envSessionId: process.env.GJC_SESSION_ID,
1981
+ });
1982
+ const history = await readAuditWindow(_cwd, args, session.gjcSessionId);
1876
1983
  return {
1877
1984
  status: 0,
1878
1985
  stdout: hasFlag(args, "--json") ? `${JSON.stringify(history, null, 2)}\n` : renderHistoryMarkdown(history),
@@ -1895,20 +2002,21 @@ async function handlePrune(
1895
2002
  cwd: string,
1896
2003
  positionalSkill: string | undefined,
1897
2004
  ): Promise<StateCommandResult> {
1898
- const selectors = await resolveSelectors(args, cwd, positionalSkill);
1899
- const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.sessionId));
2005
+ const selectors = await resolveSelectors(args, cwd, positionalSkill, "prune");
2006
+ const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.gjcSessionId));
1900
2007
  if (!mode) {
1901
2008
  throw new StateCommandError(
1902
2009
  2,
1903
- "gjc state prune requires --mode <skill>, positional <skill>, input.skill, or an active workflow in .gjc/state/skill-active-state.json",
2010
+ "gjc state prune requires --mode <skill>, positional <skill>, input.skill, or an active workflow in the current session active state",
1904
2011
  );
1905
2012
  }
1906
- const filePath = modeStateFile(cwd, mode, selectors.sessionId);
2013
+ const filePath = modeStateFile(cwd, mode, selectors.gjcSessionId);
1907
2014
  const olderThanDays = parseNonNegativeIntegerFlag(args, "--older-than");
1908
2015
  const status = flagValue(args, "--status")?.trim();
1909
2016
  const targets: GenericHardPruneTarget[] = [{ path: filePath, category: "prune" }];
1910
2017
  const audit: StateWriterAuditContext = {
1911
2018
  cwd,
2019
+ sessionId: selectors.gjcSessionId,
1912
2020
  skill: mode,
1913
2021
  category: "prune",
1914
2022
  verb: hasFlag(args, "--hard") ? "hard-prune" : "soft-delete",
@@ -1964,17 +2072,17 @@ async function handleMigrate(
1964
2072
  cwd: string,
1965
2073
  positionalSkill: string | undefined,
1966
2074
  ): Promise<StateCommandResult> {
1967
- const selectors = await resolveSelectors(args, cwd, positionalSkill);
1968
- const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.sessionId));
2075
+ const selectors = await resolveSelectors(args, cwd, positionalSkill, "migrate");
2076
+ const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.gjcSessionId));
1969
2077
  if (!mode) {
1970
2078
  throw new StateCommandError(
1971
2079
  2,
1972
- "gjc state migrate requires --mode <skill>, positional <skill>, input.skill, or an active workflow in .gjc/state/skill-active-state.json",
2080
+ "gjc state migrate requires --mode <skill>, positional <skill>, input.skill, or an active workflow in the current session active state",
1973
2081
  );
1974
2082
  }
1975
- const filePath = modeStateFile(cwd, mode, selectors.sessionId);
2083
+ const filePath = modeStateFile(cwd, mode, selectors.gjcSessionId);
1976
2084
  const forced = hasFlag(args, "--force");
1977
- const mismatchWarning = await warnAndAuditOutOfBandIfNeeded(cwd, filePath, mode, {
2085
+ const mismatchWarning = await warnAndAuditOutOfBandIfNeeded(cwd, selectors.gjcSessionId, filePath, mode, {
1978
2086
  forced,
1979
2087
  });
1980
2088
  if (mismatchWarning && !forced) {
@@ -1984,7 +2092,7 @@ async function handleMigrate(
1984
2092
  cwd,
1985
2093
  skill: mode,
1986
2094
  statePath: filePath,
1987
- sessionId: selectors.sessionId,
2095
+ sessionId: selectors.gjcSessionId,
1988
2096
  });
1989
2097
  return {
1990
2098
  status: 0,
@@ -2026,6 +2134,7 @@ export async function runNativeStateCommand(args: string[], cwd = process.cwd())
2026
2134
  }
2027
2135
  } catch (error) {
2028
2136
  if (error instanceof StateCommandError) return { status: error.exitStatus, stderr: `${error.message}\n` };
2137
+ if (error instanceof SessionResolutionError) return { status: 2, stderr: `${error.message}\n` };
2029
2138
  return { status: 1, stderr: `${error instanceof Error ? error.message : String(error)}\n` };
2030
2139
  }
2031
2140
  }