@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
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as logger from "@gajae-code/utils/logger";
|
|
2
|
+
import { activeSnapshotPath, assertNonEmptyGjcSessionId, modeStatePath } from "../gjc-runtime/session-layout";
|
|
3
|
+
import { resolveGjcSessionForRead, SessionResolutionError } from "../gjc-runtime/session-resolution";
|
|
2
4
|
import {
|
|
3
5
|
type ActiveSessionScope,
|
|
4
6
|
readActiveEntries,
|
|
@@ -6,13 +8,12 @@ import {
|
|
|
6
8
|
removeActiveEntry,
|
|
7
9
|
writeActiveEntry,
|
|
8
10
|
} from "../gjc-runtime/state-writer";
|
|
11
|
+
import { CANONICAL_GJC_WORKFLOW_SKILLS, type CanonicalGjcWorkflowSkill } from "./canonical-skills";
|
|
9
12
|
import type { WorkflowStateReceipt } from "./workflow-state-contract";
|
|
10
13
|
|
|
11
14
|
export const SKILL_ACTIVE_STATE_FILE = "skill-active-state.json";
|
|
12
15
|
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
export type CanonicalGjcWorkflowSkill = (typeof CANONICAL_GJC_WORKFLOW_SKILLS)[number];
|
|
16
|
+
export { CANONICAL_GJC_WORKFLOW_SKILLS, type CanonicalGjcWorkflowSkill };
|
|
16
17
|
export type WorkflowHudSeverity = "info" | "warning" | "blocked" | "error" | "success";
|
|
17
18
|
|
|
18
19
|
export interface WorkflowHudChip {
|
|
@@ -60,6 +61,7 @@ export interface SkillActiveEntry {
|
|
|
60
61
|
handoff_to?: string;
|
|
61
62
|
handoff_at?: string;
|
|
62
63
|
active_subskills?: ActiveSubskillEntry[];
|
|
64
|
+
source_state_revision?: number;
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
export interface SkillActiveState {
|
|
@@ -83,7 +85,7 @@ export interface SkillActiveState {
|
|
|
83
85
|
|
|
84
86
|
export interface SkillActiveStatePaths {
|
|
85
87
|
rootPath: string;
|
|
86
|
-
sessionPath
|
|
88
|
+
sessionPath: string;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
export interface SyncSkillActiveStateOptions {
|
|
@@ -102,6 +104,7 @@ export interface SyncSkillActiveStateOptions {
|
|
|
102
104
|
handoff_to?: string;
|
|
103
105
|
handoff_at?: string;
|
|
104
106
|
active_subskills?: ActiveSubskillEntry[];
|
|
107
|
+
sourceRevision?: number;
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
const HUD_TEXT_LIMIT = 80;
|
|
@@ -246,8 +249,12 @@ function unionActiveSubskillEntries(...entrySets: Array<ActiveSubskillEntry[] |
|
|
|
246
249
|
return merged;
|
|
247
250
|
}
|
|
248
251
|
|
|
249
|
-
function
|
|
250
|
-
|
|
252
|
+
function resolveBoundarySessionId(cwd: string, sessionId?: string): Promise<string> {
|
|
253
|
+
const normalizedSessionId = safeString(sessionId).trim();
|
|
254
|
+
if (normalizedSessionId) return Promise.resolve(normalizedSessionId);
|
|
255
|
+
return resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID }).then(
|
|
256
|
+
context => context.gjcSessionId,
|
|
257
|
+
);
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
function entryKey(entry: Pick<SkillActiveEntry, "skill" | "session_id">): string {
|
|
@@ -343,14 +350,10 @@ export function normalizeSkillActiveState(raw: unknown): SkillActiveState | null
|
|
|
343
350
|
}
|
|
344
351
|
|
|
345
352
|
export function getSkillActiveStatePaths(cwd: string, sessionId?: string): SkillActiveStatePaths {
|
|
346
|
-
const stateDir = path.join(cwd, ".gjc", "state");
|
|
347
|
-
const rootPath = path.join(stateDir, SKILL_ACTIVE_STATE_FILE);
|
|
348
353
|
const normalizedSessionId = safeString(sessionId).trim();
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
sessionPath: path.join(stateDir, "sessions", encodePathSegment(normalizedSessionId), SKILL_ACTIVE_STATE_FILE),
|
|
353
|
-
};
|
|
354
|
+
assertNonEmptyGjcSessionId(normalizedSessionId, "getSkillActiveStatePaths");
|
|
355
|
+
const sessionPath = activeSnapshotPath(cwd, normalizedSessionId);
|
|
356
|
+
return { rootPath: sessionPath, sessionPath };
|
|
354
357
|
}
|
|
355
358
|
|
|
356
359
|
/**
|
|
@@ -380,7 +383,12 @@ async function readRawActiveStateForHandoff(filePath: string, strict: boolean):
|
|
|
380
383
|
if (!parsed || typeof parsed !== "object") return null;
|
|
381
384
|
return parsed as SkillActiveState;
|
|
382
385
|
} catch (err) {
|
|
383
|
-
if (!strict)
|
|
386
|
+
if (!strict) {
|
|
387
|
+
logger.warn(
|
|
388
|
+
`gjc skill-state: invalid skill-active-state at ${filePath}: invalid JSON: ${(err as Error).message}`,
|
|
389
|
+
);
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
384
392
|
throw err;
|
|
385
393
|
}
|
|
386
394
|
}
|
|
@@ -419,14 +427,10 @@ function rawActiveEntries(state: SkillActiveState | null): SkillActiveEntry[] {
|
|
|
419
427
|
|
|
420
428
|
async function readModeStatePhase(
|
|
421
429
|
cwd: string,
|
|
422
|
-
sessionId: string
|
|
430
|
+
sessionId: string,
|
|
423
431
|
skill: CanonicalGjcWorkflowSkill,
|
|
424
432
|
): Promise<string | undefined> {
|
|
425
|
-
const
|
|
426
|
-
const normalizedSessionId = safeString(sessionId).trim();
|
|
427
|
-
const filePath = normalizedSessionId
|
|
428
|
-
? path.join(stateDir, "sessions", encodePathSegment(normalizedSessionId), `${skill}-state.json`)
|
|
429
|
-
: path.join(stateDir, `${skill}-state.json`);
|
|
433
|
+
const filePath = modeStatePath(cwd, sessionId, skill);
|
|
430
434
|
try {
|
|
431
435
|
const parsed = JSON.parse(await Bun.file(filePath).text());
|
|
432
436
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return undefined;
|
|
@@ -473,15 +477,6 @@ function withCanonicalRalplanPhase(entry: SkillActiveEntry, canonicalPhase: stri
|
|
|
473
477
|
};
|
|
474
478
|
}
|
|
475
479
|
|
|
476
|
-
function filterRootEntriesForSession(entries: SkillActiveEntry[], sessionId?: string): SkillActiveEntry[] {
|
|
477
|
-
const normalizedSessionId = safeString(sessionId).trim();
|
|
478
|
-
if (!normalizedSessionId) return entries;
|
|
479
|
-
return entries.filter(entry => {
|
|
480
|
-
const entrySessionId = safeString(entry.session_id).trim();
|
|
481
|
-
return entrySessionId.length === 0 || entrySessionId === normalizedSessionId;
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
|
|
485
480
|
function entryRecency(entry: SkillActiveEntry): number {
|
|
486
481
|
const stamp = entry.handoff_at || entry.updated_at || entry.activated_at;
|
|
487
482
|
const ms = stamp ? Date.parse(stamp) : Number.NaN;
|
|
@@ -603,57 +598,50 @@ export function collapsePlanningPipeline(entries: readonly SkillActiveEntry[]):
|
|
|
603
598
|
async function mergeVisibleEntries(
|
|
604
599
|
cwd: string,
|
|
605
600
|
sessionState: SkillActiveState | null,
|
|
606
|
-
|
|
607
|
-
sessionId?: string,
|
|
601
|
+
sessionId: string,
|
|
608
602
|
): Promise<SkillActiveEntry[]> {
|
|
609
603
|
// Use the raw (active + inactive) rows so a handoff demotion stays visible
|
|
610
604
|
// long enough to supersede a stale same-skill row before the active filter.
|
|
611
605
|
// Per-skill files in active/<skill>.json are authoritative and are merged
|
|
612
606
|
// after the derived snapshot cache, so a stale skill-active-state.json row
|
|
613
607
|
// cannot override the latest entry file.
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
sessionId,
|
|
617
|
-
);
|
|
618
|
-
const merged = new Map(rootEntries.map(entry => [entryKey(entry), entry]));
|
|
619
|
-
const sessionEntries = sessionId
|
|
620
|
-
? [...rawActiveEntries(sessionState), ...(await readActiveEntries(cwd, { sessionId }))]
|
|
621
|
-
: rawActiveEntries(sessionState);
|
|
622
|
-
for (const entry of sessionEntries) {
|
|
623
|
-
merged.set(entryKey(entry), entry);
|
|
624
|
-
}
|
|
608
|
+
const entries = [...rawActiveEntries(sessionState), ...(await readActiveEntries(cwd, { sessionId }))];
|
|
609
|
+
const merged = new Map(entries.map(entry => [entryKey(entry), entry]));
|
|
625
610
|
const canonicalRalplanPhase = await readModeStatePhase(cwd, sessionId, "ralplan");
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
);
|
|
611
|
+
const visibleEntries = dedupeVisibleBySkill([...merged.values()], sessionId)
|
|
612
|
+
.filter(entry => entry.active !== false)
|
|
613
|
+
.map(entry => withCanonicalRalplanPhase(entry, canonicalRalplanPhase));
|
|
614
|
+
return collapsePlanningPipeline(visibleEntries).toSorted(comparePipelineEntry);
|
|
631
615
|
}
|
|
632
616
|
|
|
633
617
|
export async function readVisibleSkillActiveState(cwd: string, sessionId?: string): Promise<SkillActiveState | null> {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
618
|
+
let resolvedSessionId: string;
|
|
619
|
+
try {
|
|
620
|
+
resolvedSessionId = await resolveBoundarySessionId(cwd, sessionId);
|
|
621
|
+
} catch (error) {
|
|
622
|
+
if (error instanceof SessionResolutionError && error.code === "no_session") return null;
|
|
623
|
+
throw error;
|
|
624
|
+
}
|
|
625
|
+
const { sessionPath } = getSkillActiveStatePaths(cwd, resolvedSessionId);
|
|
626
|
+
const sessionState = await readRawActiveStateForHandoff(sessionPath, false);
|
|
627
|
+
const activeSkills = await mergeVisibleEntries(cwd, sessionState, resolvedSessionId);
|
|
640
628
|
if (activeSkills.length === 0) return null;
|
|
641
629
|
const primary = activeSkills[0];
|
|
642
630
|
return {
|
|
643
|
-
...(rootState ?? {}),
|
|
644
631
|
...(sessionState ?? {}),
|
|
645
632
|
version: 1,
|
|
646
633
|
active: true,
|
|
647
|
-
skill: primary?.skill ?? "",
|
|
648
|
-
phase: primary?.phase ?? "",
|
|
649
|
-
session_id:
|
|
634
|
+
skill: sessionState?.skill ?? primary?.skill ?? "",
|
|
635
|
+
phase: sessionState?.phase ?? primary?.phase ?? "",
|
|
636
|
+
session_id: resolvedSessionId,
|
|
650
637
|
active_skills: activeSkills,
|
|
651
638
|
active_subskills: activeSkills.flatMap(entry => entry.active_subskills ?? []),
|
|
652
639
|
};
|
|
653
640
|
}
|
|
654
641
|
|
|
655
|
-
function activeStateWriterAudit(verb: string) {
|
|
656
|
-
|
|
642
|
+
function activeStateWriterAudit(verb: string, sessionScope?: ActiveSessionScope | string) {
|
|
643
|
+
const sessionId = typeof sessionScope === "string" ? sessionScope : sessionScope?.sessionId;
|
|
644
|
+
return { category: "state" as const, verb, owner: "gjc-runtime" as const, ...(sessionId ? { sessionId } : {}) };
|
|
657
645
|
}
|
|
658
646
|
|
|
659
647
|
async function persistActiveEntry(
|
|
@@ -664,12 +652,13 @@ async function persistActiveEntry(
|
|
|
664
652
|
if (entry.active === false) {
|
|
665
653
|
await removeActiveEntry(cwd, sessionScope, entry.skill, {
|
|
666
654
|
cwd,
|
|
667
|
-
audit: activeStateWriterAudit("remove-active-entry"),
|
|
655
|
+
audit: activeStateWriterAudit("remove-active-entry", sessionScope),
|
|
656
|
+
sourceRevision: entry.source_state_revision,
|
|
668
657
|
});
|
|
669
658
|
} else {
|
|
670
659
|
await writeActiveEntry(cwd, sessionScope, entry.skill, entry, {
|
|
671
660
|
cwd,
|
|
672
|
-
audit: activeStateWriterAudit("write-active-entry"),
|
|
661
|
+
audit: activeStateWriterAudit("write-active-entry", sessionScope),
|
|
673
662
|
});
|
|
674
663
|
}
|
|
675
664
|
}
|
|
@@ -681,12 +670,15 @@ async function writeHandoffEntry(
|
|
|
681
670
|
): Promise<void> {
|
|
682
671
|
await writeActiveEntry(cwd, sessionScope, entry.skill, entry, {
|
|
683
672
|
cwd,
|
|
684
|
-
audit: activeStateWriterAudit("write-active-entry"),
|
|
673
|
+
audit: activeStateWriterAudit("write-active-entry", sessionScope),
|
|
685
674
|
});
|
|
686
675
|
}
|
|
687
676
|
|
|
688
677
|
async function rebuildActiveState(cwd: string, sessionScope?: ActiveSessionScope): Promise<void> {
|
|
689
|
-
await rebuildActiveSnapshot(cwd, sessionScope, {
|
|
678
|
+
await rebuildActiveSnapshot(cwd, sessionScope, {
|
|
679
|
+
cwd,
|
|
680
|
+
audit: activeStateWriterAudit("rebuild-active-snapshot", sessionScope),
|
|
681
|
+
});
|
|
690
682
|
}
|
|
691
683
|
|
|
692
684
|
async function removeSupersededPlanningPipelineEntries(
|
|
@@ -698,7 +690,7 @@ async function removeSupersededPlanningPipelineEntries(
|
|
|
698
690
|
for (const skill of upstreamPlanningPipelineSkills(entry.skill)) {
|
|
699
691
|
await removeActiveEntry(cwd, sessionScope, skill, {
|
|
700
692
|
cwd,
|
|
701
|
-
audit: activeStateWriterAudit("remove-superseded-pipeline-entry"),
|
|
693
|
+
audit: activeStateWriterAudit("remove-superseded-pipeline-entry", sessionScope),
|
|
702
694
|
});
|
|
703
695
|
}
|
|
704
696
|
}
|
|
@@ -708,18 +700,17 @@ async function activeSubskillsForExistingEntry(
|
|
|
708
700
|
sessionId: string | undefined,
|
|
709
701
|
skill: string,
|
|
710
702
|
): Promise<ActiveSubskillEntry[] | undefined> {
|
|
711
|
-
const
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
]);
|
|
716
|
-
const existing = (await mergeVisibleEntries(cwd, sessionState, rootState, sessionId)).find(
|
|
703
|
+
const resolvedSessionId = await resolveBoundarySessionId(cwd, sessionId);
|
|
704
|
+
const { sessionPath } = getSkillActiveStatePaths(cwd, resolvedSessionId);
|
|
705
|
+
const sessionState = await readRawActiveStateForHandoff(sessionPath, false);
|
|
706
|
+
const existing = (await mergeVisibleEntries(cwd, sessionState, resolvedSessionId)).find(
|
|
717
707
|
entry => entry.skill === skill,
|
|
718
708
|
);
|
|
719
709
|
return existing?.active_subskills;
|
|
720
710
|
}
|
|
721
711
|
|
|
722
712
|
export async function syncSkillActiveState(options: SyncSkillActiveStateOptions): Promise<void> {
|
|
713
|
+
if (!options.sessionId) return;
|
|
723
714
|
const preservedActiveSubskills =
|
|
724
715
|
options.active_subskills === undefined
|
|
725
716
|
? await activeSubskillsForExistingEntry(options.cwd, options.sessionId, options.skill)
|
|
@@ -745,12 +736,8 @@ export async function syncSkillActiveState(options: SyncSkillActiveStateOptions)
|
|
|
745
736
|
: preservedActiveSubskills
|
|
746
737
|
? { active_subskills: preservedActiveSubskills }
|
|
747
738
|
: {}),
|
|
739
|
+
...(typeof options.sourceRevision === "number" ? { source_state_revision: options.sourceRevision } : {}),
|
|
748
740
|
};
|
|
749
|
-
await removeSupersededPlanningPipelineEntries(options.cwd, undefined, entry);
|
|
750
|
-
await persistActiveEntry(options.cwd, undefined, entry);
|
|
751
|
-
await rebuildActiveState(options.cwd);
|
|
752
|
-
|
|
753
|
-
if (!options.sessionId) return;
|
|
754
741
|
const sessionScope = { sessionId: options.sessionId };
|
|
755
742
|
await removeSupersededPlanningPipelineEntries(options.cwd, sessionScope, entry);
|
|
756
743
|
await persistActiveEntry(options.cwd, sessionScope, entry);
|
|
@@ -768,36 +755,23 @@ export interface ApplyHandoffOptions {
|
|
|
768
755
|
}
|
|
769
756
|
|
|
770
757
|
/**
|
|
771
|
-
* Atomically apply a workflow-skill handoff to
|
|
772
|
-
* root `skill-active-state.json` files in a single write per file.
|
|
773
|
-
*
|
|
774
|
-
* Write order: **session first, root last**. The session file is the
|
|
775
|
-
* source of truth for HUD; the root aggregate must never lead the session
|
|
776
|
-
* during a handoff window. Each file is rewritten once with caller demoted
|
|
777
|
-
* to `active:false` (preserving `handoff_to`/`handoff_at` lineage) and
|
|
778
|
-
* callee promoted to `active:true` (with `handoff_from`/`handoff_at`).
|
|
758
|
+
* Atomically apply a workflow-skill handoff to the session-scoped active state.
|
|
779
759
|
*/
|
|
780
760
|
export async function applyHandoffToActiveState(options: ApplyHandoffOptions): Promise<void> {
|
|
781
761
|
const nowIso = options.nowIso ?? new Date().toISOString();
|
|
782
762
|
const callerEntry = buildSyncEntry(options.caller, nowIso);
|
|
783
763
|
const calleeEntry = buildSyncEntry(options.callee, nowIso);
|
|
784
764
|
const sessionId = options.callee.sessionId ?? options.caller.sessionId;
|
|
785
|
-
|
|
765
|
+
assertNonEmptyGjcSessionId(sessionId, "applyHandoffToActiveState");
|
|
766
|
+
const { sessionPath } = getSkillActiveStatePaths(options.cwd, sessionId);
|
|
786
767
|
const readState = (filePath: string) => readRawActiveStateForHandoff(filePath, options.strict === true);
|
|
787
|
-
await
|
|
788
|
-
|
|
789
|
-
// A skill can hold more than one visible row in this session's scope — e.g.
|
|
790
|
-
// it was seeded without a session id (rendered globally) and is now handed
|
|
791
|
-
// off under a concrete session id. Supersede every same-session-scope row of
|
|
792
|
-
// the caller and callee skills, not just the exact `skill::session_id` key,
|
|
793
|
-
// so a stale `active:true` row cannot survive the demotion and keep showing
|
|
794
|
-
// in the HUD. Rows owned by other sessions are left untouched.
|
|
768
|
+
await readState(sessionPath);
|
|
769
|
+
|
|
795
770
|
const handoffSession = safeString(sessionId).trim();
|
|
796
771
|
const reassignedSkills = new Set([callerEntry.skill, calleeEntry.skill]);
|
|
797
772
|
const supersedesVisible = (entry: SkillActiveEntry): boolean => {
|
|
798
773
|
if (!reassignedSkills.has(entry.skill)) return false;
|
|
799
|
-
|
|
800
|
-
return entrySession.length === 0 || entrySession === handoffSession;
|
|
774
|
+
return safeString(entry.session_id).trim() === handoffSession;
|
|
801
775
|
};
|
|
802
776
|
const applyEntries = (entries: SkillActiveEntry[]): SkillActiveEntry[] => {
|
|
803
777
|
const callerKey = entryKey(callerEntry);
|
|
@@ -805,9 +779,6 @@ export async function applyHandoffToActiveState(options: ApplyHandoffOptions): P
|
|
|
805
779
|
entries.find(e => entryKey(e) === callerKey) ??
|
|
806
780
|
entries.find(e => e.skill === callerEntry.skill && supersedesVisible(e) && Boolean(e.handoff_from));
|
|
807
781
|
const kept = entries.filter(e => !supersedesVisible(e));
|
|
808
|
-
// Merge prior lineage into the demoted caller so multi-step handoff
|
|
809
|
-
// chains preserve `handoff_from` from the previous transition while
|
|
810
|
-
// the new `handoff_to`/`handoff_at` describe this one.
|
|
811
782
|
const mergedCaller: SkillActiveEntry = priorCaller
|
|
812
783
|
? {
|
|
813
784
|
...callerEntry,
|
|
@@ -822,10 +793,7 @@ export async function applyHandoffToActiveState(options: ApplyHandoffOptions): P
|
|
|
822
793
|
activeSubskills.length > 0 ? { ...calleeEntry, active_subskills: activeSubskills } : calleeEntry;
|
|
823
794
|
return [...kept, mergedCaller, mergedCallee];
|
|
824
795
|
};
|
|
825
|
-
const writeEntries = async (
|
|
826
|
-
sessionScope: ActiveSessionScope | undefined,
|
|
827
|
-
prior: SkillActiveState | null,
|
|
828
|
-
): Promise<void> => {
|
|
796
|
+
const writeEntries = async (sessionScope: ActiveSessionScope, prior: SkillActiveState | null): Promise<void> => {
|
|
829
797
|
const nextEntries = applyEntries(rawActiveEntries(prior));
|
|
830
798
|
for (const entry of nextEntries) {
|
|
831
799
|
await writeHandoffEntry(options.cwd, sessionScope, entry);
|
|
@@ -833,12 +801,8 @@ export async function applyHandoffToActiveState(options: ApplyHandoffOptions): P
|
|
|
833
801
|
await rebuildActiveState(options.cwd, sessionScope);
|
|
834
802
|
};
|
|
835
803
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
await writeEntries({ sessionId }, prior);
|
|
839
|
-
}
|
|
840
|
-
const priorRoot = await readState(rootPath);
|
|
841
|
-
await writeEntries(undefined, priorRoot);
|
|
804
|
+
const prior = await readState(sessionPath);
|
|
805
|
+
await writeEntries({ sessionId }, prior);
|
|
842
806
|
}
|
|
843
807
|
|
|
844
808
|
function buildSyncEntry(options: SyncSkillActiveStateOptions, nowIso: string): SkillActiveEntry {
|
|
@@ -4,6 +4,8 @@ import * as path from "node:path";
|
|
|
4
4
|
import type { AgentTool } from "@gajae-code/agent-core";
|
|
5
5
|
import { logger } from "@gajae-code/utils";
|
|
6
6
|
import { expandApplyPatchToEntries } from "../edit/modes/apply-patch";
|
|
7
|
+
import { GJC_SESSION_PREFIX, modeStatePath as sessionModeStatePath } from "../gjc-runtime/session-layout";
|
|
8
|
+
import { resolveGjcSessionForRead } from "../gjc-runtime/session-resolution";
|
|
7
9
|
import { ModeStateSchema } from "../gjc-runtime/state-schema";
|
|
8
10
|
import { LocalProtocolHandler, resolveLocalUrlToPath } from "../internal-urls/local-protocol";
|
|
9
11
|
import { resolveToCwd } from "../tools/path-utils";
|
|
@@ -31,27 +33,10 @@ function planningPhaseBlockMessage(skill: CanonicalGjcWorkflowSkill): string {
|
|
|
31
33
|
return DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
const BLOCKED_TOOL_NAMES = new Set(["edit", "write", "ast_edit"
|
|
36
|
+
const BLOCKED_TOOL_NAMES = new Set(["edit", "write", "ast_edit"]);
|
|
35
37
|
const ARCHIVE_OR_SQLITE_BASE_RE = /^(.+?\.(?:tar\.gz|sqlite3|sqlite|db3|zip|tgz|tar|db))(?:$|:)/i;
|
|
36
38
|
const INTERNAL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
37
39
|
const VIM_FILE_SWITCH_RE = /^\s*:(?:e|e!|edit|edit!)(?:\s+([^<\r\n]+))?(?:<CR>|\r|\n|$)/i;
|
|
38
|
-
const BASH_TOKEN_RE = /'[^']*'|"(?:\\.|[^"\\])*"|\S+/g;
|
|
39
|
-
const BASH_REDIRECT_RE = /^(?:\d*)>>?$/;
|
|
40
|
-
const BASH_HEREDOC_RE = /^(?:\d*)<<-?$/;
|
|
41
|
-
// Shell command-list / redirection / substitution operators. Includes `\r` and
|
|
42
|
-
// `\n` because the shell treats a newline as a command separator and tool command
|
|
43
|
-
// strings can be multiline (e.g. heredocs).
|
|
44
|
-
const BASH_CONTROL_OPERATOR_RE = /[;&|<>`\r\n]|\$\(/;
|
|
45
|
-
// Best-effort, defense-in-depth bash mutation detection. The authoritative
|
|
46
|
-
// planning-phase guard is the dedicated `write`/`edit`/`ast_edit` tools (fully
|
|
47
|
-
// pathed); this catches the common shell mutators plus all redirect targets so a
|
|
48
|
-
// cooperative agent cannot trivially side-step those tools. It is deliberately
|
|
49
|
-
// NOT exhaustive: arbitrary interpreters (`python -c`, `node -e`) and the
|
|
50
|
-
// `key=value` operand forms of utilities like `dd of=` are not parsed, and path
|
|
51
|
-
// classification is lexical (no realpath), matching the rest of this guard and
|
|
52
|
-
// the broader `.gjc` path handling. Hardening any of these would require a real
|
|
53
|
-
// shell parser / symlink resolution and is out of scope for the planning rails.
|
|
54
|
-
const BASH_MUTATION_COMMANDS = new Set(["rm", "mv", "cp", "touch", "mkdir", "ln", "tee"]);
|
|
55
40
|
|
|
56
41
|
type ToolWithEditMode = AgentTool & {
|
|
57
42
|
mode?: unknown;
|
|
@@ -93,15 +78,18 @@ function safeString(value: unknown): string {
|
|
|
93
78
|
return typeof value === "string" ? value : "";
|
|
94
79
|
}
|
|
95
80
|
|
|
96
|
-
function
|
|
97
|
-
|
|
81
|
+
async function resolveBoundarySessionId(cwd: string, sessionId?: string): Promise<string | null> {
|
|
82
|
+
const normalizedSessionId = sessionId?.trim();
|
|
83
|
+
if (normalizedSessionId) return normalizedSessionId;
|
|
84
|
+
try {
|
|
85
|
+
return (await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
98
89
|
}
|
|
99
90
|
|
|
100
|
-
function modeStatePath(cwd: string, skill: string, sessionId
|
|
101
|
-
|
|
102
|
-
const fileName = `${skill}-state.json`;
|
|
103
|
-
if (sessionId) return path.join(stateDir, "sessions", encodePathSegment(sessionId), fileName);
|
|
104
|
-
return path.join(stateDir, fileName);
|
|
91
|
+
function modeStatePath(cwd: string, skill: string, sessionId: string): string {
|
|
92
|
+
return sessionModeStatePath(cwd, sessionId, skill);
|
|
105
93
|
}
|
|
106
94
|
|
|
107
95
|
function warnInvalidModeState(filePath: string, error: string): void {
|
|
@@ -129,12 +117,8 @@ async function readValidatedModeState(filePath: string): Promise<ModeState | nul
|
|
|
129
117
|
}
|
|
130
118
|
return state;
|
|
131
119
|
}
|
|
132
|
-
async function readVisibleModeState(cwd: string, skill: string, sessionId
|
|
133
|
-
|
|
134
|
-
const sessionState = await readValidatedModeState(modeStatePath(cwd, skill, sessionId));
|
|
135
|
-
if (sessionState) return sessionState;
|
|
136
|
-
}
|
|
137
|
-
return await readValidatedModeState(modeStatePath(cwd, skill));
|
|
120
|
+
async function readVisibleModeState(cwd: string, skill: string, sessionId: string): Promise<ModeState | null> {
|
|
121
|
+
return await readValidatedModeState(modeStatePath(cwd, skill, sessionId));
|
|
138
122
|
}
|
|
139
123
|
|
|
140
124
|
/**
|
|
@@ -228,16 +212,20 @@ async function getActivePlanningSkill(
|
|
|
228
212
|
sessionId?: string,
|
|
229
213
|
threadId?: string,
|
|
230
214
|
): Promise<ActivePlanningSkill | null> {
|
|
231
|
-
const
|
|
215
|
+
const resolvedSessionId = await resolveBoundarySessionId(cwd, sessionId);
|
|
216
|
+
if (!resolvedSessionId) return null;
|
|
217
|
+
const skillState = await readVisibleSkillActiveState(cwd, resolvedSessionId);
|
|
232
218
|
if (!skillState) return null;
|
|
233
|
-
const activeEntries = listActiveSkills(skillState).filter(entry =>
|
|
219
|
+
const activeEntries = listActiveSkills(skillState).filter(entry =>
|
|
220
|
+
entryMatchesContext(entry, resolvedSessionId, threadId),
|
|
221
|
+
);
|
|
234
222
|
if (activeEntries.length === 0) return null;
|
|
235
223
|
const current = resolveCurrentWorkflowEntry(activeEntries, safeString(skillState.skill).trim());
|
|
236
224
|
if (!isPlanningSkill(current.skill)) return null;
|
|
237
|
-
const modeState = await readVisibleModeState(cwd, current.skill,
|
|
225
|
+
const modeState = await readVisibleModeState(cwd, current.skill, resolvedSessionId);
|
|
238
226
|
if (!modeState) return null;
|
|
239
227
|
if (modeState.active !== true) return null;
|
|
240
|
-
if (!modeStateMatchesContext(modeState,
|
|
228
|
+
if (!modeStateMatchesContext(modeState, resolvedSessionId, threadId)) return null;
|
|
241
229
|
const phase = String(modeState.current_phase ?? current.phase ?? "").trim();
|
|
242
230
|
if (!isBlockingPlanningPhase(current.skill, phase)) return null;
|
|
243
231
|
return { skill: current.skill, phase };
|
|
@@ -342,81 +330,10 @@ function extractEditTargets(args: unknown, tool: ToolWithEditMode): ExtractedTar
|
|
|
342
330
|
return targets;
|
|
343
331
|
}
|
|
344
332
|
|
|
345
|
-
function extractBashTargets(args: unknown): ExtractedTargets {
|
|
346
|
-
const record = getRecord(args);
|
|
347
|
-
const command = safeString(record?.command).trim();
|
|
348
|
-
const targets: ExtractedTargets = { paths: [], unknown: false };
|
|
349
|
-
if (!command) {
|
|
350
|
-
targets.unknown = true;
|
|
351
|
-
return targets;
|
|
352
|
-
}
|
|
353
|
-
// Fast path for a sanctioned `gjc …` invocation, but ONLY when it is a single
|
|
354
|
-
// command with no shell control operators or redirects. Otherwise a compound
|
|
355
|
-
// like `gjc … ; tee src/x` or `gjc … > .gjc/state/foo` would skip scanning and
|
|
356
|
-
// bypass both the planning block and the always-on `.gjc/**` block, so fall
|
|
357
|
-
// through to full token scanning (which leaves the `gjc` segment's own args
|
|
358
|
-
// unextracted but still catches the trailing mutation/redirect).
|
|
359
|
-
if (/^gjc(?:\s|$)/.test(command) && !BASH_CONTROL_OPERATOR_RE.test(command)) return targets;
|
|
360
|
-
|
|
361
|
-
const tokens = command.match(BASH_TOKEN_RE)?.map(unquoteBashToken) ?? [];
|
|
362
|
-
for (let index = 0; index < tokens.length; index++) {
|
|
363
|
-
const token = tokens[index] ?? "";
|
|
364
|
-
if (BASH_REDIRECT_RE.test(token)) {
|
|
365
|
-
addPath(targets, tokens[index + 1]);
|
|
366
|
-
index++;
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
const redirectMatch = token.match(/^(?:\d*)>>?(.+)$/);
|
|
370
|
-
if (redirectMatch?.[1]) {
|
|
371
|
-
addPath(targets, redirectMatch[1]);
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
// A heredoc delimiter (`<<EOF`) is a here-document word, NOT a filesystem
|
|
375
|
-
// target. Consume it without recording a target so a legitimate
|
|
376
|
-
// `cat <<EOF > /tmp/scratch.md` is judged solely by its redirect target.
|
|
377
|
-
if (BASH_HEREDOC_RE.test(token)) {
|
|
378
|
-
index++;
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
if (/^(?:\d*)<<-?.+$/.test(token)) {
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
if (isMutationBashCommand(tokens, index)) {
|
|
385
|
-
for (let targetIndex = index + 1; targetIndex < tokens.length; targetIndex++) {
|
|
386
|
-
const target = tokens[targetIndex] ?? "";
|
|
387
|
-
if (isBashCommandBoundary(target)) break;
|
|
388
|
-
if (target.startsWith("-")) continue;
|
|
389
|
-
addPath(targets, target);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return targets;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function unquoteBashToken(token: string): string {
|
|
397
|
-
if (token.length < 2) return token;
|
|
398
|
-
const quote = token[0];
|
|
399
|
-
if ((quote === "'" || quote === '"') && token.at(-1) === quote) return token.slice(1, -1);
|
|
400
|
-
return token;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function isBashCommandBoundary(token: string): boolean {
|
|
404
|
-
return [";", "&&", "||", "|"].includes(token);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function isMutationBashCommand(tokens: string[], index: number): boolean {
|
|
408
|
-
const token = path.basename(tokens[index] ?? "");
|
|
409
|
-
if (BASH_MUTATION_COMMANDS.has(token)) return true;
|
|
410
|
-
if (token !== "sed") return false;
|
|
411
|
-
const next = tokens[index + 1] ?? "";
|
|
412
|
-
return next === "-i" || next.startsWith("-i") || next.includes("i");
|
|
413
|
-
}
|
|
414
|
-
|
|
415
333
|
function extractTargets(tool: ToolWithEditMode, args: unknown): ExtractedTargets {
|
|
416
334
|
if (tool.name === "write") return extractWriteTargets(args);
|
|
417
335
|
if (tool.name === "ast_edit") return extractAstEditTargets(args);
|
|
418
336
|
if (tool.name === "edit") return extractEditTargets(args, tool);
|
|
419
|
-
if (tool.name === "bash") return extractBashTargets(args);
|
|
420
337
|
return { paths: [], unknown: true };
|
|
421
338
|
}
|
|
422
339
|
|
|
@@ -460,8 +377,9 @@ function relativeGjcSegments(cwd: string, rawPath: string): string[] | null {
|
|
|
460
377
|
function blockedWorkflowStateSkill(cwd: string, rawPath: string): CanonicalGjcWorkflowSkill | null {
|
|
461
378
|
const segments = relativeGjcSegments(cwd, rawPath);
|
|
462
379
|
if (segments?.[0] !== ".gjc") return null;
|
|
463
|
-
|
|
464
|
-
if (
|
|
380
|
+
const generatedRoot = segments[1]?.startsWith(GJC_SESSION_PREFIX) ? segments[2] : segments[1];
|
|
381
|
+
if (generatedRoot === "specs" || generatedRoot === "plans") return null;
|
|
382
|
+
if (generatedRoot !== "state") return null;
|
|
465
383
|
const fileName = segments.at(-1) ?? "";
|
|
466
384
|
for (const skillName of ["deep-interview", "ralplan", "ultragoal", "team"] as const) {
|
|
467
385
|
if (fileName === workflowModeStateFileName(skillName)) return skillName;
|
|
@@ -481,7 +399,8 @@ function firstBlockedWorkflowStateSkill(cwd: string, targets: ExtractedTargets):
|
|
|
481
399
|
function isAllowlistedPath(cwd: string, rawPath: string): boolean {
|
|
482
400
|
const segments = relativeGjcSegments(cwd, rawPath);
|
|
483
401
|
if (segments?.[0] !== ".gjc") return false;
|
|
484
|
-
|
|
402
|
+
const generatedRoot = segments[1]?.startsWith(GJC_SESSION_PREFIX) ? segments[2] : segments[1];
|
|
403
|
+
return generatedRoot === "specs" || generatedRoot === "plans";
|
|
485
404
|
}
|
|
486
405
|
function isBlockedGjcPath(cwd: string, rawPath: string): boolean {
|
|
487
406
|
const segments = relativeGjcSegments(cwd, rawPath);
|
|
@@ -40,7 +40,7 @@ interface UltragoalHudState extends WorkflowGateHudState {
|
|
|
40
40
|
currentGoal?: UltragoalLikeGoal;
|
|
41
41
|
counts: Record<string, number>;
|
|
42
42
|
goals: UltragoalLikeGoal[];
|
|
43
|
-
latestLedgerEvent?: { event?: string; goalId?: string; timestamp?: string };
|
|
43
|
+
latestLedgerEvent?: { event?: string; goalId?: string; timestamp?: string; kind?: string; evidence?: string };
|
|
44
44
|
updatedAt?: string;
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -237,7 +237,9 @@ export function buildUltragoalHudSummary(state: UltragoalHudState): WorkflowHudS
|
|
|
237
237
|
chip(
|
|
238
238
|
"ledger",
|
|
239
239
|
state.latestLedgerEvent?.event
|
|
240
|
-
? [state.latestLedgerEvent.event, state.latestLedgerEvent.goalId]
|
|
240
|
+
? [state.latestLedgerEvent.event, state.latestLedgerEvent.kind, state.latestLedgerEvent.goalId]
|
|
241
|
+
.filter(Boolean)
|
|
242
|
+
.join(":")
|
|
241
243
|
: undefined,
|
|
242
244
|
35,
|
|
243
245
|
),
|
|
@@ -141,10 +141,10 @@ export function sanctionedWorkflowStateCommand(skill: CanonicalGjcWorkflowSkill)
|
|
|
141
141
|
export function describeWorkflowStateContract(skill: CanonicalGjcWorkflowSkill): string[] {
|
|
142
142
|
return [
|
|
143
143
|
`Sanctioned mutation path: gjc state ${skill} read|write --input '<json>'`,
|
|
144
|
-
`Canonical active HUD state: .gjc/
|
|
145
|
-
`Skill mode state: .gjc/
|
|
144
|
+
`Canonical active HUD state: .gjc/_session-{sessionid}/state/${SKILL_ACTIVE_STATE_FILE}`,
|
|
145
|
+
`Skill mode state: .gjc/_session-{sessionid}/state/${workflowModeStateFileName(skill)}`,
|
|
146
146
|
"Receipts include version, skill, owner, command, state_path, storage_path, mutated_at, fresh_until, status, and mutation_id.",
|
|
147
147
|
"Receipts are fresh for 30 minutes; older receipts are stale and render as HUD warnings.",
|
|
148
|
-
"Planning artifacts under .gjc/specs/** and .gjc/plans/** remain writable outside the state command.",
|
|
148
|
+
"Planning artifacts under .gjc/_session-{sessionid}/specs/** and .gjc/_session-{sessionid}/plans/** remain writable outside the state command.",
|
|
149
149
|
];
|
|
150
150
|
}
|
package/src/task/agents.ts
CHANGED
|
@@ -3,20 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Agents are embedded at build time via Bun's import with { type: "text" }.
|
|
5
5
|
*/
|
|
6
|
-
import { Effort } from "@gajae-code/ai";
|
|
7
6
|
import { parseFrontmatter, prompt } from "@gajae-code/utils";
|
|
8
7
|
import { parseAgentFields } from "../discovery/helpers";
|
|
8
|
+
// Embed agent markdown files at build time
|
|
9
9
|
import architectMd from "../prompts/agents/architect.md" with { type: "text" };
|
|
10
10
|
import criticMd from "../prompts/agents/critic.md" with { type: "text" };
|
|
11
11
|
import executorMd from "../prompts/agents/executor.md" with { type: "text" };
|
|
12
|
-
import exploreMd from "../prompts/agents/explore.md" with { type: "text" };
|
|
13
|
-
// Embed agent markdown files at build time
|
|
14
12
|
import agentFrontmatterTemplate from "../prompts/agents/frontmatter.md" with { type: "text" };
|
|
15
|
-
|
|
16
|
-
import planMd from "../prompts/agents/plan.md" with { type: "text" };
|
|
17
13
|
import plannerMd from "../prompts/agents/planner.md" with { type: "text" };
|
|
18
|
-
import reviewerMd from "../prompts/agents/reviewer.md" with { type: "text" };
|
|
19
|
-
import taskMd from "../prompts/agents/task.md" with { type: "text" };
|
|
20
14
|
|
|
21
15
|
import type { AgentDefinition, AgentSource } from "./types";
|
|
22
16
|
|
|
@@ -50,21 +44,6 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
|
|
|
50
44
|
{ fileName: "architect.md", template: architectMd },
|
|
51
45
|
{ fileName: "planner.md", template: plannerMd },
|
|
52
46
|
{ fileName: "critic.md", template: criticMd },
|
|
53
|
-
{ fileName: "explore.md", template: exploreMd },
|
|
54
|
-
{ fileName: "plan.md", template: planMd },
|
|
55
|
-
{ fileName: "reviewer.md", template: reviewerMd },
|
|
56
|
-
{
|
|
57
|
-
fileName: "task.md",
|
|
58
|
-
frontmatter: {
|
|
59
|
-
name: "task",
|
|
60
|
-
description: "General-purpose subagent with full capabilities for delegated multi-step tasks",
|
|
61
|
-
spawns: "*",
|
|
62
|
-
model: "pi/default",
|
|
63
|
-
thinkingLevel: Effort.Medium,
|
|
64
|
-
hide: true,
|
|
65
|
-
},
|
|
66
|
-
template: taskMd,
|
|
67
|
-
},
|
|
68
47
|
];
|
|
69
48
|
|
|
70
49
|
// Computed lazily on first loadBundledAgents() call to avoid eager prompt.render at module load.
|