@gajae-code/coding-agent 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +1 -1
  3. package/dist/types/cli/args.d.ts +2 -0
  4. package/dist/types/commands/launch.d.ts +6 -0
  5. package/dist/types/config/model-profile-activation.d.ts +30 -0
  6. package/dist/types/config/model-profiles.d.ts +19 -0
  7. package/dist/types/config/model-registry.d.ts +25 -10
  8. package/dist/types/config/model-resolver.d.ts +1 -1
  9. package/dist/types/config/models-config-schema.d.ts +84 -0
  10. package/dist/types/config/settings-schema.d.ts +15 -0
  11. package/dist/types/edit/diff.d.ts +16 -0
  12. package/dist/types/edit/modes/replace.d.ts +7 -0
  13. package/dist/types/extensibility/gjc-plugins/activation.d.ts +14 -0
  14. package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
  15. package/dist/types/extensibility/gjc-plugins/injection.d.ts +31 -0
  16. package/dist/types/extensibility/gjc-plugins/loader.d.ts +3 -0
  17. package/dist/types/extensibility/gjc-plugins/paths.d.ts +8 -0
  18. package/dist/types/extensibility/gjc-plugins/schema.d.ts +3 -0
  19. package/dist/types/extensibility/gjc-plugins/state.d.ts +9 -0
  20. package/dist/types/extensibility/gjc-plugins/tools.d.ts +8 -0
  21. package/dist/types/extensibility/gjc-plugins/types.d.ts +64 -0
  22. package/dist/types/extensibility/gjc-plugins/validation.d.ts +4 -0
  23. package/dist/types/extensibility/skills.d.ts +9 -1
  24. package/dist/types/gjc-runtime/state-runtime.d.ts +22 -0
  25. package/dist/types/harness-control-plane/storage.d.ts +7 -0
  26. package/dist/types/lsp/client.d.ts +1 -0
  27. package/dist/types/main.d.ts +10 -1
  28. package/dist/types/modes/bridge/bridge-mode.d.ts +2 -0
  29. package/dist/types/modes/components/custom-provider-wizard.d.ts +10 -0
  30. package/dist/types/modes/components/model-selector.d.ts +6 -1
  31. package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
  32. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  33. package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -2
  34. package/dist/types/modes/rpc/rpc-client.d.ts +9 -1
  35. package/dist/types/modes/rpc/rpc-types.d.ts +179 -2
  36. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +57 -0
  37. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +16 -1
  38. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +47 -0
  39. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +7 -0
  40. package/dist/types/modes/shared/agent-wire/handshake.d.ts +11 -1
  41. package/dist/types/modes/shared/agent-wire/protocol.d.ts +3 -1
  42. package/dist/types/modes/shared/agent-wire/responses.d.ts +1 -1
  43. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +27 -0
  44. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +68 -0
  45. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +161 -0
  46. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +61 -0
  47. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +114 -0
  48. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +39 -0
  49. package/dist/types/modes/theme/theme.d.ts +2 -1
  50. package/dist/types/modes/types.d.ts +1 -0
  51. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  52. package/dist/types/sdk.d.ts +8 -1
  53. package/dist/types/session/agent-session.d.ts +10 -0
  54. package/dist/types/session/blob-store.d.ts +17 -0
  55. package/dist/types/session/messages.d.ts +3 -0
  56. package/dist/types/session/session-storage.d.ts +6 -0
  57. package/dist/types/skill-state/active-state.d.ts +13 -0
  58. package/dist/types/task/executor.d.ts +1 -0
  59. package/dist/types/thinking.d.ts +3 -2
  60. package/dist/types/tools/hindsight-recall.d.ts +0 -2
  61. package/dist/types/tools/hindsight-reflect.d.ts +0 -2
  62. package/dist/types/tools/hindsight-retain.d.ts +0 -2
  63. package/dist/types/tools/index.d.ts +7 -4
  64. package/package.json +9 -7
  65. package/src/cli/args.ts +10 -0
  66. package/src/cli.ts +14 -0
  67. package/src/commands/harness.ts +192 -7
  68. package/src/commands/launch.ts +8 -0
  69. package/src/commands/ultragoal.ts +1 -21
  70. package/src/config/model-equivalence.ts +1 -1
  71. package/src/config/model-profile-activation.ts +157 -0
  72. package/src/config/model-profiles.ts +155 -0
  73. package/src/config/model-registry.ts +51 -5
  74. package/src/config/model-resolver.ts +3 -2
  75. package/src/config/models-config-schema.ts +42 -1
  76. package/src/config/settings-schema.ts +14 -1
  77. package/src/defaults/gjc/skills/ultragoal/SKILL.md +11 -1
  78. package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +61 -0
  79. package/src/defaults/gjc-defaults.ts +7 -0
  80. package/src/discovery/claude-plugins.ts +25 -5
  81. package/src/edit/diff.ts +64 -1
  82. package/src/edit/modes/replace.ts +60 -2
  83. package/src/extensibility/gjc-plugins/activation.ts +87 -0
  84. package/src/extensibility/gjc-plugins/index.ts +9 -0
  85. package/src/extensibility/gjc-plugins/injection.ts +114 -0
  86. package/src/extensibility/gjc-plugins/loader.ts +131 -0
  87. package/src/extensibility/gjc-plugins/paths.ts +66 -0
  88. package/src/extensibility/gjc-plugins/schema.ts +79 -0
  89. package/src/extensibility/gjc-plugins/state.ts +29 -0
  90. package/src/extensibility/gjc-plugins/tools.ts +47 -0
  91. package/src/extensibility/gjc-plugins/types.ts +97 -0
  92. package/src/extensibility/gjc-plugins/validation.ts +76 -0
  93. package/src/extensibility/skills.ts +39 -7
  94. package/src/gjc-runtime/state-runtime.ts +93 -2
  95. package/src/gjc-runtime/state-writer.ts +17 -1
  96. package/src/gjc-runtime/ultragoal-runtime.ts +62 -2
  97. package/src/gjc-runtime/workflow-manifest.generated.json +5 -0
  98. package/src/gjc-runtime/workflow-manifest.ts +2 -2
  99. package/src/harness-control-plane/storage.ts +144 -2
  100. package/src/hashline/hash.ts +23 -0
  101. package/src/hooks/skill-state.ts +2 -0
  102. package/src/internal-urls/docs-index.generated.ts +8 -11
  103. package/src/lsp/client.ts +7 -0
  104. package/src/main.ts +67 -1
  105. package/src/modes/acp/acp-agent.ts +25 -2
  106. package/src/modes/bridge/bridge-mode.ts +124 -2
  107. package/src/modes/components/custom-provider-wizard.ts +318 -0
  108. package/src/modes/components/model-selector.ts +108 -18
  109. package/src/modes/components/provider-onboarding-selector.ts +6 -1
  110. package/src/modes/controllers/input-controller.ts +14 -2
  111. package/src/modes/controllers/selector-controller.ts +57 -1
  112. package/src/modes/prompt-action-autocomplete.ts +49 -10
  113. package/src/modes/rpc/rpc-client.ts +57 -3
  114. package/src/modes/rpc/rpc-mode.ts +67 -0
  115. package/src/modes/rpc/rpc-types.ts +224 -2
  116. package/src/modes/shared/agent-wire/approval-gate.ts +151 -0
  117. package/src/modes/shared/agent-wire/command-dispatch.ts +97 -4
  118. package/src/modes/shared/agent-wire/command-validation.ts +25 -1
  119. package/src/modes/shared/agent-wire/deep-interview-gate.ts +222 -0
  120. package/src/modes/shared/agent-wire/event-envelope.ts +13 -0
  121. package/src/modes/shared/agent-wire/handshake.ts +43 -3
  122. package/src/modes/shared/agent-wire/protocol.ts +7 -0
  123. package/src/modes/shared/agent-wire/responses.ts +2 -2
  124. package/src/modes/shared/agent-wire/scopes.ts +2 -0
  125. package/src/modes/shared/agent-wire/unattended-action-policy.ts +341 -0
  126. package/src/modes/shared/agent-wire/unattended-audit.ts +175 -0
  127. package/src/modes/shared/agent-wire/unattended-run-controller.ts +406 -0
  128. package/src/modes/shared/agent-wire/unattended-session.ts +180 -0
  129. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +324 -0
  130. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +331 -0
  131. package/src/modes/theme/theme.ts +6 -0
  132. package/src/modes/types.ts +1 -0
  133. package/src/prompts/memories/consolidation.md +1 -1
  134. package/src/prompts/memories/read-path.md +6 -7
  135. package/src/prompts/memories/unavailable.md +2 -2
  136. package/src/prompts/tools/bash.md +1 -1
  137. package/src/prompts/tools/irc.md +1 -1
  138. package/src/prompts/tools/read.md +2 -2
  139. package/src/prompts/tools/recall.md +1 -0
  140. package/src/prompts/tools/reflect.md +1 -0
  141. package/src/prompts/tools/retain.md +1 -0
  142. package/src/runtime-mcp/client.ts +7 -4
  143. package/src/runtime-mcp/manager.ts +45 -13
  144. package/src/runtime-mcp/transports/http.ts +40 -14
  145. package/src/runtime-mcp/transports/stdio.ts +11 -10
  146. package/src/sdk.ts +48 -1
  147. package/src/session/agent-session.ts +211 -2
  148. package/src/session/blob-store.ts +84 -0
  149. package/src/session/messages.ts +3 -0
  150. package/src/session/session-manager.ts +390 -33
  151. package/src/session/session-storage.ts +26 -0
  152. package/src/setup/provider-onboarding.ts +2 -2
  153. package/src/skill-state/active-state.ts +89 -1
  154. package/src/slash-commands/builtin-registry.ts +1 -1
  155. package/src/task/discovery.ts +7 -1
  156. package/src/task/executor.ts +18 -2
  157. package/src/task/index.ts +2 -0
  158. package/src/thinking.ts +8 -2
  159. package/src/tools/ask.ts +39 -9
  160. package/src/tools/hindsight-recall.ts +0 -2
  161. package/src/tools/hindsight-reflect.ts +0 -2
  162. package/src/tools/hindsight-retain.ts +0 -2
  163. package/src/tools/index.ts +7 -18
  164. package/src/tools/read.ts +3 -3
  165. package/src/tools/skill.ts +15 -3
  166. package/src/utils/edit-mode.ts +1 -1
@@ -0,0 +1,155 @@
1
+ import type { GjcModelAssignmentTargetId } from "./model-registry";
2
+ import type { ModelsConfig } from "./models-config-schema";
3
+
4
+ export type ModelProfileRole = GjcModelAssignmentTargetId;
5
+
6
+ export interface ModelProfileDefinition {
7
+ name: string;
8
+ requiredProviders: string[];
9
+ modelMapping: Partial<Record<ModelProfileRole, string>>;
10
+ source: "builtin" | "user";
11
+ }
12
+
13
+ export interface ResolvedProfileBinding {
14
+ defaultSelector?: string;
15
+ agentModelOverrides: Partial<Record<Exclude<ModelProfileRole, "default">, string>>;
16
+ }
17
+
18
+ function parseModelSelectorProvider(selector: string): string | undefined {
19
+ const slashIdx = selector.indexOf("/");
20
+ if (slashIdx <= 0) return undefined;
21
+ return selector.slice(0, slashIdx);
22
+ }
23
+
24
+ export function deriveModelProfileMappedProviders(definition: Pick<ModelProfileDefinition, "modelMapping">): string[] {
25
+ const providers = new Set<string>();
26
+ for (const selector of Object.values(definition.modelMapping)) {
27
+ if (!selector) continue;
28
+ const provider = parseModelSelectorProvider(selector);
29
+ if (provider) providers.add(provider);
30
+ }
31
+ return [...providers].sort((a, b) => a.localeCompare(b));
32
+ }
33
+
34
+ export function aggregateModelProfileRequiredProviders(
35
+ requiredProviders: readonly string[],
36
+ definition: Pick<ModelProfileDefinition, "modelMapping">,
37
+ ): string[] {
38
+ const providers = new Set(requiredProviders);
39
+ for (const provider of deriveModelProfileMappedProviders(definition)) {
40
+ providers.add(provider);
41
+ }
42
+ return [...providers];
43
+ }
44
+
45
+ const profile = (
46
+ name: string,
47
+ requiredProviders: string[],
48
+ modelMapping: Record<ModelProfileRole, string>,
49
+ ): ModelProfileDefinition => ({
50
+ name,
51
+ requiredProviders: aggregateModelProfileRequiredProviders(requiredProviders, { modelMapping }),
52
+ modelMapping,
53
+ source: "builtin",
54
+ });
55
+
56
+ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
57
+ profile("opencode-go-eco", ["opencode-go"], {
58
+ default: "opencode-go/deepseek-v4-flash",
59
+ executor: "opencode-go/qwen3.5-plus",
60
+ architect: "opencode-go/glm-5",
61
+ planner: "opencode-go/minimax-m2.5",
62
+ critic: "opencode-go/kimi-k2.5",
63
+ }),
64
+ profile("opencode-go-standard", ["opencode-go"], {
65
+ default: "opencode-go/kimi-k2.6",
66
+ executor: "opencode-go/qwen3.6-plus",
67
+ architect: "opencode-go/glm-5.1",
68
+ planner: "opencode-go/minimax-m2.7",
69
+ critic: "opencode-go/deepseek-v4-pro",
70
+ }),
71
+ profile("opencode-go-pro", ["opencode-go"], {
72
+ default: "opencode-go/qwen3.7-max",
73
+ executor: "opencode-go/kimi-k2.6",
74
+ architect: "opencode-go/deepseek-v4-pro:high",
75
+ planner: "opencode-go/glm-5.1:high",
76
+ critic: "opencode-go/minimax-m2.7:high",
77
+ }),
78
+ profile("codex-eco", ["openai-codex"], {
79
+ default: "openai-codex/gpt-5.4-mini",
80
+ executor: "openai-codex/gpt-5.4-nano",
81
+ architect: "openai-codex/gpt-5.4-mini",
82
+ planner: "openai-codex/gpt-5.4-mini",
83
+ critic: "openai-codex/gpt-5.4-mini",
84
+ }),
85
+ profile("codex-standard", ["openai-codex"], {
86
+ default: "openai-codex/gpt-5.4:medium",
87
+ executor: "openai-codex/gpt-5.4:low",
88
+ architect: "openai-codex/gpt-5.4:xhigh",
89
+ planner: "openai-codex/gpt-5.4:medium",
90
+ critic: "openai-codex/gpt-5.4:high",
91
+ }),
92
+ profile("codex-pro", ["openai-codex"], {
93
+ default: "openai-codex/gpt-5.5",
94
+ executor: "openai-codex/gpt-5.2-codex",
95
+ architect: "openai-codex/gpt-5.1-codex-max:high",
96
+ planner: "openai-codex/gpt-5.5:high",
97
+ critic: "openai-codex/gpt-5.3-codex-spark:high",
98
+ }),
99
+ profile("opencode-go-codex-eco", ["opencode-go", "openai-codex"], {
100
+ default: "opencode-go/deepseek-v4-flash",
101
+ executor: "opencode-go/qwen3.5-plus",
102
+ architect: "openai-codex/gpt-5.4-mini",
103
+ planner: "openai-codex/gpt-5.4-mini",
104
+ critic: "openai-codex/gpt-5.4-mini",
105
+ }),
106
+ profile("opencode-go-codex-standard", ["opencode-go", "openai-codex"], {
107
+ default: "opencode-go/kimi-k2.6",
108
+ executor: "opencode-go/qwen3.6-plus",
109
+ architect: "openai-codex/gpt-5.4",
110
+ planner: "openai-codex/gpt-5.4",
111
+ critic: "openai-codex/gpt-5.4",
112
+ }),
113
+ profile("opencode-go-codex-pro", ["opencode-go", "openai-codex"], {
114
+ default: "opencode-go/qwen3.7-max",
115
+ executor: "opencode-go/kimi-k2.6",
116
+ architect: "openai-codex/gpt-5.1-codex-max:high",
117
+ planner: "openai-codex/gpt-5.5:high",
118
+ critic: "openai-codex/gpt-5.3-codex-spark:high",
119
+ }),
120
+ ];
121
+
122
+ export function mergeModelProfiles(userProfiles?: ModelsConfig["profiles"]): Map<string, ModelProfileDefinition> {
123
+ const profiles = new Map<string, ModelProfileDefinition>();
124
+ for (const definition of BUILTIN_MODEL_PROFILES) {
125
+ profiles.set(definition.name, {
126
+ ...definition,
127
+ requiredProviders: [...definition.requiredProviders],
128
+ modelMapping: { ...definition.modelMapping },
129
+ });
130
+ }
131
+ for (const [name, definition] of Object.entries(userProfiles ?? {})) {
132
+ const modelMapping = { ...definition.model_mapping };
133
+ profiles.set(name, {
134
+ name,
135
+ requiredProviders: aggregateModelProfileRequiredProviders(definition.required_providers, { modelMapping }),
136
+ modelMapping,
137
+ source: "user",
138
+ });
139
+ }
140
+ return profiles;
141
+ }
142
+
143
+ export function resolveProfileBindings(definition: ModelProfileDefinition): ResolvedProfileBinding {
144
+ const { default: defaultSelector, executor, architect, planner, critic } = definition.modelMapping;
145
+ const agentModelOverrides: ResolvedProfileBinding["agentModelOverrides"] = {};
146
+ if (executor !== undefined) agentModelOverrides.executor = executor;
147
+ if (architect !== undefined) agentModelOverrides.architect = architect;
148
+ if (planner !== undefined) agentModelOverrides.planner = planner;
149
+ if (critic !== undefined) agentModelOverrides.critic = critic;
150
+ return { defaultSelector, agentModelOverrides };
151
+ }
152
+
153
+ export function formatAvailableProfileNames(profiles: ReadonlyMap<string, ModelProfileDefinition>): string {
154
+ return [...profiles.keys()].sort((a, b) => a.localeCompare(b)).join(", ");
155
+ }
@@ -2,6 +2,7 @@ import * as path from "node:path";
2
2
  import {
3
3
  type Api,
4
4
  type AssistantMessageEventStream,
5
+ type CacheRetention,
5
6
  type Context,
6
7
  createModelManager,
7
8
  enrichModelThinking,
@@ -43,6 +44,7 @@ import {
43
44
  formatCanonicalVariantSelector,
44
45
  type ModelEquivalenceConfig,
45
46
  } from "./model-equivalence";
47
+ import { type ModelProfileDefinition, mergeModelProfiles } from "./model-profiles";
46
48
  import {
47
49
  type ModelOverride,
48
50
  type ModelsConfig,
@@ -239,6 +241,7 @@ interface ProviderValidationConfig {
239
241
  compat?: Model<Api>["compat"];
240
242
  requestTransform?: ModelRequestTransform;
241
243
  disableStrictTools?: boolean;
244
+ cacheRetention?: CacheRetention;
242
245
  modelOverrides?: Record<string, unknown>;
243
246
  models: ProviderValidationModel[];
244
247
  }
@@ -266,11 +269,12 @@ function validateProviderConfiguration(
266
269
  !config.apiKeyEnv &&
267
270
  !config.disableStrictTools &&
268
271
  !config.requestTransform &&
272
+ !config.cacheRetention &&
269
273
  !hasModelOverrides &&
270
274
  !config.discovery
271
275
  ) {
272
276
  throw new Error(
273
- `Provider ${providerName}: must specify "baseUrl", "headers", "apiKey", "compat", "requestTransform", "disableStrictTools", "modelOverrides", "discovery", or "models"`,
277
+ `Provider ${providerName}: must specify "baseUrl", "headers", "apiKey", "compat", "requestTransform", "cacheRetention", "disableStrictTools", "modelOverrides", "discovery", or "models"`,
274
278
  );
275
279
  }
276
280
  }
@@ -383,6 +387,7 @@ export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsCon
383
387
  compat: providerConfig.compat,
384
388
  requestTransform: providerConfig.requestTransform,
385
389
  disableStrictTools: providerConfig.disableStrictTools,
390
+ cacheRetention: providerConfig.cacheRetention,
386
391
  modelOverrides: providerConfig.modelOverrides,
387
392
  models: (providerConfig.models ?? []) as ProviderValidationModel[],
388
393
  },
@@ -401,6 +406,7 @@ interface ProviderOverride {
401
406
  compat?: Model<Api>["compat"];
402
407
  transport?: Model<Api>["transport"];
403
408
  requestTransform?: ModelRequestTransform;
409
+ cacheRetention?: CacheRetention;
404
410
  }
405
411
 
406
412
  const PROVIDER_BASE_URL_ENV_ALIASES: Record<string, readonly string[]> = {
@@ -448,7 +454,10 @@ function resolveProviderBaseUrlFromEnv(provider: string): string | undefined {
448
454
  export function mergeDiscoveredModel<TApi extends Api>(
449
455
  model: Model<TApi>,
450
456
  existing: Model<Api> | undefined,
451
- providerOverride?: Pick<ProviderOverride, "baseUrl" | "headers" | "transport" | "requestTransform">,
457
+ providerOverride?: Pick<
458
+ ProviderOverride,
459
+ "baseUrl" | "headers" | "transport" | "requestTransform" | "cacheRetention"
460
+ >,
452
461
  ): Model<TApi> {
453
462
  if (existing) {
454
463
  return {
@@ -459,6 +468,7 @@ export function mergeDiscoveredModel<TApi extends Api>(
459
468
  mergeRequestTransform(existing.requestTransform, model.requestTransform),
460
469
  providerOverride?.requestTransform,
461
470
  ),
471
+ cacheRetention: model.cacheRetention ?? existing.cacheRetention ?? providerOverride?.cacheRetention,
462
472
  };
463
473
  }
464
474
  if (providerOverride) {
@@ -480,6 +490,7 @@ interface DiscoveryProviderConfig {
480
490
  headers?: Record<string, string>;
481
491
  compat?: Model<Api>["compat"];
482
492
  requestTransform?: ModelRequestTransform;
493
+ cacheRetention?: CacheRetention;
483
494
  discovery: ProviderDiscovery;
484
495
  optional?: boolean;
485
496
  }
@@ -511,6 +522,7 @@ interface CustomModelsResult {
511
522
  configuredProviders?: Set<string>;
512
523
  equivalence?: ModelEquivalenceConfig;
513
524
  modelBindings?: NonNullable<ModelsConfig["modelBindings"]>;
525
+ profiles?: ModelsConfig["profiles"];
514
526
  error?: ConfigError;
515
527
  found: boolean;
516
528
  }
@@ -676,6 +688,7 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
676
688
  if (override.reasoning !== undefined) result.reasoning = override.reasoning;
677
689
  if (override.thinking !== undefined) result.thinking = override.thinking as ThinkingConfig;
678
690
  if (override.input !== undefined) result.input = override.input as ("text" | "image")[];
691
+ if (override.cacheRetention !== undefined) result.cacheRetention = override.cacheRetention;
679
692
  if (override.contextWindow !== undefined) result.contextWindow = override.contextWindow;
680
693
  if (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;
681
694
  if (override.contextPromotionTarget !== undefined) result.contextPromotionTarget = override.contextPromotionTarget;
@@ -714,6 +727,7 @@ interface CustomModelDefinitionLike {
714
727
  premiumMultiplier?: number;
715
728
  wireModelId?: string;
716
729
  requestTransform?: ModelRequestTransform;
730
+ cacheRetention?: CacheRetention;
717
731
  }
718
732
 
719
733
  interface CustomModelBuildOptions {
@@ -738,6 +752,7 @@ type CustomModelOverlay = {
738
752
  premiumMultiplier?: number;
739
753
  wireModelId?: string;
740
754
  requestTransform?: ModelRequestTransform;
755
+ cacheRetention?: CacheRetention;
741
756
  isOAuth?: boolean;
742
757
  };
743
758
 
@@ -789,6 +804,7 @@ function buildCustomModelOverlay(
789
804
  providerCompat: Model<Api>["compat"] | undefined,
790
805
  providerRequestTransform: ModelRequestTransform | undefined,
791
806
  providerAuth: ProviderAuthMode | undefined,
807
+ providerCacheRetention: CacheRetention | undefined,
792
808
  modelDef: CustomModelDefinitionLike,
793
809
  ): CustomModelOverlay | undefined {
794
810
  const api = modelDef.api ?? providerApi;
@@ -811,6 +827,7 @@ function buildCustomModelOverlay(
811
827
  wireModelId: modelDef.wireModelId,
812
828
  contextPromotionTarget: modelDef.contextPromotionTarget,
813
829
  premiumMultiplier: modelDef.premiumMultiplier,
830
+ cacheRetention: modelDef.cacheRetention ?? providerCacheRetention,
814
831
  isOAuth: resolveCustomModelIsOAuth(api, providerAuth),
815
832
  };
816
833
  }
@@ -911,6 +928,7 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
911
928
  contextPromotionTarget: resolvedModel.contextPromotionTarget,
912
929
  wireModelId: resolvedModel.wireModelId,
913
930
  requestTransform: resolvedModel.requestTransform,
931
+ cacheRetention: resolvedModel.cacheRetention ?? reference?.cacheRetention,
914
932
  premiumMultiplier: resolvedModel.premiumMultiplier,
915
933
  isOAuth: resolvedModel.isOAuth,
916
934
  } as Model<Api>);
@@ -954,6 +972,7 @@ export class ModelRegistry {
954
972
  #modelOverrides: Map<string, Map<string, ModelOverride>> = new Map();
955
973
  #equivalenceConfig: ModelEquivalenceConfig | undefined;
956
974
  #configuredModelBindings: NonNullable<ModelsConfig["modelBindings"]> | undefined;
975
+ #modelProfiles: Map<string, ModelProfileDefinition> = mergeModelProfiles();
957
976
  #modelBindingsTargetSettings: Settings | undefined;
958
977
  #appliedModelBindingRoles = new Set<string>();
959
978
  #appliedAgentModelBindingOverrides = new Set<string>();
@@ -1093,6 +1112,7 @@ export class ModelRegistry {
1093
1112
  configuredProviders = new Set(),
1094
1113
  equivalence,
1095
1114
  modelBindings,
1115
+ profiles,
1096
1116
  error: configError,
1097
1117
  } = this.#loadCustomModels();
1098
1118
  this.#configError = configError;
@@ -1103,6 +1123,7 @@ export class ModelRegistry {
1103
1123
  this.#modelOverrides = modelOverrides;
1104
1124
  this.#equivalenceConfig = equivalence;
1105
1125
  this.#configuredModelBindings = modelBindings;
1126
+ this.#modelProfiles = mergeModelProfiles(profiles);
1106
1127
 
1107
1128
  this.#addImplicitDiscoverableProviders(configuredProviders);
1108
1129
  const builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
@@ -1134,6 +1155,7 @@ export class ModelRegistry {
1134
1155
  return {
1135
1156
  ...withTransportOverride,
1136
1157
  compat: mergeCompat(m.compat, providerOverride.compat),
1158
+ cacheRetention: m.cacheRetention ?? providerOverride.cacheRetention,
1137
1159
  };
1138
1160
  });
1139
1161
  });
@@ -1290,6 +1312,7 @@ export class ModelRegistry {
1290
1312
  requestTransform: providerConfig.requestTransform
1291
1313
  ? mergeRequestTransform(undefined, providerConfig.requestTransform)
1292
1314
  : undefined,
1315
+ cacheRetention: normalized.cacheRetention ?? providerConfig.cacheRetention,
1293
1316
  };
1294
1317
  });
1295
1318
  }
@@ -1343,6 +1366,7 @@ export class ModelRegistry {
1343
1366
  discoverableProviders: [],
1344
1367
  configuredProviders: new Set(),
1345
1368
  error,
1369
+ profiles: undefined,
1346
1370
  found: true,
1347
1371
  };
1348
1372
  } else if (status === "not-found") {
@@ -1353,6 +1377,7 @@ export class ModelRegistry {
1353
1377
  keylessProviders: new Set(),
1354
1378
  discoverableProviders: [],
1355
1379
  configuredProviders: new Set(),
1380
+ profiles: undefined,
1356
1381
  found: false,
1357
1382
  };
1358
1383
  }
@@ -1376,7 +1401,8 @@ export class ModelRegistry {
1376
1401
  providerConfig.compat ||
1377
1402
  providerConfig.disableStrictTools ||
1378
1403
  providerConfig.requestTransform ||
1379
- providerConfig.transport
1404
+ providerConfig.transport ||
1405
+ providerConfig.cacheRetention
1380
1406
  ) {
1381
1407
  const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
1382
1408
  overrides.set(providerName, {
@@ -1387,6 +1413,7 @@ export class ModelRegistry {
1387
1413
  compat: mergeCompat(providerConfig.compat, disableStrictCompat),
1388
1414
  transport: providerConfig.transport,
1389
1415
  requestTransform: providerConfig.requestTransform,
1416
+ cacheRetention: providerConfig.cacheRetention,
1390
1417
  });
1391
1418
  }
1392
1419
 
@@ -1403,6 +1430,7 @@ export class ModelRegistry {
1403
1430
  headers: providerConfig.headers,
1404
1431
  compat: providerConfig.compat,
1405
1432
  requestTransform: providerConfig.requestTransform,
1433
+ cacheRetention: providerConfig.cacheRetention,
1406
1434
  discovery: providerConfig.discovery,
1407
1435
  optional: false,
1408
1436
  });
@@ -1441,10 +1469,22 @@ export class ModelRegistry {
1441
1469
  configuredProviders,
1442
1470
  equivalence: value.equivalence,
1443
1471
  modelBindings: value.modelBindings,
1472
+ profiles: value.profiles,
1444
1473
  found: true,
1445
1474
  };
1446
1475
  }
1447
1476
 
1477
+ getModelProfiles(): Map<string, ModelProfileDefinition> {
1478
+ return new Map(this.#modelProfiles);
1479
+ }
1480
+
1481
+ getModelProfile(name: string): ModelProfileDefinition | undefined {
1482
+ return this.#modelProfiles.get(name);
1483
+ }
1484
+
1485
+ getAvailableModelProfileNames(): string[] {
1486
+ return [...this.#modelProfiles.keys()].sort((a, b) => a.localeCompare(b));
1487
+ }
1448
1488
  applyConfiguredModelBindings(targetSettings: Settings): void {
1449
1489
  this.#modelBindingsTargetSettings = targetSettings;
1450
1490
  this.#applyConfiguredModelBindingsToTarget();
@@ -2082,13 +2122,16 @@ export class ModelRegistry {
2082
2122
  compat: override.compat ? mergeCompat(baseOverride?.compat, override.compat) : baseOverride?.compat,
2083
2123
  transport: override.transport ?? baseOverride?.transport,
2084
2124
  requestTransform: mergeRequestTransform(baseOverride?.requestTransform, override.requestTransform),
2125
+ cacheRetention: override.cacheRetention ?? baseOverride?.cacheRetention,
2085
2126
  };
2086
2127
  }
2087
- #applyProviderTransportOverride<T extends { baseUrl?: string; headers?: Record<string, string> }>(
2128
+ #applyProviderTransportOverride<
2129
+ T extends { baseUrl?: string; headers?: Record<string, string>; cacheRetention?: CacheRetention },
2130
+ >(
2088
2131
  entry: T,
2089
2132
  override: Pick<
2090
2133
  ProviderOverride,
2091
- "baseUrl" | "headers" | "authHeader" | "apiKey" | "transport" | "requestTransform"
2134
+ "baseUrl" | "headers" | "authHeader" | "apiKey" | "transport" | "requestTransform" | "cacheRetention"
2092
2135
  >,
2093
2136
  ): T {
2094
2137
  const headers = mergeAuthHeader(
@@ -2107,6 +2150,7 @@ export class ModelRegistry {
2107
2150
  (entry as { requestTransform?: ModelRequestTransform }).requestTransform,
2108
2151
  override.requestTransform,
2109
2152
  ),
2153
+ cacheRetention: entry.cacheRetention ?? override.cacheRetention,
2110
2154
  };
2111
2155
  }
2112
2156
  #applyRuntimeProviderOverrides(models: Model<Api>[]): Model<Api>[] {
@@ -2195,6 +2239,7 @@ export class ModelRegistry {
2195
2239
  providerCompat,
2196
2240
  providerConfig.requestTransform,
2197
2241
  (providerConfig.auth as ProviderAuthMode | undefined) ?? undefined,
2242
+ providerConfig.cacheRetention,
2198
2243
  modelDef as CustomModelDefinitionLike,
2199
2244
  );
2200
2245
  if (!model) continue;
@@ -2557,6 +2602,7 @@ export class ModelRegistry {
2557
2602
  config.compat,
2558
2603
  config.requestTransform,
2559
2604
  undefined,
2605
+ undefined,
2560
2606
  modelDef as CustomModelDefinitionLike,
2561
2607
  );
2562
2608
  if (!overlay) {
@@ -764,6 +764,7 @@ export async function resolveModelOverrideWithAuthFallback(
764
764
  parentActiveModelPattern: string | undefined,
765
765
  modelRegistry: ModelLookupRegistry & Pick<ModelRegistry, "getApiKey">,
766
766
  settings?: Settings,
767
+ sessionId?: string,
767
768
  ): Promise<{
768
769
  model?: Model<Api>;
769
770
  thinkingLevel?: ThinkingLevel;
@@ -775,7 +776,7 @@ export async function resolveModelOverrideWithAuthFallback(
775
776
  return { ...primary, authFallbackUsed: false };
776
777
  }
777
778
 
778
- const primaryKey = await modelRegistry.getApiKey(primary.model);
779
+ const primaryKey = await modelRegistry.getApiKey(primary.model, sessionId);
779
780
  if (primaryKey === kNoAuth || isAuthenticated(primaryKey)) {
780
781
  return { ...primary, authFallbackUsed: false };
781
782
  }
@@ -787,7 +788,7 @@ export async function resolveModelOverrideWithAuthFallback(
787
788
  if (modelsAreEqual(fallback.model, primary.model)) {
788
789
  return { ...primary, authFallbackUsed: false };
789
790
  }
790
- const fallbackKey = await modelRegistry.getApiKey(fallback.model);
791
+ const fallbackKey = await modelRegistry.getApiKey(fallback.model, sessionId);
791
792
  if (!isAuthenticated(fallbackKey)) {
792
793
  return { ...primary, authFallbackUsed: false };
793
794
  }
@@ -16,6 +16,7 @@ const ReasoningEffortMapSchema = z.object({
16
16
  medium: z.string().optional(),
17
17
  high: z.string().optional(),
18
18
  xhigh: z.string().optional(),
19
+ max: z.string().optional(),
19
20
  });
20
21
 
21
22
  export const OpenAICompatSchema = z.object({
@@ -45,7 +46,8 @@ export const OpenAICompatSchema = z.object({
45
46
  toolStrictMode: z.enum(["all_strict", "none"]).optional(),
46
47
  });
47
48
 
48
- const EffortSchema = z.enum(["minimal", "low", "medium", "high", "xhigh"]);
49
+ const EffortSchema = z.enum(["minimal", "low", "medium", "high", "xhigh", "max"]);
50
+ const CacheRetentionSchema = z.enum(["none", "short", "long"]);
49
51
 
50
52
  const ThinkingControlModeSchema = z.enum([
51
53
  "effort",
@@ -76,6 +78,39 @@ const ModelBindingsSchema = z.object({
76
78
  modelRoles: z.record(z.string(), z.string().min(1)).optional(),
77
79
  agentModelOverrides: z.record(z.string(), z.string().min(1)).optional(),
78
80
  });
81
+ export const ProfileRoleSchema = z.enum(["default", "executor", "architect", "planner", "critic"]);
82
+
83
+ function isValidProfileModelSelector(value: string): boolean {
84
+ if (value.includes(",")) return false;
85
+ const slashIdx = value.indexOf("/");
86
+ if (slashIdx <= 0 || slashIdx === value.length - 1) return false;
87
+ const provider = value.slice(0, slashIdx);
88
+ const modelId = value.slice(slashIdx + 1);
89
+ if (!provider || !modelId) return false;
90
+ const parts = modelId.split(":");
91
+ if (parts.length > 2) return false;
92
+ const [base, suffix] = parts;
93
+ if (!base) return false;
94
+ return suffix === undefined || ["minimal", "low", "medium", "high", "xhigh", "max"].includes(suffix);
95
+ }
96
+
97
+ export const ProfileModelSelectorSchema = z
98
+ .string()
99
+ .min(1)
100
+ .refine(value => isValidProfileModelSelector(value), {
101
+ message: "Expected provider/modelId with optional :effort suffix",
102
+ });
103
+
104
+ export const ProfileModelMappingSchema = z.partialRecord(ProfileRoleSchema, ProfileModelSelectorSchema);
105
+
106
+ export const ProfileDefinitionSchema = z
107
+ .object({
108
+ required_providers: z.array(z.string().min(1)).min(1),
109
+ model_mapping: ProfileModelMappingSchema,
110
+ })
111
+ .strict();
112
+
113
+ export const ProfilesSchema = z.record(z.string().min(1), ProfileDefinitionSchema);
79
114
 
80
115
  const ModelDefinitionSchema = z
81
116
  .object({
@@ -116,6 +151,7 @@ const ModelDefinitionSchema = z
116
151
  contextPromotionTarget: z.string().min(1).optional(),
117
152
  wireModelId: z.string().min(1).optional(),
118
153
  requestTransform: RequestTransformSchema.optional(),
154
+ cacheRetention: CacheRetentionSchema.optional(),
119
155
  })
120
156
  .strict();
121
157
 
@@ -141,6 +177,7 @@ export const ModelOverrideSchema = z
141
177
  contextPromotionTarget: z.string().min(1).optional(),
142
178
  wireModelId: z.string().min(1).optional(),
143
179
  requestTransform: RequestTransformSchema.optional(),
180
+ cacheRetention: CacheRetentionSchema.optional(),
144
181
  })
145
182
  .strict();
146
183
 
@@ -192,6 +229,7 @@ const ProviderConfigSchema = z
192
229
  * and `apiKey` must carry the gateway bearer.
193
230
  */
194
231
  transport: z.literal("pi-native").optional(),
232
+ cacheRetention: CacheRetentionSchema.optional(),
195
233
  })
196
234
  .strict();
197
235
 
@@ -205,7 +243,10 @@ export const ModelsConfigSchema = z
205
243
  providers: z.record(z.string(), ProviderConfigSchema).optional(),
206
244
  modelBindings: ModelBindingsSchema.optional(),
207
245
  equivalence: EquivalenceConfigSchema.optional(),
246
+ profiles: ProfilesSchema.optional(),
208
247
  })
209
248
  .strict();
210
249
 
211
250
  export type ModelsConfig = z.infer<typeof ModelsConfigSchema>;
251
+ export type ModelProfileConfig = z.infer<typeof ProfileDefinitionSchema>;
252
+ export type ModelProfilesConfig = z.infer<typeof ProfilesSchema>;
@@ -1,4 +1,4 @@
1
- import { THINKING_EFFORTS } from "@gajae-code/ai";
1
+ import { THINKING_EFFORTS } from "@gajae-code/ai/model-thinking";
2
2
  import { TASK_SIMPLE_MODES } from "../task/simple-mode";
3
3
  import { getThinkingLevelMetadata } from "../thinking";
4
4
  import { EDIT_MODES } from "../utils/edit-mode";
@@ -314,6 +314,16 @@ export const SETTINGS_SCHEMA = {
314
314
  disabledExtensions: { type: "array", default: DEFAULT_DISABLED_EXTENSIONS },
315
315
 
316
316
  modelRoles: { type: "record", default: EMPTY_STRING_RECORD },
317
+ "modelProfile.default": {
318
+ type: "string",
319
+ default: undefined,
320
+ ui: {
321
+ tab: "model",
322
+ label: "Default Model Profile",
323
+ description: "Model profile applied automatically at startup",
324
+ options: "runtime",
325
+ },
326
+ },
317
327
 
318
328
  modelTags: { type: "record", default: EMPTY_MODEL_TAGS_RECORD },
319
329
 
@@ -2765,6 +2775,8 @@ export const SETTINGS_SCHEMA = {
2765
2775
  "thinkingBudgets.high": { type: "number", default: 16384 },
2766
2776
 
2767
2777
  "thinkingBudgets.xhigh": { type: "number", default: 32768 },
2778
+
2779
+ "thinkingBudgets.max": { type: "number", default: 65536 },
2768
2780
  } as const;
2769
2781
 
2770
2782
  // ═══════════════════════════════════════════════════════════════════════════
@@ -2945,6 +2957,7 @@ export interface ThinkingBudgetsSettings {
2945
2957
  medium: number;
2946
2958
  high: number;
2947
2959
  xhigh: number;
2960
+ max: number;
2948
2961
  }
2949
2962
 
2950
2963
  export interface SttSettings {
@@ -161,12 +161,22 @@ gjc ultragoal checkpoint --goal-id <id> --status complete --evidence "<team evid
161
161
 
162
162
  Workers do not own ultragoal goal state, do not create worker ultragoal ledgers, and do not checkpoint Ultragoal. Workers must not run `gjc ultragoal checkpoint`; checkpoint authority stays with the leader after worker tasks are terminal. Team launch remains explicit; Ultragoal does not auto-launch Team and performs no hidden goal mutation.
163
163
 
164
+ ## Internal Ultragoal sub-skill fragments
165
+
166
+ The completion-gate cleanup sweep is driven by `ai-slop-cleaner`, an internal Ultragoal sub-skill bundled as a `kind: "skill-fragment"` prompt with parent skill `ultragoal` (installed at `skill-fragments/ultragoal/ai-slop-cleaner.md`). It is analogous to deep-interview's auto-research fragment: loaded on demand for one specific hook, never a user-facing skill.
167
+
168
+ - It is not slash-command discoverable, has no public skill-listing entry, and is never resolvable through `skill://`.
169
+ - It is a read-only detector+reporter over the active story's changed files only: it never edits code, writes files, mutates `.gjc/`, checkpoints, calls goal tools, or spawns workflows.
170
+ - It classifies every finding as blocking or advisory across the full taxonomy (fallback-like masking vs. grounded, duplication, dead code, needless abstraction, boundary violations, UI/design slop, missing tests).
171
+ - The leader and a leader-spawned `executor` own all fixes; the cleaner reruns until zero blocking findings remain. Advisory findings live in the gate report only.
172
+ - Recursion guard: it must not spawn nested `ralplan`/`team`/`deep-interview`/`ultragoal`; broad or architectural findings are handed back to the leader as review blockers.
173
+
164
174
  ## Mandatory completion cleanup and review gate
165
175
 
166
176
  An ultragoal story cannot be checkpointed `complete` until the active agent has run the quality gate. The gate is plan-first, contract-driven, and surface-based:
167
177
 
168
178
  1. Run targeted implementation verification for the story.
169
- 2. Run a cleanup/refactor review pass on changed files only; if there are no relevant edits, the cleaner still runs and records a passed/no-op report.
179
+ 2. Run the internal ai-slop-cleaner skill fragment as the final cleanup sweep on the story's changed files only, before verification and red-team so only clean code is reviewed. It is a read-only detector that emits an `AI SLOP CLEANUP REPORT`; if there are no relevant edits it still runs and records a passed/no-op report. Every BLOCKING cleaner finding is a completion blocker: the leader spawns an `executor` to fix blocking findings only, then reruns the cleaner until blocking findings are zero. Advisory findings are included in the gate report only and are not written to the Ultragoal ledger. Carry the report through the existing `qualityGate.iteration.evidence` field; do not add a new top-level quality-gate key.
170
180
  3. Rerun verification after the cleaner pass.
171
181
  4. Delegate an `architect` review covering all three lanes:
172
182
  - architecture-side: system boundaries, layering, data/control flow, operational risks.
@@ -0,0 +1,61 @@
1
+ # Ultragoal AI Slop Cleaner Fragment
2
+
3
+ You are the AI slop cleaner for the Ultragoal completion gate. This is an internal Ultragoal sub-skill, loaded on demand as a `kind: "skill-fragment"` prompt with parent skill `ultragoal`. It is never user-facing: not slash-command discoverable, no public skill listing entry, and never resolvable through `skill://`.
4
+
5
+ You are a **read-only detector and reporter**. You never edit code, write files, run formatters, mutate `.gjc/` state, checkpoint, call goal tools, or spawn workflows. You detect slop in the active Ultragoal story's changed files, classify each finding, and emit a report. The Ultragoal leader spawns an `executor` to fix BLOCKING findings; you do not fix anything yourself.
6
+
7
+ ## Scope
8
+
9
+ - Inspect ONLY the active Ultragoal story's changed-files list. No broad rewrites, no inspection outside that scope, no new dependencies.
10
+ - Allow only narrow supporting reads needed to understand the contracts of changed files; if you need broader context, report that need to the leader instead of expanding scope.
11
+ - If there are no relevant edits, emit a passed/no-op report (`Gate Result: PASS`, `Changed Files Reviewed` listing the files as "no relevant edits").
12
+ - Recursion guard: you are already inside an Ultragoal workflow. Do NOT spawn nested `ralplan`, `team`, `deep-interview`, or `ultragoal` workflows. Broad, ambiguous, cross-layer, or architectural findings are handed to the leader as review blockers, not resolved here.
13
+
14
+ ## Taxonomy
15
+
16
+ Classify every finding against the full taxonomy:
17
+
18
+ 1. **Fallback-like code** — classify each as **masking fallback slop** or **grounded compatibility/fail-safe fallback**.
19
+ - Masking signals (blocking): swallowed errors, silent defaults, bypassed validation/tests, untested alternate execution paths, primary-contract suppression.
20
+ - Grounded signals (advisory): scoped to an external/version/fail-safe boundary, documented rationale, preserved failure evidence, and regression tests covering both primary and fallback behavior.
21
+ 2. **Duplication** — repeated logic, copy-paste branches, redundant helpers.
22
+ 3. **Dead code** — unused code, unreachable branches, stale flags, debug leftovers.
23
+ 4. **Needless abstraction** — pass-through wrappers, speculative indirection, single-use helper layers.
24
+ 5. **Boundary violations** — hidden coupling, leaky responsibilities, wrong-layer imports or side effects.
25
+ 6. **UI/design slop** — context-sensitive signals, not absolute bans; preserve intentional brand/design-system/accessibility/product rationale. Signals: small Korean body copy (challenge 11-12px; Korean body text generally needs 14px+ unless a dense accessible system supports smaller), gratuitous shadows/depth, repetitive eyebrow+title+description scaffolding and filler/emoji badges, default blue/purple palettes (e.g. #3B82F6) without rationale, over-perfect uniform 3/4-column grids, and extreme "AI demo" gradients.
26
+ 7. **Missing tests** — behavior not locked, weak regression coverage, missing edge/failure-mode cases.
27
+
28
+ ## Blocking vs advisory
29
+
30
+ - **Blocking** if it can mask failures, violate accepted contracts, weaken boundaries, leave changed behavior untested, create maintenance traps, or make later verification unsafe.
31
+ - **Advisory** if it is nice-to-have, stylistic/contextual, or outside safe story scope.
32
+ - Advisory findings stay in the gate report only; they are NOT written to the Ultragoal ledger.
33
+
34
+ ## Report
35
+
36
+ Emit exactly this text block with these mandated labels:
37
+
38
+ ```text
39
+ AI SLOP CLEANUP REPORT
40
+ ======================
41
+
42
+ Scope: [changed files inspected]
43
+ Mode: read-only detector/report; no edits performed
44
+ Blocking Findings: [none, or numbered findings with file, category, evidence, required executor fix]
45
+ Advisory Findings: [none, or numbered findings with file, category, evidence, why advisory]
46
+ Fallback Findings: [none, or finding -> masking fallback slop / grounded compatibility/fail-safe fallback -> blocking/advisory]
47
+ UI/Design Findings: [none/N/A, or signal -> blocking/advisory -> rationale]
48
+ Missing Test Findings: [none, or gap -> blocking/advisory -> required coverage]
49
+ Recursion Guard: [confirmed no nested ralplan/team/deep-interview/ultragoal spawned; broad findings handed to leader]
50
+ Changed Files Reviewed:
51
+ - [path] - [reviewed / no relevant edits]
52
+
53
+ Gate Result: PASS | BLOCKED
54
+ Leader Action:
55
+ - PASS: continue to verification, architect review, and executor red-team QA.
56
+ - BLOCKED: spawn executor to fix BLOCKING findings only, then rerun this sweep until Blocking Findings is none.
57
+ Remaining Risks:
58
+ - [none, or advisory/deferred risks]
59
+ ```
60
+
61
+ Port the oh-my-codex taxonomy and report shape, not its editing workflow. Do not instruct yourself to execute cleanup passes — detect and report only.