@gajae-code/coding-agent 0.2.2 → 0.2.4

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 (96) hide show
  1. package/CHANGELOG.md +45 -8600
  2. package/dist/types/cli/setup-cli.d.ts +1 -0
  3. package/dist/types/cli/update-cli.d.ts +3 -0
  4. package/dist/types/commands/deep-interview.d.ts +41 -0
  5. package/dist/types/commands/setup.d.ts +3 -0
  6. package/dist/types/config/settings-schema.d.ts +56 -0
  7. package/dist/types/defaults/gjc-defaults.d.ts +19 -6
  8. package/dist/types/discovery/helpers.d.ts +2 -0
  9. package/dist/types/extensibility/extensions/types.d.ts +6 -0
  10. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +18 -0
  11. package/dist/types/hooks/skill-state.d.ts +5 -0
  12. package/dist/types/memories/index.d.ts +1 -1
  13. package/dist/types/memory-backend/local-backend.d.ts +3 -3
  14. package/dist/types/modes/components/hook-selector.d.ts +7 -0
  15. package/dist/types/modes/components/settings-selector.d.ts +3 -1
  16. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  17. package/dist/types/modes/interactive-mode.d.ts +1 -0
  18. package/dist/types/modes/theme/defaults/index.d.ts +126 -0
  19. package/dist/types/modes/theme/theme.d.ts +5 -0
  20. package/dist/types/modes/types.d.ts +1 -0
  21. package/dist/types/modes/utils/context-usage.d.ts +6 -2
  22. package/dist/types/sdk.d.ts +6 -2
  23. package/dist/types/session/agent-session.d.ts +45 -1
  24. package/dist/types/session/session-manager.d.ts +3 -0
  25. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -0
  26. package/dist/types/setup/provider-onboarding.d.ts +29 -5
  27. package/dist/types/skill-state/active-state.d.ts +26 -1
  28. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
  29. package/dist/types/skill-state/initial-phase.d.ts +12 -0
  30. package/dist/types/task/executor.d.ts +2 -0
  31. package/dist/types/task/types.d.ts +11 -0
  32. package/dist/types/tools/index.d.ts +20 -1
  33. package/dist/types/tools/skill.d.ts +47 -0
  34. package/dist/types/utils/changelog.d.ts +18 -2
  35. package/package.json +7 -7
  36. package/src/cli/setup-cli.ts +26 -12
  37. package/src/cli/update-cli.ts +67 -16
  38. package/src/cli.ts +1 -0
  39. package/src/commands/deep-interview.ts +25 -2
  40. package/src/commands/setup.ts +2 -0
  41. package/src/commands/state.ts +1 -0
  42. package/src/config/settings-schema.ts +63 -0
  43. package/src/defaults/gjc/skills/deep-interview/SKILL.md +58 -5
  44. package/src/defaults/gjc/skills/deep-interview/auto-answer-uncertain.md +37 -0
  45. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +42 -0
  46. package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -0
  47. package/src/defaults/gjc/skills/team/SKILL.md +10 -0
  48. package/src/defaults/gjc/skills/ultragoal/SKILL.md +19 -6
  49. package/src/defaults/gjc-defaults.ts +68 -16
  50. package/src/discovery/helpers.ts +24 -1
  51. package/src/extensibility/extensions/types.ts +6 -0
  52. package/src/gjc-runtime/deep-interview-runtime.ts +312 -1
  53. package/src/gjc-runtime/state-runtime.ts +175 -5
  54. package/src/goals/tools/goal-tool.ts +5 -1
  55. package/src/hooks/skill-state.ts +8 -6
  56. package/src/internal-urls/docs-index.generated.ts +6 -4
  57. package/src/internal-urls/memory-protocol.ts +3 -2
  58. package/src/main.ts +2 -3
  59. package/src/memories/index.ts +6 -4
  60. package/src/memory-backend/local-backend.ts +14 -6
  61. package/src/modes/components/hook-selector.ts +156 -1
  62. package/src/modes/components/settings-selector.ts +16 -12
  63. package/src/modes/controllers/command-controller.ts +3 -4
  64. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  65. package/src/modes/controllers/selector-controller.ts +69 -9
  66. package/src/modes/interactive-mode.ts +14 -1
  67. package/src/modes/theme/defaults/blue-crab.json +126 -0
  68. package/src/modes/theme/defaults/index.ts +2 -0
  69. package/src/modes/theme/theme.ts +40 -1
  70. package/src/modes/types.ts +1 -0
  71. package/src/modes/utils/context-usage.ts +66 -17
  72. package/src/prompts/agents/architect.md +3 -0
  73. package/src/prompts/agents/executor.md +2 -0
  74. package/src/prompts/agents/frontmatter.md +1 -0
  75. package/src/prompts/memories/unavailable.md +9 -0
  76. package/src/prompts/system/subagent-system-prompt.md +6 -0
  77. package/src/prompts/tools/skill.md +28 -0
  78. package/src/prompts/tools/task.md +3 -0
  79. package/src/sdk.ts +54 -10
  80. package/src/session/agent-session.ts +204 -21
  81. package/src/session/session-manager.ts +9 -1
  82. package/src/setup/model-onboarding-guidance.ts +6 -3
  83. package/src/setup/provider-onboarding.ts +177 -16
  84. package/src/skill-state/active-state.ts +150 -25
  85. package/src/skill-state/deep-interview-mutation-guard.ts +11 -24
  86. package/src/skill-state/initial-phase.ts +17 -0
  87. package/src/slash-commands/builtin-registry.ts +62 -14
  88. package/src/slash-commands/helpers/context-report.ts +123 -13
  89. package/src/task/agents.ts +1 -0
  90. package/src/task/executor.ts +9 -1
  91. package/src/task/index.ts +91 -4
  92. package/src/task/types.ts +6 -0
  93. package/src/tools/ask.ts +2 -0
  94. package/src/tools/index.ts +23 -1
  95. package/src/tools/skill.ts +153 -0
  96. package/src/utils/changelog.ts +67 -44
@@ -3,12 +3,14 @@ import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import type { WorkflowHudSummary } from "../skill-state/active-state";
5
5
  import {
6
+ applyHandoffToActiveState,
6
7
  CANONICAL_GJC_WORKFLOW_SKILLS,
7
8
  type CanonicalGjcWorkflowSkill,
8
9
  listActiveSkills,
9
10
  readVisibleSkillActiveState,
10
11
  syncSkillActiveState,
11
12
  } from "../skill-state/active-state";
13
+ import { initialPhaseForSkill } from "../skill-state/initial-phase";
12
14
  import {
13
15
  buildDeepInterviewHudSummary,
14
16
  buildRalplanHudSummary,
@@ -60,11 +62,11 @@ function hasFlag(args: readonly string[], flag: string): boolean {
60
62
  return args.includes(flag);
61
63
  }
62
64
 
63
- const FLAGS_WITH_VALUES = new Set(["--input", "--mode", "--session-id", "--thread-id", "--turn-id"]);
64
- const ACTION_NAMES = new Set(["read", "write", "clear", "contract"]);
65
+ const FLAGS_WITH_VALUES = new Set(["--input", "--mode", "--session-id", "--thread-id", "--turn-id", "--to"]);
66
+ const ACTION_NAMES = new Set(["read", "write", "clear", "contract", "handoff"]);
65
67
 
66
68
  interface ParsedInvocation {
67
- action: "read" | "write" | "clear" | "contract";
69
+ action: "read" | "write" | "clear" | "contract" | "handoff";
68
70
  positionalSkill?: string;
69
71
  }
70
72
 
@@ -169,10 +171,20 @@ async function resolveSelectors(
169
171
  }
170
172
  if (mode) assertKnownMode(mode);
171
173
 
172
- let sessionId = flagValue(args, "--session-id")?.trim() || undefined;
174
+ const explicitSessionId = flagValue(args, "--session-id");
175
+ // Session-id resolution order: explicit --session-id flag, then payload
176
+ // session_id, then GJC_SESSION_ID env var (set by AgentSession.sdk for
177
+ // agent-initiated CLI invocations). The env-var default keeps shell
178
+ // snippets in skill docs short while still routing writes/reads to the
179
+ // caller's session-scoped state files.
180
+ let sessionId = explicitSessionId !== undefined ? explicitSessionId.trim() || undefined : undefined;
173
181
  if (!sessionId && payload && typeof payload.session_id === "string") {
174
182
  sessionId = payload.session_id.trim() || undefined;
175
183
  }
184
+ if (!sessionId && explicitSessionId === undefined) {
185
+ const envSessionId = process.env.GJC_SESSION_ID?.trim();
186
+ if (envSessionId) sessionId = envSessionId;
187
+ }
176
188
  if (sessionId) assertSafePathComponent(sessionId, "session-id");
177
189
 
178
190
  const threadId = flagValue(args, "--thread-id")?.trim() || undefined;
@@ -396,7 +408,6 @@ async function syncWorkflowSkillState(options: {
396
408
  // HUD sync is best-effort and must not change command semantics.
397
409
  }
398
410
  }
399
-
400
411
  async function handleRead(
401
412
  args: readonly string[],
402
413
  cwd: string,
@@ -527,6 +538,163 @@ async function handleClear(
527
538
  return { status: 0, stdout: `${JSON.stringify(cleared, null, 2)}\n` };
528
539
  }
529
540
 
541
+ /**
542
+ * `handoff` exists in two distinct roles:
543
+ * - As a verb: this CLI action, which atomically transitions caller→callee.
544
+ * Writes the callee mode-state first, the caller mode-state second, then
545
+ * syncs both `skill-active-state.json` files. Every intermediate crashed
546
+ * state remains HUD-coherent: the active-state file either reflects the
547
+ * old skill entirely or the new skill entirely, never both as active.
548
+ * - As a phase: `current_phase: "handoff"` is set by this verb when demoting
549
+ * the caller. Agents writing `current_phase: "handoff"` manually via
550
+ * `gjc state <skill> write` are declaring "I am ready to be handed off";
551
+ * the next agent-initiated `skill` tool call will then satisfy the phase
552
+ * guard and may chain.
553
+ *
554
+ * `handoff` is in the terminal-phase set used by `isTerminalModeState` and by
555
+ * the skill tool's chain guard. A manual `current_phase: "handoff"` write does
556
+ * NOT mark `active: false` — only this verb does that — so a skill that wrote
557
+ * the phase remains in `skill-active-state.json` until a chain call (or
558
+ * explicit `clear`) demotes it.
559
+ */
560
+ async function handleHandoff(
561
+ args: readonly string[],
562
+ cwd: string,
563
+ positionalSkill: string | undefined,
564
+ ): Promise<StateCommandResult> {
565
+ const selectors = await resolveSelectors(args, cwd, positionalSkill);
566
+ const { sessionId, threadId, turnId } = selectors;
567
+ const caller = selectors.mode ?? (await inferModeFromActiveState(cwd, sessionId));
568
+ if (!caller) {
569
+ throw new StateCommandError(
570
+ 2,
571
+ "gjc state handoff requires --mode <caller>, positional <caller>, input.skill, or an active workflow in .gjc/state/skill-active-state.json",
572
+ );
573
+ }
574
+ const calleeRaw = flagValue(args, "--to")?.trim();
575
+ if (!calleeRaw) {
576
+ throw new StateCommandError(2, "gjc state handoff requires --to <callee>");
577
+ }
578
+ assertKnownMode(calleeRaw);
579
+ const callee = calleeRaw as CanonicalGjcWorkflowSkill;
580
+ if (callee === caller) {
581
+ throw new StateCommandError(2, `gjc state handoff: --to must differ from caller (both are "${caller}")`);
582
+ }
583
+
584
+ const callerPath = modeStateFile(cwd, caller, sessionId);
585
+ const calleePath = modeStateFile(cwd, callee, sessionId);
586
+ const existingCaller = await readJsonFile(callerPath);
587
+ if (!existingCaller) {
588
+ throw new StateCommandError(
589
+ 2,
590
+ `gjc state ${caller} handoff: caller is not active (no mode-state file at ${callerPath})`,
591
+ );
592
+ }
593
+ const existingCallee = (await readJsonFile(calleePath)) ?? {};
594
+
595
+ const handoffAt = nowIso();
596
+ const callerReceipt = buildWorkflowStateReceipt({
597
+ cwd,
598
+ skill: caller,
599
+ owner: "gjc-state-cli",
600
+ command: `gjc state ${caller} handoff --to ${callee}`,
601
+ sessionId,
602
+ nowIso: handoffAt,
603
+ });
604
+ const calleeReceipt = buildWorkflowStateReceipt({
605
+ cwd,
606
+ skill: callee,
607
+ owner: "gjc-state-cli",
608
+ command: `gjc state ${caller} handoff --to ${callee}`,
609
+ sessionId,
610
+ nowIso: handoffAt,
611
+ });
612
+
613
+ const calleeInitial = initialPhaseForSkill(callee);
614
+ const mergedCalleeState: Record<string, unknown> = {
615
+ ...existingCallee,
616
+ skill: callee,
617
+ version: typeof existingCallee.version === "number" ? existingCallee.version : 1,
618
+ active: true,
619
+ current_phase: calleeInitial,
620
+ handoff_from: caller,
621
+ handoff_at: handoffAt,
622
+ updated_at: handoffAt,
623
+ receipt: calleeReceipt,
624
+ };
625
+ if (sessionId && typeof mergedCalleeState.session_id !== "string") {
626
+ mergedCalleeState.session_id = sessionId;
627
+ }
628
+ const mergedCallerState: Record<string, unknown> = {
629
+ ...existingCaller,
630
+ skill: caller,
631
+ active: false,
632
+ current_phase: "handoff",
633
+ handoff_to: callee,
634
+ handoff_at: handoffAt,
635
+ updated_at: handoffAt,
636
+ receipt: callerReceipt,
637
+ };
638
+
639
+ // Atomic write order (architecture blocker AR-3): mode-state files first,
640
+ // then a single atomic active-state mutation per file (session before root)
641
+ // via applyHandoffToActiveState. The single-write transaction prevents the
642
+ // HUD from observing a window where neither caller nor callee is active,
643
+ // and write order keeps the session-scoped source of truth ahead of the
644
+ // root aggregate. strict:true on the active-state read tolerates ENOENT
645
+ // only; corrupt JSON / IO failures propagate as non-zero CLI status.
646
+ await writeJsonAtomic(calleePath, mergedCalleeState);
647
+ await writeJsonAtomic(callerPath, mergedCallerState);
648
+ await applyHandoffToActiveState({
649
+ cwd,
650
+ nowIso: handoffAt,
651
+ strict: true,
652
+ caller: {
653
+ cwd,
654
+ skill: caller,
655
+ active: false,
656
+ phase: "handoff",
657
+ sessionId,
658
+ threadId,
659
+ turnId,
660
+ source: "gjc-state-cli",
661
+ hud: buildHudForMode(caller, mergedCallerState),
662
+ handoff_to: callee,
663
+ handoff_at: handoffAt,
664
+ receipt: callerReceipt,
665
+ },
666
+ callee: {
667
+ cwd,
668
+ skill: callee,
669
+ active: true,
670
+ phase: calleeInitial,
671
+ sessionId,
672
+ threadId,
673
+ turnId,
674
+ source: "gjc-state-cli",
675
+ hud: buildHudForMode(callee, mergedCalleeState),
676
+ handoff_from: caller,
677
+ handoff_at: handoffAt,
678
+ receipt: calleeReceipt,
679
+ },
680
+ });
681
+
682
+ return {
683
+ status: 0,
684
+ stdout: `${JSON.stringify(
685
+ {
686
+ from: caller,
687
+ to: callee,
688
+ handoff_at: handoffAt,
689
+ caller_state: mergedCallerState,
690
+ callee_state: mergedCalleeState,
691
+ },
692
+ null,
693
+ 2,
694
+ )}\n`,
695
+ };
696
+ }
697
+
530
698
  async function handleContract(
531
699
  args: readonly string[],
532
700
  cwd: string,
@@ -552,6 +720,8 @@ export async function runNativeStateCommand(args: string[], cwd = process.cwd())
552
720
  return await handleClear(args, cwd, parsed.positionalSkill);
553
721
  case "contract":
554
722
  return await handleContract(args, cwd, parsed.positionalSkill);
723
+ case "handoff":
724
+ return await handleHandoff(args, cwd, parsed.positionalSkill);
555
725
  default:
556
726
  return { status: 2, stderr: `Unknown gjc state command: ${parsed.action}\n` };
557
727
  }
@@ -16,7 +16,11 @@ import { validateGoalObjective } from "../runtime";
16
16
  import type { Goal, GoalStatus, GoalToolDetails } from "../state";
17
17
 
18
18
  const goalSchema = z.object({
19
- op: z.enum(["create", "get", "complete", "resume", "drop"]).describe("goal operation"),
19
+ op: z
20
+ .enum(["create", "get", "complete", "resume", "drop"])
21
+ .describe(
22
+ "op: get | create | complete | drop | resume — drop clears the active goal without exiting goal mode (tool stays callable for the next create)",
23
+ ),
20
24
  objective: z.string().describe("goal objective").optional(),
21
25
  });
22
26
 
@@ -112,6 +112,9 @@ export interface ModeState {
112
112
  thread_id?: string;
113
113
  cwd?: string;
114
114
  updated_at?: string;
115
+ handoff_from?: string;
116
+ handoff_to?: string;
117
+ handoff_at?: string;
115
118
  [key: string]: unknown;
116
119
  }
117
120
 
@@ -220,11 +223,10 @@ function encodeStatePathSegment(value: string): string {
220
223
  return encodeURIComponent(value).replaceAll(".", "%2E");
221
224
  }
222
225
 
223
- function initialPhaseForSkill(skill: GjcWorkflowSkill): string {
224
- if (skill === "deep-interview") return "interviewing";
225
- if (skill === "ultragoal") return "goal-planning";
226
- return "planning";
227
- }
226
+ import { initialPhaseForSkill } from "../skill-state/initial-phase";
227
+
228
+ // Re-export for existing callers and tests that imported it from this module.
229
+ export { initialPhaseForSkill };
228
230
 
229
231
  function modeStateFileName(skill: GjcWorkflowSkill): string {
230
232
  return `${skill}-state.json`;
@@ -347,7 +349,7 @@ function isTerminalModeState(state: ModeState | null): boolean {
347
349
  const phase = String(state.current_phase ?? "")
348
350
  .trim()
349
351
  .toLowerCase();
350
- return ["complete", "completed", "failed", "cancelled", "canceled", "inactive"].includes(phase);
352
+ return ["complete", "completed", "handoff", "failed", "cancelled", "canceled", "inactive"].includes(phase);
351
353
  }
352
354
 
353
355
  async function readVisibleModeState(