@gajae-code/coding-agent 0.2.1 → 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 (101) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/dist/types/commands/contribution-prep.d.ts +18 -0
  3. package/dist/types/commands/session.d.ts +24 -0
  4. package/dist/types/config/model-registry.d.ts +2 -2
  5. package/dist/types/config/models-config-schema.d.ts +17 -9
  6. package/dist/types/config/settings-schema.d.ts +1 -24
  7. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +15 -0
  8. package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
  9. package/dist/types/gjc-runtime/launch-tmux.d.ts +12 -11
  10. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +25 -0
  11. package/dist/types/gjc-runtime/state-runtime.d.ts +13 -0
  12. package/dist/types/gjc-runtime/team-runtime.d.ts +37 -5
  13. package/dist/types/gjc-runtime/tmux-common.d.ts +41 -0
  14. package/dist/types/gjc-runtime/tmux-sessions.d.ts +17 -0
  15. package/dist/types/goals/runtime.d.ts +3 -9
  16. package/dist/types/goals/state.d.ts +3 -6
  17. package/dist/types/goals/tools/goal-tool.d.ts +1 -69
  18. package/dist/types/modes/components/status-line/types.d.ts +0 -3
  19. package/dist/types/modes/components/status-line.d.ts +0 -3
  20. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  21. package/dist/types/modes/interactive-mode.d.ts +1 -12
  22. package/dist/types/modes/theme/defaults/index.d.ts +0 -2
  23. package/dist/types/modes/theme/theme.d.ts +1 -2
  24. package/dist/types/modes/types.d.ts +1 -7
  25. package/dist/types/session/agent-session.d.ts +2 -0
  26. package/dist/types/session/contribution-prep.d.ts +47 -0
  27. package/dist/types/skill-state/active-state.d.ts +4 -0
  28. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +6 -1
  29. package/dist/types/skill-state/workflow-hud.d.ts +9 -4
  30. package/dist/types/skill-state/workflow-state-contract.d.ts +34 -0
  31. package/package.json +7 -7
  32. package/src/cli/args.ts +3 -2
  33. package/src/cli.ts +6 -1
  34. package/src/commands/contribution-prep.ts +41 -0
  35. package/src/commands/deep-interview.ts +6 -22
  36. package/src/commands/launch.ts +10 -1
  37. package/src/commands/ralplan.ts +10 -22
  38. package/src/commands/session.ts +150 -0
  39. package/src/commands/state.ts +14 -4
  40. package/src/commands/team.ts +23 -3
  41. package/src/config/model-registry.ts +10 -2
  42. package/src/config/models-config-schema.ts +120 -102
  43. package/src/config/settings-schema.ts +1 -25
  44. package/src/config.ts +1 -1
  45. package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -13
  46. package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -2
  47. package/src/defaults/gjc/skills/team/SKILL.md +29 -7
  48. package/src/defaults/gjc/skills/ultragoal/SKILL.md +23 -25
  49. package/src/eval/py/prelude.py +1 -1
  50. package/src/gjc-runtime/deep-interview-runtime.ts +279 -0
  51. package/src/gjc-runtime/goal-mode-request.ts +2 -19
  52. package/src/gjc-runtime/launch-tmux.ts +83 -43
  53. package/src/gjc-runtime/ralplan-runtime.ts +460 -0
  54. package/src/gjc-runtime/state-runtime.ts +562 -0
  55. package/src/gjc-runtime/team-runtime.ts +708 -52
  56. package/src/gjc-runtime/tmux-common.ts +119 -0
  57. package/src/gjc-runtime/tmux-sessions.ts +165 -0
  58. package/src/gjc-runtime/ultragoal-guard.ts +6 -3
  59. package/src/gjc-runtime/ultragoal-runtime.ts +5 -4
  60. package/src/goals/runtime.ts +38 -144
  61. package/src/goals/state.ts +36 -7
  62. package/src/goals/tools/goal-tool.ts +15 -172
  63. package/src/hooks/skill-state.ts +31 -12
  64. package/src/internal-urls/docs-index.generated.ts +4 -3
  65. package/src/modes/components/skill-hud/render.ts +4 -0
  66. package/src/modes/components/status-line/segments.ts +5 -16
  67. package/src/modes/components/status-line/types.ts +0 -3
  68. package/src/modes/components/status-line.ts +0 -6
  69. package/src/modes/controllers/command-controller.ts +25 -1
  70. package/src/modes/controllers/input-controller.ts +0 -15
  71. package/src/modes/interactive-mode.ts +18 -219
  72. package/src/modes/theme/defaults/dark-poimandres.json +0 -1
  73. package/src/modes/theme/defaults/light-poimandres.json +0 -1
  74. package/src/modes/theme/theme.ts +0 -6
  75. package/src/modes/types.ts +1 -7
  76. package/src/prompts/goals/goal-continuation.md +1 -4
  77. package/src/prompts/goals/goal-mode-active.md +3 -5
  78. package/src/prompts/system/system-prompt.md +5 -7
  79. package/src/prompts/tools/goal.md +4 -4
  80. package/src/sdk.ts +1 -1
  81. package/src/session/agent-session.ts +18 -0
  82. package/src/session/contribution-prep.ts +320 -0
  83. package/src/skill-state/active-state.ts +38 -0
  84. package/src/skill-state/deep-interview-mutation-guard.ts +88 -24
  85. package/src/skill-state/workflow-hud.ts +23 -5
  86. package/src/skill-state/workflow-state-contract.ts +121 -0
  87. package/src/slash-commands/builtin-registry.ts +24 -12
  88. package/src/task/commands.ts +1 -5
  89. package/src/tools/gh.ts +212 -2
  90. package/src/tools/index.ts +2 -5
  91. package/dist/types/commands/gjc-runtime-bridge.d.ts +0 -30
  92. package/dist/types/commands/question.d.ts +0 -7
  93. package/dist/types/modes/loop-limit.d.ts +0 -22
  94. package/src/commands/gjc-runtime-bridge.ts +0 -227
  95. package/src/commands/question.ts +0 -12
  96. package/src/modes/loop-limit.ts +0 -140
  97. package/src/prompts/commands/orchestrate.md +0 -49
  98. package/src/prompts/goals/goal-budget-limit.md +0 -16
  99. package/src/prompts/tools/create-goal.md +0 -3
  100. package/src/prompts/tools/get-goal.md +0 -3
  101. package/src/prompts/tools/update-goal.md +0 -3
@@ -5,14 +5,20 @@ import { LocalProtocolHandler, resolveLocalUrlToPath } from "../internal-urls/lo
5
5
  import { resolveToCwd } from "../tools/path-utils";
6
6
  import { ToolError } from "../tools/tool-errors";
7
7
  import { listActiveSkills, readVisibleSkillActiveState, type SkillActiveEntry } from "./active-state";
8
+ import {
9
+ type CanonicalGjcWorkflowSkill,
10
+ sanctionedWorkflowStateCommand,
11
+ workflowModeStateFileName,
12
+ } from "./workflow-state-contract";
8
13
 
9
14
  export const DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE =
10
- "Deep-interview is active; either continue interviewing with `ask`, or write/finalize the pending spec under `.gjc/specs/` / update state under `.gjc/state/`. Do not edit product code until explicit execution approval.";
15
+ "Deep-interview is active; continue interviewing with `ask`, write/finalize pending specs through the required GJC workflow CLI, or use an explicit force override. Direct `.gjc/` and product-code edits are blocked until explicit execution approval.";
16
+ export const WORKFLOW_STATE_MUTATION_BLOCK_MESSAGE =
17
+ "Workflow state JSON is runtime-owned. Use `gjc state <skill> read|write --input '<json>'` for deep-interview, ralplan, ultragoal, and team. Planning artifacts under `.gjc/specs/` and `.gjc/plans/` remain allowed.";
11
18
 
12
19
  const BLOCKED_TOOL_NAMES = new Set(["edit", "write", "ast_edit"]);
13
20
  const ARCHIVE_OR_SQLITE_BASE_RE = /^(.+?\.(?:tar\.gz|sqlite3|sqlite|db3|zip|tgz|tar|db))(?:$|:)/i;
14
21
  const INTERNAL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
15
- const GLOB_META_RE = /[*?[\]{}]/;
16
22
  const VIM_FILE_SWITCH_RE = /^\s*:(?:e|e!|edit|edit!)(?:\s+([^<\r\n]+))?(?:<CR>|\r|\n|$)/i;
17
23
 
18
24
  type ToolWithEditMode = AgentTool & {
@@ -26,6 +32,8 @@ export interface DeepInterviewMutationGuardInput {
26
32
  threadId?: string;
27
33
  tool: ToolWithEditMode;
28
34
  args: unknown;
35
+ forceOverride?: boolean;
36
+ enforceWorkflowState?: boolean;
29
37
  }
30
38
 
31
39
  interface ExtractedTargets {
@@ -38,6 +46,7 @@ export interface DeepInterviewMutationDecision {
38
46
  message?: string;
39
47
  targets: string[];
40
48
  reason?: string;
49
+ command?: string;
41
50
  }
42
51
 
43
52
  interface ModeState {
@@ -246,34 +255,67 @@ function resolveRawPath(cwd: string, rawPath: string): { absolutePath?: string;
246
255
  }
247
256
  }
248
257
 
249
- function isAllowlistedPath(cwd: string, rawPath: string): boolean {
258
+ function relativeGjcSegments(cwd: string, rawPath: string): string[] | null {
250
259
  const { absolutePath, unknown } = resolveRawPath(cwd, rawPath);
251
- if (unknown || !absolutePath) return false;
260
+ if (unknown || !absolutePath) return null;
252
261
  const relative = path.relative(path.resolve(cwd), path.resolve(absolutePath));
253
- if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) return false;
254
- const segments = normalizePosix(relative).split("/").filter(Boolean);
255
- return segments[0] === ".gjc" && (segments[1] === "specs" || segments[1] === "state");
262
+ if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) return null;
263
+ return normalizePosix(relative).split("/").filter(Boolean);
256
264
  }
257
265
 
258
- function allTargetsAllowlisted(cwd: string, targets: ExtractedTargets): boolean {
266
+ function blockedWorkflowStateSkill(cwd: string, rawPath: string): CanonicalGjcWorkflowSkill | null {
267
+ const segments = relativeGjcSegments(cwd, rawPath);
268
+ if (!segments || segments[0] !== ".gjc") return null;
269
+ if (segments[1] === "specs" || segments[1] === "plans") return null;
270
+ if (segments[1] !== "state") return null;
271
+ const fileName = segments.at(-1) ?? "";
272
+ for (const skillName of ["deep-interview", "ralplan", "ultragoal", "team"] as const) {
273
+ if (fileName === workflowModeStateFileName(skillName)) return skillName;
274
+ }
275
+ if (fileName === "skill-active-state.json") return "deep-interview";
276
+ return null;
277
+ }
278
+
279
+ function firstBlockedWorkflowStateSkill(cwd: string, targets: ExtractedTargets): CanonicalGjcWorkflowSkill | null {
280
+ for (const rawPath of targets.paths) {
281
+ const skill = blockedWorkflowStateSkill(cwd, rawPath);
282
+ if (skill) return skill;
283
+ }
284
+ return null;
285
+ }
286
+
287
+ function isGjcManagedPath(cwd: string, rawPath: string): boolean {
288
+ const segments = relativeGjcSegments(cwd, rawPath);
289
+ return segments?.[0] === ".gjc";
290
+ }
291
+
292
+ function isAllowlistedPath(cwd: string, rawPath: string): boolean {
293
+ const segments = relativeGjcSegments(cwd, rawPath);
294
+ if (!segments || segments[0] !== ".gjc") return false;
295
+ return segments[1] === "specs" || segments[1] === "plans";
296
+ }
297
+
298
+ function hasGjcManagedTarget(cwd: string, targets: ExtractedTargets): boolean {
259
299
  if (targets.unknown || targets.paths.length === 0) return false;
260
- return targets.paths.every(rawPath => {
261
- if (GLOB_META_RE.test(rawPath)) {
262
- return isAllowlistedPath(cwd, rawPath);
263
- }
264
- return isAllowlistedPath(cwd, rawPath);
265
- });
300
+ return targets.paths.some(rawPath => isGjcManagedPath(cwd, rawPath));
266
301
  }
267
302
 
303
+ function allTargetsAllowlisted(cwd: string, targets: ExtractedTargets): boolean {
304
+ return (
305
+ !targets.unknown && targets.paths.length > 0 && targets.paths.every(rawPath => isAllowlistedPath(cwd, rawPath))
306
+ );
307
+ }
268
308
  export async function assertDeepInterviewMutationRawPathsAllowed(input: {
269
309
  cwd: string;
270
310
  sessionId?: string;
271
311
  threadId?: string;
272
312
  rawPaths: string[];
313
+ forceOverride?: boolean;
273
314
  }): Promise<void> {
315
+ if (input.forceOverride) return;
274
316
  if (!(await isActiveDeepInterview(input.cwd, input.sessionId, input.threadId))) return;
275
317
  const targets: ExtractedTargets = { paths: input.rawPaths, unknown: input.rawPaths.length === 0 };
276
- if (!allTargetsAllowlisted(input.cwd, targets)) {
318
+ if (hasGjcManagedTarget(input.cwd, targets)) {
277
319
  throw new ToolError(DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE);
278
320
  }
279
321
  }
@@ -282,19 +324,41 @@ export async function getDeepInterviewMutationDecision(
282
324
  input: DeepInterviewMutationGuardInput,
283
325
  ): Promise<DeepInterviewMutationDecision> {
284
326
  if (!BLOCKED_TOOL_NAMES.has(input.tool.name)) return { blocked: false, targets: [] };
327
+ const targets = extractTargets(input.tool, input.args);
328
+ if (input.enforceWorkflowState !== false) {
329
+ const stateSkill = firstBlockedWorkflowStateSkill(input.cwd, targets);
330
+ if (stateSkill) {
331
+ const command = sanctionedWorkflowStateCommand(stateSkill);
332
+ return {
333
+ blocked: true,
334
+ message: `${WORKFLOW_STATE_MUTATION_BLOCK_MESSAGE}\nUse: ${command}`,
335
+ targets: targets.paths,
336
+ reason: "workflow-state-target",
337
+ command,
338
+ };
339
+ }
340
+ }
285
341
  if (!(await isActiveDeepInterview(input.cwd, input.sessionId, input.threadId))) {
286
342
  return { blocked: false, targets: [] };
287
343
  }
288
- const targets = extractTargets(input.tool, input.args);
289
- if (allTargetsAllowlisted(input.cwd, targets)) {
290
- return { blocked: false, targets: targets.paths };
344
+ if (input.forceOverride) return { blocked: false, targets: [] };
345
+ if (targets.unknown) {
346
+ return {
347
+ blocked: true,
348
+ message: DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE,
349
+ targets: targets.paths,
350
+ reason: "unknown-target",
351
+ };
352
+ }
353
+ if (hasGjcManagedTarget(input.cwd, targets) && !allTargetsAllowlisted(input.cwd, targets)) {
354
+ return {
355
+ blocked: true,
356
+ message: DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE,
357
+ targets: targets.paths,
358
+ reason: "gjc-managed-target",
359
+ };
291
360
  }
292
- return {
293
- blocked: true,
294
- message: DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE,
295
- targets: targets.paths,
296
- reason: targets.unknown ? "unknown-target" : "product-target",
297
- };
361
+ return { blocked: false, targets: targets.paths };
298
362
  }
299
363
 
300
364
  export async function assertDeepInterviewMutationAllowed(input: DeepInterviewMutationGuardInput): Promise<void> {
@@ -1,6 +1,12 @@
1
1
  import type { WorkflowHudChip, WorkflowHudSummary } from "./active-state";
2
2
 
3
- interface DeepInterviewHudState {
3
+ interface WorkflowGateHudState {
4
+ approvalStatus?: string;
5
+ blockedReason?: string;
6
+ nextAction?: string;
7
+ }
8
+
9
+ interface DeepInterviewHudState extends WorkflowGateHudState {
4
10
  phase?: string;
5
11
  ambiguity?: number;
6
12
  threshold?: number;
@@ -11,7 +17,7 @@ interface DeepInterviewHudState {
11
17
  updatedAt?: string;
12
18
  }
13
19
 
14
- interface RalplanHudState {
20
+ interface RalplanHudState extends WorkflowGateHudState {
15
21
  stage?: string;
16
22
  waiting?: string;
17
23
  iteration?: number;
@@ -27,7 +33,7 @@ interface UltragoalLikeGoal {
27
33
  status: string;
28
34
  }
29
35
 
30
- interface UltragoalHudState {
36
+ interface UltragoalHudState extends WorkflowGateHudState {
31
37
  status: string;
32
38
  currentGoal?: UltragoalLikeGoal;
33
39
  counts: Record<string, number>;
@@ -41,7 +47,7 @@ interface TeamHudWorker {
41
47
  status?: string;
42
48
  }
43
49
 
44
- interface TeamHudState {
50
+ interface TeamHudState extends WorkflowGateHudState {
45
51
  phase: string;
46
52
  task_total: number;
47
53
  task_counts: Record<string, number>;
@@ -66,6 +72,14 @@ function chip(
66
72
  return { label, value, priority, ...(severity ? { severity } : {}) };
67
73
  }
68
74
 
75
+ function gateChips(state: WorkflowGateHudState, gatePriority: number): Array<WorkflowHudChip | null> {
76
+ return [
77
+ chip("gate", state.approvalStatus, gatePriority, state.approvalStatus === "approved" ? "success" : "warning"),
78
+ chip("blocked", state.blockedReason, gatePriority + 10, "blocked"),
79
+ chip("next", state.nextAction, gatePriority + 20),
80
+ ];
81
+ }
82
+
69
83
  function compactChips(chips: Array<WorkflowHudChip | null>): WorkflowHudChip[] {
70
84
  return chips.filter((item): item is WorkflowHudChip => item !== null);
71
85
  }
@@ -74,6 +88,7 @@ export function buildDeepInterviewHudSummary(state: DeepInterviewHudState): Work
74
88
  return {
75
89
  version: 1,
76
90
  chips: compactChips([
91
+ ...gateChips(state, 5),
77
92
  chip("phase", state.phase, 10),
78
93
  chip("ambiguity", [percent(state.ambiguity), percent(state.threshold)].filter(Boolean).join("/"), 20),
79
94
  chip("round", state.roundCount === undefined ? undefined : String(state.roundCount), 30),
@@ -100,6 +115,7 @@ export function buildRalplanHudSummary(state: RalplanHudState): WorkflowHudSumma
100
115
  summary: state.latestSummary,
101
116
  chips: compactChips([
102
117
  state.pendingApproval ? { label: "pending", value: "approval", priority: 5, severity: "warning" } : null,
118
+ ...gateChips(state, 6),
103
119
  chip("stage", state.stage, 10),
104
120
  chip("waiting", state.waiting, 20),
105
121
  chip("iter", state.iteration === undefined ? undefined : String(state.iteration), 30),
@@ -120,6 +136,7 @@ export function buildUltragoalHudSummary(state: UltragoalHudState): WorkflowHudS
120
136
  chip("goals", `${complete}/${total}`, 10),
121
137
  chip("current", state.currentGoal ? `${state.currentGoal.id}:${state.currentGoal.title}` : state.status, 20),
122
138
  chip("status", state.status, 30, state.status === "complete" ? "success" : undefined),
139
+ ...gateChips(state, 40),
123
140
  ]),
124
141
  details: state.latestLedgerEvent
125
142
  ? compactChips([
@@ -153,7 +170,8 @@ export function buildTeamHudSummary(state: TeamHudState): WorkflowHudSummary {
153
170
  chip("phase", state.phase, 10),
154
171
  chip("workers", `${state.workers.length - failedWorkers}/${state.workers.length}`, 20),
155
172
  chip("tasks", `${completed}/${state.task_total}`, 30),
156
- chip("latest", latest, 50),
173
+ ...gateChips(state, 40),
174
+ chip("latest", latest, 70),
157
175
  ]),
158
176
  ...(state.updated_at ? { updated_at: state.updated_at } : {}),
159
177
  };
@@ -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
+ }
@@ -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",
@@ -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