@gajae-code/coding-agent 0.6.3 → 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 +50 -0
- package/README.md +73 -1
- 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/config/settings-schema.d.ts +27 -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/tmux-sessions.d.ts +2 -0
- 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/components/welcome.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -0
- 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/setup-cli.ts +14 -1
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +2 -2
- package/src/commands/launch.ts +1 -1
- package/src/commands/migrate.ts +46 -0
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +7 -3
- package/src/config/model-registry.ts +9 -2
- package/src/config/model-resolver.ts +13 -2
- package/src/config/settings-schema.ts +17 -0
- 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/exec/bash-executor.ts +3 -1
- 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 +68 -15
- 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 +43 -2
- 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 +9 -6
- 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/components/welcome.ts +42 -9
- package/src/modes/controllers/input-controller.ts +21 -3
- package/src/modes/interactive-mode.ts +22 -1
- package/src/modes/prompt-action-autocomplete.ts +11 -1
- 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/session/session-manager.ts +19 -2
- package/src/setup/hermes/templates/operator-instructions.v1.md +8 -0
- 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/slash-commands/builtin-registry.ts +8 -4
- package/src/system-prompt.ts +11 -9
- 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
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
summarizeRalplanIndex,
|
|
13
13
|
} from "./ledger-event-renderer";
|
|
14
14
|
import { isRestrictedRoleAgentBash } from "./restricted-role-agent-bash";
|
|
15
|
+
import { modeStatePath, sessionPlansDir } from "./session-layout";
|
|
16
|
+
import { resolveGjcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
|
|
15
17
|
import { migrateWorkflowState } from "./state-migrations";
|
|
16
18
|
import { runNativeStateCommand } from "./state-runtime";
|
|
17
19
|
import {
|
|
@@ -169,18 +171,15 @@ interface ResolvedArtifactArgs {
|
|
|
169
171
|
stageN: number;
|
|
170
172
|
runId: string;
|
|
171
173
|
artifact: string;
|
|
172
|
-
sessionId: string
|
|
174
|
+
sessionId: string;
|
|
173
175
|
json: boolean;
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
function ralplanStatePath(cwd: string, sessionId: string
|
|
177
|
-
|
|
178
|
-
? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(sessionId))
|
|
179
|
-
: path.join(cwd, ".gjc", "state");
|
|
180
|
-
return path.join(stateDir, "ralplan-state.json");
|
|
178
|
+
function ralplanStatePath(cwd: string, sessionId: string): string {
|
|
179
|
+
return modeStatePath(cwd, sessionId, "ralplan");
|
|
181
180
|
}
|
|
182
181
|
|
|
183
|
-
async function readActiveRunId(cwd: string, sessionId: string
|
|
182
|
+
async function readActiveRunId(cwd: string, sessionId: string): Promise<string | undefined> {
|
|
184
183
|
const statePath = ralplanStatePath(cwd, sessionId);
|
|
185
184
|
const existingRead = await readExistingStateForMutation(statePath);
|
|
186
185
|
if (existingRead.kind === "absent") return undefined;
|
|
@@ -221,12 +220,7 @@ function advanceCurrentPhase(existingPhase: unknown, stage: RalplanStage): strin
|
|
|
221
220
|
return stage;
|
|
222
221
|
}
|
|
223
222
|
|
|
224
|
-
async function persistActiveRunId(
|
|
225
|
-
cwd: string,
|
|
226
|
-
sessionId: string | undefined,
|
|
227
|
-
runId: string,
|
|
228
|
-
stage: RalplanStage,
|
|
229
|
-
): Promise<void> {
|
|
223
|
+
async function persistActiveRunId(cwd: string, sessionId: string, runId: string, stage: RalplanStage): Promise<void> {
|
|
230
224
|
const statePath = ralplanStatePath(cwd, sessionId);
|
|
231
225
|
const existingRead = await readExistingStateForMutation(statePath);
|
|
232
226
|
if (existingRead.kind === "corrupt") {
|
|
@@ -261,7 +255,7 @@ async function persistActiveRunId(
|
|
|
261
255
|
await writeWorkflowEnvelopeAtomic(statePath, existing, {
|
|
262
256
|
cwd,
|
|
263
257
|
receipt: { cwd, skill: "ralplan", owner: "gjc-runtime", command: "gjc ralplan persist-run-id", sessionId },
|
|
264
|
-
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
258
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan", sessionId },
|
|
265
259
|
});
|
|
266
260
|
}
|
|
267
261
|
|
|
@@ -384,11 +378,7 @@ function plannerStatePayload(update: PlannerStateUpdate): Record<string, unknown
|
|
|
384
378
|
* audit/routing hint only — it records what the caller has already proven and is
|
|
385
379
|
* NOT a durable cross-process subagent registry.
|
|
386
380
|
*/
|
|
387
|
-
async function applyPlannerStateUpdate(
|
|
388
|
-
cwd: string,
|
|
389
|
-
sessionId: string | undefined,
|
|
390
|
-
update: PlannerStateUpdate,
|
|
391
|
-
): Promise<void> {
|
|
381
|
+
async function applyPlannerStateUpdate(cwd: string, sessionId: string, update: PlannerStateUpdate): Promise<void> {
|
|
392
382
|
const statePath = ralplanStatePath(cwd, sessionId);
|
|
393
383
|
const existingRead = await readExistingStateForMutation(statePath);
|
|
394
384
|
if (existingRead.kind === "corrupt") {
|
|
@@ -407,7 +397,7 @@ async function applyPlannerStateUpdate(
|
|
|
407
397
|
await writeWorkflowEnvelopeAtomic(statePath, existing, {
|
|
408
398
|
cwd,
|
|
409
399
|
receipt: { cwd, skill: "ralplan", owner: "gjc-runtime", command: "gjc ralplan planner-state", sessionId },
|
|
410
|
-
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
400
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan", sessionId },
|
|
411
401
|
});
|
|
412
402
|
}
|
|
413
403
|
|
|
@@ -423,9 +413,13 @@ async function resolveArtifactArgs(args: readonly string[], cwd: string): Promis
|
|
|
423
413
|
throw new RalplanCommandError(2, "--artifact is required for ralplan --write");
|
|
424
414
|
}
|
|
425
415
|
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
416
|
+
const session = resolveGjcSessionForWrite(cwd, {
|
|
417
|
+
flagValue: flagValue(args, "--session-id"),
|
|
418
|
+
envSessionId: process.env.GJC_SESSION_ID,
|
|
419
|
+
});
|
|
420
|
+
const sessionId = session.gjcSessionId;
|
|
421
|
+
assertSafePathComponent(sessionId, "session-id");
|
|
422
|
+
const sessionIdRaw = sessionId;
|
|
429
423
|
|
|
430
424
|
// Precedence for run_id:
|
|
431
425
|
// 1. explicit --run-id flag
|
|
@@ -469,13 +463,19 @@ async function persistArtifact(
|
|
|
469
463
|
content: string,
|
|
470
464
|
sha256: string,
|
|
471
465
|
): Promise<PersistedArtifact> {
|
|
472
|
-
const runDir = path.join(cwd,
|
|
466
|
+
const runDir = path.join(sessionPlansDir(cwd, resolved.sessionId), "ralplan", resolved.runId);
|
|
473
467
|
|
|
474
468
|
const fileName = `stage-${pad2(resolved.stageN)}-${resolved.stage}.md`;
|
|
475
469
|
const filePath = path.join(runDir, fileName);
|
|
476
470
|
await writeArtifact(filePath, content, {
|
|
477
471
|
cwd,
|
|
478
|
-
audit: {
|
|
472
|
+
audit: {
|
|
473
|
+
category: "artifact",
|
|
474
|
+
verb: "write",
|
|
475
|
+
owner: "gjc-runtime",
|
|
476
|
+
skill: "ralplan",
|
|
477
|
+
sessionId: resolved.sessionId,
|
|
478
|
+
},
|
|
479
479
|
});
|
|
480
480
|
|
|
481
481
|
const createdAt = new Date().toISOString();
|
|
@@ -488,7 +488,13 @@ async function persistArtifact(
|
|
|
488
488
|
};
|
|
489
489
|
await appendJsonlIdempotent(path.join(runDir, "index.jsonl"), indexEntry, {
|
|
490
490
|
cwd,
|
|
491
|
-
audit: {
|
|
491
|
+
audit: {
|
|
492
|
+
category: "ledger",
|
|
493
|
+
verb: "append",
|
|
494
|
+
owner: "gjc-runtime",
|
|
495
|
+
skill: "ralplan",
|
|
496
|
+
sessionId: resolved.sessionId,
|
|
497
|
+
},
|
|
492
498
|
key: ralplanIndexKey,
|
|
493
499
|
});
|
|
494
500
|
|
|
@@ -497,7 +503,13 @@ async function persistArtifact(
|
|
|
497
503
|
pendingApprovalPath = path.join(runDir, "pending-approval.md");
|
|
498
504
|
await writeArtifact(pendingApprovalPath, content, {
|
|
499
505
|
cwd,
|
|
500
|
-
audit: {
|
|
506
|
+
audit: {
|
|
507
|
+
category: "artifact",
|
|
508
|
+
verb: "write",
|
|
509
|
+
owner: "gjc-runtime",
|
|
510
|
+
skill: "ralplan",
|
|
511
|
+
sessionId: resolved.sessionId,
|
|
512
|
+
},
|
|
501
513
|
});
|
|
502
514
|
}
|
|
503
515
|
|
|
@@ -528,11 +540,12 @@ interface ExistingStageArtifact {
|
|
|
528
540
|
*/
|
|
529
541
|
async function findExistingStageArtifact(
|
|
530
542
|
cwd: string,
|
|
543
|
+
sessionId: string,
|
|
531
544
|
runId: string,
|
|
532
545
|
stage: RalplanStage,
|
|
533
546
|
stageN: number,
|
|
534
547
|
): Promise<ExistingStageArtifact | undefined> {
|
|
535
|
-
const indexPath = path.join(cwd,
|
|
548
|
+
const indexPath = path.join(sessionPlansDir(cwd, sessionId), "ralplan", runId, "index.jsonl");
|
|
536
549
|
let text: string;
|
|
537
550
|
try {
|
|
538
551
|
text = await fs.readFile(indexPath, "utf8");
|
|
@@ -566,9 +579,9 @@ async function findExistingStageArtifact(
|
|
|
566
579
|
* Read and parse the run's `index.jsonl` rows. Best-effort: returns [] when the
|
|
567
580
|
* file is absent or unreadable so HUD sync never fails on a missing index.
|
|
568
581
|
*/
|
|
569
|
-
async function readRalplanIndexRows(cwd: string, runId: string): Promise<RalplanIndexRow[]> {
|
|
582
|
+
async function readRalplanIndexRows(cwd: string, sessionId: string, runId: string): Promise<RalplanIndexRow[]> {
|
|
570
583
|
try {
|
|
571
|
-
const indexPath = path.join(cwd,
|
|
584
|
+
const indexPath = path.join(sessionPlansDir(cwd, sessionId), "ralplan", runId, "index.jsonl");
|
|
572
585
|
const text = await fs.readFile(indexPath, "utf8");
|
|
573
586
|
const rows: RalplanIndexRow[] = [];
|
|
574
587
|
for (const line of text.split(/\r?\n/)) {
|
|
@@ -583,7 +596,7 @@ async function readRalplanIndexRows(cwd: string, runId: string): Promise<Ralplan
|
|
|
583
596
|
|
|
584
597
|
async function syncRalplanHud(options: {
|
|
585
598
|
cwd: string;
|
|
586
|
-
sessionId
|
|
599
|
+
sessionId: string;
|
|
587
600
|
stage: string;
|
|
588
601
|
pendingApproval: boolean;
|
|
589
602
|
iteration?: number;
|
|
@@ -612,11 +625,12 @@ async function buildRalplanHud(options: {
|
|
|
612
625
|
iteration?: number;
|
|
613
626
|
latestSummary?: string;
|
|
614
627
|
runId?: string;
|
|
628
|
+
sessionId?: string;
|
|
615
629
|
}) {
|
|
616
630
|
let iterationFromIndex: number | undefined;
|
|
617
631
|
let stages: string | undefined;
|
|
618
|
-
if (options.runId) {
|
|
619
|
-
const rows = await readRalplanIndexRows(options.cwd, options.runId);
|
|
632
|
+
if (options.runId && options.sessionId) {
|
|
633
|
+
const rows = await readRalplanIndexRows(options.cwd, options.sessionId, options.runId);
|
|
620
634
|
if (rows.length > 0) {
|
|
621
635
|
const summary = summarizeRalplanIndex(rows);
|
|
622
636
|
iterationFromIndex = summary.iteration;
|
|
@@ -643,7 +657,13 @@ async function handleArtifactWrite(args: readonly string[], cwd: string): Promis
|
|
|
643
657
|
// Duplicate-write guard: a second `--write` for the same (stage, stage_n) must not
|
|
644
658
|
// silently clobber the artifact or append a duplicate ledger row. Classify before any
|
|
645
659
|
// state mutation so a conflict never regresses run-state phase.
|
|
646
|
-
const existingArtifact = await findExistingStageArtifact(
|
|
660
|
+
const existingArtifact = await findExistingStageArtifact(
|
|
661
|
+
cwd,
|
|
662
|
+
resolved.sessionId,
|
|
663
|
+
resolved.runId,
|
|
664
|
+
resolved.stage,
|
|
665
|
+
resolved.stageN,
|
|
666
|
+
);
|
|
647
667
|
if (existingArtifact) {
|
|
648
668
|
if (existingArtifact.sha256 !== sha256) {
|
|
649
669
|
throw new RalplanCommandError(
|
|
@@ -660,6 +680,7 @@ async function handleArtifactWrite(args: readonly string[], cwd: string): Promis
|
|
|
660
680
|
if (plannerState) {
|
|
661
681
|
await applyPlannerStateUpdate(cwd, resolved.sessionId, plannerState);
|
|
662
682
|
}
|
|
683
|
+
await writeSessionActivityMarker(cwd, resolved.sessionId, { writer: "ralplan-runtime", path: persisted.path });
|
|
663
684
|
await syncRalplanHud({
|
|
664
685
|
cwd,
|
|
665
686
|
sessionId: resolved.sessionId,
|
|
@@ -706,7 +727,12 @@ function buildDeduplicatedResult(
|
|
|
706
727
|
deduplicated: true,
|
|
707
728
|
};
|
|
708
729
|
if (resolved.stage === "final") {
|
|
709
|
-
payload.pending_approval_path = path.join(
|
|
730
|
+
payload.pending_approval_path = path.join(
|
|
731
|
+
sessionPlansDir(cwd, resolved.sessionId),
|
|
732
|
+
"ralplan",
|
|
733
|
+
resolved.runId,
|
|
734
|
+
"pending-approval.md",
|
|
735
|
+
);
|
|
710
736
|
}
|
|
711
737
|
const stdout = resolved.json
|
|
712
738
|
? `${JSON.stringify(payload, null, 2)}\n`
|
|
@@ -721,7 +747,7 @@ interface ConsensusHandoffArgs {
|
|
|
721
747
|
deliberate: boolean;
|
|
722
748
|
architectKind?: string;
|
|
723
749
|
criticKind?: string;
|
|
724
|
-
sessionId
|
|
750
|
+
sessionId: string;
|
|
725
751
|
task: string;
|
|
726
752
|
json: boolean;
|
|
727
753
|
}
|
|
@@ -747,7 +773,7 @@ function extractPositionalTask(args: readonly string[]): string {
|
|
|
747
773
|
return parts.join(" ").trim();
|
|
748
774
|
}
|
|
749
775
|
|
|
750
|
-
function resolveConsensusArgs(args: readonly string[]): ConsensusHandoffArgs {
|
|
776
|
+
function resolveConsensusArgs(args: readonly string[], cwd: string): ConsensusHandoffArgs {
|
|
751
777
|
const architectKind = flagValue(args, "--architect")?.trim() || undefined;
|
|
752
778
|
if (architectKind && !KNOWN_ARCHITECT_KINDS.has(architectKind)) {
|
|
753
779
|
throw new RalplanCommandError(
|
|
@@ -762,8 +788,12 @@ function resolveConsensusArgs(args: readonly string[]): ConsensusHandoffArgs {
|
|
|
762
788
|
`unknown --critic kind: ${criticKind}. Expected one of: ${[...KNOWN_CRITIC_KINDS].join(", ")}.`,
|
|
763
789
|
);
|
|
764
790
|
}
|
|
765
|
-
const
|
|
766
|
-
|
|
791
|
+
const session = resolveGjcSessionForWrite(cwd, {
|
|
792
|
+
flagValue: flagValue(args, "--session-id"),
|
|
793
|
+
envSessionId: process.env.GJC_SESSION_ID,
|
|
794
|
+
});
|
|
795
|
+
const sessionId = session.gjcSessionId;
|
|
796
|
+
assertSafePathComponent(sessionId, "session-id");
|
|
767
797
|
const task = extractPositionalTask(args);
|
|
768
798
|
return {
|
|
769
799
|
interactive: hasFlag(args, "--interactive"),
|
|
@@ -776,19 +806,11 @@ function resolveConsensusArgs(args: readonly string[]): ConsensusHandoffArgs {
|
|
|
776
806
|
};
|
|
777
807
|
}
|
|
778
808
|
|
|
779
|
-
function encodeSessionSegment(value: string): string {
|
|
780
|
-
return encodeURIComponent(value).replaceAll(".", "%2E");
|
|
781
|
-
}
|
|
782
|
-
|
|
783
809
|
async function seedRalplanState(
|
|
784
810
|
cwd: string,
|
|
785
811
|
resolved: ConsensusHandoffArgs,
|
|
786
812
|
): Promise<{ statePath: string; runId: string }> {
|
|
787
|
-
const
|
|
788
|
-
? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(resolved.sessionId))
|
|
789
|
-
: path.join(cwd, ".gjc", "state");
|
|
790
|
-
|
|
791
|
-
const statePath = path.join(stateDir, "ralplan-state.json");
|
|
813
|
+
const statePath = ralplanStatePath(cwd, resolved.sessionId);
|
|
792
814
|
// Reuse an existing run id when present so a re-invocation of `gjc ralplan "task"` doesn't
|
|
793
815
|
// orphan in-progress artifacts under a fresh run id.
|
|
794
816
|
const existingRunId = await readActiveRunId(cwd, resolved.sessionId);
|
|
@@ -818,13 +840,20 @@ async function seedRalplanState(
|
|
|
818
840
|
command: "gjc ralplan seed",
|
|
819
841
|
sessionId: resolved.sessionId,
|
|
820
842
|
},
|
|
821
|
-
audit: {
|
|
843
|
+
audit: {
|
|
844
|
+
category: "state",
|
|
845
|
+
verb: "write",
|
|
846
|
+
owner: "gjc-runtime",
|
|
847
|
+
skill: "ralplan",
|
|
848
|
+
sessionId: resolved.sessionId,
|
|
849
|
+
},
|
|
822
850
|
});
|
|
851
|
+
await writeSessionActivityMarker(cwd, resolved.sessionId, { writer: "ralplan-runtime", path: statePath });
|
|
823
852
|
return { statePath, runId };
|
|
824
853
|
}
|
|
825
854
|
|
|
826
855
|
async function handleConsensusHandoff(args: readonly string[], cwd: string): Promise<RalplanCommandResult> {
|
|
827
|
-
const resolved = resolveConsensusArgs(args);
|
|
856
|
+
const resolved = resolveConsensusArgs(args, cwd);
|
|
828
857
|
if (!resolved.task) {
|
|
829
858
|
throw new RalplanCommandError(2, 'gjc ralplan requires a task description, e.g. `gjc ralplan "<task>"`.');
|
|
830
859
|
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure path layout for session-scoped GJC workflow state.
|
|
3
|
+
*
|
|
4
|
+
* Every generated/runtime artifact for a GJC session lives under
|
|
5
|
+
* `<cwd>/.gjc/_session-{encodedSessionId}/...`. The `_session-` prefix is what
|
|
6
|
+
* discriminates a session directory from shared, user-authored/installed config
|
|
7
|
+
* (settings.json, secrets.yml, agents/, gjc-plugins/, agent/, python-env/, user
|
|
8
|
+
* skills/commands), which always stays at the `.gjc/` root.
|
|
9
|
+
*
|
|
10
|
+
* This module is PURE and acyclic: every export is a deterministic function of
|
|
11
|
+
* its arguments. It never reads `process.env` and never touches the filesystem.
|
|
12
|
+
* Session resolution (flag/payload/env/latest-activity-marker) and any
|
|
13
|
+
* filesystem scanning live in `session-resolution.ts`, the boundary module.
|
|
14
|
+
*/
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
|
|
17
|
+
export const GJC_DIR = ".gjc";
|
|
18
|
+
export const GJC_SESSION_PREFIX = "_session-";
|
|
19
|
+
export const GJC_SESSION_ACTIVITY_FILE = ".session-activity.json";
|
|
20
|
+
|
|
21
|
+
/** Source that produced a resolved GJC session id, for audit/diagnostics. */
|
|
22
|
+
export type GjcSessionSource = "flag" | "payload" | "env" | "latest";
|
|
23
|
+
|
|
24
|
+
export interface GjcSessionContext {
|
|
25
|
+
gjcSessionId: string;
|
|
26
|
+
sessionRoot: string;
|
|
27
|
+
source: GjcSessionSource;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Encode a session id into a single safe path segment. Matches the historical
|
|
32
|
+
* encoding used across the runtimes so ids round-trip identically:
|
|
33
|
+
* `encodeURIComponent` plus dot-escaping (dots are legal in filenames but we
|
|
34
|
+
* avoid `.`/`..` traversal ambiguity).
|
|
35
|
+
*/
|
|
36
|
+
export function encodeSessionSegment(value: string): string {
|
|
37
|
+
return encodeURIComponent(value).replaceAll(".", "%2E");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Inverse of {@link encodeSessionSegment}. */
|
|
41
|
+
export function decodeSessionSegment(segment: string): string {
|
|
42
|
+
return decodeURIComponent(segment.replaceAll("%2E", "."));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Throw when a session id is missing or blank; never let blank suppress callers. */
|
|
46
|
+
export function assertNonEmptyGjcSessionId(value: string | undefined, source: string): asserts value is string {
|
|
47
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
48
|
+
throw new Error(`a non-empty GJC session id is required (${source})`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Assert a value is safe to use as a single path segment: non-blank and free of
|
|
54
|
+
* path separators or `.`/`..` traversal. Use for already-safe identifiers
|
|
55
|
+
* (skill modes, slugs) where we want identical filenames but fail closed on
|
|
56
|
+
* traversal rather than silently normalizing out of the intended directory.
|
|
57
|
+
*/
|
|
58
|
+
export function assertSafePathComponent(value: string, label: string): void {
|
|
59
|
+
const trimmed = value.trim();
|
|
60
|
+
if (trimmed === "") throw new Error(`${label} is required`);
|
|
61
|
+
if (trimmed === "." || trimmed === ".." || /[/\\]/.test(trimmed)) {
|
|
62
|
+
throw new Error(`${label} must be a safe path component (no separators or traversal): ${value}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The shared `.gjc/` root (holds shared config; never session-scoped). */
|
|
67
|
+
export function gjcRoot(cwd: string): string {
|
|
68
|
+
return path.join(cwd, GJC_DIR);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The per-session root directory: `<cwd>/.gjc/_session-{encodedId}`. */
|
|
72
|
+
export function sessionRoot(cwd: string, gjcSessionId: string): string {
|
|
73
|
+
assertNonEmptyGjcSessionId(gjcSessionId, "sessionRoot");
|
|
74
|
+
return path.join(gjcRoot(cwd), `${GJC_SESSION_PREFIX}${encodeSessionSegment(gjcSessionId)}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Directory name (no path) for a session id, e.g. `_session-abc`. */
|
|
78
|
+
export function sessionDirName(gjcSessionId: string): string {
|
|
79
|
+
assertNonEmptyGjcSessionId(gjcSessionId, "sessionDirName");
|
|
80
|
+
return `${GJC_SESSION_PREFIX}${encodeSessionSegment(gjcSessionId)}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Return the decoded session id for a `_session-*` directory name, else undefined. */
|
|
84
|
+
export function sessionIdFromDirName(name: string): string | undefined {
|
|
85
|
+
if (!name.startsWith(GJC_SESSION_PREFIX)) return undefined;
|
|
86
|
+
const suffix = name.slice(GJC_SESSION_PREFIX.length);
|
|
87
|
+
if (suffix === "") return undefined;
|
|
88
|
+
let decoded: string;
|
|
89
|
+
try {
|
|
90
|
+
decoded = decodeSessionSegment(suffix);
|
|
91
|
+
} catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
return decoded.trim() === "" ? undefined : decoded;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Authoritative per-session activity marker path. */
|
|
98
|
+
export function sessionActivityPath(cwd: string, gjcSessionId: string): string {
|
|
99
|
+
return path.join(sessionRoot(cwd, gjcSessionId), GJC_SESSION_ACTIVITY_FILE);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---- Top-level per-category subdir resolvers ----
|
|
103
|
+
|
|
104
|
+
export function sessionStateDir(cwd: string, gjcSessionId: string): string {
|
|
105
|
+
return path.join(sessionRoot(cwd, gjcSessionId), "state");
|
|
106
|
+
}
|
|
107
|
+
export function sessionSpecsDir(cwd: string, gjcSessionId: string): string {
|
|
108
|
+
return path.join(sessionRoot(cwd, gjcSessionId), "specs");
|
|
109
|
+
}
|
|
110
|
+
export function sessionPlansDir(cwd: string, gjcSessionId: string): string {
|
|
111
|
+
return path.join(sessionRoot(cwd, gjcSessionId), "plans");
|
|
112
|
+
}
|
|
113
|
+
export function sessionUltragoalDir(cwd: string, gjcSessionId: string): string {
|
|
114
|
+
return path.join(sessionRoot(cwd, gjcSessionId), "ultragoal");
|
|
115
|
+
}
|
|
116
|
+
export function sessionAuditDir(cwd: string, gjcSessionId: string): string {
|
|
117
|
+
return path.join(sessionRoot(cwd, gjcSessionId), "audit");
|
|
118
|
+
}
|
|
119
|
+
export function sessionReportsDir(cwd: string, gjcSessionId: string): string {
|
|
120
|
+
return path.join(sessionRoot(cwd, gjcSessionId), "reports");
|
|
121
|
+
}
|
|
122
|
+
export function sessionLogsDir(cwd: string, gjcSessionId: string): string {
|
|
123
|
+
return path.join(sessionRoot(cwd, gjcSessionId), "logs");
|
|
124
|
+
}
|
|
125
|
+
export function sessionRuntimeDir(cwd: string, gjcSessionId: string): string {
|
|
126
|
+
return path.join(sessionRoot(cwd, gjcSessionId), "runtime");
|
|
127
|
+
}
|
|
128
|
+
export function sessionRlmDir(cwd: string, gjcSessionId: string): string {
|
|
129
|
+
return path.join(sessionRoot(cwd, gjcSessionId), "rlm");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---- Nested resolvers under <sessionRoot>/state ----
|
|
133
|
+
|
|
134
|
+
export function activeStateDir(cwd: string, gjcSessionId: string): string {
|
|
135
|
+
return path.join(sessionStateDir(cwd, gjcSessionId), "active");
|
|
136
|
+
}
|
|
137
|
+
export function activeSnapshotPath(cwd: string, gjcSessionId: string): string {
|
|
138
|
+
return path.join(sessionStateDir(cwd, gjcSessionId), "skill-active-state.json");
|
|
139
|
+
}
|
|
140
|
+
export function activeEntryPath(cwd: string, gjcSessionId: string, skill: string): string {
|
|
141
|
+
const normalized = skill.trim();
|
|
142
|
+
if (normalized === "") throw new Error("skill is required");
|
|
143
|
+
return path.join(activeStateDir(cwd, gjcSessionId), `${encodeSessionSegment(normalized)}.json`);
|
|
144
|
+
}
|
|
145
|
+
export function modeStatePath(cwd: string, gjcSessionId: string, mode: string): string {
|
|
146
|
+
const normalized = mode.trim();
|
|
147
|
+
assertSafePathComponent(normalized, "mode");
|
|
148
|
+
return path.join(sessionStateDir(cwd, gjcSessionId), `${normalized}-state.json`);
|
|
149
|
+
}
|
|
150
|
+
export function auditPath(cwd: string, gjcSessionId: string): string {
|
|
151
|
+
return path.join(sessionStateDir(cwd, gjcSessionId), "audit.jsonl");
|
|
152
|
+
}
|
|
153
|
+
export function transactionJournalPath(cwd: string, gjcSessionId: string, mutationId: string): string {
|
|
154
|
+
return path.join(sessionStateDir(cwd, gjcSessionId), "transactions", `${encodeSessionSegment(mutationId)}.json`);
|
|
155
|
+
}
|
|
156
|
+
export function teamStateRoot(cwd: string, gjcSessionId: string): string {
|
|
157
|
+
return path.join(sessionStateDir(cwd, gjcSessionId), "team");
|
|
158
|
+
}
|
|
159
|
+
export function workflowGatePath(cwd: string, gjcSessionId: string, gateId: string): string {
|
|
160
|
+
return path.join(sessionStateDir(cwd, gjcSessionId), "workflow-gates", `${encodeSessionSegment(gateId)}.json`);
|
|
161
|
+
}
|
|
162
|
+
export function harnessStateRoot(cwd: string, gjcSessionId: string): string {
|
|
163
|
+
return path.join(sessionStateDir(cwd, gjcSessionId), "harness");
|
|
164
|
+
}
|
|
165
|
+
export function coordinatorMcpStateRoot(cwd: string, gjcSessionId: string): string {
|
|
166
|
+
return path.join(sessionStateDir(cwd, gjcSessionId), "coordinator-mcp");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---- Nested resolvers under other top-level categories ----
|
|
170
|
+
|
|
171
|
+
export function tmuxRuntimeSessionPath(cwd: string, gjcSessionId: string, slug: string): string {
|
|
172
|
+
const normalized = slug.trim();
|
|
173
|
+
assertSafePathComponent(normalized, "slug");
|
|
174
|
+
return path.join(sessionRuntimeDir(cwd, gjcSessionId), "tmux-sessions", `${normalized}.json`);
|
|
175
|
+
}
|
|
176
|
+
export function rlmArtifactRoot(cwd: string, gjcSessionId: string, rlmSessionId: string): string {
|
|
177
|
+
const normalized = rlmSessionId.trim();
|
|
178
|
+
if (normalized === "") throw new Error("rlmSessionId is required");
|
|
179
|
+
return path.join(sessionRlmDir(cwd, gjcSessionId), encodeSessionSegment(normalized));
|
|
180
|
+
}
|