@gajae-code/coding-agent 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +1 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/model-profile-activation.d.ts +30 -0
- package/dist/types/config/model-profiles.d.ts +19 -0
- package/dist/types/config/model-registry.d.ts +25 -10
- package/dist/types/config/model-resolver.d.ts +1 -1
- package/dist/types/config/models-config-schema.d.ts +84 -0
- package/dist/types/config/settings-schema.d.ts +15 -0
- package/dist/types/edit/diff.d.ts +16 -0
- package/dist/types/edit/modes/replace.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/activation.d.ts +14 -0
- package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/injection.d.ts +31 -0
- package/dist/types/extensibility/gjc-plugins/loader.d.ts +3 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +8 -0
- package/dist/types/extensibility/gjc-plugins/schema.d.ts +3 -0
- package/dist/types/extensibility/gjc-plugins/state.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/tools.d.ts +8 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/validation.d.ts +4 -0
- package/dist/types/extensibility/skills.d.ts +9 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +22 -0
- package/dist/types/harness-control-plane/storage.d.ts +7 -0
- package/dist/types/lsp/client.d.ts +1 -0
- package/dist/types/main.d.ts +10 -1
- package/dist/types/modes/bridge/bridge-mode.d.ts +2 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +10 -0
- package/dist/types/modes/components/model-selector.d.ts +6 -1
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-client.d.ts +9 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +179 -2
- package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +57 -0
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +16 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +47 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +7 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +11 -1
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +3 -1
- package/dist/types/modes/shared/agent-wire/responses.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +27 -0
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +68 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +161 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +61 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +114 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +39 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/sdk.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +10 -0
- package/dist/types/session/blob-store.d.ts +17 -0
- package/dist/types/session/messages.d.ts +3 -0
- package/dist/types/session/session-storage.d.ts +6 -0
- package/dist/types/skill-state/active-state.d.ts +13 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/thinking.d.ts +3 -2
- package/dist/types/tools/hindsight-recall.d.ts +0 -2
- package/dist/types/tools/hindsight-reflect.d.ts +0 -2
- package/dist/types/tools/hindsight-retain.d.ts +0 -2
- package/dist/types/tools/index.d.ts +7 -4
- package/package.json +9 -7
- package/src/cli/args.ts +10 -0
- package/src/cli.ts +14 -0
- package/src/commands/harness.ts +192 -7
- package/src/commands/launch.ts +8 -0
- package/src/commands/ultragoal.ts +1 -21
- package/src/config/model-equivalence.ts +1 -1
- package/src/config/model-profile-activation.ts +157 -0
- package/src/config/model-profiles.ts +155 -0
- package/src/config/model-registry.ts +51 -5
- package/src/config/model-resolver.ts +3 -2
- package/src/config/models-config-schema.ts +42 -1
- package/src/config/settings-schema.ts +14 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +11 -1
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +61 -0
- package/src/defaults/gjc-defaults.ts +7 -0
- package/src/discovery/claude-plugins.ts +25 -5
- package/src/edit/diff.ts +64 -1
- package/src/edit/modes/replace.ts +60 -2
- package/src/extensibility/gjc-plugins/activation.ts +87 -0
- package/src/extensibility/gjc-plugins/index.ts +9 -0
- package/src/extensibility/gjc-plugins/injection.ts +114 -0
- package/src/extensibility/gjc-plugins/loader.ts +131 -0
- package/src/extensibility/gjc-plugins/paths.ts +66 -0
- package/src/extensibility/gjc-plugins/schema.ts +79 -0
- package/src/extensibility/gjc-plugins/state.ts +29 -0
- package/src/extensibility/gjc-plugins/tools.ts +47 -0
- package/src/extensibility/gjc-plugins/types.ts +97 -0
- package/src/extensibility/gjc-plugins/validation.ts +76 -0
- package/src/extensibility/skills.ts +39 -7
- package/src/gjc-runtime/state-runtime.ts +93 -2
- package/src/gjc-runtime/state-writer.ts +17 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +62 -2
- package/src/gjc-runtime/workflow-manifest.generated.json +5 -0
- package/src/gjc-runtime/workflow-manifest.ts +2 -2
- package/src/harness-control-plane/storage.ts +144 -2
- package/src/hashline/hash.ts +23 -0
- package/src/hooks/skill-state.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +8 -11
- package/src/lsp/client.ts +7 -0
- package/src/main.ts +67 -1
- package/src/modes/acp/acp-agent.ts +25 -2
- package/src/modes/bridge/bridge-mode.ts +124 -2
- package/src/modes/components/custom-provider-wizard.ts +318 -0
- package/src/modes/components/model-selector.ts +108 -18
- package/src/modes/components/provider-onboarding-selector.ts +6 -1
- package/src/modes/controllers/input-controller.ts +14 -2
- package/src/modes/controllers/selector-controller.ts +57 -1
- package/src/modes/prompt-action-autocomplete.ts +49 -10
- package/src/modes/rpc/rpc-client.ts +57 -3
- package/src/modes/rpc/rpc-mode.ts +67 -0
- package/src/modes/rpc/rpc-types.ts +224 -2
- package/src/modes/shared/agent-wire/approval-gate.ts +151 -0
- package/src/modes/shared/agent-wire/command-dispatch.ts +97 -4
- package/src/modes/shared/agent-wire/command-validation.ts +25 -1
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +222 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +13 -0
- package/src/modes/shared/agent-wire/handshake.ts +43 -3
- package/src/modes/shared/agent-wire/protocol.ts +7 -0
- package/src/modes/shared/agent-wire/responses.ts +2 -2
- package/src/modes/shared/agent-wire/scopes.ts +2 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +341 -0
- package/src/modes/shared/agent-wire/unattended-audit.ts +175 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +406 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +180 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +324 -0
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +331 -0
- package/src/modes/theme/theme.ts +6 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/memories/consolidation.md +1 -1
- package/src/prompts/memories/read-path.md +6 -7
- package/src/prompts/memories/unavailable.md +2 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/recall.md +1 -0
- package/src/prompts/tools/reflect.md +1 -0
- package/src/prompts/tools/retain.md +1 -0
- package/src/runtime-mcp/client.ts +7 -4
- package/src/runtime-mcp/manager.ts +45 -13
- package/src/runtime-mcp/transports/http.ts +40 -14
- package/src/runtime-mcp/transports/stdio.ts +11 -10
- package/src/sdk.ts +48 -1
- package/src/session/agent-session.ts +211 -2
- package/src/session/blob-store.ts +84 -0
- package/src/session/messages.ts +3 -0
- package/src/session/session-manager.ts +390 -33
- package/src/session/session-storage.ts +26 -0
- package/src/setup/provider-onboarding.ts +2 -2
- package/src/skill-state/active-state.ts +89 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/task/discovery.ts +7 -1
- package/src/task/executor.ts +18 -2
- package/src/task/index.ts +2 -0
- package/src/thinking.ts +8 -2
- package/src/tools/ask.ts +39 -9
- package/src/tools/hindsight-recall.ts +0 -2
- package/src/tools/hindsight-reflect.ts +0 -2
- package/src/tools/hindsight-retain.ts +0 -2
- package/src/tools/index.ts +7 -18
- package/src/tools/read.ts +3 -3
- package/src/tools/skill.ts +15 -3
- package/src/utils/edit-mode.ts +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { GJC_PLUGIN_KIND, GjcPluginLoadError, type GjcPluginManifest, type SubskillFrontmatter } from "./types";
|
|
2
|
+
|
|
3
|
+
const FORBIDDEN_MANIFEST_KEYS = ["skills", "slash-commands", "commands", "hooks", "mcp", "mcpServers", "agents"];
|
|
4
|
+
|
|
5
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
6
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function requireNonEmptyString(value: unknown, field: string, filePath: string): string {
|
|
10
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
11
|
+
throw new GjcPluginLoadError(
|
|
12
|
+
"invalid_frontmatter",
|
|
13
|
+
`Invalid sub-skill frontmatter in ${filePath}: ${field} must be a non-empty string`,
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function requireStringArray(value: unknown, field: string, manifestPath: string): string[] {
|
|
20
|
+
if (!Array.isArray(value) || !value.every(item => typeof item === "string")) {
|
|
21
|
+
throw new GjcPluginLoadError(
|
|
22
|
+
"invalid_manifest",
|
|
23
|
+
`Invalid GJC plugin manifest at ${manifestPath}: ${field} must be a string array`,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return [...value];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function parseManifest(raw: unknown, manifestPath: string): GjcPluginManifest {
|
|
30
|
+
if (!isRecord(raw)) {
|
|
31
|
+
throw new GjcPluginLoadError(
|
|
32
|
+
"invalid_manifest",
|
|
33
|
+
`Invalid GJC plugin manifest at ${manifestPath}: expected object`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const key of FORBIDDEN_MANIFEST_KEYS) {
|
|
38
|
+
if (Object.hasOwn(raw, key)) {
|
|
39
|
+
throw new GjcPluginLoadError("forbidden_surface", `Forbidden GJC plugin surface in ${manifestPath}: ${key}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (raw.kind !== GJC_PLUGIN_KIND) {
|
|
44
|
+
throw new GjcPluginLoadError(
|
|
45
|
+
"invalid_kind",
|
|
46
|
+
`Invalid GJC plugin kind in ${manifestPath}: expected ${GJC_PLUGIN_KIND}`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (typeof raw.name !== "string" || raw.name.trim().length === 0) {
|
|
50
|
+
throw new GjcPluginLoadError(
|
|
51
|
+
"invalid_manifest",
|
|
52
|
+
`Invalid GJC plugin manifest at ${manifestPath}: name must be a non-empty string`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (typeof raw.version !== "string" || raw.version.trim().length === 0) {
|
|
56
|
+
throw new GjcPluginLoadError(
|
|
57
|
+
"invalid_manifest",
|
|
58
|
+
`Invalid GJC plugin manifest at ${manifestPath}: version must be a non-empty string`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
name: raw.name,
|
|
64
|
+
version: raw.version,
|
|
65
|
+
kind: GJC_PLUGIN_KIND,
|
|
66
|
+
subskills: requireStringArray(raw.subskills, "subskills", manifestPath),
|
|
67
|
+
tools: requireStringArray(raw.tools, "tools", manifestPath),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function parseSubskillFrontmatter(fm: Record<string, unknown>, filePath: string): SubskillFrontmatter {
|
|
72
|
+
return {
|
|
73
|
+
name: requireNonEmptyString(fm.name, "name", filePath),
|
|
74
|
+
binds_to: requireNonEmptyString(fm.binds_to, "binds_to", filePath),
|
|
75
|
+
phase: requireNonEmptyString(fm.phase, "phase", filePath),
|
|
76
|
+
activation_arg: requireNonEmptyString(fm.activation_arg, "activation_arg", filePath),
|
|
77
|
+
description: requireNonEmptyString(fm.description, "description", filePath),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ActiveSubskillEntry } from "../../skill-state/active-state";
|
|
2
|
+
import { readVisibleSkillActiveState } from "../../skill-state/active-state";
|
|
3
|
+
import type { LoadedSubskillActivation } from "./types";
|
|
4
|
+
|
|
5
|
+
export function toActiveSubskillEntry(activation: LoadedSubskillActivation): ActiveSubskillEntry {
|
|
6
|
+
return {
|
|
7
|
+
plugin: activation.plugin,
|
|
8
|
+
subskillName: activation.subskillName,
|
|
9
|
+
parent: activation.parent,
|
|
10
|
+
bindsTo: activation.bindsTo,
|
|
11
|
+
phase: activation.phase,
|
|
12
|
+
activationArg: activation.activationArg,
|
|
13
|
+
filePath: activation.filePath,
|
|
14
|
+
toolPaths: activation.toolPaths,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function readActiveSubskillsForParent(input: {
|
|
19
|
+
cwd: string;
|
|
20
|
+
sessionId?: string;
|
|
21
|
+
parent: string;
|
|
22
|
+
phase: string;
|
|
23
|
+
}): Promise<ActiveSubskillEntry[]> {
|
|
24
|
+
const state = await readVisibleSkillActiveState(input.cwd, input.sessionId);
|
|
25
|
+
const parent = input.parent.trim();
|
|
26
|
+
const phase = input.phase.trim();
|
|
27
|
+
if (!state || !parent || !phase) return [];
|
|
28
|
+
return (state.active_subskills ?? []).filter(entry => entry.parent === parent && entry.phase === phase);
|
|
29
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { logger } from "@gajae-code/utils";
|
|
2
|
+
import { loadCustomTools } from "../custom-tools/loader";
|
|
3
|
+
import type { CustomTool } from "../custom-tools/types";
|
|
4
|
+
import { readActiveSubskillsForParent } from "./state";
|
|
5
|
+
|
|
6
|
+
export async function loadActiveSubskillTools(input: {
|
|
7
|
+
cwd: string;
|
|
8
|
+
sessionId?: string;
|
|
9
|
+
parent: string;
|
|
10
|
+
phase: string;
|
|
11
|
+
reservedToolNames?: string[];
|
|
12
|
+
}): Promise<CustomTool[]> {
|
|
13
|
+
const entries = await readActiveSubskillsForParent(input);
|
|
14
|
+
const toolPaths = [
|
|
15
|
+
...new Set(entries.flatMap(entry => entry.toolPaths ?? []).filter(path => path.trim().length > 0)),
|
|
16
|
+
];
|
|
17
|
+
if (toolPaths.length === 0) return [];
|
|
18
|
+
|
|
19
|
+
const reservedToolNames = new Set(input.reservedToolNames ?? []);
|
|
20
|
+
const result = await loadCustomTools(
|
|
21
|
+
toolPaths.map(path => ({ path })),
|
|
22
|
+
input.cwd,
|
|
23
|
+
input.reservedToolNames ?? [],
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
for (const error of result.errors) {
|
|
27
|
+
logger.warn("Skipping GJC plugin sub-skill tool", { path: error.path, error: error.error });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const tools: CustomTool[] = [];
|
|
31
|
+
const seenNames = new Set<string>();
|
|
32
|
+
for (const loadedTool of result.tools) {
|
|
33
|
+
const name = loadedTool.tool.name;
|
|
34
|
+
if (reservedToolNames.has(name)) {
|
|
35
|
+
logger.warn("Skipping GJC plugin sub-skill tool name because it conflicts with a reserved tool", { name });
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (seenNames.has(name)) {
|
|
39
|
+
logger.warn("Skipping duplicate GJC plugin sub-skill tool name", { name, path: loadedTool.path });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
seenNames.add(name);
|
|
43
|
+
tools.push(loadedTool.tool);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return tools;
|
|
47
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { CanonicalGjcWorkflowSkill } from "../../skill-state/active-state";
|
|
2
|
+
import { CANONICAL_GJC_WORKFLOW_SKILLS } from "../../skill-state/active-state";
|
|
3
|
+
|
|
4
|
+
export const GJC_PLUGIN_MANIFEST_FILENAME = "gajae-plugin.json";
|
|
5
|
+
export const GJC_PLUGIN_KIND = "gajae-code-plugin";
|
|
6
|
+
|
|
7
|
+
export const GJC_SUBSKILL_PARENT_SKILLS = CANONICAL_GJC_WORKFLOW_SKILLS;
|
|
8
|
+
export type GjcSubskillParentSkill = CanonicalGjcWorkflowSkill;
|
|
9
|
+
|
|
10
|
+
export const GJC_SUBSKILL_PARENT_AGENTS = ["executor", "architect", "planner", "critic"] as const;
|
|
11
|
+
export type GjcSubskillParentAgent = (typeof GJC_SUBSKILL_PARENT_AGENTS)[number];
|
|
12
|
+
|
|
13
|
+
export type GjcSubskillParent = GjcSubskillParentSkill | GjcSubskillParentAgent;
|
|
14
|
+
|
|
15
|
+
export const GJC_AGENT_SUBSKILL_PHASES: Record<GjcSubskillParentAgent, string[]> = {
|
|
16
|
+
executor: ["prompt"],
|
|
17
|
+
architect: ["prompt"],
|
|
18
|
+
planner: ["prompt"],
|
|
19
|
+
critic: ["prompt"],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export interface GjcPluginManifest {
|
|
23
|
+
name: string;
|
|
24
|
+
version: string;
|
|
25
|
+
kind: "gajae-code-plugin";
|
|
26
|
+
subskills: string[];
|
|
27
|
+
tools: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SubskillFrontmatter {
|
|
31
|
+
name: string;
|
|
32
|
+
binds_to: string;
|
|
33
|
+
phase: string;
|
|
34
|
+
activation_arg: string;
|
|
35
|
+
description: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface LoadedSubskillBinding {
|
|
39
|
+
plugin: string;
|
|
40
|
+
subskillName: string;
|
|
41
|
+
parent: string;
|
|
42
|
+
bindsTo: string;
|
|
43
|
+
phase: string;
|
|
44
|
+
activationArg: string;
|
|
45
|
+
description: string;
|
|
46
|
+
filePath: string;
|
|
47
|
+
body: string;
|
|
48
|
+
toolPaths: string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface LoadedSubskillActivation {
|
|
52
|
+
activationArg: string;
|
|
53
|
+
plugin: string;
|
|
54
|
+
subskillName: string;
|
|
55
|
+
parent: string;
|
|
56
|
+
bindsTo: string;
|
|
57
|
+
phase: string;
|
|
58
|
+
filePath: string;
|
|
59
|
+
toolPaths: string[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface PhaseScopedToolBinding {
|
|
63
|
+
plugin: string;
|
|
64
|
+
parent: string;
|
|
65
|
+
phase: string;
|
|
66
|
+
toolPath: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface LoadedGjcPlugin {
|
|
70
|
+
name: string;
|
|
71
|
+
version: string;
|
|
72
|
+
root: string;
|
|
73
|
+
manifestPath: string;
|
|
74
|
+
bindings: LoadedSubskillBinding[];
|
|
75
|
+
toolBindings: PhaseScopedToolBinding[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type GjcPluginLoadErrorCode =
|
|
79
|
+
| "forbidden_surface"
|
|
80
|
+
| "invalid_manifest"
|
|
81
|
+
| "invalid_frontmatter"
|
|
82
|
+
| "invalid_parent"
|
|
83
|
+
| "invalid_phase"
|
|
84
|
+
| "duplicate_arg"
|
|
85
|
+
| "duplicate_parent_phase"
|
|
86
|
+
| "missing_file"
|
|
87
|
+
| "invalid_kind";
|
|
88
|
+
|
|
89
|
+
export class GjcPluginLoadError extends Error {
|
|
90
|
+
readonly code: GjcPluginLoadErrorCode;
|
|
91
|
+
|
|
92
|
+
constructor(code: GjcPluginLoadErrorCode, message: string, options?: ErrorOptions) {
|
|
93
|
+
super(message, options);
|
|
94
|
+
this.name = "GjcPluginLoadError";
|
|
95
|
+
this.code = code;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { isKnownWorkflowState } from "../../gjc-runtime/workflow-manifest";
|
|
2
|
+
import type { CanonicalGjcWorkflowSkill } from "../../skill-state/active-state";
|
|
3
|
+
import {
|
|
4
|
+
GJC_AGENT_SUBSKILL_PHASES,
|
|
5
|
+
GJC_SUBSKILL_PARENT_AGENTS,
|
|
6
|
+
GJC_SUBSKILL_PARENT_SKILLS,
|
|
7
|
+
GjcPluginLoadError,
|
|
8
|
+
type GjcSubskillParentAgent,
|
|
9
|
+
type LoadedSubskillBinding,
|
|
10
|
+
type SubskillFrontmatter,
|
|
11
|
+
} from "./types";
|
|
12
|
+
|
|
13
|
+
function isParentSkill(value: string): value is CanonicalGjcWorkflowSkill {
|
|
14
|
+
return (GJC_SUBSKILL_PARENT_SKILLS as readonly string[]).includes(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isParentAgent(value: string): value is GjcSubskillParentAgent {
|
|
18
|
+
return (GJC_SUBSKILL_PARENT_AGENTS as readonly string[]).includes(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function validateBinding(fm: SubskillFrontmatter): void {
|
|
22
|
+
const parent = fm.binds_to;
|
|
23
|
+
if (isParentSkill(parent)) {
|
|
24
|
+
if (!isKnownWorkflowState(parent, fm.phase)) {
|
|
25
|
+
throw new GjcPluginLoadError("invalid_phase", `Invalid GJC sub-skill phase for ${parent}: ${fm.phase}`);
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (isParentAgent(parent)) {
|
|
31
|
+
if (!GJC_AGENT_SUBSKILL_PHASES[parent].includes(fm.phase)) {
|
|
32
|
+
throw new GjcPluginLoadError("invalid_phase", `Invalid GJC sub-skill phase for ${parent}: ${fm.phase}`);
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
throw new GjcPluginLoadError("invalid_parent", `Invalid GJC sub-skill parent: ${parent}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function buildParentArgMap(
|
|
41
|
+
bindings: readonly LoadedSubskillBinding[],
|
|
42
|
+
): Map<string, Map<string, LoadedSubskillBinding>> {
|
|
43
|
+
const byParent = new Map<string, Map<string, LoadedSubskillBinding>>();
|
|
44
|
+
for (const binding of bindings) {
|
|
45
|
+
let byArg = byParent.get(binding.parent);
|
|
46
|
+
if (!byArg) {
|
|
47
|
+
byArg = new Map<string, LoadedSubskillBinding>();
|
|
48
|
+
byParent.set(binding.parent, byArg);
|
|
49
|
+
}
|
|
50
|
+
const existing = byArg.get(binding.activationArg);
|
|
51
|
+
if (existing) {
|
|
52
|
+
throw new GjcPluginLoadError(
|
|
53
|
+
"duplicate_arg",
|
|
54
|
+
`Duplicate GJC sub-skill activation_arg for ${binding.parent}: ${binding.activationArg} (${existing.filePath}, ${binding.filePath})`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
byArg.set(binding.activationArg, binding);
|
|
58
|
+
}
|
|
59
|
+
return byParent;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function buildParentPhaseSet(bindings: readonly LoadedSubskillBinding[]): Set<string> {
|
|
63
|
+
const seen = new Map<string, LoadedSubskillBinding>();
|
|
64
|
+
for (const binding of bindings) {
|
|
65
|
+
const key = `${binding.parent}\u0000${binding.phase}`;
|
|
66
|
+
const existing = seen.get(key);
|
|
67
|
+
if (existing) {
|
|
68
|
+
throw new GjcPluginLoadError(
|
|
69
|
+
"duplicate_parent_phase",
|
|
70
|
+
`Duplicate GJC sub-skill parent/phase binding for ${binding.parent}/${binding.phase} (${existing.filePath}, ${binding.filePath})`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
seen.set(key, binding);
|
|
74
|
+
}
|
|
75
|
+
return new Set(seen.keys());
|
|
76
|
+
}
|
|
@@ -8,6 +8,8 @@ import { type Skill as CapabilitySkill, loadCapability } from "../discovery";
|
|
|
8
8
|
import { compareSkillOrder, scanSkillsFromDir } from "../discovery/helpers";
|
|
9
9
|
import type { SkillPromptDetails } from "../session/messages";
|
|
10
10
|
import { expandTilde } from "../tools/path-utils";
|
|
11
|
+
import type { LoadedSubskillActivation } from "./gjc-plugins";
|
|
12
|
+
import { buildSubskillInjection } from "./gjc-plugins/injection";
|
|
11
13
|
export interface Skill {
|
|
12
14
|
name: string;
|
|
13
15
|
description: string;
|
|
@@ -280,6 +282,14 @@ export interface BuiltSkillPromptMessage {
|
|
|
280
282
|
details: SkillPromptDetails;
|
|
281
283
|
}
|
|
282
284
|
|
|
285
|
+
export interface BuildSkillPromptMessageContext {
|
|
286
|
+
subskillActivation?: LoadedSubskillActivation;
|
|
287
|
+
subskillActivationSet?: LoadedSubskillActivation[];
|
|
288
|
+
currentPhase?: string;
|
|
289
|
+
cwd?: string;
|
|
290
|
+
sessionId?: string;
|
|
291
|
+
}
|
|
292
|
+
|
|
283
293
|
export function getSkillSlashCommandName(skill: Pick<Skill, "name">): string {
|
|
284
294
|
return `skill:${skill.name}`;
|
|
285
295
|
}
|
|
@@ -370,6 +380,7 @@ export function resolveSkillSlashCommands(
|
|
|
370
380
|
export async function buildSkillPromptMessage(
|
|
371
381
|
skill: Pick<Skill, "name" | "filePath" | "content">,
|
|
372
382
|
args: string,
|
|
383
|
+
context?: BuildSkillPromptMessageContext,
|
|
373
384
|
): Promise<BuiltSkillPromptMessage> {
|
|
374
385
|
const content = typeof skill.content === "string" ? skill.content : await Bun.file(skill.filePath).text();
|
|
375
386
|
const body = content.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
|
|
@@ -378,14 +389,35 @@ export async function buildSkillPromptMessage(
|
|
|
378
389
|
if (trimmedArgs) {
|
|
379
390
|
metaLines.push(`User: ${trimmedArgs}`);
|
|
380
391
|
}
|
|
381
|
-
|
|
392
|
+
let message = `${body}\n\n---\n\n${metaLines.join("\n")}`;
|
|
393
|
+
const details: SkillPromptDetails = {
|
|
394
|
+
name: skill.name,
|
|
395
|
+
path: skill.filePath,
|
|
396
|
+
args: trimmedArgs || undefined,
|
|
397
|
+
lineCount: body ? body.split("\n").length : 0,
|
|
398
|
+
};
|
|
399
|
+
if (context?.subskillActivationSet) {
|
|
400
|
+
details.subskillActivationSet = context.subskillActivationSet;
|
|
401
|
+
}
|
|
402
|
+
if (context) {
|
|
403
|
+
const injection = context.cwd
|
|
404
|
+
? await buildSubskillInjection({
|
|
405
|
+
cwd: context.cwd,
|
|
406
|
+
sessionId: context.sessionId,
|
|
407
|
+
skillName: skill.name,
|
|
408
|
+
activation: context.subskillActivation,
|
|
409
|
+
currentPhase: context.currentPhase,
|
|
410
|
+
})
|
|
411
|
+
: null;
|
|
412
|
+
if (injection) {
|
|
413
|
+
message += injection.block;
|
|
414
|
+
details.subskillActivation = injection.details ?? context.subskillActivation;
|
|
415
|
+
} else if (context.subskillActivation) {
|
|
416
|
+
details.subskillActivation = context.subskillActivation;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
382
419
|
return {
|
|
383
420
|
message,
|
|
384
|
-
details
|
|
385
|
-
name: skill.name,
|
|
386
|
-
path: skill.filePath,
|
|
387
|
-
args: trimmedArgs || undefined,
|
|
388
|
-
lineCount: body ? body.split("\n").length : 0,
|
|
389
|
-
},
|
|
421
|
+
details,
|
|
390
422
|
};
|
|
391
423
|
}
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
canonicalWorkflowSkill,
|
|
23
23
|
describeWorkflowStateContract,
|
|
24
24
|
WORKFLOW_STATE_VERSION,
|
|
25
|
+
type WorkflowStateMutationOwner,
|
|
25
26
|
type WorkflowStateReceipt,
|
|
26
27
|
} from "../skill-state/workflow-state-contract";
|
|
27
28
|
import { renderCliWriteReceipt } from "./cli-write-receipt";
|
|
@@ -686,13 +687,14 @@ async function writeJsonAtomic(
|
|
|
686
687
|
cwd: string,
|
|
687
688
|
filePath: string,
|
|
688
689
|
value: unknown,
|
|
689
|
-
verb: "write" | "clear" | "handoff" = "write",
|
|
690
|
+
verb: "write" | "clear" | "handoff" | "reconcile" = "write",
|
|
690
691
|
options?: {
|
|
691
692
|
skill?: CanonicalGjcWorkflowSkill;
|
|
692
693
|
mutationId?: string;
|
|
693
694
|
force?: boolean;
|
|
694
695
|
fromPhase?: string;
|
|
695
696
|
toPhase?: string;
|
|
697
|
+
owner?: WorkflowStateMutationOwner;
|
|
696
698
|
},
|
|
697
699
|
): Promise<{ warning?: string; stamped: Record<string, unknown> }> {
|
|
698
700
|
const warning = options?.skill
|
|
@@ -709,7 +711,7 @@ async function writeJsonAtomic(
|
|
|
709
711
|
audit: {
|
|
710
712
|
category: "state",
|
|
711
713
|
verb,
|
|
712
|
-
owner: "gjc-state-cli",
|
|
714
|
+
owner: options?.owner ?? "gjc-state-cli",
|
|
713
715
|
skill: options?.skill,
|
|
714
716
|
mutationId: options?.mutationId,
|
|
715
717
|
fromPhase: options?.fromPhase,
|
|
@@ -957,6 +959,95 @@ async function syncWorkflowSkillState(options: {
|
|
|
957
959
|
// HUD sync is best-effort and must not change command semantics.
|
|
958
960
|
}
|
|
959
961
|
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Reconcile a workflow skill's mode-state + active-state/HUD from a caller-derived
|
|
965
|
+
* payload. Unlike `gjc state write`, this is a derived repair: callers reconcile from
|
|
966
|
+
* an authoritative source (e.g. the ultragoal plan/ledger), where intermediate
|
|
967
|
+
* aggregate phases like ultragoal `active -> pending` are legitimate, so it bypasses
|
|
968
|
+
* ONLY verb transition-edge validation while preserving schema validation,
|
|
969
|
+
* unknown-phase rejection, version/checksum stamping, and audit/out-of-band tamper
|
|
970
|
+
* detection. Receipts carry `owner: "gjc-runtime"` and `verb: "reconcile"` so the
|
|
971
|
+
* provenance is distinguishable from a user-initiated write.
|
|
972
|
+
*/
|
|
973
|
+
export async function reconcileWorkflowSkillState(options: {
|
|
974
|
+
cwd: string;
|
|
975
|
+
mode: CanonicalGjcWorkflowSkill;
|
|
976
|
+
sessionId: string | undefined;
|
|
977
|
+
threadId?: string;
|
|
978
|
+
turnId?: string;
|
|
979
|
+
active: boolean;
|
|
980
|
+
phase: string;
|
|
981
|
+
payload: Record<string, unknown>;
|
|
982
|
+
}): Promise<{ stateFile: string }> {
|
|
983
|
+
const { cwd, mode, sessionId, threadId, turnId, active, payload } = options;
|
|
984
|
+
const filePath = modeStateFile(cwd, mode, sessionId);
|
|
985
|
+
const existingRead = await readExistingStateForMutation(filePath);
|
|
986
|
+
const existingPayload = existingRead.kind === "valid" ? existingRead.value : {};
|
|
987
|
+
const nowIsoStr = nowIso();
|
|
988
|
+
const mutationId = `${mode}:reconcile:${nowIsoStr}`;
|
|
989
|
+
|
|
990
|
+
const trimmedPhase = options.phase.trim();
|
|
991
|
+
const manifestStates = new Set(getSkillManifest(mode).states.map(state => state.id));
|
|
992
|
+
if (!manifestStates.has(trimmedPhase)) {
|
|
993
|
+
throw new StateCommandError(2, `unknown ${mode} phase "${trimmedPhase}" for reconciliation`);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const fromPhase =
|
|
997
|
+
typeof existingPayload.current_phase === "string" ? existingPayload.current_phase.trim() : undefined;
|
|
998
|
+
const receipt = buildWorkflowStateReceipt({
|
|
999
|
+
cwd,
|
|
1000
|
+
skill: mode,
|
|
1001
|
+
owner: "gjc-runtime",
|
|
1002
|
+
command: `gjc ${mode} (reconcile)`,
|
|
1003
|
+
sessionId,
|
|
1004
|
+
nowIso: nowIsoStr,
|
|
1005
|
+
mutationId,
|
|
1006
|
+
});
|
|
1007
|
+
receipt.verb = "reconcile";
|
|
1008
|
+
receipt.forced = true;
|
|
1009
|
+
receipt.from_phase = fromPhase;
|
|
1010
|
+
receipt.to_phase = trimmedPhase;
|
|
1011
|
+
|
|
1012
|
+
const merged = mergeWithNullDelete(existingPayload, payload);
|
|
1013
|
+
merged.skill = mode;
|
|
1014
|
+
merged.current_phase = trimmedPhase;
|
|
1015
|
+
merged.active = active;
|
|
1016
|
+
merged.version = WORKFLOW_STATE_VERSION;
|
|
1017
|
+
merged.updated_at = nowIsoStr;
|
|
1018
|
+
merged.receipt = receipt;
|
|
1019
|
+
if (sessionId && typeof merged.session_id !== "string") merged.session_id = sessionId;
|
|
1020
|
+
|
|
1021
|
+
const validation = validateWorkflowStateEnvelope(mode, merged);
|
|
1022
|
+
if (!validation.valid) throw new StateCommandError(2, validation.error ?? `invalid ${mode} state envelope`);
|
|
1023
|
+
|
|
1024
|
+
await writeJsonAtomic(cwd, filePath, merged, "reconcile", {
|
|
1025
|
+
skill: mode,
|
|
1026
|
+
mutationId,
|
|
1027
|
+
force: true,
|
|
1028
|
+
fromPhase,
|
|
1029
|
+
toPhase: trimmedPhase,
|
|
1030
|
+
owner: "gjc-runtime",
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
// Reconciliation drives the active-state/HUD update directly (not via the
|
|
1034
|
+
// best-effort syncWorkflowSkillState wrapper) so a failed HUD/active-state write
|
|
1035
|
+
// is surfaced to the caller and recorded as a reconcile failure, rather than
|
|
1036
|
+
// silently leaving a stale chip behind a freshly reconciled mode-state.
|
|
1037
|
+
await syncSkillActiveState({
|
|
1038
|
+
cwd,
|
|
1039
|
+
skill: mode,
|
|
1040
|
+
active,
|
|
1041
|
+
phase: trimmedPhase,
|
|
1042
|
+
sessionId,
|
|
1043
|
+
threadId,
|
|
1044
|
+
turnId,
|
|
1045
|
+
source: "gjc-runtime-reconcile",
|
|
1046
|
+
hud: buildHudForMode(mode, merged),
|
|
1047
|
+
receipt,
|
|
1048
|
+
});
|
|
1049
|
+
return { stateFile: filePath };
|
|
1050
|
+
}
|
|
960
1051
|
export async function readWorkflowStateJson(
|
|
961
1052
|
cwd: string,
|
|
962
1053
|
skill: CanonicalGjcWorkflowSkill,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import type { SkillActiveEntry, SkillActiveState } from "../skill-state/active-state";
|
|
4
|
+
import type { ActiveSubskillEntry, SkillActiveEntry, SkillActiveState } from "../skill-state/active-state";
|
|
5
5
|
import {
|
|
6
6
|
type AuditEntry,
|
|
7
7
|
buildWorkflowStateReceipt,
|
|
@@ -275,6 +275,21 @@ function activeEntryPath(cwd: string, sessionScope: string | ActiveSessionScope
|
|
|
275
275
|
return path.join(activeStateDir(cwd, sessionScope), `${encodePathSegment(normalizedSkill)}.json`);
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
function activeSubskillKey(entry: ActiveSubskillEntry): string {
|
|
279
|
+
return `${entry.parent}::${entry.phase}::${entry.activationArg}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function flattenActiveSubskills(entries: SkillActiveEntry[]): ActiveSubskillEntry[] {
|
|
283
|
+
const deduped = new Map<string, ActiveSubskillEntry>();
|
|
284
|
+
for (const entry of entries) {
|
|
285
|
+
if (entry.active === false || !Array.isArray(entry.active_subskills)) continue;
|
|
286
|
+
for (const subskill of entry.active_subskills) {
|
|
287
|
+
deduped.set(activeSubskillKey(subskill), subskill);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return [...deduped.values()];
|
|
291
|
+
}
|
|
292
|
+
|
|
278
293
|
function buildActiveSnapshot(entries: SkillActiveEntry[]): SkillActiveState {
|
|
279
294
|
const visible = entries.filter(entry => entry.active !== false);
|
|
280
295
|
const primary = visible[0];
|
|
@@ -288,6 +303,7 @@ function buildActiveSnapshot(entries: SkillActiveEntry[]): SkillActiveState {
|
|
|
288
303
|
thread_id: primary?.thread_id,
|
|
289
304
|
turn_id: primary?.turn_id,
|
|
290
305
|
active_skills: entries,
|
|
306
|
+
active_subskills: flattenActiveSubskills(visible),
|
|
291
307
|
};
|
|
292
308
|
}
|
|
293
309
|
|
|
@@ -5,6 +5,7 @@ import { buildUltragoalHudSummary as buildWorkflowUltragoalHudSummary } from "..
|
|
|
5
5
|
import { renderCliWriteReceipt } from "./cli-write-receipt";
|
|
6
6
|
import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
|
|
7
7
|
import { renderUltragoalStatusMarkdown } from "./state-renderer";
|
|
8
|
+
import { reconcileWorkflowSkillState } from "./state-runtime";
|
|
8
9
|
import { appendJsonl, writeArtifact, writeJsonAtomic } from "./state-writer";
|
|
9
10
|
|
|
10
11
|
export type UltragoalGjcGoalMode = "aggregate" | "per-story";
|
|
@@ -474,7 +475,6 @@ export function buildUltragoalHudSummary(
|
|
|
474
475
|
updatedAt: new Date().toISOString(),
|
|
475
476
|
});
|
|
476
477
|
}
|
|
477
|
-
|
|
478
478
|
function clampTitle(title: string): string {
|
|
479
479
|
return title.length > 80 ? `${title.slice(0, 77)}...` : title;
|
|
480
480
|
}
|
|
@@ -1307,7 +1307,7 @@ function renderCompleteHandoff(
|
|
|
1307
1307
|
].join("\n");
|
|
1308
1308
|
}
|
|
1309
1309
|
|
|
1310
|
-
|
|
1310
|
+
async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<UltragoalCommandResult> {
|
|
1311
1311
|
try {
|
|
1312
1312
|
const command = commandName(args);
|
|
1313
1313
|
const json = hasFlag(args, "--json");
|
|
@@ -1414,3 +1414,63 @@ export async function runNativeUltragoalCommand(args: string[], cwd = process.cw
|
|
|
1414
1414
|
return { status: 1, stderr: `${error instanceof Error ? error.message : String(error)}\n` };
|
|
1415
1415
|
}
|
|
1416
1416
|
}
|
|
1417
|
+
|
|
1418
|
+
const RECONCILE_COMMANDS = new Set([
|
|
1419
|
+
"status",
|
|
1420
|
+
"create",
|
|
1421
|
+
"create-goals",
|
|
1422
|
+
"complete-goals",
|
|
1423
|
+
"checkpoint",
|
|
1424
|
+
"steer",
|
|
1425
|
+
"record-review-blockers",
|
|
1426
|
+
]);
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* Derive a workflow-state payload from the ultragoal plan/ledger and reconcile the
|
|
1430
|
+
* ultragoal mode-state + active-state/HUD so `gjc state ultragoal read`, the
|
|
1431
|
+
* skill-tool chain guard, and the HUD chip mirror the plan/ledger. Session scope
|
|
1432
|
+
* follows `gjc state` (`GJC_SESSION_ID`). This is a derived repair: it never changes
|
|
1433
|
+
* the triggering command's status/stdout, but a failure is surfaced (stderr + a
|
|
1434
|
+
* `reconcile_failed` ledger audit event) rather than silently swallowed. `status` is
|
|
1435
|
+
* therefore a read PLUS a derived repair; it never mutates goals.json/ledger.jsonl
|
|
1436
|
+
* beyond that reconcile-failure audit event.
|
|
1437
|
+
*/
|
|
1438
|
+
async function reconcileUltragoalState(cwd: string): Promise<void> {
|
|
1439
|
+
const sessionId = process.env.GJC_SESSION_ID?.trim() || undefined;
|
|
1440
|
+
try {
|
|
1441
|
+
const summary = await getUltragoalStatus(cwd);
|
|
1442
|
+
const status = summary.status;
|
|
1443
|
+
const active = summary.exists && status !== "complete";
|
|
1444
|
+
const payload: Record<string, unknown> = {
|
|
1445
|
+
skill: "ultragoal",
|
|
1446
|
+
status,
|
|
1447
|
+
current_phase: status,
|
|
1448
|
+
active,
|
|
1449
|
+
goals: summary.goals.map(goal => ({ id: goal.id, title: goal.title, status: goal.status })),
|
|
1450
|
+
counts: summary.counts,
|
|
1451
|
+
active_goal_id: summary.currentGoal?.id ?? null,
|
|
1452
|
+
ledger_path: summary.paths.ledgerPath,
|
|
1453
|
+
brief_path: summary.paths.briefPath,
|
|
1454
|
+
goals_path: summary.paths.goalsPath,
|
|
1455
|
+
};
|
|
1456
|
+
if (summary.gjcObjective) payload.gjc_objective = summary.gjcObjective;
|
|
1457
|
+
await reconcileWorkflowSkillState({ cwd, mode: "ultragoal", sessionId, active, phase: status, payload });
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1460
|
+
process.stderr.write(`ultragoal state reconciliation failed: ${message}\n`);
|
|
1461
|
+
try {
|
|
1462
|
+
await appendLedger(cwd, { type: "reconcile_failed", error: message });
|
|
1463
|
+
} catch {
|
|
1464
|
+
// Best-effort audit; never let a secondary failure change command semantics.
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
export async function runNativeUltragoalCommand(args: string[], cwd = process.cwd()): Promise<UltragoalCommandResult> {
|
|
1470
|
+
const command = commandName(args);
|
|
1471
|
+
const result = await dispatchUltragoalCommand(args, cwd);
|
|
1472
|
+
if (result.status === 0 && RECONCILE_COMMANDS.has(command)) {
|
|
1473
|
+
await reconcileUltragoalState(cwd);
|
|
1474
|
+
}
|
|
1475
|
+
return result;
|
|
1476
|
+
}
|
|
@@ -1171,6 +1171,10 @@
|
|
|
1171
1171
|
],
|
|
1172
1172
|
"skill": "ultragoal",
|
|
1173
1173
|
"states": [
|
|
1174
|
+
{
|
|
1175
|
+
"id": "missing",
|
|
1176
|
+
"terminal": true
|
|
1177
|
+
},
|
|
1174
1178
|
{
|
|
1175
1179
|
"id": "goal-planning",
|
|
1176
1180
|
"initial": true
|
|
@@ -1198,6 +1202,7 @@
|
|
|
1198
1202
|
}
|
|
1199
1203
|
],
|
|
1200
1204
|
"terminalStates": [
|
|
1205
|
+
"missing",
|
|
1201
1206
|
"failed",
|
|
1202
1207
|
"complete",
|
|
1203
1208
|
"handoff"
|
|
@@ -212,8 +212,8 @@ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest>
|
|
|
212
212
|
}),
|
|
213
213
|
ultragoal: manifest({
|
|
214
214
|
skill: "ultragoal",
|
|
215
|
-
states: ["goal-planning", "pending", "active", "blocked", "failed", "complete", "handoff"],
|
|
216
|
-
terminalStates: ["failed", "complete", "handoff"],
|
|
215
|
+
states: ["missing", "goal-planning", "pending", "active", "blocked", "failed", "complete", "handoff"],
|
|
216
|
+
terminalStates: ["missing", "failed", "complete", "handoff"],
|
|
217
217
|
transitions: [
|
|
218
218
|
{ from: "goal-planning", to: "pending", verb: "create-goals" },
|
|
219
219
|
{ from: "pending", to: "active", verb: "complete-goals" },
|