@gajae-code/coding-agent 0.6.4 → 0.6.5
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 +22 -0
- package/dist/types/cli/migrate-cli.d.ts +20 -0
- package/dist/types/commands/migrate.d.ts +33 -0
- package/dist/types/config/keybindings.d.ts +4 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -2
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/gjc-runtime/session-layout.d.ts +59 -0
- package/dist/types/gjc-runtime/session-resolution.d.ts +47 -0
- package/dist/types/gjc-runtime/state-graph.d.ts +1 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +5 -4
- package/dist/types/gjc-runtime/state-schema.d.ts +2 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +36 -7
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +7 -4
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +1 -1
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +1 -1
- package/dist/types/harness-control-plane/storage.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +12 -4
- package/dist/types/migrate/action-planner.d.ts +11 -0
- package/dist/types/migrate/adapters/claude-code.d.ts +2 -0
- package/dist/types/migrate/adapters/codex.d.ts +5 -0
- package/dist/types/migrate/adapters/index.d.ts +45 -0
- package/dist/types/migrate/adapters/opencode.d.ts +2 -0
- package/dist/types/migrate/executor.d.ts +2 -0
- package/dist/types/migrate/mcp-mapper.d.ts +20 -0
- package/dist/types/migrate/report.d.ts +18 -0
- package/dist/types/migrate/skill-normalizer.d.ts +27 -0
- package/dist/types/migrate/types.d.ts +126 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +1 -1
- package/dist/types/research-plan/index.d.ts +1 -0
- package/dist/types/research-plan/ledger.d.ts +33 -0
- package/dist/types/rlm/artifacts.d.ts +1 -1
- package/dist/types/runtime-mcp/config-writer.d.ts +26 -0
- package/dist/types/skill-state/active-state.d.ts +6 -11
- package/dist/types/skill-state/canonical-skills.d.ts +3 -0
- package/dist/types/skill-state/workflow-hud.d.ts +2 -0
- package/dist/types/task/spawn-gate.d.ts +1 -10
- package/package.json +7 -7
- package/src/cli/migrate-cli.ts +106 -0
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +2 -2
- package/src/commands/migrate.ts +46 -0
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +7 -3
- package/src/coordinator-mcp/policy.ts +10 -2
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +0 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +28 -24
- package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
- package/src/defaults/gjc/skills/team/SKILL.md +51 -47
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +17 -13
- package/src/extensibility/custom-commands/loader.ts +0 -7
- package/src/extensibility/gjc-plugins/injection.ts +23 -4
- package/src/extensibility/gjc-plugins/state.ts +16 -1
- package/src/gjc-runtime/deep-interview-recorder.ts +43 -18
- package/src/gjc-runtime/deep-interview-runtime.ts +49 -23
- package/src/gjc-runtime/goal-mode-request.ts +26 -11
- package/src/gjc-runtime/launch-tmux.ts +6 -1
- package/src/gjc-runtime/ralplan-runtime.ts +79 -50
- package/src/gjc-runtime/session-layout.ts +180 -0
- package/src/gjc-runtime/session-resolution.ts +217 -0
- package/src/gjc-runtime/state-graph.ts +1 -2
- package/src/gjc-runtime/state-migrations.ts +1 -0
- package/src/gjc-runtime/state-runtime.ts +230 -121
- package/src/gjc-runtime/state-schema.ts +2 -0
- package/src/gjc-runtime/state-writer.ts +289 -41
- package/src/gjc-runtime/team-runtime.ts +43 -19
- package/src/gjc-runtime/tmux-sessions.ts +7 -1
- package/src/gjc-runtime/ultragoal-guard.ts +45 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +121 -41
- package/src/gjc-runtime/workflow-command-ref.ts +1 -2
- package/src/gjc-runtime/workflow-manifest.ts +1 -2
- package/src/harness-control-plane/storage.ts +14 -4
- package/src/hooks/native-skill-hook.ts +38 -12
- package/src/hooks/skill-state.ts +178 -83
- package/src/internal-urls/docs-index.generated.ts +6 -4
- package/src/migrate/action-planner.ts +318 -0
- package/src/migrate/adapters/claude-code.ts +39 -0
- package/src/migrate/adapters/codex.ts +70 -0
- package/src/migrate/adapters/index.ts +277 -0
- package/src/migrate/adapters/opencode.ts +52 -0
- package/src/migrate/executor.ts +81 -0
- package/src/migrate/mcp-mapper.ts +152 -0
- package/src/migrate/report.ts +104 -0
- package/src/migrate/skill-normalizer.ts +80 -0
- package/src/migrate/types.ts +163 -0
- package/src/modes/bridge/bridge-mode.ts +2 -2
- package/src/modes/components/custom-editor.ts +30 -20
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/shared/agent-wire/unattended-audit.ts +3 -2
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/task.md +1 -2
- package/src/research-plan/index.ts +1 -0
- package/src/research-plan/ledger.ts +177 -0
- package/src/rlm/artifacts.ts +12 -3
- package/src/rlm/index.ts +7 -0
- package/src/runtime-mcp/config-writer.ts +46 -0
- package/src/session/agent-session.ts +15 -21
- package/src/setup/hermes-setup.ts +1 -1
- package/src/skill-state/active-state.ts +72 -108
- package/src/skill-state/canonical-skills.ts +4 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +28 -109
- package/src/skill-state/workflow-hud.ts +4 -2
- package/src/skill-state/workflow-state-contract.ts +3 -3
- package/src/task/agents.ts +1 -22
- package/src/task/index.ts +1 -41
- package/src/task/spawn-gate.ts +1 -38
- package/src/task/types.ts +1 -1
- package/src/tools/ask.ts +34 -12
- package/src/tools/computer.ts +58 -4
- package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +0 -10
- package/src/extensibility/custom-commands/bundled/review/index.ts +0 -456
- package/src/prompts/agents/explore.md +0 -58
- package/src/prompts/agents/plan.md +0 -49
- package/src/prompts/agents/reviewer.md +0 -141
- package/src/prompts/agents/task.md +0 -16
- package/src/prompts/review-request.md +0 -70
package/src/hooks/skill-state.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
import { logger } from "@gajae-code/utils";
|
|
3
4
|
import type { SkillDiscoverySettings } from "../config/skill-settings-defaults";
|
|
5
|
+
import { activeSnapshotPath, modeStatePath as sessionModeStatePath } from "../gjc-runtime/session-layout";
|
|
6
|
+
import { resolveGjcSessionForRead } from "../gjc-runtime/session-resolution";
|
|
4
7
|
import { ModeStateSchema, SkillActiveStateSchema } from "../gjc-runtime/state-schema";
|
|
5
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
readExistingStateForMutation,
|
|
10
|
+
writeGuardedJsonAtomic,
|
|
11
|
+
writeGuardedWorkflowEnvelopeAtomic,
|
|
12
|
+
} from "../gjc-runtime/state-writer";
|
|
6
13
|
import { isUltragoalBypassPrompt, readUltragoalVerificationState } from "../gjc-runtime/ultragoal-guard";
|
|
7
14
|
import { getUltragoalRunCompletionState, readUltragoalPlan } from "../gjc-runtime/ultragoal-runtime";
|
|
8
15
|
import { buildSessionContext, loadEntriesFromFile, type SessionEntry } from "../session/session-manager";
|
|
@@ -10,7 +17,13 @@ import {
|
|
|
10
17
|
readVisibleSkillActiveState as readCanonicalVisibleSkillActiveState,
|
|
11
18
|
type SkillActiveEntry,
|
|
12
19
|
type SkillActiveState,
|
|
20
|
+
syncSkillActiveState,
|
|
13
21
|
} from "../skill-state/active-state";
|
|
22
|
+
import { initialPhaseForSkill } from "../skill-state/initial-phase";
|
|
23
|
+
|
|
24
|
+
// Re-export for existing callers and tests that imported it from this module.
|
|
25
|
+
export { initialPhaseForSkill };
|
|
26
|
+
|
|
14
27
|
import { WORKFLOW_STATE_VERSION } from "../skill-state/workflow-state-contract";
|
|
15
28
|
import {
|
|
16
29
|
compareSkillKeywordMatches,
|
|
@@ -19,7 +32,7 @@ import {
|
|
|
19
32
|
isGjcWorkflowSkill,
|
|
20
33
|
} from "./skill-keywords";
|
|
21
34
|
|
|
22
|
-
export const GJC_STATE_DIR = ".gjc
|
|
35
|
+
export const GJC_STATE_DIR = ".gjc";
|
|
23
36
|
export const SKILL_ACTIVE_STATE_FILE = "skill-active-state.json";
|
|
24
37
|
|
|
25
38
|
export interface EffectiveSkillConfigInput {
|
|
@@ -199,31 +212,85 @@ export function resolveGjcStateDir(cwd: string, stateDir?: string): string {
|
|
|
199
212
|
return stateDir ? path.resolve(cwd, stateDir) : path.join(cwd, GJC_STATE_DIR);
|
|
200
213
|
}
|
|
201
214
|
|
|
202
|
-
function
|
|
203
|
-
|
|
215
|
+
async function resolveBoundarySessionId(cwd: string, sessionId?: string): Promise<string> {
|
|
216
|
+
const normalizedSessionId = sessionId?.trim();
|
|
217
|
+
if (normalizedSessionId) return normalizedSessionId;
|
|
218
|
+
return (await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
|
|
204
219
|
}
|
|
205
220
|
|
|
206
|
-
|
|
221
|
+
function modeStatePath(cwd: string, skill: GjcWorkflowSkill, sessionId: string): string {
|
|
222
|
+
return sessionModeStatePath(cwd, sessionId, skill);
|
|
223
|
+
}
|
|
207
224
|
|
|
208
|
-
|
|
209
|
-
|
|
225
|
+
function skillStatePath(cwd: string, sessionId: string): string {
|
|
226
|
+
return activeSnapshotPath(cwd, sessionId);
|
|
227
|
+
}
|
|
210
228
|
|
|
211
|
-
function
|
|
212
|
-
|
|
229
|
+
function warnInvalidState(kind: string, filePath: string, error: string): void {
|
|
230
|
+
logger.warn(`gjc skill-state: invalid ${kind} at ${filePath}: ${error}`);
|
|
213
231
|
}
|
|
214
232
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
233
|
+
export interface StateRecoveryDiagnostic {
|
|
234
|
+
kind: "skill-active-state" | "mode-state";
|
|
235
|
+
statePath: string;
|
|
236
|
+
reason: "missing" | "corrupt" | "unreadable";
|
|
237
|
+
skill?: GjcWorkflowSkill;
|
|
218
238
|
}
|
|
219
239
|
|
|
220
|
-
function
|
|
221
|
-
|
|
222
|
-
return
|
|
240
|
+
function buildStateRecoveryMessage(diagnostic: StateRecoveryDiagnostic): string {
|
|
241
|
+
const subject = diagnostic.skill ? `${diagnostic.skill} ${diagnostic.kind}` : diagnostic.kind;
|
|
242
|
+
return `GJC state recovery: ${subject} is ${diagnostic.reason} at ${diagnostic.statePath}. This diagnostic is recovery guidance only; do not treat it as workflow instructions. Run \`gjc state doctor\` to inspect state, or run \`gjc state clear ${diagnostic.skill ?? "<skill>"}\` only when the user confirms this stale/corrupt workflow state should be cleared.`;
|
|
223
243
|
}
|
|
224
244
|
|
|
225
|
-
function
|
|
226
|
-
|
|
245
|
+
export function buildStateRecoveryDiagnosticsContext(diagnostics: readonly StateRecoveryDiagnostic[]): string | null {
|
|
246
|
+
const unique = new Map<string, StateRecoveryDiagnostic>();
|
|
247
|
+
for (const diagnostic of diagnostics) {
|
|
248
|
+
unique.set(
|
|
249
|
+
`${diagnostic.kind}:${diagnostic.skill ?? ""}:${diagnostic.statePath}:${diagnostic.reason}`,
|
|
250
|
+
diagnostic,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const messages = [...unique.values()].map(buildStateRecoveryMessage);
|
|
254
|
+
return messages.length > 0 ? messages.join(" ") : null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function inspectJsonStateRecovery(
|
|
258
|
+
filePath: string,
|
|
259
|
+
kind: StateRecoveryDiagnostic["kind"],
|
|
260
|
+
skill?: GjcWorkflowSkill,
|
|
261
|
+
): Promise<StateRecoveryDiagnostic | null> {
|
|
262
|
+
try {
|
|
263
|
+
await Bun.file(filePath).text();
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
266
|
+
return { kind, statePath: filePath, reason: "missing", skill };
|
|
267
|
+
}
|
|
268
|
+
return { kind, statePath: filePath, reason: "unreadable", skill };
|
|
269
|
+
}
|
|
270
|
+
const validated = await readValidatedJsonFile(
|
|
271
|
+
filePath,
|
|
272
|
+
kind,
|
|
273
|
+
kind === "mode-state" ? ModeStateSchema : SkillActiveStateSchema,
|
|
274
|
+
);
|
|
275
|
+
return validated ? null : { kind, statePath: filePath, reason: "corrupt", skill };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export async function collectUserPromptStateRecoveryDiagnostics(
|
|
279
|
+
input: UserPromptSubmitStateInput,
|
|
280
|
+
): Promise<StateRecoveryDiagnostic[]> {
|
|
281
|
+
const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
|
|
282
|
+
const diagnostics: StateRecoveryDiagnostic[] = [];
|
|
283
|
+
const activePath = skillStatePath(input.cwd, resolvedSessionId);
|
|
284
|
+
if (existsSync(activePath)) {
|
|
285
|
+
const activeDiagnostic = await inspectJsonStateRecovery(activePath, "skill-active-state");
|
|
286
|
+
if (activeDiagnostic) diagnostics.push(activeDiagnostic);
|
|
287
|
+
}
|
|
288
|
+
const ultragoalPath = modeStatePath(input.cwd, "ultragoal", resolvedSessionId);
|
|
289
|
+
if (existsSync(ultragoalPath)) {
|
|
290
|
+
const ultragoalDiagnostic = await inspectJsonStateRecovery(ultragoalPath, "mode-state", "ultragoal");
|
|
291
|
+
if (ultragoalDiagnostic) diagnostics.push(ultragoalDiagnostic);
|
|
292
|
+
}
|
|
293
|
+
return diagnostics;
|
|
227
294
|
}
|
|
228
295
|
|
|
229
296
|
async function readValidatedJsonFile<T>(
|
|
@@ -254,13 +321,6 @@ async function readValidatedJsonFile<T>(
|
|
|
254
321
|
return value;
|
|
255
322
|
}
|
|
256
323
|
|
|
257
|
-
async function writeJsonFile(filePath: string, value: unknown, cwd: string): Promise<void> {
|
|
258
|
-
await writeJsonAtomic(filePath, value, {
|
|
259
|
-
cwd,
|
|
260
|
-
audit: { category: "state", verb: "write", owner: "gjc-hook" },
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
324
|
function entryMatchesContext(
|
|
265
325
|
entry: SkillActiveEntry,
|
|
266
326
|
state: SkillActiveState,
|
|
@@ -286,23 +346,9 @@ function isWorkflowActiveEntry(entry: SkillActiveEntry): entry is SkillActiveEnt
|
|
|
286
346
|
export async function readVisibleSkillActiveState(
|
|
287
347
|
cwd: string,
|
|
288
348
|
sessionId?: string,
|
|
289
|
-
|
|
349
|
+
_stateDir?: string,
|
|
290
350
|
): Promise<SkillActiveState | null> {
|
|
291
|
-
|
|
292
|
-
const resolvedStateDir = resolveGjcStateDir(cwd, stateDir);
|
|
293
|
-
if (sessionId) {
|
|
294
|
-
const sessionState = await readValidatedJsonFile<SkillActiveState>(
|
|
295
|
-
skillStatePath(resolvedStateDir, sessionId),
|
|
296
|
-
"skill-active-state",
|
|
297
|
-
SkillActiveStateSchema,
|
|
298
|
-
);
|
|
299
|
-
if (sessionState) return sessionState;
|
|
300
|
-
}
|
|
301
|
-
return await readValidatedJsonFile<SkillActiveState>(
|
|
302
|
-
skillStatePath(resolvedStateDir),
|
|
303
|
-
"skill-active-state",
|
|
304
|
-
SkillActiveStateSchema,
|
|
305
|
-
);
|
|
351
|
+
return await readCanonicalVisibleSkillActiveState(cwd, await resolveBoundarySessionId(cwd, sessionId));
|
|
306
352
|
}
|
|
307
353
|
|
|
308
354
|
interface SeedSkillActivationStateInput {
|
|
@@ -320,17 +366,17 @@ async function seedSkillActivationState(
|
|
|
320
366
|
source: string,
|
|
321
367
|
input: SeedSkillActivationStateInput,
|
|
322
368
|
): Promise<SkillActiveState> {
|
|
323
|
-
const
|
|
369
|
+
const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
|
|
324
370
|
const nowIso = input.nowIso ?? new Date().toISOString();
|
|
325
371
|
const phase = initialPhaseForSkill(skill);
|
|
326
|
-
const initializedStatePath = modeStatePath(
|
|
372
|
+
const initializedStatePath = modeStatePath(input.cwd, skill, resolvedSessionId);
|
|
327
373
|
const entry: SkillActiveEntry = {
|
|
328
374
|
skill,
|
|
329
375
|
phase,
|
|
330
376
|
active: true,
|
|
331
377
|
activated_at: nowIso,
|
|
332
378
|
updated_at: nowIso,
|
|
333
|
-
|
|
379
|
+
session_id: resolvedSessionId,
|
|
334
380
|
...(input.threadId ? { thread_id: input.threadId } : {}),
|
|
335
381
|
...(input.turnId ? { turn_id: input.turnId } : {}),
|
|
336
382
|
};
|
|
@@ -343,7 +389,7 @@ async function seedSkillActivationState(
|
|
|
343
389
|
activated_at: nowIso,
|
|
344
390
|
updated_at: nowIso,
|
|
345
391
|
source,
|
|
346
|
-
|
|
392
|
+
session_id: resolvedSessionId,
|
|
347
393
|
...(input.threadId ? { thread_id: input.threadId } : {}),
|
|
348
394
|
...(input.turnId ? { turn_id: input.turnId } : {}),
|
|
349
395
|
initialized_mode: skill,
|
|
@@ -357,7 +403,7 @@ async function seedSkillActivationState(
|
|
|
357
403
|
skill,
|
|
358
404
|
cwd: input.cwd,
|
|
359
405
|
updated_at: nowIso,
|
|
360
|
-
|
|
406
|
+
session_id: resolvedSessionId,
|
|
361
407
|
...(input.threadId ? { thread_id: input.threadId } : {}),
|
|
362
408
|
...(input.turnId ? { turn_id: input.turnId } : {}),
|
|
363
409
|
};
|
|
@@ -366,20 +412,55 @@ async function seedSkillActivationState(
|
|
|
366
412
|
modeState.threshold_source = "default";
|
|
367
413
|
}
|
|
368
414
|
|
|
369
|
-
await
|
|
415
|
+
await readExistingStateForMutation(initializedStatePath);
|
|
416
|
+
const expectedRevision = 0;
|
|
417
|
+
await writeGuardedWorkflowEnvelopeAtomic(initializedStatePath, modeState, {
|
|
370
418
|
cwd: input.cwd,
|
|
419
|
+
policy: "source",
|
|
420
|
+
expectedRevision,
|
|
371
421
|
receipt: {
|
|
372
422
|
cwd: input.cwd,
|
|
373
423
|
skill,
|
|
374
424
|
owner: "gjc-hook",
|
|
375
425
|
command: source,
|
|
376
|
-
sessionId:
|
|
426
|
+
sessionId: resolvedSessionId,
|
|
377
427
|
},
|
|
378
|
-
audit: { category: "state", verb: "write", owner: "gjc-hook", skill },
|
|
428
|
+
audit: { category: "state", verb: "write", owner: "gjc-hook", skill, sessionId: resolvedSessionId },
|
|
379
429
|
});
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
430
|
+
const persistedModeState =
|
|
431
|
+
(await readValidatedJsonFile<ModeState>(initializedStatePath, "mode-state", ModeStateSchema)) ?? modeState;
|
|
432
|
+
const sourceRevision =
|
|
433
|
+
typeof persistedModeState.state_revision === "number" && Number.isFinite(persistedModeState.state_revision)
|
|
434
|
+
? persistedModeState.state_revision
|
|
435
|
+
: undefined;
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
await syncSkillActiveState({
|
|
439
|
+
cwd: input.cwd,
|
|
440
|
+
skill,
|
|
441
|
+
active: true,
|
|
442
|
+
phase,
|
|
443
|
+
sessionId: resolvedSessionId,
|
|
444
|
+
threadId: input.threadId,
|
|
445
|
+
turnId: input.turnId,
|
|
446
|
+
source,
|
|
447
|
+
receipt: undefined,
|
|
448
|
+
sourceRevision,
|
|
449
|
+
nowIso,
|
|
450
|
+
});
|
|
451
|
+
} catch {
|
|
452
|
+
// Derived active-state/HUD writes are best-effort during activation; source mode-state already persisted.
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
await writeGuardedJsonAtomic(skillStatePath(input.cwd, resolvedSessionId), state, {
|
|
456
|
+
cwd: input.cwd,
|
|
457
|
+
policy: "cache",
|
|
458
|
+
sourceRevision: (sourceRevision ?? 0) + 1,
|
|
459
|
+
receipt: undefined,
|
|
460
|
+
audit: { category: "state", verb: "write", owner: "gjc-hook", sessionId: resolvedSessionId },
|
|
461
|
+
});
|
|
462
|
+
} catch {
|
|
463
|
+
// Corrupt derived active-state is reported by recovery diagnostics; activation remains fail-open.
|
|
383
464
|
}
|
|
384
465
|
return state;
|
|
385
466
|
}
|
|
@@ -418,16 +499,17 @@ export async function ensureWorkflowSkillActivationState(
|
|
|
418
499
|
): Promise<SkillActiveState | null> {
|
|
419
500
|
const skill = input.skill.trim();
|
|
420
501
|
if (!isGjcWorkflowSkill(skill)) return null;
|
|
421
|
-
const
|
|
502
|
+
const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
|
|
503
|
+
const existing = await readVisibleSkillActiveState(input.cwd, resolvedSessionId, input.stateDir);
|
|
422
504
|
const alreadyActive = listActiveSkills(existing).some(
|
|
423
505
|
entry =>
|
|
424
506
|
entry.skill === skill &&
|
|
425
|
-
(existing ? entryMatchesContext(entry, existing,
|
|
507
|
+
(existing ? entryMatchesContext(entry, existing, resolvedSessionId, input.threadId) : true),
|
|
426
508
|
);
|
|
427
509
|
if (alreadyActive) return existing;
|
|
428
510
|
return await seedSkillActivationState(skill, `/skill:${skill}`, "gjc-skill-invocation", {
|
|
429
511
|
cwd: input.cwd,
|
|
430
|
-
sessionId:
|
|
512
|
+
sessionId: resolvedSessionId,
|
|
431
513
|
threadId: input.threadId,
|
|
432
514
|
turnId: input.turnId,
|
|
433
515
|
nowIso: input.nowIso,
|
|
@@ -565,18 +647,13 @@ async function readVisibleModeState(
|
|
|
565
647
|
cwd: string,
|
|
566
648
|
skill: GjcWorkflowSkill,
|
|
567
649
|
sessionId?: string,
|
|
568
|
-
|
|
650
|
+
_stateDir?: string,
|
|
569
651
|
): Promise<{ state: ModeState; statePath: string } | null> {
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
const rootStatePath = modeStatePath(resolvedStateDir, skill);
|
|
577
|
-
const rootState = await readValidatedJsonFile<ModeState>(rootStatePath, "mode-state", ModeStateSchema);
|
|
578
|
-
if (!rootState) return null;
|
|
579
|
-
return { state: rootState, statePath: rootStatePath };
|
|
652
|
+
const resolvedSessionId = await resolveBoundarySessionId(cwd, sessionId);
|
|
653
|
+
const sessionStatePath = modeStatePath(cwd, skill, resolvedSessionId);
|
|
654
|
+
const sessionState = await readValidatedJsonFile<ModeState>(sessionStatePath, "mode-state", ModeStateSchema);
|
|
655
|
+
if (!sessionState) return null;
|
|
656
|
+
return { state: sessionState, statePath: sessionStatePath };
|
|
580
657
|
}
|
|
581
658
|
|
|
582
659
|
function stateMatchesContext(state: ModeState, sessionId?: string, threadId?: string): boolean {
|
|
@@ -599,10 +676,11 @@ async function readCurrentGoalObjectiveFromSessionFile(sessionFile: string | und
|
|
|
599
676
|
}
|
|
600
677
|
|
|
601
678
|
export async function buildActiveUltragoalPromptContext(input: UserPromptSubmitStateInput): Promise<string | null> {
|
|
602
|
-
const
|
|
679
|
+
const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
|
|
680
|
+
const visibleModeState = await readVisibleModeState(input.cwd, "ultragoal", resolvedSessionId, input.stateDir);
|
|
603
681
|
if (!visibleModeState) return null;
|
|
604
682
|
if (isTerminalModeState(visibleModeState.state)) return null;
|
|
605
|
-
if (!stateMatchesContext(visibleModeState.state,
|
|
683
|
+
if (!stateMatchesContext(visibleModeState.state, resolvedSessionId, input.threadId)) return null;
|
|
606
684
|
|
|
607
685
|
const phase = String(visibleModeState.state.current_phase ?? "active");
|
|
608
686
|
const stateObjective =
|
|
@@ -638,21 +716,46 @@ export async function buildActiveUltragoalPromptContext(input: UserPromptSubmitS
|
|
|
638
716
|
return `Ultragoal is active (phase: ${phase}; state: ${visibleModeState.statePath}). If the user prompt is a steering request, use \`gjc ultragoal steer\` to add or steer subgoals. Normal prose should not mutate Ultragoal state.`;
|
|
639
717
|
}
|
|
640
718
|
|
|
719
|
+
function buildHandoffStopReleaseGuidance(skill: GjcWorkflowSkill): string {
|
|
720
|
+
return `Use the ask tool to present the next handoff step, then persist one concrete release action: hand off to the next workflow, run \`gjc state clear ${skill}\`, demote the skill with active:false, crystallize the spec when finishing deep-interview, or deliberately cancel the workflow.`;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function buildHandoffModeStateRecoveryMessage(skill: GjcWorkflowSkill, phase: string, statePath: string): string {
|
|
724
|
+
return `GJC handoff skill "${skill}" mode-state is missing or corrupt (phase: ${phase}; state: ${statePath}). ${buildHandoffStopReleaseGuidance(skill)}`;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function buildHandoffForceAskMessage(skill: GjcWorkflowSkill, phase: string, statePath: string): string {
|
|
728
|
+
return `GJC handoff skill "${skill}" must not stop without offering a next step (phase: ${phase}; state: ${statePath}). ${buildHandoffStopReleaseGuidance(skill)}`;
|
|
729
|
+
}
|
|
730
|
+
|
|
641
731
|
export async function buildSkillStopOutput(input: StopHookInput): Promise<Record<string, unknown> | null> {
|
|
642
|
-
const
|
|
643
|
-
const skillState = await readVisibleSkillActiveState(input.cwd,
|
|
732
|
+
const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
|
|
733
|
+
const skillState = await readVisibleSkillActiveState(input.cwd, resolvedSessionId, input.stateDir);
|
|
644
734
|
const activeEntries = listActiveSkills(skillState)
|
|
645
735
|
.filter(isWorkflowActiveEntry)
|
|
646
|
-
.filter(entry =>
|
|
736
|
+
.filter(entry =>
|
|
737
|
+
skillState ? entryMatchesContext(entry, skillState, resolvedSessionId, input.threadId) : false,
|
|
738
|
+
);
|
|
647
739
|
if (!skillState || activeEntries.length === 0) return null;
|
|
648
740
|
|
|
649
741
|
for (const entry of activeEntries) {
|
|
650
742
|
const modeState = await readValidatedJsonFile<ModeState>(
|
|
651
|
-
modeStatePath(
|
|
743
|
+
modeStatePath(input.cwd, entry.skill, resolvedSessionId),
|
|
652
744
|
"mode-state",
|
|
653
745
|
ModeStateSchema,
|
|
654
746
|
);
|
|
655
747
|
const handoffRequired = isHandoffRequiredSkill(entry.skill);
|
|
748
|
+
if (!modeState && handoffRequired) {
|
|
749
|
+
const phase = String(entry.phase ?? skillState.phase ?? "active");
|
|
750
|
+
const statePath = modeStatePath(input.cwd, entry.skill, resolvedSessionId);
|
|
751
|
+
const recoveryMessage = buildHandoffModeStateRecoveryMessage(entry.skill, phase, statePath);
|
|
752
|
+
return {
|
|
753
|
+
decision: "block",
|
|
754
|
+
reason: recoveryMessage,
|
|
755
|
+
stopReason: `gjc_skill_${entry.skill.replace(/-/g, "_")}_mode_state_recovery`,
|
|
756
|
+
systemMessage: recoveryMessage,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
656
759
|
if (modeStateReleasesStop(modeState, handoffRequired)) {
|
|
657
760
|
// A mode-state that claims it releases the Stop block must agree with
|
|
658
761
|
// authoritative durable state. If a stale/incoherent mode-state would
|
|
@@ -660,11 +763,7 @@ export async function buildSkillStopOutput(input: StopHookInput): Promise<Record
|
|
|
660
763
|
// of trusting the single file (see #659).
|
|
661
764
|
const staleRelease = await detectStaleModeStateRelease(entry.skill, input.cwd);
|
|
662
765
|
if (staleRelease) {
|
|
663
|
-
const coherenceMessage = `GJC skill "${entry.skill}" mode-state reports it released the Stop block (${modeStatePath(
|
|
664
|
-
resolvedStateDir,
|
|
665
|
-
entry.skill,
|
|
666
|
-
input.sessionId,
|
|
667
|
-
)}), but ${staleRelease}. The mode-state is incoherent with authoritative durable state; finish or explicitly clear the pending work before stopping.`;
|
|
766
|
+
const coherenceMessage = `GJC skill "${entry.skill}" mode-state reports it released the Stop block (${modeStatePath(input.cwd, entry.skill, resolvedSessionId)}), but ${staleRelease}. The mode-state is incoherent with authoritative durable state; finish or explicitly clear the pending work before stopping.`;
|
|
668
767
|
return {
|
|
669
768
|
decision: "block",
|
|
670
769
|
reason: coherenceMessage,
|
|
@@ -678,11 +777,7 @@ export async function buildSkillStopOutput(input: StopHookInput): Promise<Record
|
|
|
678
777
|
// as legitimate terminals). See #674.
|
|
679
778
|
const uncrystallized = await detectUncrystallizedDeepInterviewStop(entry.skill, modeState, input.cwd);
|
|
680
779
|
if (uncrystallized) {
|
|
681
|
-
const crystallizeMessage = `GJC deep-interview must crystallize before stopping (${modeStatePath(
|
|
682
|
-
resolvedStateDir,
|
|
683
|
-
entry.skill,
|
|
684
|
-
input.sessionId,
|
|
685
|
-
)}): ${uncrystallized}.`;
|
|
780
|
+
const crystallizeMessage = `GJC deep-interview must crystallize before stopping (${modeStatePath(input.cwd, entry.skill, resolvedSessionId)}): ${uncrystallized}.`;
|
|
686
781
|
return {
|
|
687
782
|
decision: "block",
|
|
688
783
|
reason: crystallizeMessage,
|
|
@@ -693,7 +788,7 @@ export async function buildSkillStopOutput(input: StopHookInput): Promise<Record
|
|
|
693
788
|
continue;
|
|
694
789
|
}
|
|
695
790
|
const phase = String(modeState?.current_phase ?? entry.phase ?? skillState.phase ?? "active");
|
|
696
|
-
const statePath = modeStatePath(
|
|
791
|
+
const statePath = modeStatePath(input.cwd, entry.skill, resolvedSessionId);
|
|
697
792
|
if (entry.skill === "ultragoal") {
|
|
698
793
|
const objective =
|
|
699
794
|
(await readCurrentGoalObjectiveFromSessionFile(input.sessionFile)) ??
|
|
@@ -720,7 +815,7 @@ export async function buildSkillStopOutput(input: StopHookInput): Promise<Record
|
|
|
720
815
|
}
|
|
721
816
|
}
|
|
722
817
|
const systemMessage = handoffRequired
|
|
723
|
-
?
|
|
818
|
+
? buildHandoffForceAskMessage(entry.skill, phase, statePath)
|
|
724
819
|
: `GJC skill "${entry.skill}" is still active (phase: ${phase}; state: ${statePath}). Continue or explicitly finish/cancel the skill before stopping.`;
|
|
725
820
|
return {
|
|
726
821
|
decision: "block",
|