@gajae-code/coding-agent 0.2.3 → 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 (34) hide show
  1. package/CHANGELOG.md +17 -8600
  2. package/dist/types/cli/update-cli.d.ts +3 -0
  3. package/dist/types/config/settings-schema.d.ts +20 -0
  4. package/dist/types/defaults/gjc-defaults.d.ts +19 -6
  5. package/dist/types/modes/components/settings-selector.d.ts +4 -0
  6. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  7. package/dist/types/modes/interactive-mode.d.ts +1 -0
  8. package/dist/types/modes/theme/defaults/index.d.ts +126 -0
  9. package/dist/types/modes/theme/theme.d.ts +5 -0
  10. package/dist/types/modes/types.d.ts +1 -0
  11. package/package.json +7 -7
  12. package/src/cli/update-cli.ts +67 -16
  13. package/src/config/settings-schema.ts +22 -0
  14. package/src/defaults/gjc/skills/deep-interview/SKILL.md +39 -4
  15. package/src/defaults/gjc/skills/deep-interview/auto-answer-uncertain.md +37 -0
  16. package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +42 -0
  17. package/src/defaults/gjc/skills/ultragoal/SKILL.md +9 -6
  18. package/src/defaults/gjc-defaults.ts +68 -16
  19. package/src/gjc-runtime/deep-interview-runtime.ts +44 -0
  20. package/src/gjc-runtime/state-runtime.ts +3 -2
  21. package/src/goals/tools/goal-tool.ts +5 -1
  22. package/src/internal-urls/docs-index.generated.ts +4 -2
  23. package/src/memories/index.ts +5 -4
  24. package/src/modes/components/settings-selector.ts +25 -14
  25. package/src/modes/controllers/command-controller.ts +1 -1
  26. package/src/modes/controllers/selector-controller.ts +67 -0
  27. package/src/modes/interactive-mode.ts +14 -1
  28. package/src/modes/theme/defaults/blue-crab.json +126 -0
  29. package/src/modes/theme/defaults/index.ts +2 -0
  30. package/src/modes/theme/theme.ts +40 -1
  31. package/src/modes/types.ts +1 -0
  32. package/src/prompts/memories/unavailable.md +9 -0
  33. package/src/sdk.ts +4 -0
  34. package/src/slash-commands/builtin-registry.ts +11 -1
@@ -0,0 +1,37 @@
1
+ # Deep Interview Auto Answer: Uncertain User Opt-Out
2
+
3
+ You are a read-only architect helping the deep-interview workflow resolve one question after the user opted out, answered with uncertainty, or explicitly asked the agent to decide.
4
+
5
+ Inherited context is read-only background. Do not edit code, write files, mutate `.gjc/` state, run formatters, invoke workflow handoffs, or implement anything. Use only inherited context, the opted-out question, prior interview decisions, topology/ontology notes, confirmed constraints, and read-only repo/context inspection if available.
6
+
7
+ Keep the response compact enough to fit into ambiguity scoring.
8
+
9
+ ## Task
10
+
11
+ Provide one decisive answer the parent workflow can tentatively carry forward. Choose the most conservative answer that preserves user intent, avoids irreversible assumptions, and keeps the interview moving.
12
+
13
+ ## Response Shape
14
+
15
+ Respond with only this JSON object:
16
+
17
+ ```json
18
+ {
19
+ "status": "answered",
20
+ "answer": "One concise decisive answer phrased as the assumption Deep Interview should carry.",
21
+ "rationale": [
22
+ "Context or repo fact supporting the answer."
23
+ ],
24
+ "confidence": "high|medium|low",
25
+ "uncertainty": "Explicit remaining uncertainty, or null if negligible."
26
+ }
27
+ ```
28
+
29
+ Rules:
30
+ - `answer` must be non-empty and must not contradict confirmed user constraints.
31
+ - `rationale` must contain 2-4 bullets citing inherited context, confirmed constraints, or repo facts available in the prompt.
32
+ - `confidence` must be `high`, `medium`, or `low`.
33
+ - Use `uncertainty` whenever context is thin, ambiguous, or depends on a product choice the transcript has not settled.
34
+
35
+ ## Fallback
36
+
37
+ If inherited context is insufficient for a defensible decisive answer, do not guess. Return the safest reversible default if one exists, mark confidence `low`, set `uncertainty` to `Insufficient context for a reliable answer: <missing decision or evidence>`, and clearly identify what the user must confirm before execution approval.
@@ -0,0 +1,42 @@
1
+ # Deep Interview Auto Research: Greenfield
2
+
3
+ You are a read-only architect helping the deep-interview workflow evaluate one greenfield question tagged `research: true`.
4
+
5
+ Inherited context is read-only background. Do not edit code, write files, mutate `.gjc/` state, run formatters, invoke workflow handoffs, or implement anything. Use only inherited context, the tagged question, prior interview decisions, topology/ontology notes, confirmed constraints, and read-only repo/context inspection if available.
6
+
7
+ Keep the response compact enough to fit back into the parent interview prompt.
8
+
9
+ ## Task
10
+
11
+ Return 2-3 ranked candidate answers for the tagged greenfield question. Candidates must be concrete, mutually distinct, consistent with confirmed constraints, and useful as answer options or context for the next single Socratic question.
12
+
13
+ ## Response Shape
14
+
15
+ Respond with only this JSON object:
16
+
17
+ ```json
18
+ {
19
+ "status": "answered",
20
+ "candidates": [
21
+ {
22
+ "rank": 1,
23
+ "answer": "Concise candidate answer.",
24
+ "rationale": "Why this candidate fits the inherited context and confirmed constraints.",
25
+ "risks_or_tradeoffs": "Main risk, tradeoff, or caveat for this candidate.",
26
+ "confidence": "high|medium|low"
27
+ }
28
+ ],
29
+ "recommendation": "One sentence naming the strongest candidate and why it should be offered first.",
30
+ "follow_up_gap": "One sentence naming the remaining uncertainty the user should still confirm."
31
+ }
32
+ ```
33
+
34
+ Rules:
35
+ - `candidates` must contain 2 or 3 entries when context supports that many.
36
+ - `rank` starts at 1 and increases by 1.
37
+ - `confidence` must be `high`, `medium`, or `low`.
38
+ - Every rationale must cite inherited context, confirmed constraints, or repo facts available in the prompt.
39
+
40
+ ## Fallback
41
+
42
+ If inherited context is insufficient to produce at least two meaningful candidates, say so explicitly in `follow_up_gap`, return the best single defensible candidate only if one exists, mark confidence `low`, and name the missing context. Do not fabricate certainty.
@@ -11,7 +11,7 @@ Use when the user asks for `ultragoal`, `create-goals`, `complete-goals`, durabl
11
11
 
12
12
  ## Purpose
13
13
 
14
- `ultragoal` turns a brief into repo-native artifacts and then drives a GJC goal safely through the unified `goal` tool. New plans default to a stable pointer-style aggregate GJC goal for the whole durable plan in `.gjc/ultragoal/goals.json`, including later accepted/appended stories under the original brief constraints, while GJC tracks G001/G002 story progress in the ledger. Ultragoal does not call `/goal clear`; before multiple sequential ultragoal runs in one session/thread, manually run `/goal clear` in the UI so the previous completed aggregate goal does not block or confuse the next `goal({"op":"create"})`.
14
+ `ultragoal` turns a brief into repo-native artifacts and then drives a GJC goal safely through the unified `goal` tool. New plans default to a stable pointer-style aggregate GJC goal for the whole durable plan in `.gjc/ultragoal/goals.json`, including later accepted/appended stories under the original brief constraints, while GJC tracks G001/G002 story progress in the ledger. Ultragoal does not require any `/goal` slash-command between runs. For back-to-back ultragoal runs in one session/thread, call `goal({"op":"drop"})` only when `goal({"op":"get"})` still reports an active aggregate; then call `goal({"op":"create"})`. The goal tool stays armed across drop so the next create works in-session, and no slash-command cleanup exists or is required.
15
15
 
16
16
  - `.gjc/ultragoal/brief.md`
17
17
  - `.gjc/ultragoal/goals.json`
@@ -41,9 +41,12 @@ Use these exact goal-tool calls for the inline goal state:
41
41
  goal({"op":"get"})
42
42
  goal({"op":"create","objective":"<printed aggregate or per-story objective>"})
43
43
  goal({"op":"complete"})
44
+ goal({"op":"drop"})
45
+ goal({"op":"resume"})
44
46
  ```
47
+ `drop` clears the active goal without exiting goal mode; `resume` reactivates a paused goal.
45
48
 
46
- The unified `goal` tool shares the same session goal state as `/goal`; use `goal({"op":"get"})` snapshots inside Ultragoal for ledger reconciliation.
49
+ Use `goal({"op":"get"})` snapshots inside Ultragoal for ledger reconciliation. The unified `goal` tool is the only agent-facing surface for goal state; no `/goal` subcommand is required.
47
50
 
48
51
  ## Create goals
49
52
 
@@ -61,7 +64,7 @@ Loop until `gjc ultragoal status` reports all goals complete:
61
64
  1. Run `gjc ultragoal complete-goals`.
62
65
  2. Read the printed handoff.
63
66
  3. Call `goal({"op":"get"})`.
64
- 4. If no active GJC goal exists, call `goal({"op":"create","objective":"<printed payload objective>"})` with the printed payload. In aggregate mode, if the same aggregate objective is already active, continue the current GJC story without creating a new GJC goal.
67
+ 4. If no active GJC goal exists, call `goal({"op":"create","objective":"<printed payload objective>"})` with the printed payload. In aggregate mode, if the same aggregate objective is already active, continue the current GJC story without creating a new GJC goal. If `goal({"op":"get"})` shows a stale dropped goal (status `"dropped"`) and a new aggregate must start, no extra cleanup is needed — `goal({"op":"create"})` succeeds directly. If a previous aggregate is still active and you genuinely need a fresh start in the same session, call `goal({"op":"drop"})` first, then `goal({"op":"create"})`.
65
68
  5. Complete the current GJC story only.
66
69
  6. Run a completion audit against the story objective and real artifacts/tests.
67
70
  7. Before any `--status complete` checkpoint, run the mandatory final cleanup/review gate below. In aggregate mode, do **not** call `goal({"op":"complete"})` for intermediate stories; checkpoint each story with a fresh `goal({"op":"get"})` snapshot whose aggregate objective is still `active`. On the final story, use the same fresh active snapshot to create the final aggregate receipt first; only after that receipt exists may `goal({"op":"complete"})` run.
@@ -204,9 +207,9 @@ The skill tool then dispatches `/skill:ralplan` or `/skill:deep-interview` same-
204
207
 
205
208
  ## Constraints
206
209
 
207
- - The shell command cannot directly invoke interactive `/goal`; it emits a model-facing handoff for the active GJC agent.
208
- - Ultragoal intentionally does not invoke `/goal clear` or hidden `thread/goal/clear`; use only the unified goal-tool surface: `goal({"op":"get"})`, `goal({"op":"create"})`, and `goal({"op":"complete"})`.
209
- - After a completed aggregate ultragoal run, clear the goal manually with `/goal clear` before starting another ultragoal run in the same session/thread.
210
+ - The shell command emits a model-facing handoff for the active GJC agent; it does not invoke any `/goal` slash-command and the agent loop must not depend on any `/goal` subcommand.
211
+ - Use only the unified goal-tool surface from the agent loop: `goal({"op":"get"})`, `goal({"op":"create"})`, `goal({"op":"complete"})`, `goal({"op":"drop"})`, `goal({"op":"resume"})`. `drop` clears the active goal without exiting goal mode so the next `goal({"op":"create"})` works in-session. No slash-command cleanup exists or is required; Ultragoal never calls any `/goal` subcommand.
212
+ - For back-to-back ultragoal runs in the same session/thread, when `goal({"op":"get"})` still reports an active aggregate, call `goal({"op":"drop"})` before `goal({"op":"create"})`; when no active goal exists or the prior aggregate is already complete or dropped, call `goal({"op":"create"})` directly. The goal tool remains callable across drop; no slash-command cleanup exists or is required.
210
213
  - Never call `goal({"op":"create"})` when `goal({"op":"get"})` reports a different active goal.
211
214
  - Never call `goal({"op":"complete"})` unless the aggregate run or legacy per-story goal is actually complete.
212
215
  - In aggregate mode, intermediate and final story checkpoints require a matching `active` GJC goal snapshot; the final story checkpoint creates the final aggregate receipt before `goal({"op":"complete"})` may reconcile the inline goal state.
@@ -1,5 +1,9 @@
1
1
  import * as path from "node:path";
2
2
  import { getAgentDir, isEnoent, parseFrontmatter } from "@gajae-code/utils";
3
+ import autoAnswerUncertainFragment from "./gjc/skills/deep-interview/auto-answer-uncertain.md" with { type: "text" };
4
+ import autoResearchGreenfieldFragment from "./gjc/skills/deep-interview/auto-research-greenfield.md" with {
5
+ type: "text",
6
+ };
3
7
  import deepInterviewSkill from "./gjc/skills/deep-interview/SKILL.md" with { type: "text" };
4
8
  import ralplanSkill from "./gjc/skills/ralplan/SKILL.md" with { type: "text" };
5
9
  import teamSkill from "./gjc/skills/team/SKILL.md" with { type: "text" };
@@ -7,7 +11,7 @@ import ultragoalSkill from "./gjc/skills/ultragoal/SKILL.md" with { type: "text"
7
11
 
8
12
  export const DEFAULT_GJC_DEFINITION_NAMES = ["deep-interview", "ralplan", "team", "ultragoal"] as const;
9
13
  export type DefaultGjcDefinitionName = (typeof DEFAULT_GJC_DEFINITION_NAMES)[number];
10
- export type DefaultGjcDefinitionKind = "skill";
14
+ export type DefaultGjcDefinitionKind = "skill" | "skill-fragment";
11
15
  export type EmbeddedDefaultGjcSkill = {
12
16
  name: DefaultGjcDefinitionName;
13
17
  description: string;
@@ -19,25 +23,41 @@ export type EmbeddedDefaultGjcSkill = {
19
23
  };
20
24
  export type DefaultGjcInstallStatus = "different" | "matching" | "missing" | "skipped" | "written";
21
25
 
22
- export interface DefaultGjcDefinition {
23
- kind: DefaultGjcDefinitionKind;
26
+ export interface DefaultGjcSkillDefinition {
27
+ kind: "skill";
24
28
  name: DefaultGjcDefinitionName;
25
29
  relativePath: string;
26
30
  content: string;
27
31
  }
28
32
 
33
+ export interface DefaultGjcSkillFragmentDefinition {
34
+ kind: "skill-fragment";
35
+ parentSkillName: DefaultGjcDefinitionName;
36
+ relativePath: string;
37
+ content: string;
38
+ }
39
+
40
+ export type DefaultGjcDefinition = DefaultGjcSkillDefinition | DefaultGjcSkillFragmentDefinition;
41
+
29
42
  export interface InstallDefaultGjcDefinitionsOptions {
30
43
  check?: boolean;
31
44
  force?: boolean;
32
45
  targetRoot?: string;
33
46
  }
34
47
 
35
- export interface DefaultGjcDefinitionInstallFile {
36
- kind: DefaultGjcDefinitionKind;
37
- name: DefaultGjcDefinitionName;
38
- path: string;
39
- status: DefaultGjcInstallStatus;
40
- }
48
+ export type DefaultGjcDefinitionInstallFile =
49
+ | {
50
+ kind: "skill";
51
+ name: DefaultGjcDefinitionName;
52
+ path: string;
53
+ status: DefaultGjcInstallStatus;
54
+ }
55
+ | {
56
+ kind: "skill-fragment";
57
+ parentSkillName: DefaultGjcDefinitionName;
58
+ path: string;
59
+ status: DefaultGjcInstallStatus;
60
+ };
41
61
 
42
62
  export interface DefaultGjcDefinitionInstallResult {
43
63
  targetRoot: string;
@@ -60,6 +80,18 @@ const DEFAULT_GJC_DEFINITIONS: readonly DefaultGjcDefinition[] = [
60
80
  { kind: "skill", name: "ralplan", relativePath: "skills/ralplan/SKILL.md", content: ralplanSkill },
61
81
  { kind: "skill", name: "team", relativePath: "skills/team/SKILL.md", content: teamSkill },
62
82
  { kind: "skill", name: "ultragoal", relativePath: "skills/ultragoal/SKILL.md", content: ultragoalSkill },
83
+ {
84
+ kind: "skill-fragment",
85
+ parentSkillName: "deep-interview",
86
+ relativePath: "skill-fragments/deep-interview/auto-research-greenfield.md",
87
+ content: autoResearchGreenfieldFragment,
88
+ },
89
+ {
90
+ kind: "skill-fragment",
91
+ parentSkillName: "deep-interview",
92
+ relativePath: "skill-fragments/deep-interview/auto-answer-uncertain.md",
93
+ content: autoAnswerUncertainFragment,
94
+ },
63
95
  ];
64
96
 
65
97
  export function getDefaultGjcDefinitions(): readonly DefaultGjcDefinition[] {
@@ -70,8 +102,19 @@ export function getDefaultGjcAgentDefinitions(): readonly DefaultGjcDefinition[]
70
102
  return [];
71
103
  }
72
104
 
105
+ export function getEmbeddedDefaultGjcSkillFragments(
106
+ parentSkillName: DefaultGjcDefinitionName,
107
+ ): DefaultGjcSkillFragmentDefinition[] {
108
+ return DEFAULT_GJC_DEFINITIONS.filter(
109
+ (definition): definition is DefaultGjcSkillFragmentDefinition =>
110
+ definition.kind === "skill-fragment" && definition.parentSkillName === parentSkillName,
111
+ );
112
+ }
113
+
73
114
  export function getEmbeddedDefaultGjcSkills(): EmbeddedDefaultGjcSkill[] {
74
- return DEFAULT_GJC_DEFINITIONS.filter(definition => definition.kind === "skill").map(definition => {
115
+ return DEFAULT_GJC_DEFINITIONS.filter(
116
+ (definition): definition is DefaultGjcSkillDefinition => definition.kind === "skill",
117
+ ).map(definition => {
75
118
  const { frontmatter } = parseFrontmatter(definition.content, {
76
119
  source: `embedded:gjc/${definition.relativePath}`,
77
120
  level: "warn",
@@ -110,12 +153,21 @@ export async function installDefaultGjcDefinitions(
110
153
  status = "written";
111
154
  }
112
155
 
113
- files.push({
114
- kind: definition.kind,
115
- name: definition.name,
116
- path: destination,
117
- status,
118
- });
156
+ if (definition.kind === "skill") {
157
+ files.push({
158
+ kind: definition.kind,
159
+ name: definition.name,
160
+ path: destination,
161
+ status,
162
+ });
163
+ } else {
164
+ files.push({
165
+ kind: definition.kind,
166
+ parentSkillName: definition.parentSkillName,
167
+ path: destination,
168
+ status,
169
+ });
170
+ }
119
171
  }
120
172
 
121
173
  return summarizeInstallResult(targetRoot, files);
@@ -131,9 +131,17 @@ interface ResolvedDeepInterviewArgs {
131
131
  thresholdSource: string;
132
132
  sessionId?: string;
133
133
  idea: string;
134
+ language?: DeepInterviewLanguagePreference;
134
135
  json: boolean;
135
136
  }
136
137
 
138
+ interface DeepInterviewLanguagePreference {
139
+ code: "en" | "ko";
140
+ label: "English" | "Korean";
141
+ source: "explicit-user-request" | "initial-idea";
142
+ instruction: string;
143
+ }
144
+
137
145
  export interface ResolvedDeepInterviewSpecWriteArgs {
138
146
  stage: "final";
139
147
  slug: string;
@@ -205,6 +213,35 @@ async function resolveConfiguredAmbiguityThreshold(
205
213
  return await readSettingsAmbiguityThreshold(userSettings);
206
214
  }
207
215
 
216
+ function englishLanguagePreference(): DeepInterviewLanguagePreference {
217
+ return {
218
+ code: "en",
219
+ label: "English",
220
+ source: "explicit-user-request",
221
+ instruction:
222
+ "Ask every user-facing deep-interview question in English because the user explicitly requested English.",
223
+ };
224
+ }
225
+
226
+ function resolveDeepInterviewLanguagePreference(idea: string): DeepInterviewLanguagePreference | undefined {
227
+ if (/\b(?:answer|ask|respond|reply|write|use|speak)\s+(?:only\s+)?in\s+English\b/i.test(idea)) {
228
+ return englishLanguagePreference();
229
+ }
230
+ if (/(?:영어로|영문으로|영어\s*(?:질문|답변|응답)|English\s+only)/i.test(idea)) {
231
+ return englishLanguagePreference();
232
+ }
233
+ if (/\p{Script=Hangul}/u.test(idea)) {
234
+ return {
235
+ code: "ko",
236
+ label: "Korean",
237
+ source: "initial-idea",
238
+ instruction:
239
+ "Ask every user-facing deep-interview question in Korean unless the user explicitly requests another language.",
240
+ };
241
+ }
242
+ return undefined;
243
+ }
244
+
208
245
  function isDeepInterviewSpecWriteInvocation(args: readonly string[]): boolean {
209
246
  return hasFlag(args, "--write");
210
247
  }
@@ -327,6 +364,7 @@ async function resolveDeepInterviewArgs(args: readonly string[], cwd: string): P
327
364
  thresholdSource,
328
365
  sessionId,
329
366
  idea,
367
+ language: resolveDeepInterviewLanguagePreference(idea),
330
368
  json: hasFlag(args, "--json"),
331
369
  };
332
370
  }
@@ -405,6 +443,10 @@ async function seedDeepInterviewState(cwd: string, resolved: ResolvedDeepIntervi
405
443
  },
406
444
  updated_at: now,
407
445
  };
446
+ if (resolved.language) {
447
+ payload.language = resolved.language;
448
+ (payload.state as Record<string, unknown>).language = resolved.language;
449
+ }
408
450
  if (resolved.sessionId) payload.session_id = resolved.sessionId;
409
451
  await fs.writeFile(statePath, `${JSON.stringify(payload, null, 2)}\n`);
410
452
  return statePath;
@@ -469,6 +511,7 @@ async function handleSpecWrite(args: readonly string[], cwd: string): Promise<De
469
511
 
470
512
  const handoffArgs = ["handoff", "--mode", "deep-interview", "--to", "ralplan", "--json"];
471
513
  if (resolved.sessionId) handoffArgs.push("--session-id", resolved.sessionId);
514
+ else handoffArgs.push("--session-id", "");
472
515
  const handoffResult = await runNativeStateCommand(handoffArgs, cwd);
473
516
  if (handoffResult.status !== 0) {
474
517
  throw new DeepInterviewCommandError(
@@ -527,6 +570,7 @@ export async function runNativeDeepInterviewCommand(
527
570
  threshold: resolved.threshold,
528
571
  threshold_source: resolved.thresholdSource,
529
572
  idea: resolved.idea,
573
+ language: resolved.language,
530
574
  state_path: statePath,
531
575
  handoff: "Run `/skill:deep-interview` inside the GJC agent to drive the Socratic interview loop.",
532
576
  };
@@ -171,16 +171,17 @@ async function resolveSelectors(
171
171
  }
172
172
  if (mode) assertKnownMode(mode);
173
173
 
174
+ const explicitSessionId = flagValue(args, "--session-id");
174
175
  // Session-id resolution order: explicit --session-id flag, then payload
175
176
  // session_id, then GJC_SESSION_ID env var (set by AgentSession.sdk for
176
177
  // agent-initiated CLI invocations). The env-var default keeps shell
177
178
  // snippets in skill docs short while still routing writes/reads to the
178
179
  // caller's session-scoped state files.
179
- let sessionId = flagValue(args, "--session-id")?.trim() || undefined;
180
+ let sessionId = explicitSessionId !== undefined ? explicitSessionId.trim() || undefined : undefined;
180
181
  if (!sessionId && payload && typeof payload.session_id === "string") {
181
182
  sessionId = payload.session_id.trim() || undefined;
182
183
  }
183
- if (!sessionId) {
184
+ if (!sessionId && explicitSessionId === undefined) {
184
185
  const envSessionId = process.env.GJC_SESSION_ID?.trim();
185
186
  if (envSessionId) sessionId = envSessionId;
186
187
  }
@@ -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