@gajae-code/coding-agent 0.7.3 → 0.7.4
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 +48 -0
- package/bin/gjc.js +4 -0
- package/dist/types/cli/plugin-cli.d.ts +2 -0
- package/dist/types/commands/plugin.d.ts +6 -0
- package/dist/types/commands/session.d.ts +6 -0
- package/dist/types/config/model-profile-activation.d.ts +8 -1
- package/dist/types/extensibility/gjc-plugins/compiler.d.ts +19 -0
- package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +29 -0
- package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/injection.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +13 -0
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +26 -0
- package/dist/types/extensibility/gjc-plugins/observability.d.ts +27 -0
- package/dist/types/extensibility/gjc-plugins/prompt-appendix.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +32 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +42 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +158 -2
- package/dist/types/extensibility/gjc-plugins/validation.d.ts +8 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/psmux-detect.d.ts +78 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +20 -1
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +18 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/modes/components/model-selector.d.ts +6 -0
- package/dist/types/notifications/html-format.d.ts +11 -0
- package/dist/types/notifications/index.d.ts +149 -1
- package/dist/types/notifications/lifecycle-commands.d.ts +72 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +98 -0
- package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +35 -0
- package/dist/types/notifications/telegram-daemon.d.ts +60 -0
- package/dist/types/notifications/telegram-reference.d.ts +3 -1
- package/dist/types/notifications/topic-registry.d.ts +10 -9
- package/dist/types/runtime-mcp/types.d.ts +7 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +14 -4
- package/dist/types/session/blob-store.d.ts +25 -0
- package/dist/types/session/session-manager.d.ts +57 -0
- package/dist/types/slash-commands/helpers/fast-status-report.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +9 -1
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/utils/changelog.d.ts +1 -0
- package/package.json +11 -9
- package/scripts/g004-tmux-smoke.ts +100 -0
- package/scripts/g005-daemon-smoke.ts +181 -0
- package/scripts/g011-daemon-path-smoke.ts +153 -0
- package/src/cli/plugin-cli.ts +66 -3
- package/src/cli.ts +21 -4
- package/src/commands/plugin.ts +4 -0
- package/src/commands/session.ts +18 -0
- package/src/config/model-profile-activation.ts +55 -7
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +3 -3
- package/src/defaults/gjc/skills/team/SKILL.md +5 -4
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +41 -13
- package/src/export/html/index.ts +2 -2
- package/src/extensibility/gjc-plugins/compiler.ts +351 -0
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +170 -0
- package/src/extensibility/gjc-plugins/index.ts +9 -0
- package/src/extensibility/gjc-plugins/injection.ts +109 -0
- package/src/extensibility/gjc-plugins/installer.ts +434 -0
- package/src/extensibility/gjc-plugins/loader.ts +3 -1
- package/src/extensibility/gjc-plugins/mcp-policy.ts +239 -0
- package/src/extensibility/gjc-plugins/observability.ts +84 -0
- package/src/extensibility/gjc-plugins/paths.ts +1 -1
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +109 -0
- package/src/extensibility/gjc-plugins/registry.ts +180 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +234 -0
- package/src/extensibility/gjc-plugins/schema.ts +250 -20
- package/src/extensibility/gjc-plugins/session-validation.ts +147 -0
- package/src/extensibility/gjc-plugins/types.ts +199 -3
- package/src/extensibility/gjc-plugins/validation.ts +80 -0
- package/src/extensibility/skills.ts +15 -0
- package/src/gjc-runtime/launch-tmux.ts +61 -7
- package/src/gjc-runtime/psmux-detect.ts +239 -0
- package/src/gjc-runtime/team-runtime.ts +56 -23
- package/src/gjc-runtime/tmux-common.ts +27 -2
- package/src/gjc-runtime/tmux-sessions.ts +51 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +75 -15
- package/src/internal-urls/docs-index.generated.ts +5 -4
- package/src/main.ts +14 -3
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-selector.ts +67 -43
- package/src/modes/components/model-selector.ts +44 -11
- package/src/modes/controllers/extension-ui-controller.ts +0 -27
- package/src/modes/controllers/selector-controller.ts +50 -11
- package/src/modes/interactive-mode.ts +2 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/notifications/html-format.ts +38 -0
- package/src/notifications/index.ts +242 -12
- package/src/notifications/lifecycle-commands.ts +228 -0
- package/src/notifications/lifecycle-control-runtime.ts +400 -0
- package/src/notifications/lifecycle-orchestrator.ts +358 -0
- package/src/notifications/rate-limit-pool.ts +19 -0
- package/src/notifications/recent-activity.ts +132 -0
- package/src/notifications/telegram-daemon.ts +433 -8
- package/src/notifications/telegram-reference.ts +25 -7
- package/src/notifications/topic-registry.ts +18 -9
- package/src/prompts/agents/executor.md +2 -2
- package/src/runtime-mcp/transports/stdio.ts +38 -4
- package/src/runtime-mcp/types.ts +7 -0
- package/src/sdk.ts +157 -10
- package/src/session/agent-session.ts +166 -74
- package/src/session/blob-store.ts +196 -8
- package/src/session/session-manager.ts +678 -7
- package/src/slash-commands/builtin-registry.ts +23 -3
- package/src/slash-commands/helpers/fast-status-report.ts +13 -3
- package/src/system-prompt.ts +9 -0
- package/src/task/executor.ts +31 -7
- package/src/task/index.ts +2 -0
- package/src/tools/ask.ts +5 -1
- package/src/tools/index.ts +3 -1
- package/src/utils/changelog.ts +8 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveGjcTmuxBinary } from "./psmux-detect";
|
|
2
|
+
|
|
1
3
|
export const GJC_DEFAULT_TMUX_SESSION = "gajae_code";
|
|
2
4
|
export const GJC_TMUX_SESSION_PREFIX = `${GJC_DEFAULT_TMUX_SESSION}_`;
|
|
3
5
|
export const GJC_TMUX_COMMAND_ENV = "GJC_TMUX_COMMAND";
|
|
@@ -31,10 +33,33 @@ export function envDisabled(value: string | undefined): boolean {
|
|
|
31
33
|
return normalized === "0" || normalized === "false" || normalized === "off" || normalized === "no";
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the tmux (or tmux-compatible multiplexer) command GJC should invoke.
|
|
38
|
+
*
|
|
39
|
+
* This is the shared entry point used by every GJC code path that needs to talk
|
|
40
|
+
* to a multiplexer: `gjc --tmux` planning, `gjc session ...`, `gjc team ...`,
|
|
41
|
+
* the lifecycle controller, and the harness resident owner. Routing all of
|
|
42
|
+
* them through the same resolver means a single `GJC_TMUX_COMMAND` override or
|
|
43
|
+
* a single Windows psmux / pmux detection wins for the whole process — the
|
|
44
|
+
* failure mode where `gjc --tmux` creates a psmux-backed session and then
|
|
45
|
+
* `gjc session status` fails because it queries literal `tmux` is closed off.
|
|
46
|
+
*
|
|
47
|
+
* Explicit `GJC_TMUX_COMMAND` / `GJC_TEAM_TMUX_COMMAND` overrides are honored on
|
|
48
|
+
* every platform. On native Windows without an override the resolver walks
|
|
49
|
+
* `psmux`, then `pmux`, then `tmux` and uses the first binary present on PATH.
|
|
50
|
+
* On POSIX the resolver returns `tmux` (the historical default) and only
|
|
51
|
+
* falls through to the platform-aware walker if the caller opts in.
|
|
52
|
+
*/
|
|
53
|
+
export function resolveGjcTmuxCommand(
|
|
54
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
55
|
+
platform: NodeJS.Platform = process.platform,
|
|
56
|
+
): string {
|
|
57
|
+
return resolveGjcTmuxBinary({ env, platform }).command;
|
|
36
58
|
}
|
|
37
59
|
|
|
60
|
+
export type { PsmuxProbe, ResolvedTmuxBinary, ResolveGjcTmuxBinaryOptions } from "./psmux-detect";
|
|
61
|
+
export { clearPsmuxDetectionCache, detectPsmux, probePsmux, resolveGjcTmuxBinary } from "./psmux-detect";
|
|
62
|
+
|
|
38
63
|
/**
|
|
39
64
|
* Build the exact-session target for tmux *option* commands
|
|
40
65
|
* (`show-options` / `set-option`) and `display-message -t`.
|
|
@@ -221,7 +221,13 @@ export function statusGjcTmuxSession(sessionName: string, env: NodeJS.ProcessEnv
|
|
|
221
221
|
export function createGjcTmuxSession(env: NodeJS.ProcessEnv = process.env): GjcTmuxSessionStatus {
|
|
222
222
|
const tmuxCommand = resolveGjcTmuxCommand(env);
|
|
223
223
|
const sessionName = buildGjcTmuxSessionName(env);
|
|
224
|
-
|
|
224
|
+
// Build a shell-bootstrap command appropriate for the host shell. Psmux on
|
|
225
|
+
// Windows runs the new-session command through PowerShell, so we use the
|
|
226
|
+
// $env:VAR = ... assignment form there. POSIX keeps the historical exec
|
|
227
|
+
// env form so the launched gjc inherits GJC_TMUX_LAUNCHED without leaking
|
|
228
|
+
// into the parent tmux server.
|
|
229
|
+
const platform = process.platform;
|
|
230
|
+
const command = platform === "win32" ? "$env:GJC_TMUX_LAUNCHED = '1'; gjc" : "exec env GJC_TMUX_LAUNCHED=1 gjc";
|
|
225
231
|
const created = Bun.spawnSync([tmuxCommand, "new-session", "-d", "-s", sessionName, command], {
|
|
226
232
|
stdout: "pipe",
|
|
227
233
|
stderr: "pipe",
|
|
@@ -306,6 +312,50 @@ export function removeGjcTmuxSession(sessionName: string, env: NodeJS.ProcessEnv
|
|
|
306
312
|
return session;
|
|
307
313
|
}
|
|
308
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Force-close a GJC-managed tmux session, even if a live pane is attached.
|
|
317
|
+
*
|
|
318
|
+
* This is the lifecycle-control counterpart to {@link removeGjcTmuxSession}: it
|
|
319
|
+
* intentionally does NOT refuse live/attached panes (hard-kill is the contract),
|
|
320
|
+
* but it keeps every safety check so it can only ever kill a genuinely
|
|
321
|
+
* GJC-managed session:
|
|
322
|
+
* - re-reads the exact tmux profile immediately before kill (never a non-GJC
|
|
323
|
+
* session, even one that collides by name);
|
|
324
|
+
* - when `expectedSessionId` is given, requires the `@gjc-session-id` tag match;
|
|
325
|
+
* - when `expectedStateFile` is given, requires the `@gjc-session-state-file`
|
|
326
|
+
* tag match.
|
|
327
|
+
*
|
|
328
|
+
* Returns the prior status (for audit). Throws a tagged error otherwise:
|
|
329
|
+
* `gjc_tmux_session_not_found`, `gjc_tmux_session_not_managed`,
|
|
330
|
+
* `gjc_tmux_session_id_mismatch`, or `gjc_tmux_session_state_file_mismatch`.
|
|
331
|
+
*/
|
|
332
|
+
export function forceCloseGjcTmuxSession(
|
|
333
|
+
sessionName: string,
|
|
334
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
335
|
+
expectedSessionId?: string,
|
|
336
|
+
expectedStateFile?: string,
|
|
337
|
+
): GjcTmuxSessionStatus {
|
|
338
|
+
const session = statusGjcTmuxSession(sessionName, env);
|
|
339
|
+
if (readProfileForExactTarget(session.name, env) !== GJC_TMUX_PROFILE_VALUE) {
|
|
340
|
+
throw new Error(`gjc_tmux_session_not_managed:${sessionName}`);
|
|
341
|
+
}
|
|
342
|
+
if (expectedSessionId !== undefined) {
|
|
343
|
+
const actual = readExactOptionForGc(session.name, GJC_TMUX_SESSION_ID_OPTION, env);
|
|
344
|
+
if (actual !== expectedSessionId) {
|
|
345
|
+
throw new Error(`gjc_tmux_session_id_mismatch:${sessionName}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (expectedStateFile !== undefined) {
|
|
349
|
+
const actual = readExactOptionForGc(session.name, GJC_TMUX_SESSION_STATE_FILE_OPTION, env);
|
|
350
|
+
if (actual !== expectedStateFile) {
|
|
351
|
+
throw new Error(`gjc_tmux_session_state_file_mismatch:${sessionName}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// Intentionally NOT refusing live/attached panes — force-close is hard-kill.
|
|
355
|
+
runTmux(["kill-session", "-t", `=${session.name}`], env);
|
|
356
|
+
return session;
|
|
357
|
+
}
|
|
358
|
+
|
|
309
359
|
export function attachGjcTmuxSession(sessionName: string, env: NodeJS.ProcessEnv = process.env): never {
|
|
310
360
|
const session = statusGjcTmuxSession(sessionName, env);
|
|
311
361
|
const tmuxCommand = resolveGjcTmuxCommand(env);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { inflateSync } from "node:zlib";
|
|
4
|
-
|
|
4
|
+
import { normalizeGoal } from "../goals/state";
|
|
5
|
+
import { buildSessionContext, loadEntriesFromFile, type SessionEntry } from "../session/session-manager";
|
|
5
6
|
import type { WorkflowHudSummary } from "../skill-state/active-state";
|
|
6
7
|
import { buildUltragoalHudSummary as buildWorkflowUltragoalHudSummary } from "../skill-state/workflow-hud";
|
|
7
8
|
import { renderCliWriteReceipt } from "./cli-write-receipt";
|
|
8
|
-
import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
|
|
9
|
+
import { DEFAULT_ULTRAGOAL_OBJECTIVE, GJC_SESSION_FILE_ENV } from "./goal-mode-request";
|
|
9
10
|
import { gjcRoot, sessionUltragoalDir } from "./session-layout";
|
|
10
11
|
import { resolveGjcSessionForRead, resolveGjcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
|
|
11
12
|
import { renderUltragoalStatusMarkdown } from "./state-renderer";
|
|
@@ -831,6 +832,14 @@ function normalizedEvidenceKind(row: JsonObject): string {
|
|
|
831
832
|
function evidenceKindMatches(kind: string, words: string[]): boolean {
|
|
832
833
|
return words.some(word => kind.includes(word));
|
|
833
834
|
}
|
|
835
|
+
function formatActualArtifactKinds(artifactIds: string[], kinds: string[]): string {
|
|
836
|
+
if (artifactIds.length === 0) return "none";
|
|
837
|
+
return artifactIds.map((id, index) => `${id}=${kinds[index] ?? "<missing-kind>"}`).join(", ");
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function formatExpectedKindWords(words: string[]): string {
|
|
841
|
+
return words.map(word => `"${word}"`).join(", ");
|
|
842
|
+
}
|
|
834
843
|
|
|
835
844
|
type SurfaceFamily = "web" | "cli" | "native" | "api-package" | "algorithm-math" | "unknown";
|
|
836
845
|
|
|
@@ -899,9 +908,18 @@ function categorizeComputerChangePath(value: string): UltragoalChangeCategory {
|
|
|
899
908
|
}
|
|
900
909
|
|
|
901
910
|
function isComputerControlSurfaceCategory(category: UltragoalChangeCategory): boolean {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
)
|
|
911
|
+
// The computer-use red-team suite is conditional, not universal (see the
|
|
912
|
+
// ultragoal SKILL): require it only when the change actually touches
|
|
913
|
+
// computer-control source — the computer tool (`tool`), its settings/registry
|
|
914
|
+
// wiring (`settings-registry`), or computer Rust (`code`). A bare regeneration
|
|
915
|
+
// of the SHARED native binding (`generated-binding`: packages/natives/native/
|
|
916
|
+
// index.{d.ts,js}) is NOT by itself a computer-use change: that file is
|
|
917
|
+
// generated from Rust, so any real computer-use behavior change must also
|
|
918
|
+
// touch one of the categories above and will still trigger the suite. Treating
|
|
919
|
+
// the regenerated aggregate binding as a computer surface forced the suite on
|
|
920
|
+
// unrelated features (e.g. notifications), which the SKILL explicitly warns
|
|
921
|
+
// against, so it is excluded here.
|
|
922
|
+
return category === "code" || category === "tool" || category === "settings-registry";
|
|
905
923
|
}
|
|
906
924
|
|
|
907
925
|
function isComputerControlSurfaceChangePath(row: UltragoalChangeSetPath): boolean {
|
|
@@ -967,7 +985,7 @@ function validateSurfaceArtifactCompatibility(
|
|
|
967
985
|
const hasVisual = kinds.some(kind => evidenceKindMatches(kind, ["screenshot", "image", "visual"]));
|
|
968
986
|
if (!hasBrowser || !hasVisual) {
|
|
969
987
|
throw new Error(
|
|
970
|
-
`qualityGate ${fieldName} for GUI/web surfaces must reference browser automation plus screenshot or image-verdict artifacts`,
|
|
988
|
+
`qualityGate ${fieldName} for GUI/web surfaces must reference browser automation plus screenshot or image-verdict artifacts; surface "${surface}" expected one artifact kind containing one of ${formatExpectedKindWords(["browser", "playwright", "pandawright", "automation"])} and one containing one of ${formatExpectedKindWords(["screenshot", "image", "visual"])}; actual artifact kinds: ${formatActualArtifactKinds(artifactIds, kinds)}`,
|
|
971
989
|
);
|
|
972
990
|
}
|
|
973
991
|
return;
|
|
@@ -994,7 +1012,7 @@ function validateSurfaceArtifactCompatibility(
|
|
|
994
1012
|
const expected = surfaceFamilies[family];
|
|
995
1013
|
if (!kinds.some(kind => evidenceKindMatches(kind, expected.evidence))) {
|
|
996
1014
|
throw new Error(
|
|
997
|
-
`qualityGate ${fieldName} for ${expected.label} surfaces must reference compatible artifact kinds`,
|
|
1015
|
+
`qualityGate ${fieldName} for ${expected.label} surfaces must reference compatible artifact kinds; surface "${surface}" expected at least one artifact kind containing one of ${formatExpectedKindWords(expected.evidence)}; actual artifact kinds: ${formatActualArtifactKinds(artifactIds, kinds)}`,
|
|
998
1016
|
);
|
|
999
1017
|
}
|
|
1000
1018
|
}
|
|
@@ -1490,6 +1508,20 @@ function isAllowedCliReplayCommand(command: readonly string[]): boolean {
|
|
|
1490
1508
|
if (executable === "gjc") return args.length === 1 && ["read", "status"].includes(args[0] ?? "");
|
|
1491
1509
|
return false;
|
|
1492
1510
|
}
|
|
1511
|
+
function summarizeBlockedCliReplayCommand(command: readonly string[]): string {
|
|
1512
|
+
const executable = command[0] ? basenameCommand(command[0]) : "<missing>";
|
|
1513
|
+
const argCount = Math.max(0, command.length - 1);
|
|
1514
|
+
return `${JSON.stringify(executable)} with ${argCount} arg${argCount === 1 ? "" : "s"}`;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
function cliReplayAllowlistDescription(): string {
|
|
1518
|
+
return [
|
|
1519
|
+
'`bun --version`, `node --version`, or deterministic `bun/node -e "console.log(...)"`',
|
|
1520
|
+
"`npm|pnpm|yarn --version` or `npm|pnpm|yarn list`",
|
|
1521
|
+
"read-only `git status|rev-parse|merge-base|diff|show|log` with safe args",
|
|
1522
|
+
"`gjc read` or `gjc status`",
|
|
1523
|
+
].join("; ");
|
|
1524
|
+
}
|
|
1493
1525
|
|
|
1494
1526
|
function resolveCliReplayCommand(command: string[]): string[] {
|
|
1495
1527
|
if (basenameCommand(command[0]!) === "bun") return [process.execPath, ...command.slice(1)];
|
|
@@ -1578,8 +1610,11 @@ function parseCliReplayRecord(
|
|
|
1578
1610
|
if (!command) throw new Error(`qualityGate ${fieldName}.command must be a non-empty string array`);
|
|
1579
1611
|
if (record.replaySafe !== true)
|
|
1580
1612
|
throw new Error(`qualityGate ${fieldName}.replaySafe must be true before CLI replay executes`);
|
|
1581
|
-
if (!isAllowedCliReplayCommand(command))
|
|
1582
|
-
throw new Error(
|
|
1613
|
+
if (!isAllowedCliReplayCommand(command)) {
|
|
1614
|
+
throw new Error(
|
|
1615
|
+
`qualityGate ${fieldName}.command is not in the conservative CLI replay allowlist; command ${summarizeBlockedCliReplayCommand(command)} is blocked. Allowed replay commands: ${cliReplayAllowlistDescription()}. For other commands, provide audited replayExempt metadata with reasonCode, reason, approvedBy, and fallbackArtifactRefs that point to a structurally valid fallback artifact.`,
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1583
1618
|
if (record.normalization !== undefined && record.normalization !== "default") {
|
|
1584
1619
|
throw new Error(`qualityGate ${fieldName}.normalization must be default when provided`);
|
|
1585
1620
|
}
|
|
@@ -2231,6 +2266,28 @@ function snapshotUpdatedAtMilliseconds(value: unknown): number | null {
|
|
|
2231
2266
|
const parsed = Date.parse(trimmed);
|
|
2232
2267
|
return Number.isFinite(parsed) ? parsed : null;
|
|
2233
2268
|
}
|
|
2269
|
+
|
|
2270
|
+
function singleSessionLeafId(entries: readonly SessionEntry[]): string | undefined {
|
|
2271
|
+
if (entries.length === 0) return undefined;
|
|
2272
|
+
const parentIds = new Set(
|
|
2273
|
+
entries.map(entry => entry.parentId).filter((parentId): parentId is string => typeof parentId === "string"),
|
|
2274
|
+
);
|
|
2275
|
+
const leafIds = entries.map(entry => entry.id).filter(id => !parentIds.has(id));
|
|
2276
|
+
return leafIds.length === 1 ? leafIds[0] : undefined;
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
async function readCurrentSessionGjcGoalSnapshot(): Promise<unknown | undefined> {
|
|
2280
|
+
const sessionFile = process.env[GJC_SESSION_FILE_ENV]?.trim();
|
|
2281
|
+
if (!sessionFile) return undefined;
|
|
2282
|
+
const fileEntries = await loadEntriesFromFile(sessionFile);
|
|
2283
|
+
const entries = fileEntries.filter((entry): entry is SessionEntry => entry.type !== "session");
|
|
2284
|
+
const leafId = singleSessionLeafId(entries);
|
|
2285
|
+
if (!leafId) return undefined;
|
|
2286
|
+
const context = buildSessionContext(entries, leafId);
|
|
2287
|
+
if (context.mode !== "goal" && context.mode !== "goal_paused") return undefined;
|
|
2288
|
+
const goal = normalizeGoal(context.modeData?.goal);
|
|
2289
|
+
return goal ? { goal } : undefined;
|
|
2290
|
+
}
|
|
2234
2291
|
async function readGjcGoalSnapshot(input: {
|
|
2235
2292
|
cwd: string;
|
|
2236
2293
|
value: string | undefined;
|
|
@@ -2240,13 +2297,15 @@ async function readGjcGoalSnapshot(input: {
|
|
|
2240
2297
|
errorPrefix: string;
|
|
2241
2298
|
allowCompletedLegacyBlocker?: boolean;
|
|
2242
2299
|
}): Promise<unknown> {
|
|
2243
|
-
|
|
2300
|
+
const snapshot = input.value?.trim()
|
|
2301
|
+
? await readStructuredValue(input.cwd, input.value)
|
|
2302
|
+
: await readCurrentSessionGjcGoalSnapshot();
|
|
2303
|
+
if (snapshot === undefined) {
|
|
2244
2304
|
if (!input.required) return undefined;
|
|
2245
2305
|
throw new Error(
|
|
2246
|
-
`${input.errorPrefix} require
|
|
2306
|
+
`${input.errorPrefix} require an active GJC goal-mode snapshot from the current session or --gjc-goal-json; this is the GJC goal-mode receipt, not the .gjc/ultragoal/goals.json goal record`,
|
|
2247
2307
|
);
|
|
2248
2308
|
}
|
|
2249
|
-
const snapshot = await readStructuredValue(input.cwd, input.value);
|
|
2250
2309
|
const snapshotObject = qualityGateObject(snapshot);
|
|
2251
2310
|
const detailsObject = qualityGateObject(snapshotObject?.details);
|
|
2252
2311
|
const goalObject = qualityGateObject(snapshotObject?.goal) ?? qualityGateObject(detailsObject?.goal);
|
|
@@ -3331,18 +3390,19 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
|
|
|
3331
3390
|
" --status=<value> pending|active|complete|failed|blocked|review_blocked|superseded",
|
|
3332
3391
|
" --evidence=<value> Completion or checkpoint evidence text",
|
|
3333
3392
|
" --quality-gate-json=<value> JSON string or path for complete checkpoints",
|
|
3334
|
-
|
|
3393
|
+
" --gjc-goal-json=<value> Optional JSON/path override for current goal snapshot; omitted complete checkpoints read current session goal state",
|
|
3335
3394
|
" --json Output a machine-readable receipt",
|
|
3336
3395
|
"",
|
|
3337
3396
|
"COMPLETE CHECKPOINT RECEIPTS",
|
|
3338
3397
|
" --quality-gate-json must be an object with architectReview, executorQa, and iteration.",
|
|
3339
3398
|
" executorQa.contractCoverage[] rows require an obligation field; description is not a substitute.",
|
|
3340
|
-
|
|
3399
|
+
" Complete checkpoints use the current session's active GJC goal-mode snapshot when --gjc-goal-json is omitted.",
|
|
3400
|
+
" Explicit --gjc-goal-json values must contain an active GJC goal-mode snapshot, not the .gjc/ultragoal/goals.json goal record.",
|
|
3341
3401
|
" goal.updatedAt may be epoch milliseconds or an ISO timestamp and must be fresh.",
|
|
3342
3402
|
"",
|
|
3343
3403
|
"EXAMPLES",
|
|
3344
3404
|
' $ gjc ultragoal checkpoint --goal-id G001 --status blocked --evidence "waiting on review"',
|
|
3345
|
-
' $ gjc ultragoal checkpoint --goal-id G001 --status complete --evidence "tests passed" --
|
|
3405
|
+
' $ gjc ultragoal checkpoint --goal-id G001 --status complete --evidence "tests passed" --quality-gate-json ./quality-gate.json --json',
|
|
3346
3406
|
"",
|
|
3347
3407
|
].join("\n");
|
|
3348
3408
|
}
|