@gajae-code/coding-agent 0.2.5 → 0.3.1
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 +28 -0
- package/dist/types/async/job-manager.d.ts +91 -2
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/commands/harness.d.ts +37 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +10 -4
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/debug/crash-diagnostics.d.ts +45 -0
- package/dist/types/debug/runtime-gauges.d.ts +6 -0
- package/dist/types/deep-interview/render-middleware.d.ts +6 -0
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/eval/py/kernel.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +10 -0
- 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/cli-write-receipt.d.ts +24 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
- package/dist/types/gjc-runtime/state-graph.d.ts +4 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +33 -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-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +147 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- 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 +31 -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 +23 -29
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
- package/dist/types/internal-urls/types.d.ts +4 -0
- package/dist/types/lsp/index.d.ts +10 -10
- package/dist/types/modes/bridge/auth.d.ts +12 -0
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
- package/dist/types/modes/bridge/event-stream.d.ts +8 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/hook-selector.d.ts +1 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
- package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
- package/dist/types/modes/index.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/jobs-observer.d.ts +57 -0
- package/dist/types/modes/rpc/host-tools.d.ts +1 -16
- package/dist/types/modes/rpc/host-uris.d.ts +1 -38
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
- package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
- 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 +19 -1
- package/dist/types/skill-state/active-state.d.ts +2 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +25 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/id.d.ts +7 -0
- package/dist/types/task/index.d.ts +5 -0
- package/dist/types/task/receipt.d.ts +85 -0
- package/dist/types/task/spawn-gate.d.ts +38 -0
- package/dist/types/task/types.d.ts +198 -14
- package/dist/types/tools/cron.d.ts +6 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +26 -1
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +334 -6
- package/src/cli/args.ts +9 -2
- 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/deep-interview.ts +1 -0
- package/src/commands/harness.ts +862 -0
- package/src/commands/launch.ts +2 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +54 -39
- package/src/config/keybindings.ts +6 -0
- package/src/config/settings-schema.ts +13 -3
- package/src/config/settings.ts +5 -0
- package/src/dap/client.ts +17 -3
- package/src/debug/crash-diagnostics.ts +223 -0
- package/src/debug/runtime-gauges.ts +20 -0
- package/src/deep-interview/render-middleware.ts +372 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
- package/src/defaults/gjc/skills/team/SKILL.md +47 -21
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +106 -13
- package/src/eval/py/executor.ts +21 -1
- package/src/eval/py/kernel.ts +15 -0
- package/src/exec/bash-executor.ts +41 -0
- 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/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +98 -42
- package/src/gjc-runtime/goal-mode-request.ts +11 -3
- package/src/gjc-runtime/ralplan-runtime.ts +235 -43
- package/src/gjc-runtime/state-graph.ts +86 -0
- package/src/gjc-runtime/state-migrations.ts +179 -0
- package/src/gjc-runtime/state-renderer.ts +345 -0
- package/src/gjc-runtime/state-runtime.ts +1155 -46
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-validation.ts +49 -0
- package/src/gjc-runtime/state-writer.ts +749 -0
- package/src/gjc-runtime/team-runtime.ts +1255 -189
- package/src/gjc-runtime/ultragoal-runtime.ts +460 -43
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +1601 -0
- package/src/gjc-runtime/workflow-manifest.ts +427 -0
- package/src/harness-control-plane/classifier.ts +128 -0
- package/src/harness-control-plane/control-endpoint.ts +148 -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 +600 -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 +98 -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 +197 -64
- package/src/internal-urls/agent-protocol.ts +68 -21
- package/src/internal-urls/artifact-protocol.ts +12 -17
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/registry-helpers.ts +19 -16
- package/src/internal-urls/types.ts +4 -0
- package/src/lsp/client.ts +18 -2
- package/src/main.ts +21 -5
- package/src/modes/bridge/auth.ts +41 -0
- package/src/modes/bridge/bridge-client-bridge.ts +47 -0
- package/src/modes/bridge/bridge-mode.ts +520 -0
- package/src/modes/bridge/bridge-ui-context.ts +200 -0
- package/src/modes/bridge/event-stream.ts +70 -0
- package/src/modes/components/assistant-message.ts +5 -1
- package/src/modes/components/custom-editor.ts +101 -0
- package/src/modes/components/hook-selector.ts +133 -20
- package/src/modes/components/jobs-overlay-model.ts +109 -0
- package/src/modes/components/jobs-overlay.ts +172 -0
- package/src/modes/components/status-line/presets.ts +7 -5
- package/src/modes/components/status-line/segments.ts +25 -0
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line.ts +9 -1
- package/src/modes/controllers/event-controller.ts +71 -6
- package/src/modes/controllers/extension-ui-controller.ts +43 -1
- package/src/modes/controllers/input-controller.ts +105 -9
- package/src/modes/controllers/selector-controller.ts +31 -1
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +28 -0
- package/src/modes/jobs-observer.ts +204 -0
- package/src/modes/rpc/host-tools.ts +1 -186
- package/src/modes/rpc/host-uris.ts +1 -235
- package/src/modes/rpc/rpc-client.ts +25 -10
- package/src/modes/rpc/rpc-mode.ts +12 -381
- package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
- package/src/modes/shared/agent-wire/command-validation.ts +131 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
- package/src/modes/shared/agent-wire/handshake.ts +117 -0
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
- package/src/modes/shared/agent-wire/protocol.ts +96 -0
- package/src/modes/shared/agent-wire/responses.ts +17 -0
- package/src/modes/shared/agent-wire/scopes.ts +89 -0
- package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
- package/src/modes/shared/agent-wire/ui-result.ts +48 -0
- package/src/modes/types.ts +2 -0
- package/src/prompts/agents/executor.md +13 -0
- package/src/prompts/tools/subagent.md +39 -4
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +8 -0
- package/src/session/agent-session.ts +445 -71
- package/src/session/session-manager.ts +13 -1
- package/src/skill-state/active-state.ts +58 -65
- package/src/skill-state/deep-interview-mutation-guard.ts +114 -17
- package/src/skill-state/initial-phase.ts +2 -0
- package/src/skill-state/workflow-state-contract.ts +33 -4
- package/src/skill-state/workflow-state-version.ts +3 -0
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/task/executor.ts +79 -13
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +376 -74
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +54 -134
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +104 -10
- package/src/tools/ask.ts +88 -27
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +1 -1
- package/src/tools/cron.ts +48 -0
- package/src/tools/find.ts +4 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/search.ts +1 -0
- package/src/tools/skill.ts +6 -1
- package/src/tools/subagent.ts +423 -79
|
@@ -0,0 +1,179 @@
|
|
|
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 {
|
|
5
|
+
canonicalWorkflowSkill,
|
|
6
|
+
WORKFLOW_STATE_RECEIPT_VERSION,
|
|
7
|
+
WORKFLOW_STATE_VERSION,
|
|
8
|
+
} from "../skill-state/workflow-state-contract";
|
|
9
|
+
import { writeWorkflowEnvelopeAtomic } from "./state-writer";
|
|
10
|
+
import { getSkillManifest } from "./workflow-manifest";
|
|
11
|
+
|
|
12
|
+
export interface NormalizeLegacyStateResult {
|
|
13
|
+
state: Record<string, unknown>;
|
|
14
|
+
changed: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface MigrateAndPersistLegacyStateArgs {
|
|
18
|
+
cwd: string;
|
|
19
|
+
skill: string;
|
|
20
|
+
statePath: string;
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface MigrateAndPersistLegacyStateResult {
|
|
25
|
+
migrated: boolean;
|
|
26
|
+
path: string;
|
|
27
|
+
}
|
|
28
|
+
export interface MigrateWorkflowStateResult {
|
|
29
|
+
state: Record<string, unknown>;
|
|
30
|
+
fromVersion: number;
|
|
31
|
+
toVersion: number;
|
|
32
|
+
changed: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type WorkflowStateMigration = (
|
|
36
|
+
state: Record<string, unknown>,
|
|
37
|
+
skill: CanonicalGjcWorkflowSkill,
|
|
38
|
+
) => Record<string, unknown>;
|
|
39
|
+
|
|
40
|
+
const RECEIPT_STRING_FIELDS = [
|
|
41
|
+
"command",
|
|
42
|
+
"state_path",
|
|
43
|
+
"storage_path",
|
|
44
|
+
"mutated_at",
|
|
45
|
+
"fresh_until",
|
|
46
|
+
"mutation_id",
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
function cloneRecord(record: Record<string, unknown>): Record<string, unknown> {
|
|
50
|
+
return { ...record };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function canonicalSkillOrThrow(skill: string): CanonicalGjcWorkflowSkill {
|
|
54
|
+
const canonical = canonicalWorkflowSkill(skill);
|
|
55
|
+
if (!canonical) throw new Error(`Unsupported GJC workflow skill: ${skill}`);
|
|
56
|
+
return canonical;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function safeString(value: unknown): string {
|
|
60
|
+
return typeof value === "string" ? value : "";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function legacyPhaseForSkill(skill: CanonicalGjcWorkflowSkill, phase: string): string {
|
|
64
|
+
if (phase === "planning") return initialPhaseForSkill(skill);
|
|
65
|
+
return phase;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizePhase(skill: CanonicalGjcWorkflowSkill, value: unknown): string {
|
|
69
|
+
const manifest = getSkillManifest(skill);
|
|
70
|
+
const manifestStates = new Set(manifest.states.map(state => state.id));
|
|
71
|
+
const phase = legacyPhaseForSkill(skill, safeString(value).trim());
|
|
72
|
+
return manifestStates.has(phase) ? phase : manifest.initialState;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function receiptWithRequiredFields(raw: unknown, skill: CanonicalGjcWorkflowSkill): Record<string, unknown> {
|
|
76
|
+
const receipt =
|
|
77
|
+
raw && typeof raw === "object" && !Array.isArray(raw) ? cloneRecord(raw as Record<string, unknown>) : {};
|
|
78
|
+
receipt.version = WORKFLOW_STATE_RECEIPT_VERSION;
|
|
79
|
+
receipt.skill = skill;
|
|
80
|
+
if (receipt.owner !== "gjc-state-cli" && receipt.owner !== "gjc-runtime" && receipt.owner !== "gjc-hook") {
|
|
81
|
+
receipt.owner = "gjc-state-cli";
|
|
82
|
+
}
|
|
83
|
+
if (receipt.status !== "fresh" && receipt.status !== "stale") receipt.status = "stale";
|
|
84
|
+
for (const field of RECEIPT_STRING_FIELDS) {
|
|
85
|
+
if (typeof receipt[field] !== "string") receipt[field] = "";
|
|
86
|
+
}
|
|
87
|
+
return receipt;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function recordsEqual(left: Record<string, unknown>, right: Record<string, unknown>): boolean {
|
|
91
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
92
|
+
}
|
|
93
|
+
function migrateV1ToV2(state: Record<string, unknown>, skill: CanonicalGjcWorkflowSkill): Record<string, unknown> {
|
|
94
|
+
const migrated = cloneRecord(state);
|
|
95
|
+
migrated.version = WORKFLOW_STATE_VERSION;
|
|
96
|
+
migrated.skill = skill;
|
|
97
|
+
|
|
98
|
+
const sourcePhase = typeof migrated.current_phase === "string" ? migrated.current_phase : migrated.phase;
|
|
99
|
+
const normalizedPhase = normalizePhase(skill, sourcePhase);
|
|
100
|
+
migrated.current_phase = normalizedPhase;
|
|
101
|
+
if ("phase" in migrated && typeof migrated.phase === "string") migrated.phase = normalizedPhase;
|
|
102
|
+
|
|
103
|
+
return migrated;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const MIGRATIONS: Record<number, WorkflowStateMigration> = {
|
|
107
|
+
1: migrateV1ToV2,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export function migrateWorkflowState(raw: Record<string, unknown>, skill: string): MigrateWorkflowStateResult {
|
|
111
|
+
const canonicalSkill = canonicalSkillOrThrow(skill);
|
|
112
|
+
const fromVersion = typeof raw.version === "number" ? raw.version : 1;
|
|
113
|
+
if (fromVersion >= WORKFLOW_STATE_VERSION) {
|
|
114
|
+
return { state: raw, fromVersion, toVersion: fromVersion, changed: false };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let version = fromVersion;
|
|
118
|
+
let state = raw;
|
|
119
|
+
let changed = false;
|
|
120
|
+
while (version < WORKFLOW_STATE_VERSION && MIGRATIONS[version]) {
|
|
121
|
+
state = MIGRATIONS[version](state, canonicalSkill);
|
|
122
|
+
version += 1;
|
|
123
|
+
changed = true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { state, fromVersion, toVersion: version, changed };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Pure legacy state normalizer for background/internal readers.
|
|
131
|
+
*
|
|
132
|
+
* Readers that need compatibility with old on-disk workflow state shapes must call
|
|
133
|
+
* this in-memory helper and must never call `migrateAndPersistLegacyState`. The
|
|
134
|
+
* persist variant is reserved for explicit state migration commands because it is
|
|
135
|
+
* the only path allowed to write normalized upgrades back to `.gjc/state/**`.
|
|
136
|
+
*/
|
|
137
|
+
export function normalizeLegacyState(raw: Record<string, unknown>, skill: string): NormalizeLegacyStateResult {
|
|
138
|
+
const canonicalSkill = canonicalSkillOrThrow(skill);
|
|
139
|
+
const state = cloneRecord(raw);
|
|
140
|
+
state.skill = canonicalSkill;
|
|
141
|
+
if (typeof state.version !== "number") state.version = 1;
|
|
142
|
+
if (typeof state.active !== "boolean") state.active = true;
|
|
143
|
+
if (typeof state.updated_at !== "string") state.updated_at = new Date().toISOString();
|
|
144
|
+
state.receipt = receiptWithRequiredFields(state.receipt, canonicalSkill);
|
|
145
|
+
|
|
146
|
+
const migrated = migrateWorkflowState(state, canonicalSkill).state;
|
|
147
|
+
return { state: migrated, changed: !recordsEqual(raw, migrated) };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function migrateAndPersistLegacyState(
|
|
151
|
+
args: MigrateAndPersistLegacyStateArgs,
|
|
152
|
+
): Promise<MigrateAndPersistLegacyStateResult> {
|
|
153
|
+
const canonicalSkill = canonicalSkillOrThrow(args.skill);
|
|
154
|
+
const raw = JSON.parse(await fs.readFile(args.statePath, "utf-8")) as unknown;
|
|
155
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
156
|
+
throw new Error(`Workflow state file must contain a JSON object: ${args.statePath}`);
|
|
157
|
+
}
|
|
158
|
+
const { state, changed } = normalizeLegacyState(raw as Record<string, unknown>, canonicalSkill);
|
|
159
|
+
if (!changed) return { migrated: false, path: args.statePath };
|
|
160
|
+
|
|
161
|
+
const persistedPath = await writeWorkflowEnvelopeAtomic(args.statePath, state, {
|
|
162
|
+
cwd: args.cwd,
|
|
163
|
+
receipt: {
|
|
164
|
+
cwd: args.cwd,
|
|
165
|
+
skill: canonicalSkill,
|
|
166
|
+
owner: "gjc-state-cli",
|
|
167
|
+
command: `gjc state ${canonicalSkill} migrate`,
|
|
168
|
+
sessionId: args.sessionId,
|
|
169
|
+
},
|
|
170
|
+
audit: {
|
|
171
|
+
cwd: args.cwd,
|
|
172
|
+
skill: canonicalSkill,
|
|
173
|
+
verb: "migrate",
|
|
174
|
+
owner: "gjc-state-cli",
|
|
175
|
+
category: "state",
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
return { migrated: true, path: persistedPath };
|
|
179
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import type { CanonicalGjcWorkflowSkill } from "../skill-state/active-state";
|
|
2
|
+
import type { SkillManifest } from "./workflow-manifest";
|
|
3
|
+
|
|
4
|
+
function scalar(value: unknown): string | undefined {
|
|
5
|
+
if (typeof value === "string") return value.trim() || undefined;
|
|
6
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
11
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function stateObject(stateJson: Record<string, unknown>): Record<string, unknown> {
|
|
15
|
+
const nested = stateJson.state;
|
|
16
|
+
return isRecord(nested) ? nested : stateJson;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function receiptObject(state: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
20
|
+
return isRecord(state.receipt) ? state.receipt : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function artifactLinks(state: Record<string, unknown>): string[] {
|
|
24
|
+
const links = new Set<string>();
|
|
25
|
+
for (const key of [
|
|
26
|
+
"artifact",
|
|
27
|
+
"artifact_path",
|
|
28
|
+
"artifact_url",
|
|
29
|
+
"plan_path",
|
|
30
|
+
"spec_path",
|
|
31
|
+
"ledger_path",
|
|
32
|
+
"storage_path",
|
|
33
|
+
"state_path",
|
|
34
|
+
]) {
|
|
35
|
+
const value = scalar(state[key]);
|
|
36
|
+
if (value) links.add(value);
|
|
37
|
+
}
|
|
38
|
+
const artifacts = state.artifacts;
|
|
39
|
+
if (Array.isArray(artifacts)) {
|
|
40
|
+
for (const artifact of artifacts) {
|
|
41
|
+
const value = scalar(artifact);
|
|
42
|
+
if (value) links.add(value);
|
|
43
|
+
if (isRecord(artifact)) {
|
|
44
|
+
for (const key of ["path", "url", "href"]) {
|
|
45
|
+
const nested = scalar(artifact[key]);
|
|
46
|
+
if (nested) links.add(nested);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return [...links];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function keyStateFields(state: Record<string, unknown>, manifest: SkillManifest): Array<[string, string]> {
|
|
55
|
+
const keys = new Set<string>([
|
|
56
|
+
"active",
|
|
57
|
+
"current_phase",
|
|
58
|
+
"phase",
|
|
59
|
+
"status",
|
|
60
|
+
"updated_at",
|
|
61
|
+
"session_id",
|
|
62
|
+
...manifest.hudFields,
|
|
63
|
+
]);
|
|
64
|
+
const fields: Array<[string, string]> = [];
|
|
65
|
+
for (const key of keys) {
|
|
66
|
+
const value = scalar(state[key]);
|
|
67
|
+
if (value !== undefined) fields.push([key, value]);
|
|
68
|
+
if (fields.length >= 10) break;
|
|
69
|
+
}
|
|
70
|
+
return fields;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const COMPACT_ELIDE_KEYS = new Set([
|
|
74
|
+
"rounds",
|
|
75
|
+
"ontology_snapshots",
|
|
76
|
+
"architect_findings",
|
|
77
|
+
"new_requirements",
|
|
78
|
+
"ci_gates",
|
|
79
|
+
"research_findings",
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
export const STATE_FIELD_ALLOWLIST = [
|
|
83
|
+
"skill",
|
|
84
|
+
"phase",
|
|
85
|
+
"current_phase",
|
|
86
|
+
"next",
|
|
87
|
+
"active",
|
|
88
|
+
"status",
|
|
89
|
+
"fresh",
|
|
90
|
+
"fresh_until",
|
|
91
|
+
"receipt",
|
|
92
|
+
"artifact_path",
|
|
93
|
+
"plan_path",
|
|
94
|
+
"spec_path",
|
|
95
|
+
"run_id",
|
|
96
|
+
"stage",
|
|
97
|
+
"stage_n",
|
|
98
|
+
"session_id",
|
|
99
|
+
"updated_at",
|
|
100
|
+
"handoff_to",
|
|
101
|
+
"handoff_from",
|
|
102
|
+
"counts",
|
|
103
|
+
"hud",
|
|
104
|
+
] as const;
|
|
105
|
+
|
|
106
|
+
export type StateProjectionField = (typeof STATE_FIELD_ALLOWLIST)[number];
|
|
107
|
+
|
|
108
|
+
export interface StateStatusSummary {
|
|
109
|
+
skill: CanonicalGjcWorkflowSkill;
|
|
110
|
+
phase: string;
|
|
111
|
+
active: boolean;
|
|
112
|
+
fresh: boolean;
|
|
113
|
+
fresh_until?: string;
|
|
114
|
+
next: string[];
|
|
115
|
+
receipt_status: string;
|
|
116
|
+
storage_path: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function compactStateFields(state: Record<string, unknown>): Array<[string, string]> {
|
|
120
|
+
const fields: Array<[string, string]> = [];
|
|
121
|
+
for (const key of COMPACT_ELIDE_KEYS) {
|
|
122
|
+
const value = state[key];
|
|
123
|
+
if (Array.isArray(value)) fields.push([key, `${value.length} entries (elided)`]);
|
|
124
|
+
}
|
|
125
|
+
return fields;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function compactProjectStateJson(
|
|
129
|
+
skill: CanonicalGjcWorkflowSkill,
|
|
130
|
+
stateJson: Record<string, unknown>,
|
|
131
|
+
manifest: SkillManifest,
|
|
132
|
+
): Record<string, unknown> {
|
|
133
|
+
const state = stateObject(stateJson);
|
|
134
|
+
const compact = projectStateFields(skill, stateJson, manifest, STATE_FIELD_ALLOWLIST);
|
|
135
|
+
const elisions: Record<string, unknown> = {};
|
|
136
|
+
for (const key of COMPACT_ELIDE_KEYS) {
|
|
137
|
+
const value = state[key];
|
|
138
|
+
if (Array.isArray(value)) elisions[key] = { type: "array", count: value.length, pointer: `/${key}` };
|
|
139
|
+
}
|
|
140
|
+
if (Object.keys(elisions).length) compact.elided = elisions;
|
|
141
|
+
return compact;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function projectStateFields(
|
|
145
|
+
skill: CanonicalGjcWorkflowSkill,
|
|
146
|
+
stateJson: Record<string, unknown>,
|
|
147
|
+
manifest: SkillManifest,
|
|
148
|
+
fields: readonly StateProjectionField[],
|
|
149
|
+
): Record<string, unknown> {
|
|
150
|
+
const state = stateObject(stateJson);
|
|
151
|
+
const phase = scalar(state.current_phase) ?? scalar(state.phase) ?? manifest.initialState;
|
|
152
|
+
const receipt = receiptObject(state);
|
|
153
|
+
const freshUntil = receipt ? scalar(receipt.fresh_until) : undefined;
|
|
154
|
+
const projected: Record<string, unknown> = {};
|
|
155
|
+
for (const field of fields) {
|
|
156
|
+
switch (field) {
|
|
157
|
+
case "skill":
|
|
158
|
+
projected.skill = skill;
|
|
159
|
+
break;
|
|
160
|
+
case "phase":
|
|
161
|
+
case "current_phase":
|
|
162
|
+
projected[field] = phase;
|
|
163
|
+
break;
|
|
164
|
+
case "next":
|
|
165
|
+
projected.next = manifest.transitions
|
|
166
|
+
.filter(transition => transition.from === phase)
|
|
167
|
+
.map(transition => transition.to);
|
|
168
|
+
break;
|
|
169
|
+
case "fresh":
|
|
170
|
+
projected.fresh = freshUntil ? Date.parse(freshUntil) > Date.now() : false;
|
|
171
|
+
break;
|
|
172
|
+
case "fresh_until":
|
|
173
|
+
projected.fresh_until = freshUntil;
|
|
174
|
+
break;
|
|
175
|
+
case "receipt":
|
|
176
|
+
projected.receipt = receipt;
|
|
177
|
+
break;
|
|
178
|
+
default:
|
|
179
|
+
projected[field] = state[field];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return projected;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function buildStateStatusSummary(
|
|
186
|
+
skill: CanonicalGjcWorkflowSkill,
|
|
187
|
+
stateJson: Record<string, unknown>,
|
|
188
|
+
manifest: SkillManifest,
|
|
189
|
+
storagePath: string,
|
|
190
|
+
): StateStatusSummary {
|
|
191
|
+
const state = stateObject(stateJson);
|
|
192
|
+
const phase = scalar(state.current_phase) ?? scalar(state.phase) ?? manifest.initialState;
|
|
193
|
+
const receipt = receiptObject(state);
|
|
194
|
+
const freshUntil = receipt ? scalar(receipt.fresh_until) : undefined;
|
|
195
|
+
return {
|
|
196
|
+
skill,
|
|
197
|
+
phase,
|
|
198
|
+
active: state.active !== false,
|
|
199
|
+
fresh: freshUntil ? Date.parse(freshUntil) > Date.now() : false,
|
|
200
|
+
...(freshUntil ? { fresh_until: freshUntil } : {}),
|
|
201
|
+
next: manifest.transitions.filter(transition => transition.from === phase).map(transition => transition.to),
|
|
202
|
+
receipt_status: receipt ? (scalar(receipt.status) ?? "present") : "missing",
|
|
203
|
+
storage_path: storagePath,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function renderStateStatusLine(summary: StateStatusSummary): string {
|
|
208
|
+
const freshness = summary.fresh ? "fresh" : "stale";
|
|
209
|
+
return `${summary.skill}: phase=${summary.phase} ${freshness} next=${summary.next.length ? summary.next.join(",") : "none"}\n`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function renderContractMarkdown(skill: CanonicalGjcWorkflowSkill, contract: unknown): string {
|
|
213
|
+
const record = isRecord(contract) ? contract : {};
|
|
214
|
+
const lines = [`# ${skill} state contract`, ""];
|
|
215
|
+
for (const [key, value] of Object.entries(record)) {
|
|
216
|
+
if (Array.isArray(value)) lines.push(`- ${key}: ${value.length} entries (--json for full)`);
|
|
217
|
+
else if (isRecord(value)) lines.push(`- ${key}: object (${Object.keys(value).length} keys, --json for full)`);
|
|
218
|
+
else if (value !== undefined) lines.push(`- ${key}: ${String(value)}`);
|
|
219
|
+
}
|
|
220
|
+
return `${lines.join("\n")}\n`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function renderHistoryMarkdown(history: {
|
|
224
|
+
entries: unknown[];
|
|
225
|
+
limit: number;
|
|
226
|
+
since?: string;
|
|
227
|
+
truncated: boolean;
|
|
228
|
+
}): string {
|
|
229
|
+
const lines = ["# state audit history", "", `- entries: ${history.entries.length}`, `- limit: ${history.limit}`];
|
|
230
|
+
if (history.since) lines.push(`- since: ${history.since}`);
|
|
231
|
+
lines.push(`- truncated: ${history.truncated ? "yes" : "no"}`);
|
|
232
|
+
for (const entry of history.entries) {
|
|
233
|
+
if (!isRecord(entry)) continue;
|
|
234
|
+
const ts = scalar(entry.ts) ?? "unknown-time";
|
|
235
|
+
const skill = scalar(entry.skill) ?? "unknown-skill";
|
|
236
|
+
const verb = scalar(entry.verb) ?? "unknown-verb";
|
|
237
|
+
lines.push(`- ${ts} ${skill} ${verb}`);
|
|
238
|
+
}
|
|
239
|
+
return `${lines.join("\n")}\n`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function renderUltragoalStatusMarkdown(summary: {
|
|
243
|
+
exists: boolean;
|
|
244
|
+
status: string;
|
|
245
|
+
paths: { goalsPath: string; ledgerPath?: string };
|
|
246
|
+
gjcObjective?: string;
|
|
247
|
+
currentGoal?: { id: string; status: string; title?: string; objective?: string };
|
|
248
|
+
counts: Record<string, number>;
|
|
249
|
+
goals: unknown[];
|
|
250
|
+
}): string {
|
|
251
|
+
if (!summary.exists)
|
|
252
|
+
return `# ultragoal status\n\n- status: missing\n- No ultragoal plan found at ${summary.paths.goalsPath}. Run \`gjc ultragoal create-goals --brief "..."\` first.\n`;
|
|
253
|
+
const counts = Object.entries(summary.counts)
|
|
254
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
255
|
+
.join(" ");
|
|
256
|
+
const lines = [
|
|
257
|
+
"# ultragoal status",
|
|
258
|
+
"",
|
|
259
|
+
`- status: ${summary.status}`,
|
|
260
|
+
`- goals: ${summary.goals.length} (${counts})`,
|
|
261
|
+
];
|
|
262
|
+
if (summary.gjcObjective) lines.push(`- objective: ${summary.gjcObjective}`);
|
|
263
|
+
if (summary.currentGoal) lines.push(`- current: ${summary.currentGoal.id} (${summary.currentGoal.status})`);
|
|
264
|
+
lines.push(`- goals_path: ${summary.paths.goalsPath}`);
|
|
265
|
+
if (summary.paths.ledgerPath) lines.push(`- ledger_path: ${summary.paths.ledgerPath}`);
|
|
266
|
+
return `${lines.join("\n")}\n`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function renderTeamStatusMarkdown(snapshot: {
|
|
270
|
+
team_name: string;
|
|
271
|
+
phase: string;
|
|
272
|
+
tmux_target?: string;
|
|
273
|
+
tmux_session?: string;
|
|
274
|
+
state_dir: string;
|
|
275
|
+
task_total: number;
|
|
276
|
+
task_counts: Record<string, number>;
|
|
277
|
+
workers: Array<{ id: string; status: string }>;
|
|
278
|
+
notification_summary?: { total: number; replay_eligible: number; by_state: Record<string, number> };
|
|
279
|
+
integration_by_worker?: Record<string, { status?: string; conflict_files?: string[] }>;
|
|
280
|
+
}): string {
|
|
281
|
+
const counts = Object.entries(snapshot.task_counts)
|
|
282
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
283
|
+
.join(" ");
|
|
284
|
+
const lines = [
|
|
285
|
+
"# team status",
|
|
286
|
+
"",
|
|
287
|
+
`- team: ${snapshot.team_name}`,
|
|
288
|
+
`- phase: ${snapshot.phase}`,
|
|
289
|
+
`- tmux: ${snapshot.tmux_target || snapshot.tmux_session || "none"}`,
|
|
290
|
+
`- state: ${snapshot.state_dir}`,
|
|
291
|
+
`- tasks: ${snapshot.task_total} (${counts})`,
|
|
292
|
+
`- workers: ${snapshot.workers.length} (${snapshot.workers.map(worker => `${worker.id}:${worker.status}`).join(" ")})`,
|
|
293
|
+
];
|
|
294
|
+
if (snapshot.notification_summary) {
|
|
295
|
+
lines.push(
|
|
296
|
+
`- notifications: total=${snapshot.notification_summary.total} replay_eligible=${snapshot.notification_summary.replay_eligible}`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
const integrations = Object.entries(snapshot.integration_by_worker ?? {});
|
|
300
|
+
if (integrations.length)
|
|
301
|
+
lines.push(
|
|
302
|
+
`- integrations: ${integrations.map(([worker, state]) => `${worker}:${state.status ?? "unknown"}`).join(" ")}`,
|
|
303
|
+
);
|
|
304
|
+
return `${lines.join("\n")}\n`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function renderStateMarkdown(
|
|
308
|
+
skill: CanonicalGjcWorkflowSkill,
|
|
309
|
+
stateJson: Record<string, unknown>,
|
|
310
|
+
manifest: SkillManifest,
|
|
311
|
+
): string {
|
|
312
|
+
const state = stateObject(stateJson);
|
|
313
|
+
const phase = scalar(state.current_phase) ?? scalar(state.phase) ?? manifest.initialState;
|
|
314
|
+
const next = manifest.transitions.filter(transition => transition.from === phase).map(transition => transition.to);
|
|
315
|
+
const receipt = receiptObject(state);
|
|
316
|
+
const receiptStatus = receipt ? (scalar(receipt.status) ?? "present") : "missing";
|
|
317
|
+
const artifacts = artifactLinks(state);
|
|
318
|
+
const fields = keyStateFields(state, manifest);
|
|
319
|
+
|
|
320
|
+
const lines = [`# ${skill} state`, "", `- Current phase: ${phase}`];
|
|
321
|
+
lines.push(`- Valid next transitions: ${next.length ? next.join(", ") : "none"}`);
|
|
322
|
+
if (fields.length) {
|
|
323
|
+
lines.push("- Key fields:");
|
|
324
|
+
for (const [key, value] of fields) lines.push(` - ${key}: ${value}`);
|
|
325
|
+
} else {
|
|
326
|
+
lines.push("- Key fields: none");
|
|
327
|
+
}
|
|
328
|
+
lines.push(`- Receipt: ${receiptStatus}`);
|
|
329
|
+
if (receipt) {
|
|
330
|
+
const mutationId = scalar(receipt.mutation_id);
|
|
331
|
+
const freshUntil = scalar(receipt.fresh_until);
|
|
332
|
+
if (mutationId) lines.push(` - mutation_id: ${mutationId}`);
|
|
333
|
+
if (freshUntil) lines.push(` - fresh_until: ${freshUntil}`);
|
|
334
|
+
}
|
|
335
|
+
if (artifacts.length) {
|
|
336
|
+
lines.push("- Artifacts:");
|
|
337
|
+
for (const artifact of artifacts) lines.push(` - ${artifact}`);
|
|
338
|
+
}
|
|
339
|
+
const compactFields = compactStateFields(state);
|
|
340
|
+
if (compactFields.length) {
|
|
341
|
+
lines.push("- Compact elisions:");
|
|
342
|
+
for (const [key, value] of compactFields) lines.push(` - ${key}: ${value}`);
|
|
343
|
+
}
|
|
344
|
+
return `${lines.join("\n")}\n`;
|
|
345
|
+
}
|