@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/types/config/settings-schema.d.ts +2 -2
  3. package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
  4. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -3
  5. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +6 -0
  6. package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
  7. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  8. package/dist/types/modes/controllers/event-controller.d.ts +1 -1
  9. package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
  10. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  11. package/dist/types/modes/interactive-mode.d.ts +9 -0
  12. package/dist/types/modes/theme/theme.d.ts +3 -1
  13. package/dist/types/modes/types.d.ts +32 -0
  14. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
  15. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -2
  16. package/dist/types/session/agent-session.d.ts +2 -0
  17. package/dist/types/session/internal/managed-session-storage.d.ts +22 -3
  18. package/dist/types/task/index.d.ts +4 -0
  19. package/dist/types/task/output-manager.d.ts +1 -0
  20. package/dist/types/task/receipt.d.ts +2 -0
  21. package/dist/types/tools/index.d.ts +2 -0
  22. package/package.json +8 -8
  23. package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
  24. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  25. package/scripts/run-acp-conformance.ts +5 -0
  26. package/src/async/job-manager.ts +22 -20
  27. package/src/config/settings-schema.ts +2 -2
  28. package/src/debug/index.ts +42 -17
  29. package/src/defaults/gjc/skills/ralplan/SKILL.md +9 -7
  30. package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -2
  31. package/src/gjc-runtime/launch-worktree.ts +103 -1
  32. package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
  33. package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
  34. package/src/gjc-runtime/ralplan-runtime.ts +80 -10
  35. package/src/gjc-runtime/ultragoal-evidence.ts +6 -74
  36. package/src/gjc-runtime/ultragoal-runtime.ts +236 -3
  37. package/src/gjc-runtime/workflow-manifest.generated.json +29 -0
  38. package/src/gjc-runtime/workflow-manifest.ts +17 -2
  39. package/src/internal-urls/docs-index.generated.ts +1 -1
  40. package/src/modes/DESIGN.md +53 -0
  41. package/src/modes/components/irc-sidebar.ts +30 -1
  42. package/src/modes/components/tool-status-header.ts +0 -1
  43. package/src/modes/controllers/command-controller.ts +38 -34
  44. package/src/modes/controllers/event-controller.ts +27 -16
  45. package/src/modes/controllers/extension-ui-controller.ts +186 -34
  46. package/src/modes/controllers/selector-controller.ts +115 -22
  47. package/src/modes/interactive-mode.ts +189 -62
  48. package/src/modes/jobs-observer.ts +1 -1
  49. package/src/modes/theme/theme.ts +11 -9
  50. package/src/modes/types.ts +88 -0
  51. package/src/sdk/broker/lifecycle.ts +18 -13
  52. package/src/sdk/bus/chat-daemon-control.ts +4 -3
  53. package/src/sdk/bus/telegram-daemon-contract.ts +3 -2
  54. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  55. package/src/sdk/session.ts +1 -0
  56. package/src/session/agent-session.ts +22 -1
  57. package/src/session/internal/managed-session-scope.ts +158 -107
  58. package/src/session/internal/managed-session-storage.ts +445 -57
  59. package/src/session/session-storage.ts +29 -44
  60. package/src/task/index.ts +257 -31
  61. package/src/task/output-manager.ts +24 -14
  62. package/src/task/receipt.ts +11 -1
  63. 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
- const inline = qualityGateObject(row.replay) ?? (row.kind === "cli-replay" ? row : null);
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)"}; only validate is supported\n`,
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: ["planner", "architect", "critic", "revision", "post-interview", "adr", "final", "handoff"],
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"] },