@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
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
|
-
import * as fs from "node:fs/promises";
|
|
3
2
|
import * as path from "node:path";
|
|
4
3
|
import type { WorkflowHudSummary } from "../skill-state/active-state";
|
|
5
4
|
import { buildUltragoalHudSummary as buildWorkflowUltragoalHudSummary } from "../skill-state/workflow-hud";
|
|
5
|
+
import { renderCliWriteReceipt } from "./cli-write-receipt";
|
|
6
6
|
import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
|
|
7
|
+
import { renderUltragoalStatusMarkdown } from "./state-renderer";
|
|
8
|
+
import { appendJsonl, writeArtifact, writeJsonAtomic } from "./state-writer";
|
|
7
9
|
|
|
8
10
|
export type UltragoalGjcGoalMode = "aggregate" | "per-story";
|
|
9
11
|
export type UltragoalGoalStatus =
|
|
@@ -103,6 +105,12 @@ const TERMINAL_OR_SKIPPED_STATUSES = new Set<UltragoalGoalStatus>(["complete", "
|
|
|
103
105
|
const CLEAN_ARCHITECT_STATUS = "CLEAR";
|
|
104
106
|
const APPROVE_RECOMMENDATION = "APPROVE";
|
|
105
107
|
const PASSED_STATUS = "passed";
|
|
108
|
+
const NOT_APPLICABLE_STATUS = "not_applicable";
|
|
109
|
+
const COVERED_STATUS = "covered";
|
|
110
|
+
const ACCEPTED_PROOF_STATUSES = new Set([COVERED_STATUS, "passed", "verified"]);
|
|
111
|
+
const MIN_SUBSTANTIVE_EVIDENCE_WORDS = 5;
|
|
112
|
+
const MIN_SUBSTANTIVE_EVIDENCE_CHARS = 32;
|
|
113
|
+
|
|
106
114
|
const GJC_GOAL_SNAPSHOT_MAX_AGE_MILLISECONDS = 10 * 60 * 1000;
|
|
107
115
|
const GJC_GOAL_SNAPSHOT_MAX_FUTURE_SKEW_MILLISECONDS = 60 * 1000;
|
|
108
116
|
|
|
@@ -143,19 +151,17 @@ function isEnoent(error: unknown): boolean {
|
|
|
143
151
|
);
|
|
144
152
|
}
|
|
145
153
|
|
|
146
|
-
async function ensureUltragoalDir(paths: UltragoalPaths): Promise<void> {
|
|
147
|
-
await fs.mkdir(paths.dir, { recursive: true });
|
|
148
|
-
}
|
|
149
|
-
|
|
150
154
|
async function appendLedger(cwd: string, event: JsonObject): Promise<UltragoalLedgerEvent> {
|
|
151
155
|
const paths = getUltragoalPaths(cwd);
|
|
152
|
-
await ensureUltragoalDir(paths);
|
|
153
156
|
const entry: UltragoalLedgerEvent = {
|
|
154
157
|
eventId: typeof event.eventId === "string" ? event.eventId : crypto.randomUUID(),
|
|
155
158
|
...event,
|
|
156
159
|
timestamp: new Date().toISOString(),
|
|
157
160
|
};
|
|
158
|
-
await
|
|
161
|
+
await appendJsonl(paths.ledgerPath, entry, {
|
|
162
|
+
cwd,
|
|
163
|
+
audit: { category: "ledger", verb: "append", owner: "gjc-runtime" },
|
|
164
|
+
});
|
|
159
165
|
return entry;
|
|
160
166
|
}
|
|
161
167
|
|
|
@@ -175,9 +181,14 @@ export async function readUltragoalLedger(cwd: string): Promise<UltragoalLedgerE
|
|
|
175
181
|
|
|
176
182
|
async function writePlan(cwd: string, plan: UltragoalPlan): Promise<void> {
|
|
177
183
|
const paths = getUltragoalPaths(cwd);
|
|
178
|
-
await
|
|
179
|
-
|
|
180
|
-
|
|
184
|
+
await writeArtifact(paths.briefPath, `${plan.brief.trim()}\n`, {
|
|
185
|
+
cwd,
|
|
186
|
+
audit: { category: "artifact", verb: "write", owner: "gjc-runtime" },
|
|
187
|
+
});
|
|
188
|
+
await writeJsonAtomic(paths.goalsPath, plan, {
|
|
189
|
+
cwd,
|
|
190
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime" },
|
|
191
|
+
});
|
|
181
192
|
}
|
|
182
193
|
|
|
183
194
|
function requiredUltragoalGoals(plan: UltragoalPlan): UltragoalGoal[] {
|
|
@@ -464,13 +475,58 @@ export function buildUltragoalHudSummary(
|
|
|
464
475
|
});
|
|
465
476
|
}
|
|
466
477
|
|
|
467
|
-
function
|
|
468
|
-
|
|
478
|
+
function clampTitle(title: string): string {
|
|
479
|
+
return title.length > 80 ? `${title.slice(0, 77)}...` : title;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function firstNonEmptyLine(text: string): string | undefined {
|
|
483
|
+
return text
|
|
469
484
|
.split(/\r?\n/)
|
|
470
485
|
.map(line => line.trim())
|
|
471
486
|
.find(line => line.length > 0);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function titleFromBrief(brief: string): string {
|
|
490
|
+
const firstLine = firstNonEmptyLine(brief);
|
|
472
491
|
if (!firstLine) return "Complete ultragoal brief";
|
|
473
|
-
return firstLine
|
|
492
|
+
return clampTitle(firstLine);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// A reserved, column-0 (unindented) `@goal` line opens a story. The character
|
|
496
|
+
// right after `@goal` must be `:`, an ASCII space or tab, or end-of-line, so
|
|
497
|
+
// `@goalish`, `@goals:`, `@goal-foo`, `@goal.foo`, `@goal/foo`, a non-breaking
|
|
498
|
+
// space, and indented or mid-line `@goal:` are all ordinary objective text and
|
|
499
|
+
// never delimiters.
|
|
500
|
+
const GOAL_DELIMITER = /^@goal(?::|[ \t]+|$)[ \t]*(.*)$/;
|
|
501
|
+
|
|
502
|
+
interface ParsedGoal {
|
|
503
|
+
title: string;
|
|
504
|
+
objective: string;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function parseGoalsFromBrief(brief: string): ParsedGoal[] {
|
|
508
|
+
const sections: { title: string; body: string[] }[] = [];
|
|
509
|
+
let current: { title: string; body: string[] } | undefined;
|
|
510
|
+
for (const line of brief.split(/\r?\n/)) {
|
|
511
|
+
const match = GOAL_DELIMITER.exec(line);
|
|
512
|
+
if (match) {
|
|
513
|
+
current = { title: match[1].trim(), body: [] };
|
|
514
|
+
sections.push(current);
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
current?.body.push(line);
|
|
518
|
+
}
|
|
519
|
+
if (sections.length === 0) {
|
|
520
|
+
return [{ title: titleFromBrief(brief), objective: brief.trim() }];
|
|
521
|
+
}
|
|
522
|
+
return sections.map((section, index) => {
|
|
523
|
+
const body = section.body.join("\n").trim();
|
|
524
|
+
const title = section.title || firstNonEmptyLine(body) || "";
|
|
525
|
+
if (!title && !body) {
|
|
526
|
+
throw new Error(`ultragoal @goal block ${index + 1} has no title or objective`);
|
|
527
|
+
}
|
|
528
|
+
return { title: clampTitle(title), objective: body || title };
|
|
529
|
+
});
|
|
474
530
|
}
|
|
475
531
|
|
|
476
532
|
export async function createUltragoalPlan(input: {
|
|
@@ -481,21 +537,23 @@ export async function createUltragoalPlan(input: {
|
|
|
481
537
|
const brief = input.brief.trim();
|
|
482
538
|
if (!brief) throw new Error("ultragoal brief is required");
|
|
483
539
|
const now = new Date().toISOString();
|
|
540
|
+
// Parse the untrimmed brief so the raw-line delimiter contract holds: a
|
|
541
|
+
// leading-indented `@goal` on the first line must stay objective text rather
|
|
542
|
+
// than being promoted to column 0 by trimming.
|
|
543
|
+
const goals: UltragoalGoal[] = parseGoalsFromBrief(input.brief).map((goal, index) => ({
|
|
544
|
+
id: `G${String(index + 1).padStart(3, "0")}`,
|
|
545
|
+
title: goal.title,
|
|
546
|
+
objective: goal.objective,
|
|
547
|
+
status: "pending",
|
|
548
|
+
createdAt: now,
|
|
549
|
+
updatedAt: now,
|
|
550
|
+
}));
|
|
484
551
|
const plan: UltragoalPlan = {
|
|
485
552
|
version: 1,
|
|
486
553
|
brief,
|
|
487
554
|
gjcGoalMode: input.gjcGoalMode ?? "aggregate",
|
|
488
555
|
gjcObjective: DEFAULT_ULTRAGOAL_OBJECTIVE,
|
|
489
|
-
goals
|
|
490
|
-
{
|
|
491
|
-
id: "G001",
|
|
492
|
-
title: titleFromBrief(brief),
|
|
493
|
-
objective: brief,
|
|
494
|
-
status: "pending",
|
|
495
|
-
createdAt: now,
|
|
496
|
-
updatedAt: now,
|
|
497
|
-
},
|
|
498
|
-
],
|
|
556
|
+
goals,
|
|
499
557
|
createdAt: now,
|
|
500
558
|
updatedAt: now,
|
|
501
559
|
};
|
|
@@ -565,8 +623,330 @@ function requireEmptyBlockers(value: unknown, fieldName: string): void {
|
|
|
565
623
|
throw new Error(`qualityGate ${fieldName} must be an empty blockers array`);
|
|
566
624
|
}
|
|
567
625
|
}
|
|
626
|
+
function requireQualityGateObject(value: unknown, fieldName: string): JsonObject {
|
|
627
|
+
const object = qualityGateObject(value);
|
|
628
|
+
if (!object) throw new Error(`qualityGate ${fieldName} must be an object`);
|
|
629
|
+
return object;
|
|
630
|
+
}
|
|
568
631
|
|
|
569
|
-
function
|
|
632
|
+
function requireObjectArray(value: unknown, fieldName: string): JsonObject[] {
|
|
633
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
634
|
+
throw new Error(`qualityGate ${fieldName} must be a non-empty object array`);
|
|
635
|
+
}
|
|
636
|
+
return value.map((item, index) => requireQualityGateObject(item, `${fieldName}[${index}]`));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function requiredStringField(row: JsonObject, key: string, fieldName: string): string {
|
|
640
|
+
const value = row[key];
|
|
641
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
642
|
+
throw new Error(`qualityGate ${fieldName}.${key} must be a non-empty string`);
|
|
643
|
+
}
|
|
644
|
+
return value.trim();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function optionalStatusField(row: JsonObject, fieldName: string): string | null {
|
|
648
|
+
if (row.status === undefined) return null;
|
|
649
|
+
const status = requiredStringField(row, "status", fieldName).toLowerCase();
|
|
650
|
+
if (status === "todo") throw new Error(`qualityGate ${fieldName}.status must not be todo`);
|
|
651
|
+
return status;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function requireProofStatus(status: string, fieldName: string): void {
|
|
655
|
+
if (!ACCEPTED_PROOF_STATUSES.has(status) && status !== NOT_APPLICABLE_STATUS) {
|
|
656
|
+
throw new Error(`qualityGate ${fieldName}.status must be covered, passed, verified, or not_applicable`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
function requireSuccessStatus(status: string, fieldName: string): void {
|
|
660
|
+
requireProofStatus(status, fieldName);
|
|
661
|
+
if (status === NOT_APPLICABLE_STATUS) {
|
|
662
|
+
throw new Error(`qualityGate ${fieldName}.status must be covered, passed, or verified`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function rowOutcomeStatuses(row: JsonObject, fieldName: string): string[] {
|
|
667
|
+
const statuses: string[] = [];
|
|
668
|
+
const status = optionalStatusField(row, fieldName);
|
|
669
|
+
if (status) statuses.push(status);
|
|
670
|
+
const verdict = row.verdict;
|
|
671
|
+
if (typeof verdict === "string" && verdict.trim().length > 0) statuses.push(verdict.trim().toLowerCase());
|
|
672
|
+
const result = row.result;
|
|
673
|
+
if (typeof result === "string" && result.trim().length > 0) statuses.push(result.trim().toLowerCase());
|
|
674
|
+
if (statuses.length === 0) throw new Error(`qualityGate ${fieldName}.verdict must be a non-empty string`);
|
|
675
|
+
return statuses;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function requireSuccessfulRowOutcome(row: JsonObject, fieldName: string): void {
|
|
679
|
+
for (const status of rowOutcomeStatuses(row, fieldName)) {
|
|
680
|
+
requireSuccessStatus(status, fieldName);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function requireStringLinks(value: unknown, fieldName: string): string[] {
|
|
685
|
+
const strings = nonEmptyStringArray(value);
|
|
686
|
+
if (!strings) throw new Error(`qualityGate ${fieldName} must be a non-empty string array`);
|
|
687
|
+
return strings.map(item => item.trim());
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function optionalStringLinks(row: JsonObject, key: string, fieldName: string): string[] | null {
|
|
691
|
+
if (row[key] === undefined) return null;
|
|
692
|
+
return requireStringLinks(row[key], `${fieldName}.${key}`);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function buildRowIdMap(rows: JsonObject[], fieldName: string): Map<string, JsonObject> {
|
|
696
|
+
const ids = new Map<string, JsonObject>();
|
|
697
|
+
for (const [index, row] of rows.entries()) {
|
|
698
|
+
const id = requiredStringField(row, "id", `${fieldName}[${index}]`);
|
|
699
|
+
if (ids.has(id)) throw new Error(`qualityGate ${fieldName} contains duplicate id ${id}`);
|
|
700
|
+
ids.set(id, row);
|
|
701
|
+
}
|
|
702
|
+
return ids;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function requireResolvedLinks(ids: string[], map: Map<string, JsonObject>, fieldName: string): void {
|
|
706
|
+
for (const id of ids) {
|
|
707
|
+
if (!map.has(id)) throw new Error(`qualityGate ${fieldName} references unknown id ${id}`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function successfulLinkedRows(ids: string[], map: Map<string, JsonObject>, fieldName: string): JsonObject[] {
|
|
711
|
+
const rows: JsonObject[] = [];
|
|
712
|
+
for (const id of ids) {
|
|
713
|
+
const row = map.get(id);
|
|
714
|
+
if (!row) throw new Error(`qualityGate ${fieldName} references unknown id ${id}`);
|
|
715
|
+
requireSuccessfulRowOutcome(row, `${fieldName}.${id}`);
|
|
716
|
+
rows.push(row);
|
|
717
|
+
}
|
|
718
|
+
return rows;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function normalizedEvidenceKind(row: JsonObject): string {
|
|
722
|
+
return requiredStringField(row, "kind", "executorQa.artifactRefs[]").toLowerCase().replaceAll("_", "-");
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function evidenceKindMatches(kind: string, words: string[]): boolean {
|
|
726
|
+
return words.some(word => kind.includes(word));
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function validateSurfaceArtifactCompatibility(
|
|
730
|
+
surface: string,
|
|
731
|
+
artifactIds: string[],
|
|
732
|
+
artifactRefs: Map<string, JsonObject>,
|
|
733
|
+
fieldName: string,
|
|
734
|
+
): void {
|
|
735
|
+
const normalizedSurface = surface.toLowerCase().replaceAll("_", "-");
|
|
736
|
+
const kinds = artifactIds.map(id => normalizedEvidenceKind(artifactRefs.get(id)!));
|
|
737
|
+
const isGuiOrWeb = ["gui", "web", "browser", "ui", "visual"].some(word => normalizedSurface.includes(word));
|
|
738
|
+
if (isGuiOrWeb) {
|
|
739
|
+
const hasBrowser = kinds.some(kind =>
|
|
740
|
+
evidenceKindMatches(kind, ["browser", "playwright", "pandawright", "automation"]),
|
|
741
|
+
);
|
|
742
|
+
const hasVisual = kinds.some(kind => evidenceKindMatches(kind, ["screenshot", "image", "visual"]));
|
|
743
|
+
if (!hasBrowser || !hasVisual) {
|
|
744
|
+
throw new Error(
|
|
745
|
+
`qualityGate ${fieldName} for GUI/web surfaces must reference browser automation plus screenshot or image-verdict artifacts`,
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
const surfaceFamilies: Array<{ surface: string[]; evidence: string[]; label: string }> = [
|
|
751
|
+
{
|
|
752
|
+
surface: ["cli", "terminal", "command"],
|
|
753
|
+
evidence: ["cli", "log", "transcript", "terminal", "command", "test-report"],
|
|
754
|
+
label: "CLI",
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
surface: ["api", "package", "library", "sdk"],
|
|
758
|
+
evidence: ["api", "package", "consumer", "black-box", "test-report"],
|
|
759
|
+
label: "API/package",
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
surface: ["algorithm", "math", "mathematical", "equation"],
|
|
763
|
+
evidence: ["property", "boundary", "edge", "adversarial", "failure", "math", "algorithm", "test-report"],
|
|
764
|
+
label: "algorithm/math",
|
|
765
|
+
},
|
|
766
|
+
];
|
|
767
|
+
for (const family of surfaceFamilies) {
|
|
768
|
+
if (family.surface.some(word => normalizedSurface.includes(word))) {
|
|
769
|
+
if (!kinds.some(kind => evidenceKindMatches(kind, family.evidence))) {
|
|
770
|
+
throw new Error(
|
|
771
|
+
`qualityGate ${fieldName} for ${family.label} surfaces must reference compatible artifact kinds`,
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function isSubstantiveEvidence(value: unknown): boolean {
|
|
780
|
+
if (typeof value !== "string") return false;
|
|
781
|
+
const trimmed = value.trim();
|
|
782
|
+
if (trimmed.length < MIN_SUBSTANTIVE_EVIDENCE_CHARS) return false;
|
|
783
|
+
const words = trimmed.split(/\s+/).filter(word => /[a-z0-9]/i.test(word));
|
|
784
|
+
if (words.length < MIN_SUBSTANTIVE_EVIDENCE_WORDS) return false;
|
|
785
|
+
const normalized = trimmed.toLowerCase();
|
|
786
|
+
return !["todo", "tbd", "n/a", "na", "none", "placeholder", "empty", "stub"].includes(normalized);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function hasTypedVerifiedReceipt(value: unknown): boolean {
|
|
790
|
+
const receipt = qualityGateObject(value);
|
|
791
|
+
if (!receipt) return false;
|
|
792
|
+
const type = nonEmptyString(receipt.type) ?? nonEmptyString(receipt.kind) ?? nonEmptyString(receipt.receiptType);
|
|
793
|
+
const id = nonEmptyString(receipt.id) ?? nonEmptyString(receipt.receiptId) ?? nonEmptyString(receipt.ref);
|
|
794
|
+
const status = (nonEmptyString(receipt.status) ?? nonEmptyString(receipt.verdict) ?? "").toLowerCase();
|
|
795
|
+
return Boolean(type && id && (status === "verified" || status === "passed"));
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
async function hasExistingNonEmptyArtifact(cwd: string, value: unknown): Promise<boolean> {
|
|
799
|
+
const artifactPath = nonEmptyString(value);
|
|
800
|
+
if (!artifactPath) return false;
|
|
801
|
+
const resolved = path.resolve(cwd, artifactPath);
|
|
802
|
+
try {
|
|
803
|
+
const file = Bun.file(resolved);
|
|
804
|
+
return (await file.exists()) && file.size > 0;
|
|
805
|
+
} catch (error) {
|
|
806
|
+
if (isEnoent(error)) return false;
|
|
807
|
+
throw error;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
async function requireSubstantiveArtifactEvidence(cwd: string, row: JsonObject, fieldName: string): Promise<void> {
|
|
812
|
+
if (isSubstantiveEvidence(row.inlineEvidence) || isSubstantiveEvidence(row.evidence)) return;
|
|
813
|
+
if (hasTypedVerifiedReceipt(row.verifiedReceipt) || hasTypedVerifiedReceipt(row.receipt)) return;
|
|
814
|
+
if (await hasExistingNonEmptyArtifact(cwd, row.path)) return;
|
|
815
|
+
throw new Error(
|
|
816
|
+
`qualityGate ${fieldName} must reference an existing non-empty artifact path, substantive inlineEvidence, or a typed verifiedReceipt`,
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
async function validateArtifactRefs(cwd: string, executorQa: JsonObject): Promise<Map<string, JsonObject>> {
|
|
821
|
+
const rows = requireObjectArray(executorQa.artifactRefs, "executorQa.artifactRefs");
|
|
822
|
+
const idMap = buildRowIdMap(rows, "executorQa.artifactRefs");
|
|
823
|
+
for (const [index, row] of rows.entries()) {
|
|
824
|
+
const fieldName = `executorQa.artifactRefs[${index}]`;
|
|
825
|
+
requiredStringField(row, "kind", fieldName);
|
|
826
|
+
requiredStringField(row, "description", fieldName);
|
|
827
|
+
await requireSubstantiveArtifactEvidence(cwd, row, fieldName);
|
|
828
|
+
}
|
|
829
|
+
return idMap;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function validateSurfaceEvidence(
|
|
833
|
+
executorQa: JsonObject,
|
|
834
|
+
artifactRefs: Map<string, JsonObject>,
|
|
835
|
+
): Map<string, JsonObject> {
|
|
836
|
+
const rows = requireObjectArray(executorQa.surfaceEvidence, "executorQa.surfaceEvidence");
|
|
837
|
+
const idMap = buildRowIdMap(rows, "executorQa.surfaceEvidence");
|
|
838
|
+
for (const [index, row] of rows.entries()) {
|
|
839
|
+
const fieldName = `executorQa.surfaceEvidence[${index}]`;
|
|
840
|
+
const status = optionalStatusField(row, fieldName);
|
|
841
|
+
requiredStringField(row, "contractRef", fieldName);
|
|
842
|
+
if (status === NOT_APPLICABLE_STATUS) {
|
|
843
|
+
requiredStringField(row, "reason", fieldName);
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
const surface = requiredStringField(row, "surface", fieldName);
|
|
847
|
+
requireSuccessfulRowOutcome(row, fieldName);
|
|
848
|
+
requiredStringField(row, "invocation", fieldName);
|
|
849
|
+
if (typeof row.verdict !== "string" || row.verdict.trim().length === 0) {
|
|
850
|
+
requiredStringField(row, "result", fieldName);
|
|
851
|
+
}
|
|
852
|
+
const artifactIds = requireStringLinks(row.artifactRefs, `${fieldName}.artifactRefs`);
|
|
853
|
+
requireResolvedLinks(artifactIds, artifactRefs, `${fieldName}.artifactRefs`);
|
|
854
|
+
validateSurfaceArtifactCompatibility(surface, artifactIds, artifactRefs, `${fieldName}.artifactRefs`);
|
|
855
|
+
}
|
|
856
|
+
return idMap;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function validateAdversarialCases(
|
|
860
|
+
executorQa: JsonObject,
|
|
861
|
+
artifactRefs: Map<string, JsonObject>,
|
|
862
|
+
): Map<string, JsonObject> {
|
|
863
|
+
const rows = requireObjectArray(executorQa.adversarialCases, "executorQa.adversarialCases");
|
|
864
|
+
const idMap = buildRowIdMap(rows, "executorQa.adversarialCases");
|
|
865
|
+
for (const [index, row] of rows.entries()) {
|
|
866
|
+
const fieldName = `executorQa.adversarialCases[${index}]`;
|
|
867
|
+
const status = optionalStatusField(row, fieldName);
|
|
868
|
+
if (status === NOT_APPLICABLE_STATUS) {
|
|
869
|
+
throw new Error(`qualityGate ${fieldName}.status must not be not_applicable`);
|
|
870
|
+
}
|
|
871
|
+
requireSuccessfulRowOutcome(row, fieldName);
|
|
872
|
+
requiredStringField(row, "contractRef", fieldName);
|
|
873
|
+
requiredStringField(row, "scenario", fieldName);
|
|
874
|
+
requiredStringField(row, "expectedBehavior", fieldName);
|
|
875
|
+
if (typeof row.verdict !== "string" || row.verdict.trim().length === 0) {
|
|
876
|
+
requiredStringField(row, "result", fieldName);
|
|
877
|
+
}
|
|
878
|
+
const artifactIds = requireStringLinks(row.artifactRefs, `${fieldName}.artifactRefs`);
|
|
879
|
+
requireResolvedLinks(artifactIds, artifactRefs, `${fieldName}.artifactRefs`);
|
|
880
|
+
}
|
|
881
|
+
return idMap;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function validateContractCoverage(
|
|
885
|
+
executorQa: JsonObject,
|
|
886
|
+
surfaceEvidence: Map<string, JsonObject>,
|
|
887
|
+
adversarialCases: Map<string, JsonObject>,
|
|
888
|
+
artifactRefs: Map<string, JsonObject>,
|
|
889
|
+
): void {
|
|
890
|
+
const rows = requireObjectArray(executorQa.contractCoverage, "executorQa.contractCoverage");
|
|
891
|
+
buildRowIdMap(rows, "executorQa.contractCoverage");
|
|
892
|
+
let hasSuccessfulContractCoverage = false;
|
|
893
|
+
for (const [index, row] of rows.entries()) {
|
|
894
|
+
const fieldName = `executorQa.contractCoverage[${index}]`;
|
|
895
|
+
requiredStringField(row, "contractRef", fieldName);
|
|
896
|
+
const status = optionalStatusField(row, fieldName);
|
|
897
|
+
if (status === NOT_APPLICABLE_STATUS) {
|
|
898
|
+
requiredStringField(row, "reason", fieldName);
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
requiredStringField(row, "obligation", fieldName);
|
|
902
|
+
if (!status) throw new Error(`qualityGate ${fieldName}.status must be a non-empty string`);
|
|
903
|
+
requireSuccessStatus(status, fieldName);
|
|
904
|
+
hasSuccessfulContractCoverage = true;
|
|
905
|
+
const surfaceIds = optionalStringLinks(row, "surfaceEvidenceRefs", fieldName);
|
|
906
|
+
const adversarialIds = optionalStringLinks(row, "adversarialCaseRefs", fieldName);
|
|
907
|
+
const artifactIds = optionalStringLinks(row, "artifactRefs", fieldName);
|
|
908
|
+
if (!surfaceIds && !adversarialIds && !artifactIds) {
|
|
909
|
+
throw new Error(
|
|
910
|
+
`qualityGate ${fieldName} must link to surfaceEvidenceRefs, adversarialCaseRefs, or artifactRefs`,
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
let successfulProofLinks = 0;
|
|
914
|
+
if (surfaceIds)
|
|
915
|
+
successfulProofLinks += successfulLinkedRows(
|
|
916
|
+
surfaceIds,
|
|
917
|
+
surfaceEvidence,
|
|
918
|
+
`${fieldName}.surfaceEvidenceRefs`,
|
|
919
|
+
).length;
|
|
920
|
+
if (adversarialIds) {
|
|
921
|
+
successfulProofLinks += successfulLinkedRows(
|
|
922
|
+
adversarialIds,
|
|
923
|
+
adversarialCases,
|
|
924
|
+
`${fieldName}.adversarialCaseRefs`,
|
|
925
|
+
).length;
|
|
926
|
+
}
|
|
927
|
+
if (artifactIds) {
|
|
928
|
+
requireResolvedLinks(artifactIds, artifactRefs, `${fieldName}.artifactRefs`);
|
|
929
|
+
successfulProofLinks += artifactIds.length;
|
|
930
|
+
}
|
|
931
|
+
if (successfulProofLinks === 0) {
|
|
932
|
+
throw new Error(`qualityGate ${fieldName} must link to at least one successful proof row or artifact`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (!hasSuccessfulContractCoverage) {
|
|
936
|
+
throw new Error(
|
|
937
|
+
"qualityGate executorQa.contractCoverage must include at least one row with status covered, passed, or verified",
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
async function validateExecutorQaRedTeamEvidence(cwd: string, executorQa: JsonObject): Promise<void> {
|
|
943
|
+
const artifactRefs = await validateArtifactRefs(cwd, executorQa);
|
|
944
|
+
const surfaceEvidence = validateSurfaceEvidence(executorQa, artifactRefs);
|
|
945
|
+
const adversarialCases = validateAdversarialCases(executorQa, artifactRefs);
|
|
946
|
+
validateContractCoverage(executorQa, surfaceEvidence, adversarialCases, artifactRefs);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
async function validateCompletionQualityGate(cwd: string, gate: JsonObject): Promise<void> {
|
|
570
950
|
const codeReview = qualityGateObject(gate.codeReview);
|
|
571
951
|
if (codeReview) {
|
|
572
952
|
throw new Error(
|
|
@@ -611,6 +991,7 @@ function validateCompletionQualityGate(gate: JsonObject): void {
|
|
|
611
991
|
}
|
|
612
992
|
requireNonEmptyString(executorQa.evidence, "executorQa.evidence");
|
|
613
993
|
requireEmptyBlockers(executorQa.blockers, "executorQa.blockers");
|
|
994
|
+
await validateExecutorQaRedTeamEvidence(cwd, executorQa);
|
|
614
995
|
if (iteration.status !== PASSED_STATUS || iteration.fullRerun !== true) {
|
|
615
996
|
throw new Error("qualityGate iteration must be passed with fullRerun true");
|
|
616
997
|
}
|
|
@@ -630,7 +1011,7 @@ async function readRequiredCompletionQualityGate(cwd: string, value: string | un
|
|
|
630
1011
|
const gate = await readStructuredValue(cwd, value);
|
|
631
1012
|
const gateObject = qualityGateObject(gate);
|
|
632
1013
|
if (!gateObject) throw new Error("qualityGate must be a JSON object");
|
|
633
|
-
validateCompletionQualityGate(gateObject);
|
|
1014
|
+
await validateCompletionQualityGate(cwd, gateObject);
|
|
634
1015
|
return gate;
|
|
635
1016
|
}
|
|
636
1017
|
|
|
@@ -896,26 +1277,32 @@ async function readBrief(cwd: string, args: readonly string[]): Promise<string>
|
|
|
896
1277
|
|
|
897
1278
|
function renderStatus(summary: UltragoalStatusSummary, json: boolean): string {
|
|
898
1279
|
if (json) return `${JSON.stringify(summary, null, 2)}\n`;
|
|
899
|
-
|
|
900
|
-
return `No ultragoal plan found at ${summary.paths.goalsPath}. Run \`gjc ultragoal create-goals --brief "..."\` first.\n`;
|
|
901
|
-
}
|
|
902
|
-
const current = summary.currentGoal ? ` Current: ${summary.currentGoal.id} (${summary.currentGoal.status}).` : "";
|
|
903
|
-
return `Ultragoal ${summary.status}: ${summary.counts.complete}/${summary.goals.length} complete.${current}\n`;
|
|
1280
|
+
return renderUltragoalStatusMarkdown(summary);
|
|
904
1281
|
}
|
|
905
1282
|
|
|
906
1283
|
function renderCompleteHandoff(
|
|
907
1284
|
result: { plan: UltragoalPlan; goal?: UltragoalGoal; allComplete: boolean },
|
|
908
1285
|
json: boolean,
|
|
1286
|
+
cwd: string,
|
|
909
1287
|
): string {
|
|
910
|
-
if (json)
|
|
911
|
-
|
|
912
|
-
|
|
1288
|
+
if (json) {
|
|
1289
|
+
return renderCliWriteReceipt({
|
|
1290
|
+
ok: true,
|
|
1291
|
+
all_complete: result.allComplete,
|
|
1292
|
+
next_action: result.allComplete ? "none" : "execute-goal",
|
|
1293
|
+
goal_id: result.goal?.id,
|
|
1294
|
+
goal_status: result.goal?.status,
|
|
1295
|
+
gjc_objective: result.plan.gjcObjective,
|
|
1296
|
+
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
if (result.allComplete) return "ultragoal complete all=true\n";
|
|
1300
|
+
if (!result.goal) return "ultragoal next-action=none\n";
|
|
913
1301
|
return [
|
|
914
|
-
`
|
|
915
|
-
`
|
|
916
|
-
`
|
|
917
|
-
|
|
918
|
-
"Before checkpointing complete, obtain a passing architectReview (architecture/product/code CLEAR + APPROVE) and executorQa (e2e/red-team passed); record blockers instead of completing on any finding.",
|
|
1302
|
+
`ultragoal next-action=execute-goal goal-id=${result.goal.id}`,
|
|
1303
|
+
`objective=${result.goal.objective}`,
|
|
1304
|
+
`gjc-objective=${result.plan.gjcObjective}`,
|
|
1305
|
+
"checkpoint requires=architectReview:CLEAR+APPROVE,executorQa:passed",
|
|
919
1306
|
"",
|
|
920
1307
|
].join("\n");
|
|
921
1308
|
}
|
|
@@ -935,8 +1322,13 @@ export async function runNativeUltragoalCommand(args: string[], cwd = process.cw
|
|
|
935
1322
|
status: 0,
|
|
936
1323
|
createdPlan: true,
|
|
937
1324
|
stdout: json
|
|
938
|
-
?
|
|
939
|
-
|
|
1325
|
+
? renderCliWriteReceipt({
|
|
1326
|
+
ok: true,
|
|
1327
|
+
goals_count: plan.goals.length,
|
|
1328
|
+
goal_ids: plan.goals.map(goal => goal.id),
|
|
1329
|
+
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
1330
|
+
})
|
|
1331
|
+
: `Created ultragoal plan with ${plan.goals.length} goal${plan.goals.length === 1 ? "" : "s"} at ${getUltragoalPaths(cwd).goalsPath}.\n`,
|
|
940
1332
|
};
|
|
941
1333
|
}
|
|
942
1334
|
case "complete-goals":
|
|
@@ -945,6 +1337,7 @@ export async function runNativeUltragoalCommand(args: string[], cwd = process.cw
|
|
|
945
1337
|
stdout: renderCompleteHandoff(
|
|
946
1338
|
await startNextUltragoalGoal({ cwd, retryFailed: hasFlag(args, "--retry-failed") }),
|
|
947
1339
|
json,
|
|
1340
|
+
cwd,
|
|
948
1341
|
),
|
|
949
1342
|
};
|
|
950
1343
|
case "checkpoint": {
|
|
@@ -959,9 +1352,19 @@ export async function runNativeUltragoalCommand(args: string[], cwd = process.cw
|
|
|
959
1352
|
gjcGoalJson: flagValue(args, "--gjc-goal-json"),
|
|
960
1353
|
qualityGateJson: flagValue(args, "--quality-gate-json"),
|
|
961
1354
|
});
|
|
1355
|
+
const goal = plan.goals.find(item => item.id === goalId);
|
|
962
1356
|
return {
|
|
963
1357
|
status: 0,
|
|
964
|
-
stdout: json
|
|
1358
|
+
stdout: json
|
|
1359
|
+
? renderCliWriteReceipt({
|
|
1360
|
+
ok: true,
|
|
1361
|
+
goal_id: goalId,
|
|
1362
|
+
status,
|
|
1363
|
+
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
1364
|
+
completion_receipt_kind: goal?.completionVerification?.receiptKind,
|
|
1365
|
+
quality_gate_hash: goal?.completionVerification?.qualityGateHash,
|
|
1366
|
+
})
|
|
1367
|
+
: `ultragoal checkpoint goal-id=${goalId} status=${status}\n`,
|
|
965
1368
|
};
|
|
966
1369
|
}
|
|
967
1370
|
case "steer": {
|
|
@@ -974,9 +1377,17 @@ export async function runNativeUltragoalCommand(args: string[], cwd = process.cw
|
|
|
974
1377
|
evidence: flagValue(args, "--evidence") ?? "",
|
|
975
1378
|
rationale: flagValue(args, "--rationale") ?? "",
|
|
976
1379
|
});
|
|
1380
|
+
const goal = plan.goals.at(-1);
|
|
977
1381
|
return {
|
|
978
1382
|
status: 0,
|
|
979
|
-
stdout: json
|
|
1383
|
+
stdout: json
|
|
1384
|
+
? renderCliWriteReceipt({
|
|
1385
|
+
ok: true,
|
|
1386
|
+
kind,
|
|
1387
|
+
goal_id: goal?.id,
|
|
1388
|
+
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
1389
|
+
})
|
|
1390
|
+
: "Accepted add_subgoal steering.\n",
|
|
980
1391
|
};
|
|
981
1392
|
}
|
|
982
1393
|
case "record-review-blockers": {
|
|
@@ -988,7 +1399,13 @@ export async function runNativeUltragoalCommand(args: string[], cwd = process.cw
|
|
|
988
1399
|
evidence: flagValue(args, "--evidence") ?? "",
|
|
989
1400
|
gjcGoalJson: flagValue(args, "--gjc-goal-json"),
|
|
990
1401
|
});
|
|
991
|
-
|
|
1402
|
+
const goal = plan.goals.at(-1);
|
|
1403
|
+
return {
|
|
1404
|
+
status: 0,
|
|
1405
|
+
stdout: json
|
|
1406
|
+
? renderCliWriteReceipt({ ok: true, goal_id: goal?.id, goals_path: getUltragoalPaths(cwd).goalsPath })
|
|
1407
|
+
: "Recorded review blockers.\n",
|
|
1408
|
+
};
|
|
992
1409
|
}
|
|
993
1410
|
default:
|
|
994
1411
|
return { status: 1, stderr: `Unknown gjc ultragoal command: ${command}\n` };
|