@gajae-code/coding-agent 0.6.0 → 0.6.3
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 +32 -0
- package/dist/types/cli/update-cli.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/lsp/startup-events.d.ts +1 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +5 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +0 -7
- package/src/cli/update-cli.ts +53 -3
- package/src/config/models-config-schema.ts +1 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +3 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +2 -0
- package/src/gjc-runtime/state-runtime.ts +22 -14
- package/src/gjc-runtime/state-writer.ts +21 -1
- package/src/internal-urls/docs-index.generated.ts +3 -4
- package/src/lsp/startup-events.ts +24 -0
- package/src/modes/interactive-mode.ts +5 -18
- package/src/session/agent-session.ts +28 -20
- package/src/skill-state/active-state.ts +53 -30
- package/src/skill-state/deep-interview-mutation-guard.ts +238 -30
- package/src/tools/ast-edit.ts +2 -2
- package/src/utils/edit-mode.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.6.3] - 2026-06-19
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Reverted the experimental minified npm-bundle distribution introduced in 0.6.2. The published `@gajae-code/coding-agent` shipped both `src/` and ~30MB of `dist/` bundles (`cli.js` plus stats/browser/eval worker bundles), which pushed the package past npm's registry payload limit (`E413 Payload Too Large`) and blocked publishing of `@gajae-code/coding-agent` and the `gajae-code` wrapper (so 0.6.2 only partially published the sibling libraries). The CLI `bin`/`./cli` export ships from `src/` again, matching the layout that published cleanly through 0.6.1; the embedded tiktoken/o200k tokenizer removal is unaffected. Local measurement showed the bundle gave no idle-RAM benefit over running from source.
|
|
10
|
+
- Fixed `edit-mode.ts` importing the full `@gajae-code/utils` barrel (which re-exports native-addon-backed `ptree`/`procmgr`); it now imports `$env` from the `@gajae-code/utils/env` subpath, so schema generation and other lightweight paths no longer eagerly load the native addon.
|
|
11
|
+
|
|
12
|
+
## [0.6.2] - 2026-06-19
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Reconciled the planning-phase mutation guard into one uniform policy across skill states (`deep-interview-mutation-guard.ts`). Previously only `deep-interview` blocked product-code mutation (and it blocked *all* `write`/`edit`/`ast_edit` targets, including neutral `/tmp` scratch), while `ralplan`/`ultragoal` planning enforced nothing beyond the always-on `.gjc/**` runtime-owned block, and `bash` got a free pass to mutate product code during the interview. Now: (1) the phase-boundary block is shared by every pre-approval planning phase — `deep-interview`, `ralplan`, and `ultragoal`'s `goal-planning` phase (`team` and executing `ultragoal` are unaffected); (2) `bash` reaches parity with `write`/`edit`/`ast_edit` so product-mutating shell commands are blocked too; and (3) neutral scratch writes to a system temp directory (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) outside the project tree are always allowed, so an agent can stage a draft and persist it through the sanctioned CLI (`gjc deep-interview --write --spec <temp-path>`, `gjc ralplan --write --artifact <temp-path>`). The `.gjc/**` block is unchanged. Each planning skill now emits its own block message.
|
|
17
|
+
- Made the reconciled mutation guard skill-transition/return safe by keying the block off the single canonical *current* workflow skill (the resolved top-level `skill` the HUD and skill-tool chain guard already use) instead of independently scanning every skill. Phase semantics now match the manifest and the Stop hook's `STOP_RELEASING_PHASES`: `handoff` and ralplan's pre-approval `final` keep blocking for `deep-interview`/`ralplan` (until the skill is demoted or cleared), executor phases (`ultragoal` `pending`/`active`/`blocked`) release, and a missing/corrupt mode-state still fails open. As a result a handoff (e.g. ralplan → ultragoal) never lets a stale planning entry block the executor, and a return (e.g. re-entering ralplan/deep-interview after a goal completes) reliably re-blocks.
|
|
18
|
+
- Hardened the reconciled guard after architect + red-team review: the `gjc …` bash fast-path no longer skips scanning for compound/redirected/multiline commands (`gjc …; tee src/x`, `gjc … && echo x > .gjc/state/foo`, and newline-separated `gjc …\ntouch src/x` are now caught); the current-skill resolver prefers the most-recently-updated active entry so a stale planning row can never block a newer executor; neutral-temp classification canonicalizes paths (realpath of the nearest existing ancestor) so a `/tmp` symlink or macOS `/tmp`→`/private/tmp` alias pointing back into the project/`.gjc` is blocked; the deferred `ast_edit` apply path now mirrors the always-on `.gjc/**` block; and a heredoc delimiter (`<<EOF`) is no longer mis-read as a write target. Bash mutation detection remains best-effort defense-in-depth (the authoritative guard is the fully-pathed `write`/`edit`/`ast_edit` tools). Added generic guard exports (`getWorkflowMutationDecision`/`assertWorkflowMutationAllowed`/`assertWorkflowMutationRawPathsAllowed`) used by the session and `ast_edit` callers, with the `*DeepInterview*` names retained as compatibility aliases.
|
|
19
|
+
- The published `@gajae-code/coding-agent` npm package now ships a prebuilt **minified** `dist/cli.js` (built with `bun build --minify`, not `--compile`) as the CLI entrypoint; the native addon and the stats/browser/eval worker entrypoints are emitted as externals so the bundle loads them from `node_modules` at runtime, and release compiled binaries also gain `--minify`. Measured `gjc --help` RSS dropped from ~302MB (running from source) to ~120MB (#879, #881).
|
|
20
|
+
- Lazy-loaded the `eval` tool and its Python-kernel backend via dynamic import, so the kernel and its dependencies are no longer eagerly imported at startup and load only when the `eval` tool actually runs (#879).
|
|
21
|
+
- `rust-analyzer` is now treated as an optional LSP server: its startup failure no longer raises a startup warning (it is auto-installed lazily on demand), while non-optional LSP server startup failures still warn (#872).
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Fixed planning-pipeline stage precedence so activating a downstream stage (`deep-interview → ralplan → ultragoal`) supersedes upstream stages by pipeline rank, preventing a stale upstream row from continuing to own the HUD, mutation gate, or primary active-state snapshot (#878).
|
|
26
|
+
- Made `gjc state doctor` resolve the session id like every other state command (explicit `--session-id`, then payload `session_id`, then the `GJC_SESSION_ID` env var set for agent-initiated invocations), so it inspects the caller's session-scoped state files instead of a default location (#880).
|
|
27
|
+
- Fixed a second workspace-relative import that the 0.6.0 #867 fix missed: `edit-mode.ts` now imports `$env` through the `@gajae-code/utils` package boundary instead of `../../../utils/src/env`, so global Bun installs no longer crash resolving edit mode, with package-boundary regression coverage (#868).
|
|
28
|
+
|
|
29
|
+
## [0.6.1] - 2026-06-18
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Fixed the `computer` tool (and any other `z.union`/discriminated-union tool) shipping a bare top-level `anyOf`/`oneOf`/`allOf` `input_schema` root that strict providers (Amazon Bedrock Converse incl. Kiro/CodeWhisperer relays, OpenAI strict mode, Gemini) reject. Tool schema roots are now flattened to a single `type: "object"` across all providers via the shared `flattenToolRootCombinators`. See `@gajae-code/ai` 0.6.1.
|
|
34
|
+
- `gjc update` now runs the freshly installed `gjc --smoke-test` after version verification and tells users to restart running sessions, surfacing stale or partial runtime updates such as native-addon release mismatches immediately.
|
|
35
|
+
|
|
5
36
|
## [0.6.0] - 2026-06-18
|
|
6
37
|
### Added
|
|
7
38
|
|
|
8
39
|
- Exposed the existing goal-pause capability through the `goal` tool as `goal({op:"pause"})`. The runtime `pauseGoal()` method and `paused` status already existed and were reachable via the `/goal pause` slash command and the goal menu, but the agent-facing `goal` tool only enumerated `create | get | complete | resume | drop` — so an agent could not park a goal whose remaining work was blocked on human input. It was forced to either `drop` (clearing the goal) or leave the goal `active`, which re-fired the hidden autonomous-continuation steer every turn with no exit condition. `pause` reuses the existing `paused` status and continuation gate (`buildContinuationPrompt` already returns `undefined` when `enabled=false`), parks the goal without dropping it, persists as `goal_paused`, and is resumable via the existing `resume` op. The active-goal and continuation prompts now instruct the agent to pause when every outstanding deliverable is genuinely human-blocked. `pauseGoal()` now rejects any goal whose status is not `active`, so a completed or dropped goal cannot be driven into a paused-mode lifecycle when paused through the tool.
|
|
9
40
|
|
|
10
41
|
### Fixed
|
|
42
|
+
- Fixed global Bun installs crashing during interactive startup when edit-mode resolution followed a workspace-relative `packages/utils/src` import that is absent from the published package layout; coding-agent now imports `$env` through the `@gajae-code/utils` package boundary and has regression coverage for sibling workspace source imports (#867).
|
|
11
43
|
|
|
12
44
|
- Restored steer-by-default while the agent is busy: `busyPromptMode` now defaults to `steer`, so Enter on a normal prompt interrupts the active turn. Queueing for the next turn is reserved for the explicit Ctrl+Enter follow-up keystroke (or `busyPromptMode: "queue"`); existing steer/cancel plus explicit queue/dequeue controls remain separate (#829).
|
|
13
45
|
- Fixed `gjc rlm "<question>"` consuming the seeded question as a one-shot autonomous run that exited immediately; a seeded prompt now lands in the interactive composer so the research session stays interactive.
|
|
@@ -3,6 +3,8 @@ export interface InstalledVersionVerification {
|
|
|
3
3
|
ok: boolean;
|
|
4
4
|
actual?: string;
|
|
5
5
|
path?: string;
|
|
6
|
+
smokeTestFailed?: boolean;
|
|
7
|
+
smokeTestOutput?: string;
|
|
6
8
|
}
|
|
7
9
|
/** Paths and verifier used while replacing a downloaded binary update. */
|
|
8
10
|
export interface BinaryReplacementOptions {
|
|
@@ -24,6 +26,7 @@ export declare function resolveUpdateMethodForTest(ompPath: string, bunBinDir: s
|
|
|
24
26
|
export declare function formatBinaryDownloadFailureMessageForTest(binaryName: string, url: string, status: string | number, platform?: NodeJS.Platform): string;
|
|
25
27
|
export declare function buildReleaseBinaryUrlForTest(version: string, platform?: NodeJS.Platform, arch?: string): string;
|
|
26
28
|
export declare function formatManualUpdateInstructionsForTest(platform?: NodeJS.Platform): string;
|
|
29
|
+
export declare function formatVerificationFailureForTest(result: InstalledVersionVerification, expectedVersion: string): string;
|
|
27
30
|
/**
|
|
28
31
|
* Atomically replace the installed binary and roll back if version verification fails.
|
|
29
32
|
*/
|
|
@@ -51,6 +51,7 @@ export declare const ModelsConfigFile: ConfigFile<{
|
|
|
51
51
|
compat?: {
|
|
52
52
|
supportsStore?: boolean | undefined;
|
|
53
53
|
supportsDeveloperRole?: boolean | undefined;
|
|
54
|
+
sendSessionHeaders?: boolean | undefined;
|
|
54
55
|
supportsMultipleSystemMessages?: boolean | undefined;
|
|
55
56
|
supportsReasoningEffort?: boolean | undefined;
|
|
56
57
|
reasoningEffortMap?: {
|
|
@@ -129,6 +130,7 @@ export declare const ModelsConfigFile: ConfigFile<{
|
|
|
129
130
|
compat?: {
|
|
130
131
|
supportsStore?: boolean | undefined;
|
|
131
132
|
supportsDeveloperRole?: boolean | undefined;
|
|
133
|
+
sendSessionHeaders?: boolean | undefined;
|
|
132
134
|
supportsMultipleSystemMessages?: boolean | undefined;
|
|
133
135
|
supportsReasoningEffort?: boolean | undefined;
|
|
134
136
|
reasoningEffortMap?: {
|
|
@@ -202,6 +204,7 @@ export declare const ModelsConfigFile: ConfigFile<{
|
|
|
202
204
|
compat?: {
|
|
203
205
|
supportsStore?: boolean | undefined;
|
|
204
206
|
supportsDeveloperRole?: boolean | undefined;
|
|
207
|
+
sendSessionHeaders?: boolean | undefined;
|
|
205
208
|
supportsMultipleSystemMessages?: boolean | undefined;
|
|
206
209
|
supportsReasoningEffort?: boolean | undefined;
|
|
207
210
|
reasoningEffortMap?: {
|
|
@@ -2,6 +2,7 @@ import * as z from "zod/v4";
|
|
|
2
2
|
export declare const OpenAICompatSchema: z.ZodObject<{
|
|
3
3
|
supportsStore: z.ZodOptional<z.ZodBoolean>;
|
|
4
4
|
supportsDeveloperRole: z.ZodOptional<z.ZodBoolean>;
|
|
5
|
+
sendSessionHeaders: z.ZodOptional<z.ZodBoolean>;
|
|
5
6
|
supportsMultipleSystemMessages: z.ZodOptional<z.ZodBoolean>;
|
|
6
7
|
supportsReasoningEffort: z.ZodOptional<z.ZodBoolean>;
|
|
7
8
|
reasoningEffortMap: z.ZodOptional<z.ZodObject<{
|
|
@@ -163,6 +164,7 @@ export declare const ModelOverrideSchema: z.ZodObject<{
|
|
|
163
164
|
compat: z.ZodOptional<z.ZodObject<{
|
|
164
165
|
supportsStore: z.ZodOptional<z.ZodBoolean>;
|
|
165
166
|
supportsDeveloperRole: z.ZodOptional<z.ZodBoolean>;
|
|
167
|
+
sendSessionHeaders: z.ZodOptional<z.ZodBoolean>;
|
|
166
168
|
supportsMultipleSystemMessages: z.ZodOptional<z.ZodBoolean>;
|
|
167
169
|
supportsReasoningEffort: z.ZodOptional<z.ZodBoolean>;
|
|
168
170
|
reasoningEffortMap: z.ZodOptional<z.ZodObject<{
|
|
@@ -276,6 +278,7 @@ export declare const ModelsConfigSchema: z.ZodObject<{
|
|
|
276
278
|
compat: z.ZodOptional<z.ZodObject<{
|
|
277
279
|
supportsStore: z.ZodOptional<z.ZodBoolean>;
|
|
278
280
|
supportsDeveloperRole: z.ZodOptional<z.ZodBoolean>;
|
|
281
|
+
sendSessionHeaders: z.ZodOptional<z.ZodBoolean>;
|
|
279
282
|
supportsMultipleSystemMessages: z.ZodOptional<z.ZodBoolean>;
|
|
280
283
|
supportsReasoningEffort: z.ZodOptional<z.ZodBoolean>;
|
|
281
284
|
reasoningEffortMap: z.ZodOptional<z.ZodObject<{
|
|
@@ -442,6 +445,7 @@ export declare const ModelsConfigSchema: z.ZodObject<{
|
|
|
442
445
|
compat: z.ZodOptional<z.ZodObject<{
|
|
443
446
|
supportsStore: z.ZodOptional<z.ZodBoolean>;
|
|
444
447
|
supportsDeveloperRole: z.ZodOptional<z.ZodBoolean>;
|
|
448
|
+
sendSessionHeaders: z.ZodOptional<z.ZodBoolean>;
|
|
445
449
|
supportsMultipleSystemMessages: z.ZodOptional<z.ZodBoolean>;
|
|
446
450
|
supportsReasoningEffort: z.ZodOptional<z.ZodBoolean>;
|
|
447
451
|
reasoningEffortMap: z.ZodOptional<z.ZodObject<{
|
|
@@ -582,6 +586,7 @@ export declare const ModelsConfigSchema: z.ZodObject<{
|
|
|
582
586
|
compat: z.ZodOptional<z.ZodObject<{
|
|
583
587
|
supportsStore: z.ZodOptional<z.ZodBoolean>;
|
|
584
588
|
supportsDeveloperRole: z.ZodOptional<z.ZodBoolean>;
|
|
589
|
+
sendSessionHeaders: z.ZodOptional<z.ZodBoolean>;
|
|
585
590
|
supportsMultipleSystemMessages: z.ZodOptional<z.ZodBoolean>;
|
|
586
591
|
supportsReasoningEffort: z.ZodOptional<z.ZodBoolean>;
|
|
587
592
|
reasoningEffortMap: z.ZodOptional<z.ZodObject<{
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AgentTool } from "@gajae-code/agent-core";
|
|
2
2
|
export declare const DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE = "Deep-interview phase boundary: continue gathering context/questions/risks and emit a handoff/spec before code edits. Mutation tools and patch execution are blocked while deep-interview is active; finalize specs through `gjc deep-interview --write --stage final` or hand off to an execution phase.";
|
|
3
3
|
export declare const WORKFLOW_STATE_MUTATION_BLOCK_MESSAGE = ".gjc workflow state and artifacts are runtime-owned. Agent mutation tools cannot edit `.gjc/**`; use the sanctioned `gjc` CLI instead.";
|
|
4
|
+
export declare const RALPLAN_MUTATION_BLOCK_MESSAGE = "Ralplan planning phase boundary: keep refining the consensus plan and persist plan artifacts through `gjc ralplan --write` (stage scratch files under a temp dir if needed). Product-code mutation tools and patch execution are blocked while ralplan is active; mutate only after the plan is approved and execution begins.";
|
|
5
|
+
export declare const ULTRAGOAL_GOAL_PLANNING_MUTATION_BLOCK_MESSAGE = "Ultragoal goal-planning phase boundary: finish goal planning and record goals through `gjc ultragoal` before editing code. Product-code mutation tools and patch execution are blocked until goal planning completes and execution begins.";
|
|
4
6
|
type ToolWithEditMode = AgentTool & {
|
|
5
7
|
mode?: unknown;
|
|
6
8
|
customWireName?: unknown;
|
|
@@ -30,4 +32,7 @@ export declare function assertDeepInterviewMutationRawPathsAllowed(input: {
|
|
|
30
32
|
}): Promise<void>;
|
|
31
33
|
export declare function getDeepInterviewMutationDecision(input: DeepInterviewMutationGuardInput): Promise<DeepInterviewMutationDecision>;
|
|
32
34
|
export declare function assertDeepInterviewMutationAllowed(input: DeepInterviewMutationGuardInput): Promise<void>;
|
|
35
|
+
export declare const getWorkflowMutationDecision: typeof getDeepInterviewMutationDecision;
|
|
36
|
+
export declare const assertWorkflowMutationAllowed: typeof assertDeepInterviewMutationAllowed;
|
|
37
|
+
export declare const assertWorkflowMutationRawPathsAllowed: typeof assertDeepInterviewMutationRawPathsAllowed;
|
|
33
38
|
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/coding-agent",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.3",
|
|
5
5
|
"description": "Gajae Code CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://gaebal-gajae.dev",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -51,12 +51,12 @@
|
|
|
51
51
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
52
52
|
"@babel/parser": "^7.29.3",
|
|
53
53
|
"@mozilla/readability": "^0.6.0",
|
|
54
|
-
"@gajae-code/stats": "0.6.
|
|
55
|
-
"@gajae-code/agent-core": "0.6.
|
|
56
|
-
"@gajae-code/ai": "0.6.
|
|
57
|
-
"@gajae-code/natives": "0.6.
|
|
58
|
-
"@gajae-code/tui": "0.6.
|
|
59
|
-
"@gajae-code/utils": "0.6.
|
|
54
|
+
"@gajae-code/stats": "0.6.3",
|
|
55
|
+
"@gajae-code/agent-core": "0.6.3",
|
|
56
|
+
"@gajae-code/ai": "0.6.3",
|
|
57
|
+
"@gajae-code/natives": "0.6.3",
|
|
58
|
+
"@gajae-code/tui": "0.6.3",
|
|
59
|
+
"@gajae-code/utils": "0.6.3",
|
|
60
60
|
"@puppeteer/browsers": "^2.13.0",
|
|
61
61
|
"@types/turndown": "5.0.6",
|
|
62
62
|
"@xterm/headless": "^6.0.0",
|
package/scripts/build-binary.ts
CHANGED
|
@@ -5,12 +5,6 @@ import * as path from "node:path";
|
|
|
5
5
|
const packageDir = path.join(import.meta.dir, "..");
|
|
6
6
|
const outputPath = path.join(packageDir, "dist", "gjc");
|
|
7
7
|
const nativeDir = path.join(packageDir, "..", "natives", "native");
|
|
8
|
-
// Lazy native tokenizer entrypoint. `agent-core/compaction` loads this from
|
|
9
|
-
// the explicit native entrypoint instead of a package-name dynamic require of
|
|
10
|
-
// `@gajae-code/natives`, because those fail inside Bun standalone `$bunfs`.
|
|
11
|
-
// Listing the module here makes the absolute target path exist in the compiled
|
|
12
|
-
// bunfs.
|
|
13
|
-
const nativeTokenizerEntrypoint = "../natives/native/index.js";
|
|
14
8
|
|
|
15
9
|
function shouldAdhocSignDarwinBinary(): boolean {
|
|
16
10
|
return process.platform === "darwin";
|
|
@@ -72,7 +66,6 @@ async function main(): Promise<void> {
|
|
|
72
66
|
"../stats/src/sync-worker.ts",
|
|
73
67
|
"./src/tools/browser/tab-worker-entry.ts",
|
|
74
68
|
"./src/eval/js/worker-entry.ts",
|
|
75
|
-
nativeTokenizerEntrypoint,
|
|
76
69
|
"--outfile",
|
|
77
70
|
"dist/gjc",
|
|
78
71
|
],
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -25,6 +25,8 @@ export interface InstalledVersionVerification {
|
|
|
25
25
|
ok: boolean;
|
|
26
26
|
actual?: string;
|
|
27
27
|
path?: string;
|
|
28
|
+
smokeTestFailed?: boolean;
|
|
29
|
+
smokeTestOutput?: string;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
/** Paths and verifier used while replacing a downloaded binary update. */
|
|
@@ -226,6 +228,36 @@ async function verifyInstalledVersion(expectedVersion: string): Promise<Installe
|
|
|
226
228
|
}
|
|
227
229
|
}
|
|
228
230
|
|
|
231
|
+
async function verifyInstalledRuntime(expectedVersion: string): Promise<InstalledVersionVerification> {
|
|
232
|
+
const versionResult = await verifyInstalledVersion(expectedVersion);
|
|
233
|
+
if (!versionResult.ok || !versionResult.path) {
|
|
234
|
+
return versionResult;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const smokeResult = await $`${versionResult.path} --smoke-test`.quiet().nothrow();
|
|
238
|
+
if (smokeResult.exitCode === 0) {
|
|
239
|
+
return versionResult;
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
...versionResult,
|
|
243
|
+
ok: false,
|
|
244
|
+
smokeTestFailed: true,
|
|
245
|
+
smokeTestOutput: smokeResult.text().trim(),
|
|
246
|
+
};
|
|
247
|
+
} catch (error) {
|
|
248
|
+
return {
|
|
249
|
+
...versionResult,
|
|
250
|
+
ok: false,
|
|
251
|
+
smokeTestFailed: true,
|
|
252
|
+
smokeTestOutput: error instanceof Error ? error.message : String(error),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function printRestartGuidance(): void {
|
|
258
|
+
console.log(chalk.dim(`Restart ${APP_NAME} to use the new version`));
|
|
259
|
+
}
|
|
260
|
+
|
|
229
261
|
function printVerifiedVersion(expectedVersion: string): void {
|
|
230
262
|
console.log(chalk.green(`\n${theme.status.success} Updated to ${expectedVersion}`));
|
|
231
263
|
}
|
|
@@ -289,20 +321,38 @@ export function formatManualUpdateInstructionsForTest(platform: NodeJS.Platform
|
|
|
289
321
|
return formatManualUpdateInstructions(platform);
|
|
290
322
|
}
|
|
291
323
|
|
|
324
|
+
function normalizeVerificationOutput(output: string | undefined): string {
|
|
325
|
+
return output?.replace(/\s+/g, " ").trim() ?? "";
|
|
326
|
+
}
|
|
327
|
+
|
|
292
328
|
function formatVerificationFailure(result: InstalledVersionVerification, expectedVersion: string): string {
|
|
329
|
+
if (result.smokeTestFailed) {
|
|
330
|
+
const output = normalizeVerificationOutput(result.smokeTestOutput);
|
|
331
|
+
const outputSuffix = output ? `: ${output}` : "";
|
|
332
|
+
const pathSuffix = result.path ? ` at ${result.path}` : "";
|
|
333
|
+
return `${APP_NAME}${pathSuffix} reports ${result.actual ?? expectedVersion}, but --smoke-test failed${outputSuffix}. Close running ${APP_NAME} sessions and reinstall to repair a stale or partial update.`;
|
|
334
|
+
}
|
|
293
335
|
if (result.actual) {
|
|
294
336
|
return `${APP_NAME} at ${result.path} still reports ${result.actual} (expected ${expectedVersion})`;
|
|
295
337
|
}
|
|
296
338
|
return `could not verify updated version${result.path ? ` at ${result.path}` : ""}`;
|
|
297
339
|
}
|
|
298
340
|
|
|
341
|
+
export function formatVerificationFailureForTest(
|
|
342
|
+
result: InstalledVersionVerification,
|
|
343
|
+
expectedVersion: string,
|
|
344
|
+
): string {
|
|
345
|
+
return formatVerificationFailure(result, expectedVersion);
|
|
346
|
+
}
|
|
347
|
+
|
|
299
348
|
/**
|
|
300
349
|
* Print post-update verification result.
|
|
301
350
|
*/
|
|
302
351
|
async function printVerification(expectedVersion: string): Promise<void> {
|
|
303
|
-
const result = await
|
|
352
|
+
const result = await verifyInstalledRuntime(expectedVersion);
|
|
304
353
|
if (result.ok) {
|
|
305
354
|
printVerifiedVersion(expectedVersion);
|
|
355
|
+
printRestartGuidance();
|
|
306
356
|
return;
|
|
307
357
|
}
|
|
308
358
|
console.log(chalk.yellow(`\nWarning: ${formatVerificationFailure(result, expectedVersion)}`));
|
|
@@ -385,10 +435,10 @@ async function updateViaBinaryAt(targetPath: string, expectedVersion: string): P
|
|
|
385
435
|
tempPath,
|
|
386
436
|
backupPath,
|
|
387
437
|
expectedVersion,
|
|
388
|
-
verifyInstalledVersion,
|
|
438
|
+
verifyInstalledVersion: verifyInstalledRuntime,
|
|
389
439
|
});
|
|
390
440
|
printVerifiedVersion(expectedVersion);
|
|
391
|
-
|
|
441
|
+
printRestartGuidance();
|
|
392
442
|
}
|
|
393
443
|
|
|
394
444
|
/**
|
|
@@ -22,6 +22,7 @@ const ReasoningEffortMapSchema = z.object({
|
|
|
22
22
|
export const OpenAICompatSchema = z.object({
|
|
23
23
|
supportsStore: z.boolean().optional(),
|
|
24
24
|
supportsDeveloperRole: z.boolean().optional(),
|
|
25
|
+
sendSessionHeaders: z.boolean().optional(),
|
|
25
26
|
supportsMultipleSystemMessages: z.boolean().optional(),
|
|
26
27
|
supportsReasoningEffort: z.boolean().optional(),
|
|
27
28
|
reasoningEffortMap: ReasoningEffortMapSchema.optional(),
|
|
@@ -123,6 +123,8 @@ Deep Interview threshold: <resolvedThresholdPercent> (source: <resolvedThreshold
|
|
|
123
123
|
- Final specs MUST resolve to `.gjc/specs/deep-interview-{slug}.md` exactly.
|
|
124
124
|
- Write final specs and all ephemeral interview artifacts through the active GJC workflow/state CLI when available.
|
|
125
125
|
- Direct `.gjc/` file edits are forbidden unless an explicit force override is active; do not use `write`, `edit`, or `ast_edit` against `.gjc/specs`, `.gjc/plans`, `.gjc/state`, or other `.gjc/` paths during normal workflow operation.
|
|
126
|
+
- Preferred: pass the spec markdown **inline** to the native deep-interview write command (`--write … --spec "<markdown>"`) — no scratch file is needed. The CLI is the only sanctioned writer for `.gjc/specs`.
|
|
127
|
+
- Only if a spec is too large to pass inline, stage it with the `write` tool to a system temp directory (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) outside the project tree, then pass that path to `--spec`. The planning phase-boundary block tolerates these neutral temp writes; never stage interview artifacts inside the repo or under `.gjc/`, and do not improvise repo-relative scratch files.
|
|
126
128
|
|
|
127
129
|
4. **Initialize state** via `gjc state write`:
|
|
128
130
|
|
|
@@ -495,7 +497,7 @@ When ambiguity ≤ threshold (or hard cap / early exit):
|
|
|
495
497
|
- Apply `language.instruction` when present so user-facing prose in the spec preserves the session language; keep code identifiers, file paths, commands, JSON/settings keys, and quoted source text unchanged.
|
|
496
498
|
- Apply the self-proofread once to newly generated spec prose before persistence, including generated natural-language table cells such as coverage notes, while preserving transcript answers, quoted/source text, code identifiers, file paths, commands, JSON/settings keys, table structure/fixed labels, and `.gjc/specs/deep-interview-{slug}.md` unchanged.
|
|
497
499
|
2. **Write the final spec through the workflow CLI**: persist the artifact at `.gjc/specs/deep-interview-{slug}.md`
|
|
498
|
-
- Always use this exact final spec path.
|
|
500
|
+
- Always use this exact final spec path. Prefer passing the spec markdown **inline** as the `--spec` value; only when it is too large to pass inline, stage it as a file in a system temp directory (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) outside the project tree and pass that path — never write scratch specs to the repo root, the project tree, or `.gjc/`.
|
|
499
501
|
- Use the native deep-interview write command with `--write --stage final --slug {slug} --spec <markdown-or-path> [--json]` for artifact and state persistence; direct `.gjc/` file edits are forbidden unless an explicit force override is active.
|
|
500
502
|
- Persist the final `spec_path` in state when available so downstream skills and resumed sessions can pass the artifact path explicitly.
|
|
501
503
|
- If the user preselected the deliberate ralplan path, use the native deep-interview write command with `--write --stage final --slug {slug} --spec <markdown-or-path> --deliberate [--json]` so the final spec is persisted before deep-interview hands off to ralplan.
|
|
@@ -45,6 +45,8 @@ gjc ralplan --write --stage <type> --stage_n <N> --artifact "markdown file path
|
|
|
45
45
|
|
|
46
46
|
Use stage values that match the producer or artifact kind, such as `planner`, `architect`, `critic`, `revision`, `adr`, or `final`. Increment `--stage_n` for each consensus-loop pass. The `--artifact` value may be either a markdown file path prepared outside `.gjc/` for ingestion or the markdown content string itself. The native `--write` handler persists markdown under `.gjc/plans/ralplan/<run-id>/stage-<NN>-<stage>.md`, maintains an `index.jsonl` audit log, and for `final` stages additionally writes a `pending-approval.md` copy. Direct `write`, `edit`, or `ast_edit` calls against `.gjc/specs`, `.gjc/plans`, `.gjc/state`, or any other `.gjc/` path are forbidden unless an explicit force override is active.
|
|
47
47
|
|
|
48
|
+
While ralplan is active it is a pre-approval planning phase: product-code mutation tools (`write`/`edit`/`ast_edit`) and product-mutating `bash` (e.g. `tee src/...`, redirects into the project tree) are blocked, exactly like deep-interview. Prefer passing the `--artifact` markdown **inline** (the content string) so no scratch file is needed; this is mandatory for restricted role agents (see below). Only the leader, and only when an artifact is too large to pass inline, may stage it as a file in a system temp directory (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) outside the project tree and pass that path — never write scratch files into the repo or `.gjc/`. Product code is mutated only after the plan is approved and execution begins.
|
|
49
|
+
|
|
48
50
|
Restricted read-only role agents (`planner`, `architect`, and `critic`) must pass markdown content directly in `--artifact`; their restricted bash environment intentionally disables artifact file-path ingestion so a verdict command cannot persist arbitrary file contents.
|
|
49
51
|
|
|
50
52
|
After a role agent persists a stage artifact, its model-facing response to the caller SHOULD be receipt-only: return the `gjc ralplan --write --json` receipt (`run_id`, `path`, `stage`, `stage_n`, `sha256`, `created_at`) plus the minimal verdict/status fields the caller needs for routing, and do **not** paste the full persisted markdown back into the parent conversation. Downstream reviewers should receive the artifact path/receipt and read the persisted file themselves when they actually need the body. This preserves the audit trail while preventing Planner/Architect/Critic verdict bodies from being duplicated into the main-agent context.
|
|
@@ -297,12 +297,26 @@ async function resolveSelectors(
|
|
|
297
297
|
}
|
|
298
298
|
if (mode) assertKnownMode(mode);
|
|
299
299
|
|
|
300
|
+
const sessionId = resolveSessionIdFromArgs(args, payload);
|
|
301
|
+
|
|
302
|
+
const threadId = flagValue(args, "--thread-id")?.trim() || undefined;
|
|
303
|
+
if (threadId) assertSafePathComponent(threadId, "thread-id");
|
|
304
|
+
const turnId = flagValue(args, "--turn-id")?.trim() || undefined;
|
|
305
|
+
if (turnId) assertSafePathComponent(turnId, "turn-id");
|
|
306
|
+
|
|
307
|
+
return { mode: mode as CanonicalGjcWorkflowSkill | undefined, sessionId, threadId, turnId, payload };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Session-id resolution order: explicit --session-id flag, then payload
|
|
311
|
+
// session_id, then GJC_SESSION_ID env var (set by AgentSession.sdk for
|
|
312
|
+
// agent-initiated CLI invocations). The env-var default keeps shell
|
|
313
|
+
// snippets in skill docs short while still routing state commands to the
|
|
314
|
+
// caller's session-scoped state files.
|
|
315
|
+
function resolveSessionIdFromArgs(
|
|
316
|
+
args: readonly string[],
|
|
317
|
+
payload: Record<string, unknown> | undefined,
|
|
318
|
+
): string | undefined {
|
|
300
319
|
const explicitSessionId = flagValue(args, "--session-id");
|
|
301
|
-
// Session-id resolution order: explicit --session-id flag, then payload
|
|
302
|
-
// session_id, then GJC_SESSION_ID env var (set by AgentSession.sdk for
|
|
303
|
-
// agent-initiated CLI invocations). The env-var default keeps shell
|
|
304
|
-
// snippets in skill docs short while still routing writes/reads to the
|
|
305
|
-
// caller's session-scoped state files.
|
|
306
320
|
let sessionId = explicitSessionId !== undefined ? explicitSessionId.trim() || undefined : undefined;
|
|
307
321
|
if (!sessionId && payload && typeof payload.session_id === "string") {
|
|
308
322
|
sessionId = payload.session_id.trim() || undefined;
|
|
@@ -312,13 +326,7 @@ async function resolveSelectors(
|
|
|
312
326
|
if (envSessionId) sessionId = envSessionId;
|
|
313
327
|
}
|
|
314
328
|
if (sessionId) assertSafePathComponent(sessionId, "session-id");
|
|
315
|
-
|
|
316
|
-
const threadId = flagValue(args, "--thread-id")?.trim() || undefined;
|
|
317
|
-
if (threadId) assertSafePathComponent(threadId, "thread-id");
|
|
318
|
-
const turnId = flagValue(args, "--turn-id")?.trim() || undefined;
|
|
319
|
-
if (turnId) assertSafePathComponent(turnId, "turn-id");
|
|
320
|
-
|
|
321
|
-
return { mode: mode as CanonicalGjcWorkflowSkill | undefined, sessionId, threadId, turnId, payload };
|
|
329
|
+
return sessionId;
|
|
322
330
|
}
|
|
323
331
|
|
|
324
332
|
async function inferModeFromActiveState(
|
|
@@ -718,8 +726,8 @@ async function handleDoctor(
|
|
|
718
726
|
): Promise<StateCommandResult> {
|
|
719
727
|
const rawSkill = flagValue(args, "--skill")?.trim() || flagValue(args, "--mode")?.trim() || positionalSkill?.trim();
|
|
720
728
|
if (rawSkill) assertKnownMode(rawSkill);
|
|
721
|
-
const
|
|
722
|
-
|
|
729
|
+
const payload = await readInputJson(flagValue(args, "--input"), cwd);
|
|
730
|
+
const sessionId = resolveSessionIdFromArgs(args, payload);
|
|
723
731
|
const summary = await collectDoctorSummary(cwd, rawSkill as CanonicalGjcWorkflowSkill | undefined, sessionId);
|
|
724
732
|
return {
|
|
725
733
|
status: summary.ok ? 0 : 1,
|
|
@@ -298,8 +298,28 @@ function flattenActiveSubskills(entries: SkillActiveEntry[]): ActiveSubskillEntr
|
|
|
298
298
|
return [...deduped.values()];
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
const CANONICAL_PIPELINE_RANK = new Map<string, number>([
|
|
302
|
+
["deep-interview", 0],
|
|
303
|
+
["ralplan", 1],
|
|
304
|
+
["ultragoal", 2],
|
|
305
|
+
]);
|
|
306
|
+
|
|
307
|
+
function canonicalPipelineRank(skill: string): number | undefined {
|
|
308
|
+
return CANONICAL_PIPELINE_RANK.get(skill);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function compareActiveEntryPrimary(a: SkillActiveEntry, b: SkillActiveEntry): number {
|
|
312
|
+
const aRank = canonicalPipelineRank(a.skill);
|
|
313
|
+
const bRank = canonicalPipelineRank(b.skill);
|
|
314
|
+
if (aRank !== undefined || bRank !== undefined) return (bRank ?? -1) - (aRank ?? -1);
|
|
315
|
+
const aTime = Date.parse(safeString(a.updated_at));
|
|
316
|
+
const bTime = Date.parse(safeString(b.updated_at));
|
|
317
|
+
if (Number.isFinite(aTime) || Number.isFinite(bTime)) return (bTime || 0) - (aTime || 0);
|
|
318
|
+
return 0;
|
|
319
|
+
}
|
|
320
|
+
|
|
301
321
|
function buildActiveSnapshot(entries: SkillActiveEntry[]): SkillActiveState {
|
|
302
|
-
const visible = entries.filter(entry => entry.active !== false);
|
|
322
|
+
const visible = entries.filter(entry => entry.active !== false).toSorted(compareActiveEntryPrimary);
|
|
303
323
|
const primary = visible[0];
|
|
304
324
|
return {
|
|
305
325
|
version: 1,
|