@gajae-code/coding-agent 0.12.1 → 0.12.2
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 +30 -0
- package/dist/types/config/settings-schema.d.ts +2 -2
- package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -3
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +6 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +1 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +9 -0
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +32 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -2
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +22 -3
- package/dist/types/task/index.d.ts +4 -0
- package/dist/types/task/output-manager.d.ts +1 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/package.json +8 -8
- package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-acp-conformance.ts +5 -0
- package/src/async/job-manager.ts +22 -20
- package/src/config/settings-schema.ts +2 -2
- package/src/debug/index.ts +42 -17
- package/src/defaults/gjc/skills/ralplan/SKILL.md +9 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -2
- package/src/gjc-runtime/launch-worktree.ts +103 -1
- package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
- package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
- package/src/gjc-runtime/ralplan-runtime.ts +80 -10
- package/src/gjc-runtime/ultragoal-evidence.ts +6 -74
- package/src/gjc-runtime/ultragoal-runtime.ts +236 -3
- package/src/gjc-runtime/workflow-manifest.generated.json +29 -0
- package/src/gjc-runtime/workflow-manifest.ts +17 -2
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/modes/DESIGN.md +53 -0
- package/src/modes/components/irc-sidebar.ts +30 -1
- package/src/modes/components/tool-status-header.ts +0 -1
- package/src/modes/controllers/command-controller.ts +38 -34
- package/src/modes/controllers/event-controller.ts +27 -16
- package/src/modes/controllers/extension-ui-controller.ts +186 -34
- package/src/modes/controllers/selector-controller.ts +115 -22
- package/src/modes/interactive-mode.ts +189 -62
- package/src/modes/jobs-observer.ts +1 -1
- package/src/modes/theme/theme.ts +11 -9
- package/src/modes/types.ts +88 -0
- package/src/sdk/broker/lifecycle.ts +18 -13
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -2
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/session.ts +1 -0
- package/src/session/agent-session.ts +22 -1
- package/src/session/internal/managed-session-scope.ts +158 -107
- package/src/session/internal/managed-session-storage.ts +445 -57
- package/src/session/session-storage.ts +29 -44
- package/src/task/index.ts +257 -31
- package/src/task/output-manager.ts +24 -14
- package/src/task/receipt.ts +11 -1
- package/src/tools/index.ts +2 -0
|
@@ -382,26 +382,6 @@ function clampCliReplayTimeout(value: unknown): number {
|
|
|
382
382
|
function basenameCommand(value: string): string {
|
|
383
383
|
return path.basename(value).toLowerCase();
|
|
384
384
|
}
|
|
385
|
-
|
|
386
|
-
function isDeterministicConsoleLogReplay(code: string): boolean {
|
|
387
|
-
let remaining = code.trim();
|
|
388
|
-
if (remaining.length === 0) return false;
|
|
389
|
-
let matched = false;
|
|
390
|
-
while (remaining.length > 0) {
|
|
391
|
-
const match =
|
|
392
|
-
/^console\.log\(\s*("(?:\\[\s\S]|[^"\\])*"|'(?:\\[\s\S]|[^'\\])*'|`(?:\\[\s\S]|[^`\\$])*`)\s*\)\s*;?\s*/.exec(
|
|
393
|
-
remaining,
|
|
394
|
-
);
|
|
395
|
-
if (!match) return false;
|
|
396
|
-
const statement = match[0]!;
|
|
397
|
-
const literal = match[1]!;
|
|
398
|
-
if (literal.startsWith("`") && literal.includes("${")) return false;
|
|
399
|
-
matched = true;
|
|
400
|
-
remaining = remaining.slice(statement.length);
|
|
401
|
-
}
|
|
402
|
-
return matched;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
385
|
function hasShellRedirectionToken(value: string): boolean {
|
|
406
386
|
return /^(?:[<>]|\d?[<>]|\d?>&\d|\|\|?|&&|;)$/.test(value) || /(?:^|[^\w])-?>/.test(value);
|
|
407
387
|
}
|
|
@@ -433,54 +413,6 @@ export function isAllowedGitReplayCommand(args: readonly string[]): boolean {
|
|
|
433
413
|
return true;
|
|
434
414
|
}
|
|
435
415
|
|
|
436
|
-
function isBareExecutableName(value: string): boolean {
|
|
437
|
-
// The allowlist is keyed on the basename, but the raw command[0] is what gets spawned.
|
|
438
|
-
// Reject path-qualified or case-spoofed executables (e.g. ./git, /tmp/npm, scripts/node, GIT)
|
|
439
|
-
// so an attacker-controlled binary cannot impersonate a trusted tool.
|
|
440
|
-
return (
|
|
441
|
-
value.length > 0 &&
|
|
442
|
-
!value.includes("/") &&
|
|
443
|
-
!value.includes("\\") &&
|
|
444
|
-
value === path.basename(value) &&
|
|
445
|
-
value === value.toLowerCase()
|
|
446
|
-
);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
function isAllowedCliReplayCommand(command: readonly string[]): boolean {
|
|
450
|
-
if (
|
|
451
|
-
command.length === 0 ||
|
|
452
|
-
command.some(arg => arg.trim() !== arg || arg.length === 0 || hasShellRedirectionToken(arg))
|
|
453
|
-
)
|
|
454
|
-
return false;
|
|
455
|
-
if (!isBareExecutableName(command[0]!)) return false;
|
|
456
|
-
const executable = basenameCommand(command[0]!);
|
|
457
|
-
const args = command.slice(1);
|
|
458
|
-
if (executable === "bun" || executable === "node") {
|
|
459
|
-
if (args.length === 1 && args[0] === "--version") return true;
|
|
460
|
-
return args.length === 2 && args[0] === "-e" && isDeterministicConsoleLogReplay(args[1]!);
|
|
461
|
-
}
|
|
462
|
-
if (executable === "npm" || executable === "pnpm" || executable === "yarn") {
|
|
463
|
-
return (args.length === 1 && args[0] === "--version") || (args.length === 1 && args[0] === "list");
|
|
464
|
-
}
|
|
465
|
-
if (executable === "git") return isAllowedGitReplayCommand(args);
|
|
466
|
-
if (executable === "gjc") return args.length === 1 && ["read", "status"].includes(args[0] ?? "");
|
|
467
|
-
return false;
|
|
468
|
-
}
|
|
469
|
-
function summarizeBlockedCliReplayCommand(command: readonly string[]): string {
|
|
470
|
-
const executable = command[0] ? basenameCommand(command[0]) : "<missing>";
|
|
471
|
-
const argCount = Math.max(0, command.length - 1);
|
|
472
|
-
return `${JSON.stringify(executable)} with ${argCount} arg${argCount === 1 ? "" : "s"}`;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function cliReplayAllowlistDescription(): string {
|
|
476
|
-
return [
|
|
477
|
-
'`bun --version`, `node --version`, or deterministic `bun/node -e "console.log(...)"`',
|
|
478
|
-
"`npm|pnpm|yarn --version` or `npm|pnpm|yarn list`",
|
|
479
|
-
"read-only `git status|rev-parse|merge-base|diff|show|log` with safe args",
|
|
480
|
-
"`gjc read` or `gjc status`",
|
|
481
|
-
].join("; ");
|
|
482
|
-
}
|
|
483
|
-
|
|
484
416
|
function resolveCliReplayCommand(command: string[]): string[] {
|
|
485
417
|
if (basenameCommand(command[0]!) === "bun") return [process.execPath, ...command.slice(1)];
|
|
486
418
|
return command;
|
|
@@ -535,7 +467,12 @@ function normalizeCliReplayOutput(value: string, cwd: string): string {
|
|
|
535
467
|
}
|
|
536
468
|
|
|
537
469
|
export async function readCliReplayRecord(cwd: string, row: JsonObject, fieldName: string): Promise<JsonObject | null> {
|
|
538
|
-
|
|
470
|
+
// A row is an inline replay record only when it actually carries replay fields.
|
|
471
|
+
// `kind: "cli-replay"` alone is the natural kind for an artifactRef that *points*
|
|
472
|
+
// at a replay file, so treating that as inline meant the referenced `path` was
|
|
473
|
+
// never read and surfaced a confusing `schemaVersion must be 1` error on the ref.
|
|
474
|
+
const inline =
|
|
475
|
+
qualityGateObject(row.replay) ?? (row.kind === "cli-replay" && row.command !== undefined ? row : null);
|
|
539
476
|
if (inline) return inline;
|
|
540
477
|
if (!evidenceKindMatches(normalizedEvidenceKind(row), ["cli-replay", "command-replay"])) return null;
|
|
541
478
|
const bytes = await readArtifactBytes(cwd, row, fieldName);
|
|
@@ -568,11 +505,6 @@ function parseCliReplayRecord(
|
|
|
568
505
|
if (!command) throw new Error(`qualityGate ${fieldName}.command must be a non-empty string array`);
|
|
569
506
|
if (record.replaySafe !== true)
|
|
570
507
|
throw new Error(`qualityGate ${fieldName}.replaySafe must be true before CLI replay executes`);
|
|
571
|
-
if (!isAllowedCliReplayCommand(command)) {
|
|
572
|
-
throw new Error(
|
|
573
|
-
`qualityGate ${fieldName}.command is not in the conservative CLI replay allowlist; command ${summarizeBlockedCliReplayCommand(command)} is blocked. Allowed replay commands: ${cliReplayAllowlistDescription()}. For other commands, provide audited replayExempt metadata with reasonCode, reason, approvedBy, and fallbackArtifactRefs that point to a structurally valid fallback artifact.`,
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
508
|
if (record.normalization !== undefined && record.normalization !== "default") {
|
|
577
509
|
throw new Error(`qualityGate ${fieldName}.normalization must be default when provided`);
|
|
578
510
|
}
|
|
@@ -2775,6 +2775,182 @@ function hydrateReviewedBatchReplacementClose(input: {
|
|
|
2775
2775
|
},
|
|
2776
2776
|
};
|
|
2777
2777
|
}
|
|
2778
|
+
/**
|
|
2779
|
+
* Scaffold a schema-shaped quality-gate template for selected surfaces (#3474).
|
|
2780
|
+
* The template is intentionally incomplete for live artifact proofs so `quality-gate
|
|
2781
|
+
* validate` can report remaining evidence gaps in one pass after the author fills paths.
|
|
2782
|
+
*/
|
|
2783
|
+
export function buildQualityGateInitTemplate(surfaces: readonly string[]): JsonObject {
|
|
2784
|
+
const normalized = surfaces.length > 0 ? surfaces.map(surface => surface.trim()).filter(Boolean) : ["web"];
|
|
2785
|
+
const unique: string[] = [];
|
|
2786
|
+
for (const surface of normalized) {
|
|
2787
|
+
if (!unique.includes(surface)) unique.push(surface);
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
const artifactRefs: JsonObject[] = [
|
|
2791
|
+
{
|
|
2792
|
+
id: "adversarial-report",
|
|
2793
|
+
kind: "failure-mode-test",
|
|
2794
|
+
path: "artifacts/adversarial-report.txt",
|
|
2795
|
+
description: "Adversarial boundary and failure-mode test output",
|
|
2796
|
+
inlineEvidence:
|
|
2797
|
+
"Adversarial boundary cases exercised invalid input, missing state, and repeated submission without violating the contract.",
|
|
2798
|
+
},
|
|
2799
|
+
];
|
|
2800
|
+
const surfaceEvidence: JsonObject[] = [];
|
|
2801
|
+
const contractCoverage: JsonObject[] = [];
|
|
2802
|
+
const adversarialCases: JsonObject[] = [
|
|
2803
|
+
{
|
|
2804
|
+
id: "case-invalid-input",
|
|
2805
|
+
contractRef: "approved-plan:goal",
|
|
2806
|
+
scenario: "Submit invalid or boundary input through the user-facing surface",
|
|
2807
|
+
expectedBehavior: "The implementation rejects or handles the case according to the approved contract",
|
|
2808
|
+
verdict: "passed",
|
|
2809
|
+
artifactRefs: ["adversarial-report"],
|
|
2810
|
+
},
|
|
2811
|
+
];
|
|
2812
|
+
|
|
2813
|
+
unique.forEach((surface, index) => {
|
|
2814
|
+
const surfaceId = `surface-${index + 1}`;
|
|
2815
|
+
const family = surface.toLowerCase();
|
|
2816
|
+
const linked: string[] = [];
|
|
2817
|
+
if (family.includes("web") || family.includes("gui")) {
|
|
2818
|
+
const browserId = `browser-run-${index + 1}`;
|
|
2819
|
+
const shotId = `gui-screenshot-${index + 1}`;
|
|
2820
|
+
linked.push(browserId, shotId);
|
|
2821
|
+
artifactRefs.push(
|
|
2822
|
+
{
|
|
2823
|
+
id: browserId,
|
|
2824
|
+
kind: "browser-automation",
|
|
2825
|
+
path: `artifacts/${browserId}.json`,
|
|
2826
|
+
description: "Browser automation transcript for the approved user-facing flow",
|
|
2827
|
+
inlineEvidence:
|
|
2828
|
+
"Browser automation executed the approved flow, asserted the expected visible result, and captured the final DOM state.",
|
|
2829
|
+
},
|
|
2830
|
+
{
|
|
2831
|
+
id: shotId,
|
|
2832
|
+
kind: "screenshot",
|
|
2833
|
+
path: `artifacts/${shotId}.png`,
|
|
2834
|
+
description: "Screenshot evidence for the GUI/web surface verdict",
|
|
2835
|
+
inlineEvidence:
|
|
2836
|
+
"Screenshot review confirmed the approved screen state, including the success message and absence of regression indicators.",
|
|
2837
|
+
},
|
|
2838
|
+
);
|
|
2839
|
+
} else if (family.includes("cli")) {
|
|
2840
|
+
const replayId = `cli-replay-${index + 1}`;
|
|
2841
|
+
linked.push(replayId);
|
|
2842
|
+
artifactRefs.push({
|
|
2843
|
+
id: replayId,
|
|
2844
|
+
kind: "cli-replay",
|
|
2845
|
+
path: `artifacts/${replayId}.json`,
|
|
2846
|
+
description: "CLI argv replay transcript for the approved command surface",
|
|
2847
|
+
inlineEvidence:
|
|
2848
|
+
"CLI replay executed the allowlisted command and verified recorded stdout against the approved contract.",
|
|
2849
|
+
});
|
|
2850
|
+
} else if (family.includes("api") || family.includes("package")) {
|
|
2851
|
+
const reportId = `api-report-${index + 1}`;
|
|
2852
|
+
linked.push(reportId);
|
|
2853
|
+
artifactRefs.push({
|
|
2854
|
+
id: reportId,
|
|
2855
|
+
kind: "test-report",
|
|
2856
|
+
path: `artifacts/${reportId}.xml`,
|
|
2857
|
+
description: "API/package black-box test report",
|
|
2858
|
+
inlineEvidence:
|
|
2859
|
+
"API/package suite covered happy path, auth failure, and contract-breaking payloads with non-empty report output.",
|
|
2860
|
+
});
|
|
2861
|
+
} else {
|
|
2862
|
+
const evidenceId = `surface-evidence-${index + 1}`;
|
|
2863
|
+
linked.push(evidenceId);
|
|
2864
|
+
artifactRefs.push({
|
|
2865
|
+
id: evidenceId,
|
|
2866
|
+
kind: "transcript",
|
|
2867
|
+
path: `artifacts/${evidenceId}.txt`,
|
|
2868
|
+
description: `Evidence transcript for surface ${surface}`,
|
|
2869
|
+
inlineEvidence: `Surface ${surface} was exercised against the approved contract with recorded transcript evidence.`,
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
surfaceEvidence.push({
|
|
2874
|
+
id: surfaceId,
|
|
2875
|
+
surface: family.includes("web") || family.includes("gui") ? "gui/web" : surface,
|
|
2876
|
+
contractRef: "approved-plan:goal",
|
|
2877
|
+
invocation: `Exercise surface ${surface} against the approved contract`,
|
|
2878
|
+
verdict: "passed",
|
|
2879
|
+
artifactRefs: linked,
|
|
2880
|
+
});
|
|
2881
|
+
contractCoverage.push({
|
|
2882
|
+
id: `contract-${index + 1}`,
|
|
2883
|
+
contractRef: "approved-plan:goal",
|
|
2884
|
+
obligation: `The completed story satisfies the approved contract on surface ${surface}`,
|
|
2885
|
+
status: "covered",
|
|
2886
|
+
surfaceEvidenceRefs: [surfaceId],
|
|
2887
|
+
adversarialCaseRefs: ["case-invalid-input"],
|
|
2888
|
+
});
|
|
2889
|
+
});
|
|
2890
|
+
|
|
2891
|
+
return {
|
|
2892
|
+
architectReview: {
|
|
2893
|
+
architectureStatus: "CLEAR",
|
|
2894
|
+
productStatus: "CLEAR",
|
|
2895
|
+
codeStatus: "CLEAR",
|
|
2896
|
+
recommendation: "APPROVE",
|
|
2897
|
+
evidence: "architect reviewed architecture, product behavior, and code changes",
|
|
2898
|
+
commands: ["architect-review"],
|
|
2899
|
+
blockers: [],
|
|
2900
|
+
},
|
|
2901
|
+
executorQa: {
|
|
2902
|
+
status: "passed",
|
|
2903
|
+
e2eStatus: "passed",
|
|
2904
|
+
redTeamStatus: "passed",
|
|
2905
|
+
evidence: "executor built and ran e2e plus red-team QA suite",
|
|
2906
|
+
e2eCommands: ["bun test:e2e"],
|
|
2907
|
+
redTeamCommands: ["bun test:red-team"],
|
|
2908
|
+
artifactRefs,
|
|
2909
|
+
contractCoverage,
|
|
2910
|
+
surfaceEvidence,
|
|
2911
|
+
adversarialCases,
|
|
2912
|
+
blockers: [],
|
|
2913
|
+
},
|
|
2914
|
+
criticReview: {
|
|
2915
|
+
verdict: "OKAY",
|
|
2916
|
+
evidence: "critic approved final aggregate",
|
|
2917
|
+
blockers: [],
|
|
2918
|
+
},
|
|
2919
|
+
iteration: {
|
|
2920
|
+
status: "passed",
|
|
2921
|
+
evidence: "no verification findings remain after steering iterations",
|
|
2922
|
+
fullRerun: true,
|
|
2923
|
+
reviewCohort: {
|
|
2924
|
+
reviewGeneration: 1,
|
|
2925
|
+
sourceHash: "sha256:replace-with-frozen-source-hash",
|
|
2926
|
+
joined: true,
|
|
2927
|
+
lanes: {
|
|
2928
|
+
cleaner: {
|
|
2929
|
+
status: "passed",
|
|
2930
|
+
sourceHash: "sha256:replace-with-frozen-source-hash",
|
|
2931
|
+
evidence: "cleaner clean",
|
|
2932
|
+
blockers: [],
|
|
2933
|
+
},
|
|
2934
|
+
architect: {
|
|
2935
|
+
status: "CLEAR",
|
|
2936
|
+
sourceHash: "sha256:replace-with-frozen-source-hash",
|
|
2937
|
+
evidence: "architect clear",
|
|
2938
|
+
blockers: [],
|
|
2939
|
+
},
|
|
2940
|
+
qa: {
|
|
2941
|
+
status: "passed",
|
|
2942
|
+
sourceHash: "sha256:replace-with-frozen-source-hash",
|
|
2943
|
+
evidence: "qa passed",
|
|
2944
|
+
blockers: [],
|
|
2945
|
+
},
|
|
2946
|
+
},
|
|
2947
|
+
},
|
|
2948
|
+
rerunCommands: ["bun test:e2e", "bun test:red-team"],
|
|
2949
|
+
blockers: [],
|
|
2950
|
+
},
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2778
2954
|
/**
|
|
2779
2955
|
* Read-only quality-gate validation (#3474). Applies exactly the same rules as
|
|
2780
2956
|
* `checkpoint --status complete` — including deferred-vs-boundary gate selection and
|
|
@@ -3957,6 +4133,18 @@ function flagValue(args: readonly string[], flag: string): string | undefined {
|
|
|
3957
4133
|
return args[index + 1];
|
|
3958
4134
|
}
|
|
3959
4135
|
|
|
4136
|
+
function flagValues(args: readonly string[], flag: string): string[] {
|
|
4137
|
+
const values: string[] = [];
|
|
4138
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
4139
|
+
if (args[index] !== flag) continue;
|
|
4140
|
+
const value = args[index + 1];
|
|
4141
|
+
if (value === undefined || value.startsWith("-")) continue;
|
|
4142
|
+
values.push(value);
|
|
4143
|
+
index += 1;
|
|
4144
|
+
}
|
|
4145
|
+
return values;
|
|
4146
|
+
}
|
|
4147
|
+
|
|
3960
4148
|
function hasFlag(args: readonly string[], flag: string): boolean {
|
|
3961
4149
|
return args.includes(flag);
|
|
3962
4150
|
}
|
|
@@ -3985,6 +4173,8 @@ const FLAGS_WITH_VALUES = new Set([
|
|
|
3985
4173
|
"--order-json",
|
|
3986
4174
|
"--classification",
|
|
3987
4175
|
"--validation-batch-json",
|
|
4176
|
+
"--out",
|
|
4177
|
+
"--surface",
|
|
3988
4178
|
]);
|
|
3989
4179
|
|
|
3990
4180
|
function isHelpArg(arg: string): boolean {
|
|
@@ -4114,6 +4304,28 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
|
|
|
4114
4304
|
].join("\n");
|
|
4115
4305
|
}
|
|
4116
4306
|
|
|
4307
|
+
if (subject === "quality-gate") {
|
|
4308
|
+
return [
|
|
4309
|
+
"Run native GJC Ultragoal workflow commands",
|
|
4310
|
+
"",
|
|
4311
|
+
"USAGE",
|
|
4312
|
+
" $ gjc ultragoal quality-gate init [--surface <name> ...] --out <path>",
|
|
4313
|
+
" $ gjc ultragoal quality-gate validate --quality-gate-json <json-or-path> [--goal-id <id>] [--json]",
|
|
4314
|
+
"",
|
|
4315
|
+
"FLAGS",
|
|
4316
|
+
" --surface=<value> Surface to scaffold (repeatable; default web)",
|
|
4317
|
+
" --out=<value> Output path for quality-gate init",
|
|
4318
|
+
" --quality-gate-json=<value> JSON string or path for quality-gate validate",
|
|
4319
|
+
" --goal-id=<value> Optional durable goal id for rule-identical validation",
|
|
4320
|
+
" --json Machine-readable output",
|
|
4321
|
+
"",
|
|
4322
|
+
"EXAMPLES",
|
|
4323
|
+
" $ gjc ultragoal quality-gate init --surface web --surface api --out ./quality-gate.json",
|
|
4324
|
+
" $ gjc ultragoal quality-gate validate --quality-gate-json ./quality-gate.json --json",
|
|
4325
|
+
"",
|
|
4326
|
+
].join("\n");
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4117
4329
|
return [
|
|
4118
4330
|
"Run native GJC Ultragoal workflow commands",
|
|
4119
4331
|
"",
|
|
@@ -4131,10 +4343,11 @@ function renderUltragoalHelp(args: readonly string[]): string | null {
|
|
|
4131
4343
|
" classify-blocker",
|
|
4132
4344
|
" record-critic-verdict",
|
|
4133
4345
|
" record-critic-gate-override",
|
|
4346
|
+
" quality-gate init",
|
|
4134
4347
|
" quality-gate validate",
|
|
4135
4348
|
|
|
4136
4349
|
"",
|
|
4137
|
-
"Run `gjc ultragoal checkpoint --help`, `gjc ultragoal review --help`, `gjc ultragoal classify-blocker --help`, `gjc ultragoal record-critic-verdict --help`, or `gjc ultragoal record-critic-gate-override --help` for command-specific requirements.",
|
|
4350
|
+
"Run `gjc ultragoal checkpoint --help`, `gjc ultragoal review --help`, `gjc ultragoal classify-blocker --help`, `gjc ultragoal record-critic-verdict --help`, or `gjc ultragoal record-critic-gate-override --help`, or `gjc ultragoal quality-gate --help` for command-specific requirements.",
|
|
4138
4351
|
"",
|
|
4139
4352
|
].join("\n");
|
|
4140
4353
|
}
|
|
@@ -4510,12 +4723,32 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
|
|
|
4510
4723
|
};
|
|
4511
4724
|
}
|
|
4512
4725
|
case "quality-gate": {
|
|
4513
|
-
const positional = args.filter(arg => !arg.startsWith("
|
|
4726
|
+
const positional = args.filter(arg => !arg.startsWith("-"));
|
|
4514
4727
|
const subcommand = positional[1];
|
|
4728
|
+
if (subcommand === "init") {
|
|
4729
|
+
const out = flagValue(args, "--out");
|
|
4730
|
+
if (!out?.trim()) {
|
|
4731
|
+
return { status: 1, stderr: "quality-gate init requires --out <path>\n" };
|
|
4732
|
+
}
|
|
4733
|
+
const surfaces = flagValues(args, "--surface");
|
|
4734
|
+
const template = buildQualityGateInitTemplate(surfaces);
|
|
4735
|
+
const resolved = path.resolve(cwd, out);
|
|
4736
|
+
await Bun.write(resolved, `${JSON.stringify(template, null, 2)}\n`);
|
|
4737
|
+
if (json) {
|
|
4738
|
+
return {
|
|
4739
|
+
status: 0,
|
|
4740
|
+
stdout: `${JSON.stringify({ ok: true, out: resolved, surfaces: surfaces.length > 0 ? surfaces : ["web"] }, null, 2)}\n`,
|
|
4741
|
+
};
|
|
4742
|
+
}
|
|
4743
|
+
return {
|
|
4744
|
+
status: 0,
|
|
4745
|
+
stdout: `Wrote quality-gate template to ${resolved}\n`,
|
|
4746
|
+
};
|
|
4747
|
+
}
|
|
4515
4748
|
if (subcommand !== "validate") {
|
|
4516
4749
|
return {
|
|
4517
4750
|
status: 1,
|
|
4518
|
-
stderr: `Unknown gjc ultragoal quality-gate subcommand: ${subcommand ?? "(missing)"};
|
|
4751
|
+
stderr: `Unknown gjc ultragoal quality-gate subcommand: ${subcommand ?? "(missing)"}; supported: init, validate\n`,
|
|
4519
4752
|
};
|
|
4520
4753
|
}
|
|
4521
4754
|
const qualityGateJson = flagValue(args, "--quality-gate-json");
|
|
@@ -466,6 +466,9 @@
|
|
|
466
466
|
{
|
|
467
467
|
"id": "critic"
|
|
468
468
|
},
|
|
469
|
+
{
|
|
470
|
+
"id": "disposition"
|
|
471
|
+
},
|
|
469
472
|
{
|
|
470
473
|
"id": "revision"
|
|
471
474
|
},
|
|
@@ -507,6 +510,21 @@
|
|
|
507
510
|
"to": "critic",
|
|
508
511
|
"verb": "write-artifact"
|
|
509
512
|
},
|
|
513
|
+
{
|
|
514
|
+
"from": "critic",
|
|
515
|
+
"to": "disposition",
|
|
516
|
+
"verb": "write-artifact"
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
"from": "architect",
|
|
520
|
+
"to": "disposition",
|
|
521
|
+
"verb": "write-artifact"
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
"from": "disposition",
|
|
525
|
+
"to": "revision",
|
|
526
|
+
"verb": "write-artifact"
|
|
527
|
+
},
|
|
510
528
|
{
|
|
511
529
|
"from": "critic",
|
|
512
530
|
"to": "revision",
|
|
@@ -522,6 +540,11 @@
|
|
|
522
540
|
"to": "post-interview",
|
|
523
541
|
"verb": "write-artifact"
|
|
524
542
|
},
|
|
543
|
+
{
|
|
544
|
+
"from": "disposition",
|
|
545
|
+
"to": "post-interview",
|
|
546
|
+
"verb": "write-artifact"
|
|
547
|
+
},
|
|
525
548
|
{
|
|
526
549
|
"from": "post-interview",
|
|
527
550
|
"to": "revision",
|
|
@@ -557,6 +580,11 @@
|
|
|
557
580
|
"to": "handoff",
|
|
558
581
|
"verb": "handoff"
|
|
559
582
|
},
|
|
583
|
+
{
|
|
584
|
+
"from": "disposition",
|
|
585
|
+
"to": "handoff",
|
|
586
|
+
"verb": "handoff"
|
|
587
|
+
},
|
|
560
588
|
{
|
|
561
589
|
"from": "revision",
|
|
562
590
|
"to": "handoff",
|
|
@@ -727,6 +755,7 @@
|
|
|
727
755
|
"planner",
|
|
728
756
|
"architect",
|
|
729
757
|
"critic",
|
|
758
|
+
"disposition",
|
|
730
759
|
"revision",
|
|
731
760
|
"post-interview",
|
|
732
761
|
"adr",
|
|
@@ -215,14 +215,28 @@ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest>
|
|
|
215
215
|
}),
|
|
216
216
|
ralplan: manifest({
|
|
217
217
|
skill: "ralplan",
|
|
218
|
-
states: [
|
|
218
|
+
states: [
|
|
219
|
+
"planner",
|
|
220
|
+
"architect",
|
|
221
|
+
"critic",
|
|
222
|
+
"disposition",
|
|
223
|
+
"revision",
|
|
224
|
+
"post-interview",
|
|
225
|
+
"adr",
|
|
226
|
+
"final",
|
|
227
|
+
"handoff",
|
|
228
|
+
],
|
|
219
229
|
terminalStates: ["final", "handoff"],
|
|
220
230
|
transitions: [
|
|
221
231
|
{ from: "planner", to: "architect", verb: "write-artifact" },
|
|
222
232
|
{ from: "architect", to: "critic", verb: "write-artifact" },
|
|
233
|
+
{ from: "critic", to: "disposition", verb: "write-artifact" },
|
|
234
|
+
{ from: "architect", to: "disposition", verb: "write-artifact" },
|
|
235
|
+
{ from: "disposition", to: "revision", verb: "write-artifact" },
|
|
223
236
|
{ from: "critic", to: "revision", verb: "write-artifact" },
|
|
224
237
|
{ from: "revision", to: "post-interview", verb: "write-artifact" },
|
|
225
238
|
{ from: "critic", to: "post-interview", verb: "write-artifact" },
|
|
239
|
+
{ from: "disposition", to: "post-interview", verb: "write-artifact" },
|
|
226
240
|
{ from: "post-interview", to: "revision", verb: "write-artifact" },
|
|
227
241
|
{ from: "post-interview", to: "adr", verb: "write-artifact" },
|
|
228
242
|
{ from: "revision", to: "adr", verb: "write-artifact" },
|
|
@@ -230,6 +244,7 @@ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest>
|
|
|
230
244
|
{ from: "planner", to: "handoff", verb: "handoff" },
|
|
231
245
|
{ from: "architect", to: "handoff", verb: "handoff" },
|
|
232
246
|
{ from: "critic", to: "handoff", verb: "handoff" },
|
|
247
|
+
{ from: "disposition", to: "handoff", verb: "handoff" },
|
|
233
248
|
{ from: "revision", to: "handoff", verb: "handoff" },
|
|
234
249
|
{ from: "adr", to: "handoff", verb: "handoff" },
|
|
235
250
|
{ from: "post-interview", to: "handoff", verb: "handoff" },
|
|
@@ -244,7 +259,7 @@ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest>
|
|
|
244
259
|
{
|
|
245
260
|
name: "stage",
|
|
246
261
|
type: "enum",
|
|
247
|
-
enumValues: ["planner", "architect", "critic", "revision", "post-interview", "adr", "final"],
|
|
262
|
+
enumValues: ["planner", "architect", "critic", "disposition", "revision", "post-interview", "adr", "final"],
|
|
248
263
|
appliesToVerbs: ["write-artifact"],
|
|
249
264
|
},
|
|
250
265
|
{ name: "stage_n", type: "number", appliesToVerbs: ["write-artifact"] },
|