@gajae-code/coding-agent 0.2.4 → 0.3.0
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 +27 -0
- package/README.md +1 -1
- package/dist/types/async/job-manager.d.ts +145 -2
- package/dist/types/commands/harness.d.ts +37 -0
- package/dist/types/config/settings-schema.d.ts +13 -3
- package/dist/types/config/settings.d.ts +3 -1
- package/dist/types/deep-interview/render-middleware.d.ts +5 -0
- package/dist/types/discovery/helpers.d.ts +1 -0
- package/dist/types/exec/bash-executor.d.ts +8 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +6 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- package/dist/types/gjc-runtime/restricted-role-agent-bash.d.ts +2 -0
- package/dist/types/gjc-runtime/state-graph.d.ts +4 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +24 -0
- package/dist/types/gjc-runtime/state-renderer.d.ts +65 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +137 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +54 -0
- package/dist/types/harness-control-plane/classifier.d.ts +13 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +30 -0
- package/dist/types/harness-control-plane/finalize.d.ts +47 -0
- package/dist/types/harness-control-plane/frame-mapper.d.ts +29 -0
- package/dist/types/harness-control-plane/operate.d.ts +35 -0
- package/dist/types/harness-control-plane/owner.d.ts +46 -0
- package/dist/types/harness-control-plane/preserve.d.ts +19 -0
- package/dist/types/harness-control-plane/receipts.d.ts +88 -0
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
- package/dist/types/harness-control-plane/seams.d.ts +21 -0
- package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +19 -0
- package/dist/types/harness-control-plane/storage.d.ts +53 -0
- package/dist/types/harness-control-plane/types.d.ts +162 -0
- package/dist/types/hooks/skill-keywords.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +2 -29
- package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
- package/dist/types/modes/components/hook-selector.d.ts +1 -0
- package/dist/types/modes/components/skill-hud/render.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/theme/defaults/index.d.ts +45 -9477
- package/dist/types/modes/theme/theme.d.ts +1 -5
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +8 -0
- package/dist/types/session/streaming-output.d.ts +11 -0
- package/dist/types/skill-state/active-state.d.ts +3 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +24 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/types.d.ts +56 -3
- package/dist/types/tools/bash-allowed-prefixes.d.ts +5 -0
- package/dist/types/tools/bash.d.ts +24 -0
- package/dist/types/tools/cron.d.ts +110 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/monitor.d.ts +54 -0
- package/dist/types/tools/subagent.d.ts +11 -1
- package/dist/types/web/search/index.d.ts +1 -0
- package/dist/types/web/search/provider.d.ts +11 -4
- package/dist/types/web/search/providers/duckduckgo.d.ts +57 -0
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +7 -7
- package/src/async/job-manager.ts +522 -6
- package/src/cli/agents-cli.ts +3 -0
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/config-cli.ts +10 -2
- package/src/cli.ts +2 -0
- package/src/commands/harness.ts +592 -0
- package/src/commands/team.ts +36 -39
- package/src/config/settings-schema.ts +15 -2
- package/src/config/settings.ts +49 -7
- package/src/deep-interview/render-middleware.ts +366 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +9 -2
- package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
- package/src/defaults/gjc/skills/team/SKILL.md +47 -21
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +78 -11
- package/src/discovery/helpers.ts +5 -0
- package/src/eval/js/shared/rewrite-imports.ts +1 -2
- package/src/exec/bash-executor.ts +20 -9
- package/src/extensibility/custom-tools/types.ts +1 -0
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/extensibility/shared-events.ts +1 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +40 -21
- package/src/gjc-runtime/goal-mode-request.ts +11 -3
- package/src/gjc-runtime/ralplan-runtime.ts +27 -10
- package/src/gjc-runtime/restricted-role-agent-bash.ts +5 -0
- package/src/gjc-runtime/state-graph.ts +86 -0
- package/src/gjc-runtime/state-migrations.ts +132 -0
- package/src/gjc-runtime/state-renderer.ts +345 -0
- package/src/gjc-runtime/state-runtime.ts +733 -21
- package/src/gjc-runtime/state-validation.ts +49 -0
- package/src/gjc-runtime/state-writer.ts +718 -0
- package/src/gjc-runtime/team-runtime.ts +1083 -89
- package/src/gjc-runtime/ultragoal-runtime.ts +348 -19
- package/src/gjc-runtime/workflow-manifest.generated.json +1497 -0
- package/src/gjc-runtime/workflow-manifest.ts +425 -0
- package/src/harness-control-plane/classifier.ts +128 -0
- package/src/harness-control-plane/control-endpoint.ts +137 -0
- package/src/harness-control-plane/finalize.ts +222 -0
- package/src/harness-control-plane/frame-mapper.ts +286 -0
- package/src/harness-control-plane/operate.ts +225 -0
- package/src/harness-control-plane/owner.ts +553 -0
- package/src/harness-control-plane/preserve.ts +102 -0
- package/src/harness-control-plane/receipts.ts +216 -0
- package/src/harness-control-plane/rpc-adapter.ts +276 -0
- package/src/harness-control-plane/seams.ts +39 -0
- package/src/harness-control-plane/session-lease.ts +388 -0
- package/src/harness-control-plane/state-machine.ts +97 -0
- package/src/harness-control-plane/storage.ts +257 -0
- package/src/harness-control-plane/types.ts +214 -0
- package/src/hooks/skill-keywords.ts +4 -2
- package/src/hooks/skill-state.ts +25 -42
- package/src/internal-urls/docs-index.generated.ts +6 -4
- package/src/lsp/render.ts +1 -1
- package/src/modes/acp/acp-agent.ts +1 -1
- package/src/modes/acp/acp-client-bridge.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +1 -1
- package/src/modes/components/assistant-message.ts +5 -1
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/hook-selector.ts +72 -2
- package/src/modes/components/skill-hud/render.ts +7 -2
- package/src/modes/controllers/event-controller.ts +71 -6
- package/src/modes/controllers/extension-ui-controller.ts +6 -0
- package/src/modes/controllers/input-controller.ts +19 -3
- package/src/modes/controllers/selector-controller.ts +3 -2
- package/src/modes/interactive-mode.ts +21 -2
- package/src/modes/theme/defaults/index.ts +0 -196
- package/src/modes/theme/theme.ts +35 -35
- package/src/modes/types.ts +2 -0
- package/src/prompts/agents/architect.md +5 -1
- package/src/prompts/agents/critic.md +5 -1
- package/src/prompts/agents/executor.md +13 -0
- package/src/prompts/agents/frontmatter.md +1 -0
- package/src/prompts/agents/planner.md +5 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/cron.md +25 -0
- package/src/prompts/tools/monitor.md +30 -0
- package/src/prompts/tools/subagent.md +33 -3
- package/src/runtime-mcp/oauth-flow.ts +4 -2
- package/src/sdk.ts +7 -0
- package/src/session/agent-session.ts +247 -38
- package/src/session/session-manager.ts +13 -1
- package/src/session/streaming-output.ts +21 -0
- package/src/skill-state/active-state.ts +222 -78
- package/src/skill-state/deep-interview-mutation-guard.ts +91 -13
- package/src/skill-state/initial-phase.ts +2 -0
- package/src/skill-state/workflow-state-contract.ts +26 -0
- package/src/task/agents.ts +1 -0
- package/src/task/executor.ts +51 -8
- package/src/task/index.ts +120 -8
- package/src/task/render.ts +6 -3
- package/src/task/types.ts +57 -3
- package/src/tools/ask.ts +28 -7
- package/src/tools/bash-allowed-prefixes.ts +169 -0
- package/src/tools/bash.ts +190 -29
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/cron.ts +665 -0
- package/src/tools/index.ts +20 -2
- package/src/tools/monitor.ts +136 -0
- package/src/tools/subagent.ts +255 -64
- package/src/vim/engine.ts +3 -3
- package/src/web/search/index.ts +31 -18
- package/src/web/search/provider.ts +57 -12
- package/src/web/search/providers/duckduckgo.ts +279 -0
- package/src/web/search/types.ts +2 -0
- package/src/modes/theme/dark.json +0 -95
- package/src/modes/theme/defaults/alabaster.json +0 -93
- package/src/modes/theme/defaults/amethyst.json +0 -96
- package/src/modes/theme/defaults/anthracite.json +0 -93
- package/src/modes/theme/defaults/basalt.json +0 -91
- package/src/modes/theme/defaults/birch.json +0 -95
- package/src/modes/theme/defaults/dark-abyss.json +0 -91
- package/src/modes/theme/defaults/dark-arctic.json +0 -104
- package/src/modes/theme/defaults/dark-aurora.json +0 -95
- package/src/modes/theme/defaults/dark-catppuccin.json +0 -107
- package/src/modes/theme/defaults/dark-cavern.json +0 -91
- package/src/modes/theme/defaults/dark-copper.json +0 -95
- package/src/modes/theme/defaults/dark-cosmos.json +0 -90
- package/src/modes/theme/defaults/dark-cyberpunk.json +0 -102
- package/src/modes/theme/defaults/dark-dracula.json +0 -98
- package/src/modes/theme/defaults/dark-eclipse.json +0 -91
- package/src/modes/theme/defaults/dark-ember.json +0 -95
- package/src/modes/theme/defaults/dark-equinox.json +0 -90
- package/src/modes/theme/defaults/dark-forest.json +0 -96
- package/src/modes/theme/defaults/dark-github.json +0 -105
- package/src/modes/theme/defaults/dark-gruvbox.json +0 -112
- package/src/modes/theme/defaults/dark-lavender.json +0 -95
- package/src/modes/theme/defaults/dark-lunar.json +0 -89
- package/src/modes/theme/defaults/dark-midnight.json +0 -95
- package/src/modes/theme/defaults/dark-monochrome.json +0 -94
- package/src/modes/theme/defaults/dark-monokai.json +0 -98
- package/src/modes/theme/defaults/dark-nebula.json +0 -90
- package/src/modes/theme/defaults/dark-nord.json +0 -97
- package/src/modes/theme/defaults/dark-ocean.json +0 -101
- package/src/modes/theme/defaults/dark-one.json +0 -100
- package/src/modes/theme/defaults/dark-poimandres.json +0 -141
- package/src/modes/theme/defaults/dark-rainforest.json +0 -91
- package/src/modes/theme/defaults/dark-reef.json +0 -91
- package/src/modes/theme/defaults/dark-retro.json +0 -92
- package/src/modes/theme/defaults/dark-rose-pine.json +0 -96
- package/src/modes/theme/defaults/dark-sakura.json +0 -95
- package/src/modes/theme/defaults/dark-slate.json +0 -95
- package/src/modes/theme/defaults/dark-solarized.json +0 -97
- package/src/modes/theme/defaults/dark-solstice.json +0 -90
- package/src/modes/theme/defaults/dark-starfall.json +0 -91
- package/src/modes/theme/defaults/dark-sunset.json +0 -99
- package/src/modes/theme/defaults/dark-swamp.json +0 -90
- package/src/modes/theme/defaults/dark-synthwave.json +0 -103
- package/src/modes/theme/defaults/dark-taiga.json +0 -91
- package/src/modes/theme/defaults/dark-terminal.json +0 -95
- package/src/modes/theme/defaults/dark-tokyo-night.json +0 -101
- package/src/modes/theme/defaults/dark-tundra.json +0 -91
- package/src/modes/theme/defaults/dark-twilight.json +0 -91
- package/src/modes/theme/defaults/dark-volcanic.json +0 -91
- package/src/modes/theme/defaults/graphite.json +0 -92
- package/src/modes/theme/defaults/light-arctic.json +0 -107
- package/src/modes/theme/defaults/light-aurora-day.json +0 -91
- package/src/modes/theme/defaults/light-canyon.json +0 -91
- package/src/modes/theme/defaults/light-catppuccin.json +0 -106
- package/src/modes/theme/defaults/light-cirrus.json +0 -90
- package/src/modes/theme/defaults/light-coral.json +0 -95
- package/src/modes/theme/defaults/light-cyberpunk.json +0 -96
- package/src/modes/theme/defaults/light-dawn.json +0 -90
- package/src/modes/theme/defaults/light-dunes.json +0 -91
- package/src/modes/theme/defaults/light-eucalyptus.json +0 -95
- package/src/modes/theme/defaults/light-forest.json +0 -100
- package/src/modes/theme/defaults/light-frost.json +0 -95
- package/src/modes/theme/defaults/light-github.json +0 -115
- package/src/modes/theme/defaults/light-glacier.json +0 -91
- package/src/modes/theme/defaults/light-gruvbox.json +0 -108
- package/src/modes/theme/defaults/light-haze.json +0 -90
- package/src/modes/theme/defaults/light-honeycomb.json +0 -95
- package/src/modes/theme/defaults/light-lagoon.json +0 -91
- package/src/modes/theme/defaults/light-lavender.json +0 -95
- package/src/modes/theme/defaults/light-meadow.json +0 -91
- package/src/modes/theme/defaults/light-mint.json +0 -95
- package/src/modes/theme/defaults/light-monochrome.json +0 -101
- package/src/modes/theme/defaults/light-ocean.json +0 -99
- package/src/modes/theme/defaults/light-one.json +0 -99
- package/src/modes/theme/defaults/light-opal.json +0 -91
- package/src/modes/theme/defaults/light-orchard.json +0 -91
- package/src/modes/theme/defaults/light-paper.json +0 -95
- package/src/modes/theme/defaults/light-poimandres.json +0 -141
- package/src/modes/theme/defaults/light-prism.json +0 -90
- package/src/modes/theme/defaults/light-retro.json +0 -98
- package/src/modes/theme/defaults/light-sand.json +0 -95
- package/src/modes/theme/defaults/light-savanna.json +0 -91
- package/src/modes/theme/defaults/light-solarized.json +0 -102
- package/src/modes/theme/defaults/light-soleil.json +0 -90
- package/src/modes/theme/defaults/light-sunset.json +0 -99
- package/src/modes/theme/defaults/light-synthwave.json +0 -98
- package/src/modes/theme/defaults/light-tokyo-night.json +0 -111
- package/src/modes/theme/defaults/light-wetland.json +0 -91
- package/src/modes/theme/defaults/light-zenith.json +0 -89
- package/src/modes/theme/defaults/limestone.json +0 -94
- package/src/modes/theme/defaults/mahogany.json +0 -97
- package/src/modes/theme/defaults/marble.json +0 -93
- package/src/modes/theme/defaults/obsidian.json +0 -91
- package/src/modes/theme/defaults/onyx.json +0 -91
- package/src/modes/theme/defaults/pearl.json +0 -93
- package/src/modes/theme/defaults/porcelain.json +0 -91
- package/src/modes/theme/defaults/quartz.json +0 -96
- package/src/modes/theme/defaults/sandstone.json +0 -95
- package/src/modes/theme/defaults/titanium.json +0 -90
- package/src/modes/theme/light.json +0 -93
|
@@ -2,10 +2,12 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { Settings } from "../config/settings";
|
|
5
6
|
import { syncSkillActiveState } from "../skill-state/active-state";
|
|
6
7
|
import { buildDeepInterviewHudSummary } from "../skill-state/workflow-hud";
|
|
7
8
|
import { runNativeRalplanCommand } from "./ralplan-runtime";
|
|
8
9
|
import { runNativeStateCommand } from "./state-runtime";
|
|
10
|
+
import { appendJsonl, writeArtifact, writeJsonAtomic } from "./state-writer";
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Native implementation of `gjc deep-interview`.
|
|
@@ -104,13 +106,6 @@ async function readJsonObject(filePath: string): Promise<Record<string, unknown>
|
|
|
104
106
|
return {};
|
|
105
107
|
}
|
|
106
108
|
|
|
107
|
-
async function writeJsonAtomic(filePath: string, value: unknown): Promise<void> {
|
|
108
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
109
|
-
const tmp = `${filePath}.tmp-${randomBytes(6).toString("hex")}`;
|
|
110
|
-
await fs.writeFile(tmp, `${JSON.stringify(value, null, 2)}\n`);
|
|
111
|
-
await fs.rename(tmp, filePath);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
109
|
async function resolveSpecContent(rawSpec: string, cwd: string): Promise<string> {
|
|
115
110
|
const candidate = path.isAbsolute(rawSpec) ? rawSpec : path.resolve(cwd, rawSpec);
|
|
116
111
|
try {
|
|
@@ -202,9 +197,29 @@ async function readSettingsAmbiguityThreshold(
|
|
|
202
197
|
return { threshold: candidate, source: settingsPath };
|
|
203
198
|
}
|
|
204
199
|
|
|
200
|
+
async function readModernSettingsAmbiguityThreshold(
|
|
201
|
+
cwd: string,
|
|
202
|
+
): Promise<{ threshold: number; source: string } | undefined> {
|
|
203
|
+
const settings = await Settings.init({ cwd });
|
|
204
|
+
const modernConfigPath = path.join(settings.getAgentDir(), "config.yml");
|
|
205
|
+
let parsed: unknown;
|
|
206
|
+
try {
|
|
207
|
+
parsed = (await import("bun")).YAML.parse(await fs.readFile(modernConfigPath, "utf-8"));
|
|
208
|
+
} catch {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
const candidate = (parsed as { gjc?: { deepInterview?: { ambiguityThreshold?: unknown } } })?.gjc?.deepInterview
|
|
212
|
+
?.ambiguityThreshold;
|
|
213
|
+
if (typeof candidate !== "number" || !Number.isFinite(candidate) || candidate <= 0 || candidate > 1)
|
|
214
|
+
return undefined;
|
|
215
|
+
return { threshold: candidate, source: modernConfigPath };
|
|
216
|
+
}
|
|
217
|
+
|
|
205
218
|
async function resolveConfiguredAmbiguityThreshold(
|
|
206
219
|
cwd: string,
|
|
207
220
|
): Promise<{ threshold: number; source: string } | undefined> {
|
|
221
|
+
const modernValue = await readModernSettingsAmbiguityThreshold(cwd);
|
|
222
|
+
if (modernValue) return modernValue;
|
|
208
223
|
const projectSettings = path.join(cwd, ".gjc", "settings.json");
|
|
209
224
|
const projectValue = await readSettingsAmbiguityThreshold(projectSettings);
|
|
210
225
|
if (projectValue) return projectValue;
|
|
@@ -373,17 +388,19 @@ export async function persistDeepInterviewSpec(
|
|
|
373
388
|
cwd: string,
|
|
374
389
|
resolved: ResolvedDeepInterviewSpecWriteArgs,
|
|
375
390
|
): Promise<PersistedDeepInterviewSpec> {
|
|
376
|
-
const
|
|
377
|
-
await fs.mkdir(specsDir, { recursive: true });
|
|
378
|
-
const specPath = path.join(specsDir, `deep-interview-${resolved.slug}.md`);
|
|
391
|
+
const specPath = path.join(cwd, ".gjc", "specs", `deep-interview-${resolved.slug}.md`);
|
|
379
392
|
const content = resolved.spec.endsWith("\n") ? resolved.spec : `${resolved.spec}\n`;
|
|
380
|
-
await
|
|
393
|
+
await writeArtifact(specPath, content, {
|
|
394
|
+
cwd,
|
|
395
|
+
audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
|
|
396
|
+
});
|
|
381
397
|
|
|
382
398
|
const sha256 = createHash("sha256").update(content).digest("hex");
|
|
383
399
|
const createdAt = new Date().toISOString();
|
|
384
|
-
await
|
|
385
|
-
path.join(
|
|
386
|
-
|
|
400
|
+
await appendJsonl(
|
|
401
|
+
path.join(cwd, ".gjc", "specs", "deep-interview-index.jsonl"),
|
|
402
|
+
{ slug: resolved.slug, stage: resolved.stage, path: specPath, created_at: createdAt, sha256 },
|
|
403
|
+
{ cwd, audit: { category: "ledger", verb: "append", owner: "gjc-runtime", skill: "deep-interview" } },
|
|
387
404
|
);
|
|
388
405
|
|
|
389
406
|
const statePath = deepInterviewStatePath(cwd, resolved.sessionId);
|
|
@@ -402,7 +419,10 @@ export async function persistDeepInterviewSpec(
|
|
|
402
419
|
updated_at: createdAt,
|
|
403
420
|
};
|
|
404
421
|
if (resolved.sessionId) payload.session_id = resolved.sessionId;
|
|
405
|
-
await writeJsonAtomic(statePath, payload
|
|
422
|
+
await writeJsonAtomic(statePath, payload, {
|
|
423
|
+
cwd,
|
|
424
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
|
|
425
|
+
});
|
|
406
426
|
await syncDeepInterviewHud({
|
|
407
427
|
cwd,
|
|
408
428
|
sessionId: resolved.sessionId,
|
|
@@ -421,11 +441,7 @@ export async function persistDeepInterviewSpec(
|
|
|
421
441
|
}
|
|
422
442
|
|
|
423
443
|
async function seedDeepInterviewState(cwd: string, resolved: ResolvedDeepInterviewArgs): Promise<string> {
|
|
424
|
-
const
|
|
425
|
-
? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(resolved.sessionId))
|
|
426
|
-
: path.join(cwd, ".gjc", "state");
|
|
427
|
-
await fs.mkdir(stateDir, { recursive: true });
|
|
428
|
-
const statePath = path.join(stateDir, "deep-interview-state.json");
|
|
444
|
+
const statePath = deepInterviewStatePath(cwd, resolved.sessionId);
|
|
429
445
|
const now = new Date().toISOString();
|
|
430
446
|
const payload: Record<string, unknown> = {
|
|
431
447
|
active: true,
|
|
@@ -448,7 +464,10 @@ async function seedDeepInterviewState(cwd: string, resolved: ResolvedDeepIntervi
|
|
|
448
464
|
(payload.state as Record<string, unknown>).language = resolved.language;
|
|
449
465
|
}
|
|
450
466
|
if (resolved.sessionId) payload.session_id = resolved.sessionId;
|
|
451
|
-
await
|
|
467
|
+
await writeJsonAtomic(statePath, payload, {
|
|
468
|
+
cwd,
|
|
469
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
|
|
470
|
+
});
|
|
452
471
|
return statePath;
|
|
453
472
|
}
|
|
454
473
|
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ModeChangeEntry,
|
|
9
9
|
type SessionEntry,
|
|
10
10
|
} from "../session/session-manager";
|
|
11
|
+
import { removeFileAudited, writeJsonAtomic } from "./state-writer";
|
|
11
12
|
|
|
12
13
|
export const GJC_SESSION_FILE_ENV = "GJC_SESSION_FILE";
|
|
13
14
|
export const GJC_SESSION_ID_ENV = "GJC_SESSION_ID";
|
|
@@ -88,8 +89,10 @@ export async function writePendingGoalModeRequest(input: {
|
|
|
88
89
|
goalsPath: input.goalsPath,
|
|
89
90
|
};
|
|
90
91
|
const filePath = requestPath(input.cwd);
|
|
91
|
-
await
|
|
92
|
-
|
|
92
|
+
await writeJsonAtomic(filePath, request, {
|
|
93
|
+
cwd: input.cwd,
|
|
94
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime" },
|
|
95
|
+
});
|
|
93
96
|
return request;
|
|
94
97
|
}
|
|
95
98
|
|
|
@@ -153,6 +156,8 @@ export async function writeCurrentSessionGoalModeState(input: {
|
|
|
153
156
|
mode: "goal",
|
|
154
157
|
data: { goal: state.goal },
|
|
155
158
|
};
|
|
159
|
+
// The session transcript file lives outside `.gjc/` (GJC_SESSION_FILE), so it is not a
|
|
160
|
+
// sanctioned-writer target; append directly.
|
|
156
161
|
await fs.appendFile(sessionFile, `${JSON.stringify(entry)}\n`);
|
|
157
162
|
return { status: "updated", goal: state.goal, sessionFile };
|
|
158
163
|
}
|
|
@@ -176,7 +181,10 @@ export async function consumePendingGoalModeRequest(cwd: string): Promise<Pendin
|
|
|
176
181
|
) {
|
|
177
182
|
return null;
|
|
178
183
|
}
|
|
179
|
-
await
|
|
184
|
+
await removeFileAudited(filePath, {
|
|
185
|
+
cwd,
|
|
186
|
+
audit: { category: "prune", verb: "remove", owner: "gjc-runtime" },
|
|
187
|
+
}).catch(error => {
|
|
180
188
|
if (!isEnoent(error)) throw error;
|
|
181
189
|
});
|
|
182
190
|
return { ...candidate, objective: candidate.objective.trim() } as PendingGoalModeRequest;
|
|
@@ -3,6 +3,8 @@ import * as fs from "node:fs/promises";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { syncSkillActiveState } from "../skill-state/active-state";
|
|
5
5
|
import { buildRalplanHudSummary } from "../skill-state/workflow-hud";
|
|
6
|
+
import { isRestrictedRoleAgentBash } from "./restricted-role-agent-bash";
|
|
7
|
+
import { appendJsonl, writeArtifact, writeJsonAtomic } from "./state-writer";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Native implementation of `gjc ralplan`.
|
|
@@ -110,6 +112,7 @@ function defaultRunId(now: Date = new Date()): string {
|
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
async function resolveArtifactContent(rawArtifact: string, cwd: string): Promise<string> {
|
|
115
|
+
if (isRestrictedRoleAgentBash()) return rawArtifact;
|
|
113
116
|
const candidate = path.isAbsolute(rawArtifact) ? rawArtifact : path.resolve(cwd, rawArtifact);
|
|
114
117
|
try {
|
|
115
118
|
const stat = await fs.stat(candidate);
|
|
@@ -171,8 +174,10 @@ async function persistActiveRunId(cwd: string, sessionId: string | undefined, ru
|
|
|
171
174
|
if (typeof existing.skill !== "string") existing.skill = "ralplan";
|
|
172
175
|
if (typeof existing.active !== "boolean") existing.active = true;
|
|
173
176
|
existing.updated_at = new Date().toISOString();
|
|
174
|
-
await
|
|
175
|
-
|
|
177
|
+
await writeJsonAtomic(statePath, existing, {
|
|
178
|
+
cwd,
|
|
179
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
180
|
+
});
|
|
176
181
|
}
|
|
177
182
|
|
|
178
183
|
async function resolveArtifactArgs(args: readonly string[], cwd: string): Promise<ResolvedArtifactArgs> {
|
|
@@ -218,27 +223,36 @@ interface PersistedArtifact {
|
|
|
218
223
|
|
|
219
224
|
async function persistArtifact(resolved: ResolvedArtifactArgs, cwd: string): Promise<PersistedArtifact> {
|
|
220
225
|
const runDir = path.join(cwd, ".gjc", "plans", "ralplan", resolved.runId);
|
|
221
|
-
|
|
226
|
+
|
|
222
227
|
const fileName = `stage-${pad2(resolved.stageN)}-${resolved.stage}.md`;
|
|
223
228
|
const filePath = path.join(runDir, fileName);
|
|
224
229
|
const content = resolved.artifact.endsWith("\n") ? resolved.artifact : `${resolved.artifact}\n`;
|
|
225
|
-
await
|
|
230
|
+
await writeArtifact(filePath, content, {
|
|
231
|
+
cwd,
|
|
232
|
+
audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
233
|
+
});
|
|
226
234
|
|
|
227
235
|
const sha256 = createHash("sha256").update(content).digest("hex");
|
|
228
236
|
const createdAt = new Date().toISOString();
|
|
229
|
-
const
|
|
237
|
+
const indexEntry = {
|
|
230
238
|
stage: resolved.stage,
|
|
231
239
|
stage_n: resolved.stageN,
|
|
232
240
|
path: filePath,
|
|
233
241
|
created_at: createdAt,
|
|
234
242
|
sha256,
|
|
235
|
-
}
|
|
236
|
-
await
|
|
243
|
+
};
|
|
244
|
+
await appendJsonl(path.join(runDir, "index.jsonl"), indexEntry, {
|
|
245
|
+
cwd,
|
|
246
|
+
audit: { category: "ledger", verb: "append", owner: "gjc-runtime", skill: "ralplan" },
|
|
247
|
+
});
|
|
237
248
|
|
|
238
249
|
let pendingApprovalPath: string | undefined;
|
|
239
250
|
if (resolved.stage === "final") {
|
|
240
251
|
pendingApprovalPath = path.join(runDir, "pending-approval.md");
|
|
241
|
-
await
|
|
252
|
+
await writeArtifact(pendingApprovalPath, content, {
|
|
253
|
+
cwd,
|
|
254
|
+
audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
255
|
+
});
|
|
242
256
|
}
|
|
243
257
|
|
|
244
258
|
return {
|
|
@@ -380,7 +394,7 @@ async function seedRalplanState(
|
|
|
380
394
|
const stateDir = resolved.sessionId
|
|
381
395
|
? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(resolved.sessionId))
|
|
382
396
|
: path.join(cwd, ".gjc", "state");
|
|
383
|
-
|
|
397
|
+
|
|
384
398
|
const statePath = path.join(stateDir, "ralplan-state.json");
|
|
385
399
|
// Reuse an existing run id when present so a re-invocation of `gjc ralplan "task"` doesn't
|
|
386
400
|
// orphan in-progress artifacts under a fresh run id.
|
|
@@ -401,7 +415,10 @@ async function seedRalplanState(
|
|
|
401
415
|
if (resolved.architectKind) payload.architect_kind = resolved.architectKind;
|
|
402
416
|
if (resolved.criticKind) payload.critic_kind = resolved.criticKind;
|
|
403
417
|
if (resolved.sessionId) payload.session_id = resolved.sessionId;
|
|
404
|
-
await
|
|
418
|
+
await writeJsonAtomic(statePath, payload, {
|
|
419
|
+
cwd,
|
|
420
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
421
|
+
});
|
|
405
422
|
return { statePath, runId };
|
|
406
423
|
}
|
|
407
424
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { CanonicalGjcWorkflowSkill } from "../skill-state/active-state";
|
|
2
|
+
import { CANONICAL_GJC_WORKFLOW_SKILLS } from "../skill-state/active-state";
|
|
3
|
+
import { getSkillManifest } from "./workflow-manifest";
|
|
4
|
+
|
|
5
|
+
export type StateGraphSkill = CanonicalGjcWorkflowSkill | "all";
|
|
6
|
+
export type StateGraphFormat = "ascii" | "mermaid" | "dot";
|
|
7
|
+
|
|
8
|
+
function assertGraphFormat(format: string): asserts format is StateGraphFormat {
|
|
9
|
+
if (format !== "ascii" && format !== "mermaid" && format !== "dot") {
|
|
10
|
+
throw new Error(`Invalid graph format: ${format}. Expected one of: ascii, mermaid, dot.`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function skillsFor(skill: StateGraphSkill): CanonicalGjcWorkflowSkill[] {
|
|
15
|
+
return skill === "all" ? [...CANONICAL_GJC_WORKFLOW_SKILLS] : [skill];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function renderAscii(skill: StateGraphSkill): string {
|
|
19
|
+
const chunks = skillsFor(skill).map(item => {
|
|
20
|
+
const manifest = getSkillManifest(item);
|
|
21
|
+
const states = manifest.states
|
|
22
|
+
.map(state => {
|
|
23
|
+
const markers = [state.initial ? "initial" : undefined, state.terminal ? "terminal" : undefined]
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.join(", ");
|
|
26
|
+
return ` - ${state.id}${markers ? ` (${markers})` : ""}`;
|
|
27
|
+
})
|
|
28
|
+
.join("\n");
|
|
29
|
+
const transitions = manifest.transitions
|
|
30
|
+
.map(transition => ` - ${transition.from} -> ${transition.to} [${transition.verb}]`)
|
|
31
|
+
.join("\n");
|
|
32
|
+
return `${manifest.skill} (${manifest.graphLabel})\nstates:\n${states}\ntransitions:\n${transitions}`;
|
|
33
|
+
});
|
|
34
|
+
return `${chunks.join("\n\n")}\n`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderMermaid(skill: StateGraphSkill): string {
|
|
38
|
+
const lines = ["stateDiagram-v2"];
|
|
39
|
+
for (const item of skillsFor(skill)) {
|
|
40
|
+
const manifest = getSkillManifest(item);
|
|
41
|
+
lines.push(` state "${manifest.graphLabel}" as ${item} {`);
|
|
42
|
+
lines.push(` [*] --> ${manifest.initialState}`);
|
|
43
|
+
for (const transition of manifest.transitions) {
|
|
44
|
+
lines.push(` ${transition.from} --> ${transition.to}: ${transition.verb}`);
|
|
45
|
+
}
|
|
46
|
+
for (const terminal of manifest.terminalStates) {
|
|
47
|
+
lines.push(` ${terminal} --> [*]`);
|
|
48
|
+
}
|
|
49
|
+
lines.push(" }");
|
|
50
|
+
}
|
|
51
|
+
return `${lines.join("\n")}\n`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function dotId(skill: CanonicalGjcWorkflowSkill, state: string): string {
|
|
55
|
+
return `"${skill}:${state}"`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function renderDot(skill: StateGraphSkill): string {
|
|
59
|
+
const lines = ["digraph gjc_state {", " rankdir=LR;"];
|
|
60
|
+
for (const item of skillsFor(skill)) {
|
|
61
|
+
const manifest = getSkillManifest(item);
|
|
62
|
+
lines.push(` subgraph "cluster_${item}" {`);
|
|
63
|
+
lines.push(` label="${manifest.graphLabel}";`);
|
|
64
|
+
for (const state of manifest.states) {
|
|
65
|
+
const shape = state.terminal ? "doublecircle" : "circle";
|
|
66
|
+
lines.push(` ${dotId(item, state.id)} [label="${state.id}", shape=${shape}];`);
|
|
67
|
+
}
|
|
68
|
+
lines.push(` "${item}:__start" [label="", shape=point];`);
|
|
69
|
+
lines.push(` "${item}:__start" -> ${dotId(item, manifest.initialState)};`);
|
|
70
|
+
for (const transition of manifest.transitions) {
|
|
71
|
+
lines.push(
|
|
72
|
+
` ${dotId(item, transition.from)} -> ${dotId(item, transition.to)} [label="${transition.verb}"];`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
lines.push(" }");
|
|
76
|
+
}
|
|
77
|
+
lines.push("}");
|
|
78
|
+
return `${lines.join("\n")}\n`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function renderStateGraph(skill: StateGraphSkill, format: string = "ascii"): string {
|
|
82
|
+
assertGraphFormat(format);
|
|
83
|
+
if (format === "mermaid") return renderMermaid(skill);
|
|
84
|
+
if (format === "dot") return renderDot(skill);
|
|
85
|
+
return renderAscii(skill);
|
|
86
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import type { CanonicalGjcWorkflowSkill } from "../skill-state/active-state";
|
|
3
|
+
import { initialPhaseForSkill } from "../skill-state/initial-phase";
|
|
4
|
+
import { canonicalWorkflowSkill, WORKFLOW_STATE_RECEIPT_VERSION } from "../skill-state/workflow-state-contract";
|
|
5
|
+
import { writeWorkflowEnvelopeAtomic } from "./state-writer";
|
|
6
|
+
import { getSkillManifest } from "./workflow-manifest";
|
|
7
|
+
|
|
8
|
+
export interface NormalizeLegacyStateResult {
|
|
9
|
+
state: Record<string, unknown>;
|
|
10
|
+
changed: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface MigrateAndPersistLegacyStateArgs {
|
|
14
|
+
cwd: string;
|
|
15
|
+
skill: string;
|
|
16
|
+
statePath: string;
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MigrateAndPersistLegacyStateResult {
|
|
21
|
+
migrated: boolean;
|
|
22
|
+
path: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const RECEIPT_STRING_FIELDS = [
|
|
26
|
+
"command",
|
|
27
|
+
"state_path",
|
|
28
|
+
"storage_path",
|
|
29
|
+
"mutated_at",
|
|
30
|
+
"fresh_until",
|
|
31
|
+
"mutation_id",
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
function cloneRecord(record: Record<string, unknown>): Record<string, unknown> {
|
|
35
|
+
return { ...record };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function canonicalSkillOrThrow(skill: string): CanonicalGjcWorkflowSkill {
|
|
39
|
+
const canonical = canonicalWorkflowSkill(skill);
|
|
40
|
+
if (!canonical) throw new Error(`Unsupported GJC workflow skill: ${skill}`);
|
|
41
|
+
return canonical;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function safeString(value: unknown): string {
|
|
45
|
+
return typeof value === "string" ? value : "";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function legacyPhaseForSkill(skill: CanonicalGjcWorkflowSkill, phase: string): string {
|
|
49
|
+
if (phase === "planning") return initialPhaseForSkill(skill);
|
|
50
|
+
return phase;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizePhase(skill: CanonicalGjcWorkflowSkill, value: unknown): string {
|
|
54
|
+
const manifest = getSkillManifest(skill);
|
|
55
|
+
const manifestStates = new Set(manifest.states.map(state => state.id));
|
|
56
|
+
const phase = legacyPhaseForSkill(skill, safeString(value).trim());
|
|
57
|
+
return manifestStates.has(phase) ? phase : manifest.initialState;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function receiptWithRequiredFields(raw: unknown, skill: CanonicalGjcWorkflowSkill): Record<string, unknown> {
|
|
61
|
+
const receipt =
|
|
62
|
+
raw && typeof raw === "object" && !Array.isArray(raw) ? cloneRecord(raw as Record<string, unknown>) : {};
|
|
63
|
+
receipt.version = WORKFLOW_STATE_RECEIPT_VERSION;
|
|
64
|
+
receipt.skill = skill;
|
|
65
|
+
if (receipt.owner !== "gjc-state-cli" && receipt.owner !== "gjc-runtime" && receipt.owner !== "gjc-hook") {
|
|
66
|
+
receipt.owner = "gjc-state-cli";
|
|
67
|
+
}
|
|
68
|
+
if (receipt.status !== "fresh" && receipt.status !== "stale") receipt.status = "stale";
|
|
69
|
+
for (const field of RECEIPT_STRING_FIELDS) {
|
|
70
|
+
if (typeof receipt[field] !== "string") receipt[field] = "";
|
|
71
|
+
}
|
|
72
|
+
return receipt;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function recordsEqual(left: Record<string, unknown>, right: Record<string, unknown>): boolean {
|
|
76
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Pure legacy state normalizer for background/internal readers.
|
|
81
|
+
*
|
|
82
|
+
* Readers that need compatibility with old on-disk workflow state shapes must call
|
|
83
|
+
* this in-memory helper and must never call `migrateAndPersistLegacyState`. The
|
|
84
|
+
* persist variant is reserved for explicit state migration commands because it is
|
|
85
|
+
* the only path allowed to write normalized upgrades back to `.gjc/state/**`.
|
|
86
|
+
*/
|
|
87
|
+
export function normalizeLegacyState(raw: Record<string, unknown>, skill: string): NormalizeLegacyStateResult {
|
|
88
|
+
const canonicalSkill = canonicalSkillOrThrow(skill);
|
|
89
|
+
const state = cloneRecord(raw);
|
|
90
|
+
state.skill = canonicalSkill;
|
|
91
|
+
if (typeof state.version !== "number") state.version = 1;
|
|
92
|
+
if (typeof state.active !== "boolean") state.active = true;
|
|
93
|
+
|
|
94
|
+
const sourcePhase = typeof state.current_phase === "string" ? state.current_phase : state.phase;
|
|
95
|
+
const normalizedPhase = normalizePhase(canonicalSkill, sourcePhase);
|
|
96
|
+
state.current_phase = normalizedPhase;
|
|
97
|
+
if ("phase" in state && typeof state.phase === "string") state.phase = normalizedPhase;
|
|
98
|
+
state.receipt = receiptWithRequiredFields(state.receipt, canonicalSkill);
|
|
99
|
+
|
|
100
|
+
return { state, changed: !recordsEqual(raw, state) };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function migrateAndPersistLegacyState(
|
|
104
|
+
args: MigrateAndPersistLegacyStateArgs,
|
|
105
|
+
): Promise<MigrateAndPersistLegacyStateResult> {
|
|
106
|
+
const canonicalSkill = canonicalSkillOrThrow(args.skill);
|
|
107
|
+
const raw = JSON.parse(await fs.readFile(args.statePath, "utf-8")) as unknown;
|
|
108
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
109
|
+
throw new Error(`Workflow state file must contain a JSON object: ${args.statePath}`);
|
|
110
|
+
}
|
|
111
|
+
const { state, changed } = normalizeLegacyState(raw as Record<string, unknown>, canonicalSkill);
|
|
112
|
+
if (!changed) return { migrated: false, path: args.statePath };
|
|
113
|
+
|
|
114
|
+
const persistedPath = await writeWorkflowEnvelopeAtomic(args.statePath, state, {
|
|
115
|
+
cwd: args.cwd,
|
|
116
|
+
receipt: {
|
|
117
|
+
cwd: args.cwd,
|
|
118
|
+
skill: canonicalSkill,
|
|
119
|
+
owner: "gjc-state-cli",
|
|
120
|
+
command: `gjc state ${canonicalSkill} migrate`,
|
|
121
|
+
sessionId: args.sessionId,
|
|
122
|
+
},
|
|
123
|
+
audit: {
|
|
124
|
+
cwd: args.cwd,
|
|
125
|
+
skill: canonicalSkill,
|
|
126
|
+
verb: "migrate",
|
|
127
|
+
owner: "gjc-state-cli",
|
|
128
|
+
category: "state",
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
return { migrated: true, path: persistedPath };
|
|
132
|
+
}
|