@gajae-code/coding-agent 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/dist/types/cli/skills-cli.d.ts +9 -0
  3. package/dist/types/commands/contribution-prep.d.ts +18 -0
  4. package/dist/types/commands/session.d.ts +24 -0
  5. package/dist/types/commands/skills.d.ts +26 -0
  6. package/dist/types/config/model-registry.d.ts +33 -4
  7. package/dist/types/config/models-config-schema.d.ts +52 -5
  8. package/dist/types/config/settings-schema.d.ts +1 -24
  9. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +15 -0
  10. package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
  11. package/dist/types/gjc-runtime/launch-tmux.d.ts +12 -11
  12. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +25 -0
  13. package/dist/types/gjc-runtime/state-runtime.d.ts +13 -0
  14. package/dist/types/gjc-runtime/team-runtime.d.ts +37 -5
  15. package/dist/types/gjc-runtime/tmux-common.d.ts +41 -0
  16. package/dist/types/gjc-runtime/tmux-sessions.d.ts +17 -0
  17. package/dist/types/goals/runtime.d.ts +3 -9
  18. package/dist/types/goals/state.d.ts +3 -6
  19. package/dist/types/goals/tools/goal-tool.d.ts +1 -69
  20. package/dist/types/modes/components/model-selector.d.ts +21 -1
  21. package/dist/types/modes/components/status-line/types.d.ts +0 -3
  22. package/dist/types/modes/components/status-line.d.ts +0 -3
  23. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  24. package/dist/types/modes/interactive-mode.d.ts +1 -12
  25. package/dist/types/modes/theme/defaults/index.d.ts +0 -2
  26. package/dist/types/modes/theme/theme.d.ts +1 -2
  27. package/dist/types/modes/types.d.ts +1 -7
  28. package/dist/types/session/agent-session.d.ts +2 -0
  29. package/dist/types/session/contribution-prep.d.ts +47 -0
  30. package/dist/types/skill-state/active-state.d.ts +4 -0
  31. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +6 -1
  32. package/dist/types/skill-state/workflow-hud.d.ts +9 -4
  33. package/dist/types/skill-state/workflow-state-contract.d.ts +34 -0
  34. package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
  35. package/package.json +7 -7
  36. package/src/cli/args.ts +17 -2
  37. package/src/cli/skills-cli.ts +88 -0
  38. package/src/cli.ts +7 -1
  39. package/src/commands/contribution-prep.ts +41 -0
  40. package/src/commands/deep-interview.ts +6 -22
  41. package/src/commands/launch.ts +10 -1
  42. package/src/commands/ralplan.ts +10 -22
  43. package/src/commands/session.ts +150 -0
  44. package/src/commands/skills.ts +48 -0
  45. package/src/commands/state.ts +14 -4
  46. package/src/commands/team.ts +23 -3
  47. package/src/commit/agentic/index.ts +1 -0
  48. package/src/commit/pipeline.ts +1 -0
  49. package/src/config/model-registry.ts +269 -10
  50. package/src/config/models-config-schema.ts +124 -88
  51. package/src/config/settings-schema.ts +1 -25
  52. package/src/config.ts +1 -1
  53. package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -13
  54. package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -2
  55. package/src/defaults/gjc/skills/team/SKILL.md +29 -7
  56. package/src/defaults/gjc/skills/ultragoal/SKILL.md +23 -25
  57. package/src/eval/py/prelude.py +1 -1
  58. package/src/gjc-runtime/deep-interview-runtime.ts +279 -0
  59. package/src/gjc-runtime/goal-mode-request.ts +2 -19
  60. package/src/gjc-runtime/launch-tmux.ts +83 -43
  61. package/src/gjc-runtime/ralplan-runtime.ts +460 -0
  62. package/src/gjc-runtime/state-runtime.ts +562 -0
  63. package/src/gjc-runtime/team-runtime.ts +708 -52
  64. package/src/gjc-runtime/tmux-common.ts +119 -0
  65. package/src/gjc-runtime/tmux-sessions.ts +165 -0
  66. package/src/gjc-runtime/ultragoal-guard.ts +6 -3
  67. package/src/gjc-runtime/ultragoal-runtime.ts +5 -4
  68. package/src/goals/runtime.ts +38 -144
  69. package/src/goals/state.ts +36 -7
  70. package/src/goals/tools/goal-tool.ts +15 -172
  71. package/src/hooks/skill-state.ts +31 -12
  72. package/src/internal-urls/docs-index.generated.ts +4 -3
  73. package/src/main.ts +10 -1
  74. package/src/modes/components/model-selector.ts +109 -28
  75. package/src/modes/components/skill-hud/render.ts +4 -0
  76. package/src/modes/components/status-line/segments.ts +5 -16
  77. package/src/modes/components/status-line/types.ts +0 -3
  78. package/src/modes/components/status-line.ts +0 -6
  79. package/src/modes/controllers/command-controller.ts +25 -1
  80. package/src/modes/controllers/input-controller.ts +0 -15
  81. package/src/modes/controllers/selector-controller.ts +42 -2
  82. package/src/modes/interactive-mode.ts +18 -219
  83. package/src/modes/theme/defaults/dark-poimandres.json +0 -1
  84. package/src/modes/theme/defaults/light-poimandres.json +0 -1
  85. package/src/modes/theme/theme.ts +0 -6
  86. package/src/modes/types.ts +1 -7
  87. package/src/prompts/goals/goal-continuation.md +1 -4
  88. package/src/prompts/goals/goal-mode-active.md +3 -5
  89. package/src/prompts/system/system-prompt.md +5 -7
  90. package/src/prompts/tools/goal.md +4 -4
  91. package/src/sdk.ts +2 -1
  92. package/src/session/agent-session.ts +18 -0
  93. package/src/session/contribution-prep.ts +320 -0
  94. package/src/setup/provider-onboarding.ts +2 -0
  95. package/src/skill-state/active-state.ts +38 -0
  96. package/src/skill-state/deep-interview-mutation-guard.ts +88 -24
  97. package/src/skill-state/workflow-hud.ts +23 -5
  98. package/src/skill-state/workflow-state-contract.ts +121 -0
  99. package/src/slash-commands/acp-builtins.ts +11 -2
  100. package/src/slash-commands/builtin-registry.ts +40 -13
  101. package/src/task/commands.ts +1 -5
  102. package/src/tools/gh.ts +212 -2
  103. package/src/tools/index.ts +2 -5
  104. package/dist/types/commands/gjc-runtime-bridge.d.ts +0 -30
  105. package/dist/types/commands/question.d.ts +0 -7
  106. package/dist/types/modes/loop-limit.d.ts +0 -22
  107. package/src/commands/gjc-runtime-bridge.ts +0 -227
  108. package/src/commands/question.ts +0 -12
  109. package/src/modes/loop-limit.ts +0 -140
  110. package/src/prompts/commands/orchestrate.md +0 -49
  111. package/src/prompts/goals/goal-budget-limit.md +0 -16
  112. package/src/prompts/tools/create-goal.md +0 -3
  113. package/src/prompts/tools/get-goal.md +0 -3
  114. package/src/prompts/tools/update-goal.md +0 -3
@@ -0,0 +1,121 @@
1
+ import * as path from "node:path";
2
+ import { CANONICAL_GJC_WORKFLOW_SKILLS, type CanonicalGjcWorkflowSkill, SKILL_ACTIVE_STATE_FILE } from "./active-state";
3
+
4
+ export type { CanonicalGjcWorkflowSkill };
5
+
6
+ export const WORKFLOW_STATE_RECEIPT_VERSION = 1;
7
+ export const WORKFLOW_STATE_RECEIPT_FRESH_MS = 30 * 60 * 1000;
8
+
9
+ export type WorkflowStateMutationOwner = "gjc-state-cli" | "gjc-runtime" | "gjc-hook";
10
+ export type WorkflowStateReceiptStatus = "fresh" | "stale";
11
+
12
+ export interface WorkflowStateReceipt {
13
+ version: 1;
14
+ skill: CanonicalGjcWorkflowSkill;
15
+ owner: WorkflowStateMutationOwner;
16
+ command: string;
17
+ state_path: string;
18
+ storage_path: string;
19
+ mutated_at: string;
20
+ fresh_until: string;
21
+ status: WorkflowStateReceiptStatus;
22
+ mutation_id: string;
23
+ }
24
+
25
+ function safeString(value: unknown): string {
26
+ return typeof value === "string" ? value : "";
27
+ }
28
+
29
+ function encodePathSegment(value: string): string {
30
+ return encodeURIComponent(value).replaceAll(".", "%2E");
31
+ }
32
+
33
+ export function workflowModeStateFileName(skill: CanonicalGjcWorkflowSkill): string {
34
+ return `${skill}-state.json`;
35
+ }
36
+
37
+ export function workflowStateStoragePath(cwd: string, skill: CanonicalGjcWorkflowSkill, sessionId?: string): string {
38
+ const normalizedSessionId = safeString(sessionId).trim();
39
+ if (normalizedSessionId) {
40
+ return path.join(
41
+ cwd,
42
+ ".gjc",
43
+ "state",
44
+ "sessions",
45
+ encodePathSegment(normalizedSessionId),
46
+ workflowModeStateFileName(skill),
47
+ );
48
+ }
49
+ return path.join(cwd, ".gjc", "state", workflowModeStateFileName(skill));
50
+ }
51
+
52
+ export function workflowActiveStatePath(cwd: string, sessionId?: string): string {
53
+ const normalizedSessionId = safeString(sessionId).trim();
54
+ if (normalizedSessionId) {
55
+ return path.join(
56
+ cwd,
57
+ ".gjc",
58
+ "state",
59
+ "sessions",
60
+ encodePathSegment(normalizedSessionId),
61
+ SKILL_ACTIVE_STATE_FILE,
62
+ );
63
+ }
64
+ return path.join(cwd, ".gjc", "state", SKILL_ACTIVE_STATE_FILE);
65
+ }
66
+
67
+ export function buildWorkflowStateReceipt(input: {
68
+ cwd: string;
69
+ skill: CanonicalGjcWorkflowSkill;
70
+ owner: WorkflowStateMutationOwner;
71
+ command: string;
72
+ sessionId?: string;
73
+ nowIso?: string;
74
+ mutationId?: string;
75
+ }): WorkflowStateReceipt {
76
+ const mutatedAt = input.nowIso ?? new Date().toISOString();
77
+ const freshUntil = new Date(Date.parse(mutatedAt) + WORKFLOW_STATE_RECEIPT_FRESH_MS).toISOString();
78
+ return {
79
+ version: WORKFLOW_STATE_RECEIPT_VERSION,
80
+ skill: input.skill,
81
+ owner: input.owner,
82
+ command: input.command,
83
+ state_path: workflowActiveStatePath(input.cwd, input.sessionId),
84
+ storage_path: workflowStateStoragePath(input.cwd, input.skill, input.sessionId),
85
+ mutated_at: mutatedAt,
86
+ fresh_until: freshUntil,
87
+ status: "fresh",
88
+ mutation_id: input.mutationId ?? `${input.skill}:${mutatedAt}`,
89
+ };
90
+ }
91
+
92
+ export function workflowReceiptStatus(
93
+ receipt: WorkflowStateReceipt | undefined,
94
+ nowMs = Date.now(),
95
+ ): WorkflowStateReceiptStatus | undefined {
96
+ if (!receipt) return undefined;
97
+ const freshUntilMs = Date.parse(receipt.fresh_until);
98
+ if (!Number.isFinite(freshUntilMs)) return "stale";
99
+ return nowMs <= freshUntilMs ? "fresh" : "stale";
100
+ }
101
+
102
+ export function canonicalWorkflowSkill(value: string): CanonicalGjcWorkflowSkill | null {
103
+ return (CANONICAL_GJC_WORKFLOW_SKILLS as readonly string[]).includes(value)
104
+ ? (value as CanonicalGjcWorkflowSkill)
105
+ : null;
106
+ }
107
+
108
+ export function sanctionedWorkflowStateCommand(skill: CanonicalGjcWorkflowSkill): string {
109
+ return `gjc state ${skill} write --input '<json>'`;
110
+ }
111
+
112
+ export function describeWorkflowStateContract(skill: CanonicalGjcWorkflowSkill): string[] {
113
+ return [
114
+ `Sanctioned mutation path: gjc state ${skill} read|write --input '<json>'`,
115
+ `Canonical active HUD state: .gjc/state/${SKILL_ACTIVE_STATE_FILE} and .gjc/state/sessions/<session>/${SKILL_ACTIVE_STATE_FILE}`,
116
+ `Skill mode state: .gjc/state/${workflowModeStateFileName(skill)} or .gjc/state/sessions/<session>/${workflowModeStateFileName(skill)}`,
117
+ "Receipts include version, skill, owner, command, state_path, storage_path, mutated_at, fresh_until, status, and mutation_id.",
118
+ "Receipts are fresh for 30 minutes; older receipts are stale and render as HUD warnings.",
119
+ "Planning artifacts under .gjc/specs/** and .gjc/plans/** remain writable outside the state command.",
120
+ ];
121
+ }
@@ -1,5 +1,9 @@
1
1
  import type { AvailableCommand } from "@agentclientprotocol/sdk";
2
- import { BUILTIN_SLASH_COMMANDS_INTERNAL, lookupBuiltinSlashCommand } from "./builtin-registry";
2
+ import {
3
+ BUILTIN_SLASH_COMMANDS_INTERNAL,
4
+ formatUnknownBuiltinSlashCommandDiagnostic,
5
+ lookupBuiltinSlashCommand,
6
+ } from "./builtin-registry";
3
7
  import { parseSlashCommand } from "./helpers/parse";
4
8
  import type { AcpBuiltinCommandRuntime, AcpBuiltinSlashCommandResult } from "./types";
5
9
 
@@ -39,7 +43,12 @@ export async function executeAcpBuiltinSlashCommand(
39
43
  const parsed = parseSlashCommand(text);
40
44
  if (!parsed) return false;
41
45
  const command = lookupBuiltinSlashCommand(parsed.name);
42
- if (!command?.handle) return false;
46
+ if (!command?.handle) {
47
+ const diagnostic = formatUnknownBuiltinSlashCommandDiagnostic(parsed.name);
48
+ if (!diagnostic) return false;
49
+ await runtime.output(diagnostic);
50
+ return { consumed: true };
51
+ }
43
52
  const result = await command.handle(parsed, runtime);
44
53
  if (result === undefined) return { consumed: true };
45
54
  return result;
@@ -202,17 +202,6 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
202
202
  runtime.ctx.editor.setText("");
203
203
  },
204
204
  },
205
- {
206
- name: "loop",
207
- description:
208
- "Toggle loop mode. While enabled, the next prompt you send re-submits after every yield. Esc cancels the current iteration; /loop again to disable.",
209
- inlineHint: "[count|duration]",
210
- allowArgs: true,
211
- handleTui: async (command, runtime) => {
212
- await runtime.ctx.handleLoopCommand(command.args);
213
- runtime.ctx.editor.setText("");
214
- },
215
- },
216
205
  {
217
206
  name: "goal",
218
207
  description: "Toggle goal mode (persistent autonomous objective for this session)",
@@ -222,7 +211,6 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
222
211
  { name: "pause", description: "Pause the current goal" },
223
212
  { name: "resume", description: "Resume a paused goal" },
224
213
  { name: "drop", description: "Drop the current goal" },
225
- { name: "budget", description: "Adjust the token budget", usage: "<N|off>" },
226
214
  ],
227
215
  inlineHint: "[objective]",
228
216
  allowArgs: true,
@@ -793,6 +781,30 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
793
781
  await runtime.ctx.handleCompactCommand(customInstructions);
794
782
  },
795
783
  },
784
+ {
785
+ name: "contribute-pr",
786
+ aliases: ["contribution-prep"],
787
+ description: "Dump redacted session context and spawn a fresh contribute-pr worker",
788
+ inlineHint: "[focus instructions]",
789
+ allowArgs: true,
790
+ handle: async (command, runtime) => {
791
+ const result = await runtime.session.prepareContributionPrep({
792
+ customInstructions: command.args || undefined,
793
+ spawnWorker: true,
794
+ });
795
+ await runtime.output(
796
+ [
797
+ "Contribution prep artifacts written.",
798
+ `Manifest: ${result.manifestPath}`,
799
+ `Worker prompt: ${result.workerPromptPath}`,
800
+ ].join("\n"),
801
+ );
802
+ return commandConsumed();
803
+ },
804
+ handleTui: async (command, runtime) => {
805
+ await runtime.ctx.handleContributionPrepCommand(command.args || undefined);
806
+ },
807
+ },
796
808
  {
797
809
  name: "resume",
798
810
  description: "Resume a different session",
@@ -993,6 +1005,15 @@ for (const command of ACTIVE_BUILTIN_SLASH_COMMAND_REGISTRY) {
993
1005
  }
994
1006
  }
995
1007
 
1008
+ export function formatUnknownBuiltinSlashCommandDiagnostic(commandName: string): string | undefined {
1009
+ if (commandName !== "provicer") return undefined;
1010
+ return [
1011
+ "Unknown slash command: /provicer.",
1012
+ "Did you mean /provider?",
1013
+ "Run: /provider add --compat <openai|anthropic> --provider <id> --base-url <url> --api-key-env <ENV> --model <model>",
1014
+ ].join("\n");
1015
+ }
1016
+
996
1017
  /** Builtin command metadata used for slash-command autocomplete and help text. */
997
1018
  export const BUILTIN_SLASH_COMMAND_DEFS: ReadonlyArray<BuiltinSlashCommand> = ACTIVE_BUILTIN_SLASH_COMMAND_REGISTRY.map(
998
1019
  command => ({
@@ -1025,7 +1046,13 @@ export async function executeBuiltinSlashCommand(
1025
1046
  if (!parsed) return false;
1026
1047
 
1027
1048
  const command = BUILTIN_SLASH_COMMAND_LOOKUP.get(parsed.name);
1028
- if (!command) return false;
1049
+ if (!command) {
1050
+ const diagnostic = formatUnknownBuiltinSlashCommandDiagnostic(parsed.name);
1051
+ if (!diagnostic) return false;
1052
+ runtime.ctx.showError(diagnostic);
1053
+ runtime.ctx.editor.setText("");
1054
+ return true;
1055
+ }
1029
1056
  if (parsed.args.length > 0 && !command.allowArgs) {
1030
1057
  return false;
1031
1058
  }
@@ -9,12 +9,8 @@ import { type SlashCommand, slashCommandCapability } from "../capability/slash-c
9
9
  import { loadCapability } from "../discovery";
10
10
  // Embed command markdown files at build time
11
11
  import initMd from "../prompts/agents/init.md" with { type: "text" };
12
- import orchestrateMd from "../prompts/commands/orchestrate.md" with { type: "text" };
13
12
 
14
- const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
15
- { name: "init.md", content: prompt.render(initMd) },
16
- { name: "orchestrate.md", content: prompt.render(orchestrateMd) },
17
- ];
13
+ const EMBEDDED_COMMANDS: { name: string; content: string }[] = [{ name: "init.md", content: prompt.render(initMd) }];
18
14
 
19
15
  export const EMBEDDED_COMMAND_TEMPLATES: ReadonlyArray<{ name: string; content: string }> = EMBEDDED_COMMANDS;
20
16
 
package/src/tools/gh.ts CHANGED
@@ -225,6 +225,10 @@ const RUN_SUCCESS_CONCLUSIONS = new Set(["success", "neutral", "skipped"]);
225
225
  const RUN_FAILURE_CONCLUSIONS = new Set(["failure", "timed_out", "cancelled", "action_required", "startup_failure"]);
226
226
  const JOB_FAILURE_CONCLUSIONS = new Set(["failure", "timed_out", "cancelled", "action_required"]);
227
227
 
228
+ const PR_CREATE_BASE_CONFIG_KEYS = ["github.prBase", "gh.prBase", "gjc.github.prBase"] as const;
229
+ const ISSUE_CLOSING_REFERENCE_PATTERN =
230
+ /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+(?:https:\/\/github\.com\/([^\s/]+\/[^\s/]+)\/issues\/)?#(\d+)\b/gi;
231
+
228
232
  const githubSchema = z
229
233
  .object({
230
234
  op: z
@@ -407,6 +411,23 @@ interface GhIssueViewData {
407
411
  updatedAt?: string;
408
412
  url?: string;
409
413
  }
414
+ interface GhPrListData {
415
+ baseRefName?: string;
416
+ headRefName?: string;
417
+ isDraft?: boolean;
418
+ number?: number;
419
+ state?: string;
420
+ title?: string;
421
+ url?: string;
422
+ }
423
+
424
+ interface PrCreateDuplicateCheck {
425
+ base: string;
426
+ head: string;
427
+ issue?: GhIssueViewData;
428
+ linkedPr?: GhPrListData;
429
+ pr?: GhPrListData;
430
+ }
410
431
 
411
432
  interface GhPrFile {
412
433
  path?: string;
@@ -659,6 +680,185 @@ function resolveSearchLimit(value: number | undefined): number {
659
680
 
660
681
  return Math.min(Math.floor(value), SEARCH_LIMIT_MAX);
661
682
  }
683
+ async function resolvePrCreateBase(
684
+ cwd: string,
685
+ explicitBase: string | undefined,
686
+ repo: string | undefined,
687
+ signal?: AbortSignal,
688
+ ): Promise<string | undefined> {
689
+ if (explicitBase) return explicitBase;
690
+ try {
691
+ for (const key of PR_CREATE_BASE_CONFIG_KEYS) {
692
+ const configured = normalizeOptionalString(await git.config.get(cwd, key, signal));
693
+ if (configured) return configured;
694
+ }
695
+ } catch {
696
+ // Repository config is optional for pr_create; prefer GitHub metadata when
697
+ // local git metadata is unavailable.
698
+ }
699
+ try {
700
+ const resolvedRepo = repo ?? (await resolveDefaultRepoMemoized(cwd, signal));
701
+ const repoView = await git.github.json<GhRepoViewData>(
702
+ cwd,
703
+ ["repo", "view", resolvedRepo, "--json", "defaultBranchRef"],
704
+ signal,
705
+ { repoProvided: true },
706
+ );
707
+ const defaultBranch = normalizeOptionalString(repoView.defaultBranchRef?.name);
708
+ if (defaultBranch) return defaultBranch;
709
+ } catch {
710
+ // Fall back to local git metadata below so pr_create can still work when gh
711
+ // cannot resolve repository metadata in otherwise-valid checkouts.
712
+ }
713
+ return (await git.branch.default(cwd, signal)) ?? undefined;
714
+ }
715
+
716
+ function normalizePrHead(value: string): string {
717
+ const separator = value.lastIndexOf(":");
718
+ return separator >= 0 ? value.slice(separator + 1) : value;
719
+ }
720
+
721
+ function extractClosingIssueReferences(body: string | undefined, repo: string | undefined): number[] {
722
+ if (!body) return [];
723
+ const issueNumbers: number[] = [];
724
+ ISSUE_CLOSING_REFERENCE_PATTERN.lastIndex = 0;
725
+ let match = ISSUE_CLOSING_REFERENCE_PATTERN.exec(body);
726
+ while (match !== null) {
727
+ const issueRepo = normalizeOptionalString(match[1]);
728
+ if (!issueRepo || !repo || issueRepo.toLowerCase() === repo.toLowerCase()) {
729
+ const issueNumber = Number(match[2]);
730
+ if (Number.isInteger(issueNumber) && issueNumber > 0) issueNumbers.push(issueNumber);
731
+ }
732
+ match = ISSUE_CLOSING_REFERENCE_PATTERN.exec(body);
733
+ }
734
+ return issueNumbers;
735
+ }
736
+
737
+ async function resolvePrCreateHead(
738
+ cwd: string,
739
+ explicitHead: string | undefined,
740
+ signal?: AbortSignal,
741
+ ): Promise<string | undefined> {
742
+ if (explicitHead) return explicitHead;
743
+ try {
744
+ return (await git.branch.current(cwd, signal)) ?? undefined;
745
+ } catch {
746
+ return undefined;
747
+ }
748
+ }
749
+
750
+ async function fetchPrCreateDuplicateCheck(
751
+ cwd: string,
752
+ repo: string | undefined,
753
+ base: string | undefined,
754
+ head: string | undefined,
755
+ body: string | undefined,
756
+ signal: AbortSignal | undefined,
757
+ ): Promise<PrCreateDuplicateCheck | undefined> {
758
+ if (!base || !head) return undefined;
759
+ const resolvedRepo = repo ?? (await resolveDefaultRepoMemoized(cwd, signal));
760
+ const normalizedHead = normalizePrHead(head);
761
+ const prs = await git.github.json<GhPrListData[]>(
762
+ cwd,
763
+ [
764
+ "pr",
765
+ "list",
766
+ "--repo",
767
+ resolvedRepo,
768
+ "--head",
769
+ normalizedHead,
770
+ "--base",
771
+ base,
772
+ "--state",
773
+ "all",
774
+ "--json",
775
+ "number,title,state,url,baseRefName,headRefName,isDraft",
776
+ ],
777
+ signal,
778
+ { repoProvided: true },
779
+ );
780
+ const existingPr = prs.find(pr => pr.headRefName === normalizedHead && pr.baseRefName === base) ?? prs[0];
781
+ const issueNumbers = existingPr ? [] : extractClosingIssueReferences(body, resolvedRepo);
782
+ let issue: GhIssueViewData | undefined;
783
+ let linkedPr: GhPrListData | undefined;
784
+ for (const issueNumber of issueNumbers) {
785
+ const candidateIssue = await git.github.json<GhIssueViewData>(
786
+ cwd,
787
+ [
788
+ "issue",
789
+ "view",
790
+ String(issueNumber),
791
+ "--repo",
792
+ resolvedRepo,
793
+ "--json",
794
+ GH_ISSUE_FIELDS_NO_COMMENTS.join(","),
795
+ ],
796
+ signal,
797
+ { repoProvided: true },
798
+ );
799
+ issue = candidateIssue;
800
+ if (candidateIssue.state && candidateIssue.state.toUpperCase() !== "OPEN") break;
801
+ const linkedPrs = await git.github.json<GhPrListData[]>(
802
+ cwd,
803
+ [
804
+ "pr",
805
+ "list",
806
+ "--repo",
807
+ resolvedRepo,
808
+ "--search",
809
+ `${issueNumber} linked:issue`,
810
+ "--state",
811
+ "open",
812
+ "--json",
813
+ "number,title,state,url,baseRefName,headRefName,isDraft",
814
+ ],
815
+ signal,
816
+ { repoProvided: true },
817
+ );
818
+ linkedPr = linkedPrs.find(pr => pr.headRefName !== normalizedHead) ?? linkedPrs[0];
819
+ if (linkedPr) break;
820
+ }
821
+ return { base, head: normalizedHead, issue, linkedPr, pr: existingPr };
822
+ }
823
+
824
+ function formatPrCreateExistingResult(check: PrCreateDuplicateCheck): string | undefined {
825
+ if (check.issue?.state && check.issue.state.toUpperCase() !== "OPEN") {
826
+ const lines = [
827
+ "# Pull Request Not Created",
828
+ "",
829
+ `Issue #${check.issue.number ?? ""} is ${check.issue.state.toLowerCase()}.`,
830
+ ];
831
+ pushLine(lines, "Issue", check.issue.url);
832
+ if (check.pr) pushLine(lines, "Existing PR", check.pr.url);
833
+ pushLine(lines, "Base", check.base);
834
+ pushLine(lines, "Head", check.head);
835
+ return lines.join("\n").trim();
836
+ }
837
+ if (check.linkedPr) {
838
+ const number = check.linkedPr.number !== undefined ? ` #${check.linkedPr.number}` : "";
839
+ const title = check.linkedPr.title ? `: ${check.linkedPr.title}` : "";
840
+ const lines = [`# Pull Request Already Linked${number}${title}`, ""];
841
+ pushLine(lines, "URL", check.linkedPr.url);
842
+ pushLine(lines, "Issue", check.issue?.url);
843
+ pushLine(lines, "State", check.linkedPr.state);
844
+ pushLine(lines, "Draft", check.linkedPr.isDraft);
845
+ pushLine(lines, "Base", check.linkedPr.baseRefName ?? check.base);
846
+ pushLine(lines, "Head", check.linkedPr.headRefName ?? check.head);
847
+ return lines.join("\n").trim();
848
+ }
849
+ if (check.pr) {
850
+ const number = check.pr.number !== undefined ? ` #${check.pr.number}` : "";
851
+ const title = check.pr.title ? `: ${check.pr.title}` : "";
852
+ const lines = [`# Pull Request Already Exists${number}${title}`, ""];
853
+ pushLine(lines, "URL", check.pr.url);
854
+ pushLine(lines, "State", check.pr.state);
855
+ pushLine(lines, "Draft", check.pr.isDraft);
856
+ pushLine(lines, "Base", check.pr.baseRefName ?? check.base);
857
+ pushLine(lines, "Head", check.pr.headRefName ?? check.head);
858
+ return lines.join("\n").trim();
859
+ }
860
+ return undefined;
861
+ }
662
862
 
663
863
  function resolveTailLimit(value: number | undefined): number {
664
864
  if (value === undefined) {
@@ -3117,8 +3317,8 @@ async function executePrCreate(
3117
3317
  const repo = normalizeOptionalString(params.repo);
3118
3318
  const title = normalizeOptionalString(params.title);
3119
3319
  const body = params.body;
3120
- const base = normalizeOptionalString(params.base);
3121
- const head = normalizeOptionalString(params.head);
3320
+ const requestedBase = normalizeOptionalString(params.base);
3321
+ const requestedHead = normalizeOptionalString(params.head);
3122
3322
  const draft = params.draft ?? false;
3123
3323
  const fill = params.fill ?? false;
3124
3324
  const reviewers = normalizePrIdentifierList(params.reviewer);
@@ -3131,6 +3331,16 @@ async function executePrCreate(
3131
3331
  if (fill && (title || body !== undefined)) {
3132
3332
  throw new ToolError("fill is mutually exclusive with title and body");
3133
3333
  }
3334
+ const base = await resolvePrCreateBase(session.cwd, requestedBase, repo, signal);
3335
+ const head = await resolvePrCreateHead(session.cwd, requestedHead, signal);
3336
+ const duplicateCheck = await fetchPrCreateDuplicateCheck(session.cwd, repo, base, head, body, signal);
3337
+ const existingText = duplicateCheck ? formatPrCreateExistingResult(duplicateCheck) : undefined;
3338
+ if (existingText) {
3339
+ return buildTextResult(
3340
+ existingText,
3341
+ duplicateCheck?.pr?.url ?? duplicateCheck?.linkedPr?.url ?? duplicateCheck?.issue?.url,
3342
+ );
3343
+ }
3134
3344
 
3135
3345
  const args = ["pr", "create"];
3136
3346
  appendRepoFlag(args, repo);
@@ -7,7 +7,7 @@ import { EditTool } from "../edit";
7
7
  import { checkPythonKernelAvailability } from "../eval/py/kernel";
8
8
  import type { Skill } from "../extensibility/skills";
9
9
  import type { GoalModeState, GoalRuntime } from "../goals";
10
- import { CreateGoalTool, GetGoalTool, GoalTool, UpdateGoalTool } from "../goals/tools/goal-tool";
10
+ import { GoalTool } from "../goals/tools/goal-tool";
11
11
  import type { HindsightSessionState } from "../hindsight/state";
12
12
  import { LspTool } from "../lsp";
13
13
  import type { PlanModeState } from "../plan-mode/state";
@@ -309,12 +309,9 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
309
309
  recall: HindsightRecallTool.createIf,
310
310
  reflect: HindsightReflectTool.createIf,
311
311
  goal: s => new GoalTool(s),
312
- get_goal: GetGoalTool.createIf,
313
- create_goal: CreateGoalTool.createIf,
314
- update_goal: UpdateGoalTool.createIf,
315
312
  };
316
313
 
317
- const GOAL_MODE_TOOL_NAMES = ["get_goal", "create_goal", "update_goal"] as const;
314
+ const GOAL_MODE_TOOL_NAMES = [] as const;
318
315
 
319
316
  export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
320
317
  yield: s => new YieldTool(s),
@@ -1,30 +0,0 @@
1
- import { type WorkflowHudSummary } from "../skill-state/active-state";
2
- export declare const WORKFLOW_HUD_PROTOCOL = "workflow-hud-summary-v1";
3
- export interface GjcRuntimeBridgeResult {
4
- status: number;
5
- error?: string;
6
- }
7
- export interface WorkflowHudBridgePayload {
8
- version: 1;
9
- skill: string;
10
- phase?: string;
11
- active?: boolean;
12
- session_id?: string;
13
- thread_id?: string;
14
- turn_id?: string;
15
- hud: WorkflowHudSummary;
16
- }
17
- export interface GjcRuntimeHudBridgeResult extends GjcRuntimeBridgeResult {
18
- hudPayload?: WorkflowHudBridgePayload;
19
- }
20
- export interface GjcRuntimeHudBridgeOptions {
21
- cwd?: string;
22
- env?: NodeJS.ProcessEnv;
23
- sidecarSkill: string;
24
- onHudPayload?: (payload: WorkflowHudBridgePayload) => Promise<void> | void;
25
- pollIntervalMs?: number;
26
- }
27
- export declare function normalizeWorkflowHudBridgePayload(raw: unknown, expectedSkill: string): WorkflowHudBridgePayload | null;
28
- export declare function runGjcRuntimeBridge(endpoint: string, args: string[], env?: NodeJS.ProcessEnv): GjcRuntimeBridgeResult;
29
- export declare function runGjcRuntimeBridgeWithHudSidecar(endpoint: string, args: string[], options: GjcRuntimeHudBridgeOptions): Promise<GjcRuntimeHudBridgeResult>;
30
- export declare function runBridgedRuntimeEndpoint(endpoint: string, args: string[]): Promise<void>;
@@ -1,7 +0,0 @@
1
- import { Command } from "@gajae-code/utils/cli";
2
- export default class Question extends Command {
3
- static description: string;
4
- static strict: boolean;
5
- static examples: string[];
6
- run(): Promise<void>;
7
- }
@@ -1,22 +0,0 @@
1
- export type LoopLimitConfig = {
2
- kind: "iterations";
3
- iterations: number;
4
- } | {
5
- kind: "duration";
6
- durationMs: number;
7
- };
8
- export type LoopLimitRuntime = {
9
- kind: "iterations";
10
- initial: number;
11
- remaining: number;
12
- } | {
13
- kind: "duration";
14
- durationMs: number;
15
- deadlineMs: number;
16
- };
17
- export declare function parseLoopLimitArgs(args: string): LoopLimitConfig | undefined | string;
18
- export declare function createLoopLimitRuntime(config: LoopLimitConfig | undefined, nowMs?: number): LoopLimitRuntime | undefined;
19
- export declare function consumeLoopLimitIteration(limit: LoopLimitRuntime | undefined, nowMs?: number): boolean;
20
- export declare function isLoopDurationExpired(limit: LoopLimitRuntime | undefined, nowMs?: number): boolean;
21
- export declare function describeLoopLimit(config: LoopLimitConfig): string;
22
- export declare function describeLoopLimitRuntime(limit: LoopLimitRuntime): string;