@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.
- package/CHANGELOG.md +31 -1
- package/dist/types/commands/contribution-prep.d.ts +18 -0
- package/dist/types/commands/session.d.ts +24 -0
- package/dist/types/config/model-registry.d.ts +2 -2
- package/dist/types/config/models-config-schema.d.ts +17 -9
- package/dist/types/config/settings-schema.d.ts +1 -24
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +15 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +12 -11
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +13 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +37 -5
- package/dist/types/gjc-runtime/tmux-common.d.ts +41 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +17 -0
- package/dist/types/goals/runtime.d.ts +3 -9
- package/dist/types/goals/state.d.ts +3 -6
- package/dist/types/goals/tools/goal-tool.d.ts +1 -69
- package/dist/types/modes/components/status-line/types.d.ts +0 -3
- package/dist/types/modes/components/status-line.d.ts +0 -3
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -12
- package/dist/types/modes/theme/defaults/index.d.ts +0 -2
- package/dist/types/modes/theme/theme.d.ts +1 -2
- package/dist/types/modes/types.d.ts +1 -7
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/contribution-prep.d.ts +47 -0
- package/dist/types/skill-state/active-state.d.ts +4 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +6 -1
- package/dist/types/skill-state/workflow-hud.d.ts +9 -4
- package/dist/types/skill-state/workflow-state-contract.d.ts +34 -0
- package/package.json +7 -7
- package/src/cli/args.ts +3 -2
- package/src/cli.ts +6 -1
- package/src/commands/contribution-prep.ts +41 -0
- package/src/commands/deep-interview.ts +6 -22
- package/src/commands/launch.ts +10 -1
- package/src/commands/ralplan.ts +10 -22
- package/src/commands/session.ts +150 -0
- package/src/commands/state.ts +14 -4
- package/src/commands/team.ts +23 -3
- package/src/config/model-registry.ts +10 -2
- package/src/config/models-config-schema.ts +120 -102
- package/src/config/settings-schema.ts +1 -25
- package/src/config.ts +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -13
- package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -2
- package/src/defaults/gjc/skills/team/SKILL.md +29 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +23 -25
- package/src/eval/py/prelude.py +1 -1
- package/src/gjc-runtime/deep-interview-runtime.ts +279 -0
- package/src/gjc-runtime/goal-mode-request.ts +2 -19
- package/src/gjc-runtime/launch-tmux.ts +83 -43
- package/src/gjc-runtime/ralplan-runtime.ts +460 -0
- package/src/gjc-runtime/state-runtime.ts +562 -0
- package/src/gjc-runtime/team-runtime.ts +708 -52
- package/src/gjc-runtime/tmux-common.ts +119 -0
- package/src/gjc-runtime/tmux-sessions.ts +165 -0
- package/src/gjc-runtime/ultragoal-guard.ts +6 -3
- package/src/gjc-runtime/ultragoal-runtime.ts +5 -4
- package/src/goals/runtime.ts +38 -144
- package/src/goals/state.ts +36 -7
- package/src/goals/tools/goal-tool.ts +15 -172
- package/src/hooks/skill-state.ts +31 -12
- package/src/internal-urls/docs-index.generated.ts +4 -3
- package/src/modes/components/skill-hud/render.ts +4 -0
- package/src/modes/components/status-line/segments.ts +5 -16
- package/src/modes/components/status-line/types.ts +0 -3
- package/src/modes/components/status-line.ts +0 -6
- package/src/modes/controllers/command-controller.ts +25 -1
- package/src/modes/controllers/input-controller.ts +0 -15
- package/src/modes/interactive-mode.ts +18 -219
- package/src/modes/theme/defaults/dark-poimandres.json +0 -1
- package/src/modes/theme/defaults/light-poimandres.json +0 -1
- package/src/modes/theme/theme.ts +0 -6
- package/src/modes/types.ts +1 -7
- package/src/prompts/goals/goal-continuation.md +1 -4
- package/src/prompts/goals/goal-mode-active.md +3 -5
- package/src/prompts/system/system-prompt.md +5 -7
- package/src/prompts/tools/goal.md +4 -4
- package/src/sdk.ts +1 -1
- package/src/session/agent-session.ts +18 -0
- package/src/session/contribution-prep.ts +320 -0
- package/src/skill-state/active-state.ts +38 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +88 -24
- package/src/skill-state/workflow-hud.ts +23 -5
- package/src/skill-state/workflow-state-contract.ts +121 -0
- package/src/slash-commands/builtin-registry.ts +24 -12
- package/src/task/commands.ts +1 -5
- package/src/tools/gh.ts +212 -2
- package/src/tools/index.ts +2 -5
- package/dist/types/commands/gjc-runtime-bridge.d.ts +0 -30
- package/dist/types/commands/question.d.ts +0 -7
- package/dist/types/modes/loop-limit.d.ts +0 -22
- package/src/commands/gjc-runtime-bridge.ts +0 -227
- package/src/commands/question.ts +0 -12
- package/src/modes/loop-limit.ts +0 -140
- package/src/prompts/commands/orchestrate.md +0 -49
- package/src/prompts/goals/goal-budget-limit.md +0 -16
- package/src/prompts/tools/create-goal.md +0 -3
- package/src/prompts/tools/get-goal.md +0 -3
- 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;
|
|
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
|
|
258
|
+
function relativeGjcSegments(cwd: string, rawPath: string): string[] | null {
|
|
250
259
|
const { absolutePath, unknown } = resolveRawPath(cwd, rawPath);
|
|
251
|
-
if (unknown || !absolutePath) return
|
|
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
|
|
254
|
-
|
|
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
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
289
|
-
if (
|
|
290
|
-
return {
|
|
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
|
|
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
|
-
|
|
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",
|
package/src/task/commands.ts
CHANGED
|
@@ -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
|
|