@ggui-ai/ui-gen 0.26.0 → 0.28.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 (77) hide show
  1. package/dist/{adapter-BJwn74rx.d.ts → adapter-h4_V69LI.d.ts} +1 -1
  2. package/dist/adapters/base.d.ts +6 -1
  3. package/dist/adapters/base.js.map +1 -1
  4. package/dist/adapters/generation-dispatch.d.ts +2 -2
  5. package/dist/adapters/generation-dispatch.js +233 -17
  6. package/dist/adapters/generation-dispatch.js.map +1 -1
  7. package/dist/adapters/index.js +36 -14
  8. package/dist/adapters/index.js.map +1 -1
  9. package/dist/advanced/index.d.ts +4 -4
  10. package/dist/advanced/index.js +268 -28
  11. package/dist/advanced/index.js.map +1 -1
  12. package/dist/boilerplate.js +4 -6
  13. package/dist/boilerplate.js.map +1 -1
  14. package/dist/check/index.d.ts +1 -7
  15. package/dist/check/index.js +50 -4
  16. package/dist/check/index.js.map +1 -1
  17. package/dist/{create-ui-generator-N4gRXpcL.d.ts → create-ui-generator-BrZHF0cf.d.ts} +2 -2
  18. package/dist/{dispatch-BUoXRj_r.d.ts → dispatch-B07TI30d.d.ts} +1 -1
  19. package/dist/evaluation/axis-checks/index.d.ts +3 -3
  20. package/dist/evaluation/axis-checks/index.js +97 -1
  21. package/dist/evaluation/axis-checks/index.js.map +1 -1
  22. package/dist/evaluation/axis-checks/registry.d.ts +1 -1
  23. package/dist/evaluation/axis-checks/registry.js +97 -1
  24. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  25. package/dist/evaluation/index.d.ts +22 -12
  26. package/dist/evaluation/index.js +138 -14
  27. package/dist/evaluation/index.js.map +1 -1
  28. package/dist/evaluation/types.d.ts +1 -1
  29. package/dist/harness/check/runtime-render/index.d.ts +3 -3
  30. package/dist/harness/index.d.ts +1 -1
  31. package/dist/harness/index.js +6 -6
  32. package/dist/harness/index.js.map +1 -1
  33. package/dist/harness/prompts.js +1 -4
  34. package/dist/harness/prompts.js.map +1 -1
  35. package/dist/harness/result-types.d.ts +14 -3
  36. package/dist/harness/result-types.js.map +1 -1
  37. package/dist/harness/runtime.d.ts +1 -1
  38. package/dist/harness/runtime.js +6 -7
  39. package/dist/harness/runtime.js.map +1 -1
  40. package/dist/harness/types-public.d.ts +1 -1
  41. package/dist/harness/types-public.js +4 -6
  42. package/dist/harness/types-public.js.map +1 -1
  43. package/dist/harness/validator-trace-sink.d.ts +1 -1
  44. package/dist/index.d.ts +6 -6
  45. package/dist/index.js +268 -28
  46. package/dist/index.js.map +1 -1
  47. package/dist/patch.d.ts +1 -1
  48. package/dist/{types-public-B7wn4ce5.d.ts → types-public-h8IMnYuh.d.ts} +4 -0
  49. package/dist/{visual-evaluator-C9L-o6ap.d.ts → visual-evaluator-C2strxqD.d.ts} +1 -1
  50. package/dist/workflows.d.ts +1 -1
  51. package/package.json +13 -13
  52. package/src/adapters/base.ts +6 -1
  53. package/src/adapters/claude/message-parser.ts +5 -6
  54. package/src/adapters/claude/sdk-tool-pins.ts +44 -0
  55. package/src/adapters/claude/sdk.ts +3 -3
  56. package/src/adapters/claude/source-capture.ts +20 -0
  57. package/src/adapters/provider-router.ts +1 -0
  58. package/src/boilerplate/system-prompt.ts +3 -2
  59. package/src/check/run-tier0.ts +61 -0
  60. package/src/create-ui-generator.ts +4 -2
  61. package/src/design-system-docs.ts +2 -1
  62. package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
  63. package/src/evaluation/axis-checks/helpers.ts +125 -0
  64. package/src/evaluation/criteria/resolve.ts +19 -1
  65. package/src/evaluation/evaluator.ts +2 -2
  66. package/src/evaluation/llm-evaluator.ts +2 -2
  67. package/src/evaluation/loop.ts +25 -4
  68. package/src/evaluation/message-parsing.ts +11 -12
  69. package/src/evaluation/types-public.ts +4 -0
  70. package/src/evaluation/visual-evaluator.ts +1 -1
  71. package/src/harness/coding/assemble-result.ts +1 -0
  72. package/src/harness/coding/dupe-break.ts +70 -0
  73. package/src/harness/coding/generate-task-runner.ts +5 -1
  74. package/src/harness/coding/run-coding-turn.ts +56 -1
  75. package/src/harness/result-types.ts +12 -0
  76. package/src/index.ts +1 -1
  77. package/src/taught-tokens.ts +5 -6
@@ -1,7 +1,7 @@
1
1
  import { RuntimeRenderCheck } from './harness/types-public.js';
2
2
  import { GenerationRuntimeProbeCheck } from '@ggui-ai/mcp-server-core';
3
3
  import { JsonObject, DataContract } from '@ggui-ai/protocol';
4
- import { f as ProbeHostLoad, g as ProbePendingAffordance } from './types-public-B7wn4ce5.js';
4
+ import { f as ProbeHostLoad, g as ProbePendingAffordance } from './types-public-h8IMnYuh.js';
5
5
 
6
6
  /**
7
7
  * The subprocess bounds of one isolated check (ggui#1380). They are
@@ -41,8 +41,13 @@ interface ClaudeSdkConfig extends AdapterConfig {
41
41
  env?: Record<string, string>;
42
42
  /** MCP server configs for Claude Agent SDK (keyed by name) */
43
43
  mcpServers?: Record<string, McpServerConfig>;
44
- /** Allowed tool names for Claude Agent SDK */
44
+ /** Tool names to approve. Absent → every tool of every server in `mcpServers` (ggui#1714). */
45
45
  allowedTools?: string[];
46
+ /**
47
+ * CLI built-in tools to offer the model (e.g. `['Write']`). Default: none.
48
+ * An explicit opt-in: a built-in that writes or executes acts on the host (ggui#1714).
49
+ */
50
+ builtinTools?: readonly string[];
46
51
  /** Subagent definitions for Claude Agent SDK */
47
52
  agents?: Record<string, AgentDefinition>;
48
53
  /** Custom CLI path for Docker deployments */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/base.ts"],"names":[],"mappings":";AA2FO,IAAe,mBAAf,MAAgC;AAAA,EAKrC,YAA+B,MAAA,EAA0B;AAA1B,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAA2B;AAAA,EAA3B,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiB/B,eAAe,cAAA,EAAgC;AAC7C,IAAA,OAAO,iBAAiB,cAAc,CAAA;AAAA,EACxC;AACF;AAQA,SAAS,iBAAiB,OAAA,EAAyB;AACjD,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,OAAA,CAAQ,GAAG,CAAA;AACtC,EAAA,OAAO,cAAc,CAAA,GAAI,OAAA,CAAQ,KAAA,CAAM,UAAA,GAAa,CAAC,CAAA,GAAI,OAAA;AAC3D;AAKO,SAAS,cAAA,CACd,WACG,WAAA,EACM;AACT,EAAA,IAAI,MAAA,CAAO,QAAQ,OAAO,IAAA;AAC1B,EAAA,IAAI,MAAA,CAAO,YAAY,OAAO,IAAA;AAC9B,EAAA,IAAI,MAAA,CAAO,iBAAiB,OAAO,IAAA;AACnC,EAAA,OAAO,WAAA,CAAY,KAAK,CAAC,IAAA,KAAS,CAAC,CAAC,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAC,CAAA;AACvD","file":"base.js","sourcesContent":["// packages/ui-gen/src/adapters/base.ts\n//\n// Abstract base class for all generator adapters.\n\nimport type { McpServerConfig, AgentDefinition } from '@anthropic-ai/claude-agent-sdk';\nimport type { JsonObject } from '@ggui-ai/protocol';\nimport type { AdapterResult, ToolDefinition, ProviderName, AdapterMode } from './types';\n\n/**\n * Core configuration shared by all adapters.\n */\nexport interface AdapterConfig {\n /** API key for the provider (BYOK) */\n apiKey?: string;\n /** Use Bedrock instead of direct API (Claude only) */\n useBedrock?: boolean;\n /**\n * ggui#1185 — run on the machine's own Claude Code login instead of a key.\n * Claude SDK adapter only. The adapter strips every provider key from the\n * spawned env and pins the run tool-less, config-less and non-bare (see\n * `claude/claude-code-login.ts`). Opt-in; absent ⇒ key-based auth.\n */\n claudeCodeLogin?: boolean;\n /**\n * Thinking level for models with extended thinking (Gemini 3.x Flash\n * via the Interactions API). Mirrors the API's `thinking_level` enum —\n * `'minimal'` is the floor (the API has NO true off), then `'low'` /\n * `'medium'` / `'high'`. Omitted → provider default, and Gemini 3.5\n * Flash defaults thinking ON with no cap — the dominant latency cost\n * in contract-bounded UI generation. Only the Google adapter consumes\n * this today.\n */\n thinkingLevel?: 'minimal' | 'low' | 'medium' | 'high';\n}\n\n/**\n * Extended configuration for Claude Agent SDK adapter.\n * Separated from AdapterConfig to avoid polluting the base interface\n * with provider-specific concerns.\n */\nexport interface ClaudeSdkConfig extends AdapterConfig {\n /** Working directory for SDK subprocess */\n cwd?: string;\n /** Environment variables for SDK subprocess */\n env?: Record<string, string>;\n /** MCP server configs for Claude Agent SDK (keyed by name) */\n mcpServers?: Record<string, McpServerConfig>;\n /** Allowed tool names for Claude Agent SDK */\n allowedTools?: string[];\n /** Subagent definitions for Claude Agent SDK */\n agents?: Record<string, AgentDefinition>;\n /** Custom CLI path for Docker deployments */\n cliPath?: string;\n /** stderr callback for debug logging */\n stderr?: (data: string) => void;\n /** Progress callback */\n onProgress?: (event: { type: string; [key: string]: unknown }) => void;\n}\n\n/**\n * Union config type — adapters accept their specific config\n * but the registry uses this union so callers can pass any config.\n */\nexport type AnyAdapterConfig = AdapterConfig | ClaudeSdkConfig;\n\n/**\n * Parameters for a single generation run.\n */\nexport interface GenerateParams {\n /** System prompt (HOW to build — shared across all SDKs) */\n systemPrompt: string;\n /** User prompt (WHAT to build — the specific request) */\n userPrompt: string;\n /** Model identifier (provider-native format, e.g., 'claude-sonnet-4-6') */\n model: string;\n /** SDK-agnostic tool definitions */\n tools: ToolDefinition[];\n /** Maximum agentic loop turns */\n maxTurns: number;\n /** Model-specific options (e.g., speed_priority for Claude fast mode) */\n modelOptions?: JsonObject;\n}\n\n/**\n * Abstract base class for generator adapters.\n *\n * Each adapter wraps a specific provider SDK (Claude, OpenAI, Google)\n * in either raw API mode or agent SDK mode, and implements the tool-use\n * loop using the SDK's native API. All adapters produce the same\n * output format (AdapterResult).\n */\nexport abstract class GeneratorAdapter {\n abstract readonly provider: ProviderName;\n abstract readonly mode: AdapterMode;\n abstract readonly displayName: string;\n\n constructor(protected readonly config: AnyAdapterConfig) {}\n\n /**\n * Run UI generation with the given tools, prompt, and model.\n */\n abstract generate(params: GenerateParams): Promise<AdapterResult>;\n\n /**\n * Check if this adapter can run (SDK installed, credentials available).\n */\n abstract isAvailable(): boolean;\n\n /**\n * Map a LiteLLM-format model ID to this SDK's native format.\n * Default: strips the provider prefix (e.g., 'anthropic/claude-sonnet-4-6' -> 'claude-sonnet-4-6').\n * Override only if the prefix convention differs.\n */\n resolveModelId(litellmModelId: string): string {\n return stripModelPrefix(litellmModelId);\n }\n}\n\n/**\n * Strip provider prefix from LiteLLM-format model IDs.\n * 'anthropic/claude-sonnet-4-6' -> 'claude-sonnet-4-6'\n * 'openai/gpt-5.3-codex' -> 'gpt-5.3-codex'\n * 'gemini/gemini-3-flash-preview' -> 'gemini-3-flash-preview'\n */\nfunction stripModelPrefix(modelId: string): string {\n const slashIndex = modelId.indexOf('/');\n return slashIndex >= 0 ? modelId.slice(slashIndex + 1) : modelId;\n}\n\n/**\n * Check if credentials are available from config or environment variables.\n */\nexport function hasCredentials(\n config: AdapterConfig,\n ...envVarNames: string[]\n): boolean {\n if (config.apiKey) return true;\n if (config.useBedrock) return true;\n if (config.claudeCodeLogin) return true;\n return envVarNames.some((name) => !!process.env[name]);\n}\n"]}
1
+ {"version":3,"sources":["../../src/adapters/base.ts"],"names":[],"mappings":";AAgGO,IAAe,mBAAf,MAAgC;AAAA,EAKrC,YAA+B,MAAA,EAA0B;AAA1B,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAA2B;AAAA,EAA3B,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiB/B,eAAe,cAAA,EAAgC;AAC7C,IAAA,OAAO,iBAAiB,cAAc,CAAA;AAAA,EACxC;AACF;AAQA,SAAS,iBAAiB,OAAA,EAAyB;AACjD,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,OAAA,CAAQ,GAAG,CAAA;AACtC,EAAA,OAAO,cAAc,CAAA,GAAI,OAAA,CAAQ,KAAA,CAAM,UAAA,GAAa,CAAC,CAAA,GAAI,OAAA;AAC3D;AAKO,SAAS,cAAA,CACd,WACG,WAAA,EACM;AACT,EAAA,IAAI,MAAA,CAAO,QAAQ,OAAO,IAAA;AAC1B,EAAA,IAAI,MAAA,CAAO,YAAY,OAAO,IAAA;AAC9B,EAAA,IAAI,MAAA,CAAO,iBAAiB,OAAO,IAAA;AACnC,EAAA,OAAO,WAAA,CAAY,KAAK,CAAC,IAAA,KAAS,CAAC,CAAC,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAC,CAAA;AACvD","file":"base.js","sourcesContent":["// packages/ui-gen/src/adapters/base.ts\n//\n// Abstract base class for all generator adapters.\n\nimport type { McpServerConfig, AgentDefinition } from '@anthropic-ai/claude-agent-sdk';\nimport type { JsonObject } from '@ggui-ai/protocol';\nimport type { AdapterResult, ToolDefinition, ProviderName, AdapterMode } from './types';\n\n/**\n * Core configuration shared by all adapters.\n */\nexport interface AdapterConfig {\n /** API key for the provider (BYOK) */\n apiKey?: string;\n /** Use Bedrock instead of direct API (Claude only) */\n useBedrock?: boolean;\n /**\n * ggui#1185 — run on the machine's own Claude Code login instead of a key.\n * Claude SDK adapter only. The adapter strips every provider key from the\n * spawned env and pins the run tool-less, config-less and non-bare (see\n * `claude/claude-code-login.ts`). Opt-in; absent ⇒ key-based auth.\n */\n claudeCodeLogin?: boolean;\n /**\n * Thinking level for models with extended thinking (Gemini 3.x Flash\n * via the Interactions API). Mirrors the API's `thinking_level` enum —\n * `'minimal'` is the floor (the API has NO true off), then `'low'` /\n * `'medium'` / `'high'`. Omitted → provider default, and Gemini 3.5\n * Flash defaults thinking ON with no cap — the dominant latency cost\n * in contract-bounded UI generation. Only the Google adapter consumes\n * this today.\n */\n thinkingLevel?: 'minimal' | 'low' | 'medium' | 'high';\n}\n\n/**\n * Extended configuration for Claude Agent SDK adapter.\n * Separated from AdapterConfig to avoid polluting the base interface\n * with provider-specific concerns.\n */\nexport interface ClaudeSdkConfig extends AdapterConfig {\n /** Working directory for SDK subprocess */\n cwd?: string;\n /** Environment variables for SDK subprocess */\n env?: Record<string, string>;\n /** MCP server configs for Claude Agent SDK (keyed by name) */\n mcpServers?: Record<string, McpServerConfig>;\n /** Tool names to approve. Absent → every tool of every server in `mcpServers` (ggui#1714). */\n allowedTools?: string[];\n /**\n * CLI built-in tools to offer the model (e.g. `['Write']`). Default: none.\n * An explicit opt-in: a built-in that writes or executes acts on the host (ggui#1714).\n */\n builtinTools?: readonly string[];\n /** Subagent definitions for Claude Agent SDK */\n agents?: Record<string, AgentDefinition>;\n /** Custom CLI path for Docker deployments */\n cliPath?: string;\n /** stderr callback for debug logging */\n stderr?: (data: string) => void;\n /** Progress callback */\n onProgress?: (event: { type: string; [key: string]: unknown }) => void;\n}\n\n/**\n * Union config type — adapters accept their specific config\n * but the registry uses this union so callers can pass any config.\n */\nexport type AnyAdapterConfig = AdapterConfig | ClaudeSdkConfig;\n\n/**\n * Parameters for a single generation run.\n */\nexport interface GenerateParams {\n /** System prompt (HOW to build — shared across all SDKs) */\n systemPrompt: string;\n /** User prompt (WHAT to build — the specific request) */\n userPrompt: string;\n /** Model identifier (provider-native format, e.g., 'claude-sonnet-4-6') */\n model: string;\n /** SDK-agnostic tool definitions */\n tools: ToolDefinition[];\n /** Maximum agentic loop turns */\n maxTurns: number;\n /** Model-specific options (e.g., speed_priority for Claude fast mode) */\n modelOptions?: JsonObject;\n}\n\n/**\n * Abstract base class for generator adapters.\n *\n * Each adapter wraps a specific provider SDK (Claude, OpenAI, Google)\n * in either raw API mode or agent SDK mode, and implements the tool-use\n * loop using the SDK's native API. All adapters produce the same\n * output format (AdapterResult).\n */\nexport abstract class GeneratorAdapter {\n abstract readonly provider: ProviderName;\n abstract readonly mode: AdapterMode;\n abstract readonly displayName: string;\n\n constructor(protected readonly config: AnyAdapterConfig) {}\n\n /**\n * Run UI generation with the given tools, prompt, and model.\n */\n abstract generate(params: GenerateParams): Promise<AdapterResult>;\n\n /**\n * Check if this adapter can run (SDK installed, credentials available).\n */\n abstract isAvailable(): boolean;\n\n /**\n * Map a LiteLLM-format model ID to this SDK's native format.\n * Default: strips the provider prefix (e.g., 'anthropic/claude-sonnet-4-6' -> 'claude-sonnet-4-6').\n * Override only if the prefix convention differs.\n */\n resolveModelId(litellmModelId: string): string {\n return stripModelPrefix(litellmModelId);\n }\n}\n\n/**\n * Strip provider prefix from LiteLLM-format model IDs.\n * 'anthropic/claude-sonnet-4-6' -> 'claude-sonnet-4-6'\n * 'openai/gpt-5.3-codex' -> 'gpt-5.3-codex'\n * 'gemini/gemini-3-flash-preview' -> 'gemini-3-flash-preview'\n */\nfunction stripModelPrefix(modelId: string): string {\n const slashIndex = modelId.indexOf('/');\n return slashIndex >= 0 ? modelId.slice(slashIndex + 1) : modelId;\n}\n\n/**\n * Check if credentials are available from config or environment variables.\n */\nexport function hasCredentials(\n config: AdapterConfig,\n ...envVarNames: string[]\n): boolean {\n if (config.apiKey) return true;\n if (config.useBedrock) return true;\n if (config.claudeCodeLogin) return true;\n return envVarNames.some((name) => !!process.env[name]);\n}\n"]}
@@ -2,7 +2,7 @@ import { EvaluationConfig } from '../evaluation/types.js';
2
2
  import { RuntimeRenderCheck } from '../harness/types-public.js';
3
3
  import { ModelRoles, GenerationResult } from '../harness/result-types.js';
4
4
  import { ProviderName, ToolDefinition } from './types.js';
5
- import { Q as QualityConfig, b as AgentConfig, V as VisionProvider } from '../types-public-B7wn4ce5.js';
5
+ import { Q as QualityConfig, b as AgentConfig, V as VisionProvider } from '../types-public-h8IMnYuh.js';
6
6
  import { DataContract, JsonObject, GadgetDescriptor } from '@ggui-ai/protocol';
7
7
  import { C as CanvasClass, D as DesignMode } from '../design-mode-DGWN_Gkm.js';
8
8
  import { C as CriteriaBank } from '../bank-Ctdp3FUA.js';
@@ -12,7 +12,7 @@ import '../axes-CKVCzR_x.js';
12
12
  import '../llm.js';
13
13
  import '../types-Dh1s4K9j.js';
14
14
  import '../policy.js';
15
- import '../visual-evaluator-C9L-o6ap.js';
15
+ import '../visual-evaluator-C2strxqD.js';
16
16
  import 'puppeteer-core';
17
17
  import 'zod';
18
18
  import '@ggui-ai/mcp-server-core';
@@ -532,10 +532,7 @@ var init_json_schema_ts = __esm({
532
532
  var NOT_YET_TAUGHT_TOKENS, taughtTokenManifest;
533
533
  var init_taught_tokens = __esm({
534
534
  "src/taught-tokens.ts"() {
535
- NOT_YET_TAUGHT_TOKENS = /* @__PURE__ */ new Set([
536
- "--ggui-color-controlAccentOutline",
537
- "--ggui-color-controlOutline"
538
- ]);
535
+ NOT_YET_TAUGHT_TOKENS = /* @__PURE__ */ new Set([]);
539
536
  taughtTokenManifest = consumedTokenManifest.filter(
540
537
  (token) => !NOT_YET_TAUGHT_TOKENS.has(token)
541
538
  );
@@ -714,6 +711,8 @@ var init_claude_code_login = __esm({
714
711
  function getBedrockModelId(model) {
715
712
  const BEDROCK_MAP = {
716
713
  "anthropic/claude-haiku-4-5": "us.anthropic.claude-haiku-4-5-20251001-v1:0",
714
+ "anthropic/claude-haiku-5-5": "us.anthropic.claude-haiku-5-5",
715
+ // listed ACTIVE in us-east-1 (ggui#1743)
717
716
  "anthropic/claude-sonnet-4-6": "us.anthropic.claude-sonnet-4-6",
718
717
  "anthropic/claude-opus-4-6": "us.anthropic.claude-opus-4-6-v1:0",
719
718
  // Claude 5-family (ggui#706, strings verified 2026-09-02 against
@@ -4626,6 +4625,40 @@ function detectComponentNeverReturns(sourceCode) {
4626
4625
  for (const st of body.statements) if (!isFunctionLike(st)) visit(st);
4627
4626
  return returnsValue ? null : { jsxStatementLine };
4628
4627
  }
4628
+ function kitControlTags(source) {
4629
+ const out = [];
4630
+ const open = new RegExp(`<(${KIT_CONTROLS.join("|")})\\b`, "g");
4631
+ for (const m of source.matchAll(open)) {
4632
+ const start = (m.index ?? 0) + m[0].length;
4633
+ let depth = 0;
4634
+ let quote = null;
4635
+ let i = start;
4636
+ for (; i < source.length; i += 1) {
4637
+ const c = source[i];
4638
+ if (quote !== null) {
4639
+ if (c === quote && source[i - 1] !== "\\") quote = null;
4640
+ } else if (c === '"' || c === "'" || c === "`") quote = c;
4641
+ else if (c === "{") depth += 1;
4642
+ else if (c === "}") depth -= 1;
4643
+ else if (c === ">" && depth === 0) break;
4644
+ }
4645
+ out.push({ name: m[1] ?? "", attrs: source.slice(start, i), line: source.slice(0, m.index ?? 0).split("\n").length });
4646
+ }
4647
+ return out;
4648
+ }
4649
+ function inlineBorderKey(attrs, source) {
4650
+ const BORDER = /\b(border(?:Top|Right|Bottom|Left)?(?:Color|Width|Style)?)\s*:/;
4651
+ const style = attrs.match(/\bstyle=\{\{([\s\S]*)\}\}/);
4652
+ if (style !== null) {
4653
+ const hit2 = (style[1] ?? "").match(BORDER);
4654
+ if (hit2 !== null) return hit2[1] ?? null;
4655
+ }
4656
+ const named = attrs.match(/\bstyle=\{\s*([A-Za-z_$][\w$]*)\s*\}/);
4657
+ if (named === null) return null;
4658
+ const decl = source.match(new RegExp(`\\b(?:const|let)\\s+${named[1]}\\b[^=]*=\\s*\\{([^{}]*)\\}`));
4659
+ const hit = decl !== null ? (decl[1] ?? "").match(BORDER) : null;
4660
+ return hit !== null ? hit[1] ?? null : null;
4661
+ }
4629
4662
  async function runTier0Checks(sourceCode, contract, buildErrors, gadgetTypes, designMode = DEFAULT_DESIGN_MODE) {
4630
4663
  const issues = [];
4631
4664
  const lines = sourceCode.split("\n");
@@ -5023,6 +5056,20 @@ ${errorDetail}`,
5023
5056
  });
5024
5057
  }
5025
5058
  }
5059
+ for (const tag of enforceDesignVocabulary ? kitControlTags(sourceCode) : []) {
5060
+ const key = inlineBorderKey(tag.attrs, sourceCode);
5061
+ if (key === null) continue;
5062
+ issues.push({
5063
+ tier: 0,
5064
+ result: "fail",
5065
+ category: "tokens",
5066
+ subcategory: "control-inline-border",
5067
+ severity: "critical",
5068
+ description: `control-inline-border: <${tag.name}> sets an inline \`${key}\` \u2014 a kit control draws its own boundary, and an inline border paints over it`,
5069
+ fix: `Remove the border from <${tag.name}>'s style (keep borderRadius if you need it). An outline chip is <Button variant="outline"> exactly as it comes; a control-like edge on your own element uses var(--ggui-color-controlOutline).`,
5070
+ line: tag.line
5071
+ });
5072
+ }
5026
5073
  if (!sourceCode.includes("interface Props") && !sourceCode.includes("type Props")) {
5027
5074
  issues.push({
5028
5075
  tier: 0,
@@ -5294,7 +5341,7 @@ async function runTier0(sourceCode, contract, designMode = DEFAULT_DESIGN_MODE)
5294
5341
  }
5295
5342
  return { issues, pass };
5296
5343
  }
5297
- var MANIFEST_TOKENS, RETIRED_TOKEN_SUCCESSORS, TOKEN_REF_RE, HOOK_NAME_FOR, CONTRACT_FIELD_FOR;
5344
+ var MANIFEST_TOKENS, RETIRED_TOKEN_SUCCESSORS, TOKEN_REF_RE, HOOK_NAME_FOR, CONTRACT_FIELD_FOR, KIT_CONTROLS;
5298
5345
  var init_run_tier0 = __esm({
5299
5346
  "src/check/run-tier0.ts"() {
5300
5347
  init_design_mode();
@@ -5323,6 +5370,7 @@ var init_run_tier0 = __esm({
5323
5370
  stream: "streamSpec",
5324
5371
  context: "contextSpec"
5325
5372
  };
5373
+ KIT_CONTROLS = ["Button", "Input", "Select", "TextArea", "Checkbox", "RadioGroup", "Toggle", "SearchField"];
5326
5374
  }
5327
5375
  });
5328
5376
 
@@ -5649,10 +5697,16 @@ var dupe_break_exports = {};
5649
5697
  __export(dupe_break_exports, {
5650
5698
  SAME_EXCHANGE_BREAK_AT: () => SAME_EXCHANGE_BREAK_AT,
5651
5699
  SAME_EXCHANGE_NUDGE: () => SAME_EXCHANGE_NUDGE,
5700
+ SAME_FAILURE_BREAK_AT: () => SAME_FAILURE_BREAK_AT,
5701
+ SAME_FAILURE_NUDGE: () => SAME_FAILURE_NUDGE,
5702
+ SAME_FAILURE_NUDGE_AT: () => SAME_FAILURE_NUDGE_AT,
5703
+ clearWriteFailures: () => clearWriteFailures,
5652
5704
  computePatchFingerprint: () => computePatchFingerprint,
5653
5705
  createDupeBreakState: () => createDupeBreakState,
5654
5706
  errorClassBucket: () => errorClassBucket,
5655
- recordToolExchange: () => recordToolExchange
5707
+ failureSignatures: () => failureSignatures,
5708
+ recordToolExchange: () => recordToolExchange,
5709
+ recordWriteFailure: () => recordWriteFailure
5656
5710
  });
5657
5711
  function recordToolExchange(state, name, input, resultText) {
5658
5712
  const key = createHash("sha256").update(name).update("\0").update(JSON.stringify(input) ?? "undefined").update("\0").update(resultText).digest("hex");
@@ -5664,6 +5718,25 @@ function recordToolExchange(state, name, input, resultText) {
5664
5718
  }
5665
5719
  return state.consecutiveIdenticalExchanges;
5666
5720
  }
5721
+ function failureSignatures(resultText) {
5722
+ const norm = (text) => text.replace(/[0-9]+/g, "#").replace(/\s+/g, " ").trim();
5723
+ const violations = [...resultText.matchAll(/^\[[^\]\n]+\] [^\n]+/gm)].map((m) => norm(m[0]));
5724
+ if (violations.length > 0) return [...new Set(violations)];
5725
+ const rest = norm(resultText.replace(/^Committed [^\n]*\n/, ""));
5726
+ return rest.length > 0 ? [rest] : [];
5727
+ }
5728
+ function recordWriteFailure(state, resultText) {
5729
+ let top;
5730
+ for (const signature of failureSignatures(resultText)) {
5731
+ const repeats = (state.failureCounts.get(signature) ?? 0) + 1;
5732
+ state.failureCounts.set(signature, repeats);
5733
+ if (top === void 0 || repeats > top.repeats) top = { signature, repeats };
5734
+ }
5735
+ return top;
5736
+ }
5737
+ function clearWriteFailures(state) {
5738
+ state.failureCounts.clear();
5739
+ }
5667
5740
  function createDupeBreakState() {
5668
5741
  return {
5669
5742
  lastFailedPatchFingerprint: null,
@@ -5680,7 +5753,8 @@ function createDupeBreakState() {
5680
5753
  consecutiveBrokenApplies: 0,
5681
5754
  consecutiveNoToolCalls: 0,
5682
5755
  lastExchangeKey: null,
5683
- consecutiveIdenticalExchanges: 0
5756
+ consecutiveIdenticalExchanges: 0,
5757
+ failureCounts: /* @__PURE__ */ new Map()
5684
5758
  };
5685
5759
  }
5686
5760
  function errorClassBucket(errText) {
@@ -5705,11 +5779,14 @@ function computePatchFingerprint(changes, errText) {
5705
5779
  h.update(errBucket);
5706
5780
  return h.digest("hex");
5707
5781
  }
5708
- var SAME_EXCHANGE_BREAK_AT, SAME_EXCHANGE_NUDGE;
5782
+ var SAME_EXCHANGE_BREAK_AT, SAME_EXCHANGE_NUDGE, SAME_FAILURE_NUDGE_AT, SAME_FAILURE_BREAK_AT, SAME_FAILURE_NUDGE;
5709
5783
  var init_dupe_break = __esm({
5710
5784
  "src/harness/coding/dupe-break.ts"() {
5711
5785
  SAME_EXCHANGE_BREAK_AT = 3;
5712
5786
  SAME_EXCHANGE_NUDGE = "\n\n[harness] This is the SAME tool call with the SAME result as your previous turn \u2014 repeating it cannot produce new information. Use what you already have and emit an apply_changes (or scoped) call now; one more identical repeat terminates the run.";
5787
+ SAME_FAILURE_NUDGE_AT = 3;
5788
+ SAME_FAILURE_BREAK_AT = 5;
5789
+ SAME_FAILURE_NUDGE = "\n\n[harness] This same failure has now been reported for three of your patches. Changing other code does not clear it: re-read that check above and change exactly what it names. Two more reports of it end the run.";
5713
5790
  }
5714
5791
  });
5715
5792
 
@@ -6101,13 +6178,13 @@ Return your evaluation using the evaluate_${criterion} tool.`;
6101
6178
  function getDefaultEvalModel(provider) {
6102
6179
  switch (provider) {
6103
6180
  case "claude":
6104
- return "claude-haiku-4-5-20251001";
6181
+ return "claude-haiku-5-5";
6105
6182
  case "openai":
6106
6183
  return "gpt-5.6-luna";
6107
6184
  case "google":
6108
6185
  return "gemini-3.5-flash-lite";
6109
6186
  default:
6110
- return "claude-haiku-4-5-20251001";
6187
+ return "claude-haiku-5-5";
6111
6188
  }
6112
6189
  }
6113
6190
  function callTokens(response) {
@@ -6567,6 +6644,7 @@ function instrumentRead(kind, m) {
6567
6644
  return { verdict: "n/a", evidence: `fill ${m.inkRatio.toFixed(3)} reported; no floor ruled` };
6568
6645
  }
6569
6646
  function readRow(row, answers, m, unansweredReason) {
6647
+ if (row.evaluation === "judge" && REQUIRES_POST_ACTION_CAPTURE.has(row.id) && m.postActionCapture !== true) return { verdict: "n/a", evidence: NO_POST_ACTION };
6570
6648
  if (row.evaluation === "judge") return majority(row.id, answers, unansweredReason);
6571
6649
  if (row.evaluation === "human") return { verdict: "n/a", evidence: `reader's column (status ${row.status})` };
6572
6650
  if (row.status !== "live") return { verdict: "n/a", evidence: `not implemented (status ${row.status})` };
@@ -6625,14 +6703,14 @@ ${text}
6625
6703
  }
6626
6704
  function buildCriteriaJudgeBlock(bank, selected, inputs = {}) {
6627
6705
  const byId = new Map(bankRows(bank).map((r) => [r.id, r]));
6628
- const asked = selected.selection.map((s) => byId.get(s.id)).filter((r) => r !== void 0 && r.evaluation === "judge");
6706
+ const asked = selected.selection.map((s) => byId.get(s.id)).filter((r) => r !== void 0 && r.evaluation === "judge").filter((r) => inputs.postActionCapture === true || !REQUIRES_POST_ACTION_CAPTURE.has(r.id));
6629
6707
  if (asked.length === 0) return "";
6630
6708
  const lines = asked.map((r) => `- ${r.id} (${r.severity}): ${r.text} Evidence must name: ${r.evidence}`);
6631
6709
  return "## Criteria \u2014 answer each one from the FRAME only\nRead these off the screenshot. Never infer them from the request, the styling profile or any direction text above.\n" + (inputs.frame !== void 0 ? `${criteriaFrameLine(inputs.frame)}
6632
6710
  ` : "") + (inputs.propsJson !== void 0 ? criteriaPropsSection(inputs.propsJson) : "") + `${lines.join("\n")}
6633
6711
  Add to your JSON a top-level "criteria" array with exactly these ids: [{ "id": "<id>", "verdict": "pass" | "fail" | "n/a", "evidence": "<one sentence naming the region or element you read it from>" }]. Every row here was selected as applicable to this card, so "n/a" never means "does not apply": use it ONLY when this frame cannot show the criterion \u2014 it is then recorded as not evaluated.`;
6634
6712
  }
6635
- var INSTRUMENT_BY_ID, INSTRUMENT_BESIDE_JUDGE, CRITERIA_PROPS_MAX_CHARS;
6713
+ var INSTRUMENT_BY_ID, INSTRUMENT_BESIDE_JUDGE, REQUIRES_POST_ACTION_CAPTURE, NO_POST_ACTION, CRITERIA_PROPS_MAX_CHARS;
6636
6714
  var init_resolve = __esm({
6637
6715
  "src/evaluation/criteria/resolve.ts"() {
6638
6716
  init_bank();
@@ -6645,6 +6723,8 @@ var init_resolve = __esm({
6645
6723
  INSTRUMENT_BESIDE_JUDGE = {
6646
6724
  "space.edge": "edge"
6647
6725
  };
6726
+ REQUIRES_POST_ACTION_CAPTURE = /* @__PURE__ */ new Set(["state.feedback"]);
6727
+ NO_POST_ACTION = "not evaluated: no post-action capture";
6648
6728
  CRITERIA_PROPS_MAX_CHARS = 2e3;
6649
6729
  }
6650
6730
  });
@@ -8182,7 +8262,7 @@ function parseCriteriaAnswers(raw) {
8182
8262
  function getDefaultVisualModel(provider) {
8183
8263
  switch (provider) {
8184
8264
  case "claude":
8185
- return "claude-haiku-4-5-20251001";
8265
+ return "claude-haiku-5-5";
8186
8266
  case "google":
8187
8267
  return "gemini-3-flash-preview";
8188
8268
  }
@@ -9451,10 +9531,96 @@ function getInitialValuePropNames(contract) {
9451
9531
  }
9452
9532
  return names;
9453
9533
  }
9534
+ function objectLiteralKeysAt(src, open) {
9535
+ if (src[open] !== "{") return void 0;
9536
+ const keys = [];
9537
+ let depth = 0;
9538
+ let expectKey = true;
9539
+ for (let i = open; i < src.length; i++) {
9540
+ const c = src[i];
9541
+ if (c === '"' || c === "'" || c === "`") {
9542
+ let j = i + 1;
9543
+ while (j < src.length && src[j] !== c) j += src[j] === "\\" ? 2 : 1;
9544
+ if (depth === 1 && expectKey && /^\s*:/.test(src.slice(j + 1))) keys.push(src.slice(i + 1, j));
9545
+ i = j;
9546
+ expectKey = false;
9547
+ continue;
9548
+ }
9549
+ if (c === "{" || c === "[" || c === "(") {
9550
+ depth++;
9551
+ if (depth === 1) expectKey = true;
9552
+ continue;
9553
+ }
9554
+ if (c === "}" || c === "]" || c === ")") {
9555
+ depth--;
9556
+ if (depth === 0) return keys;
9557
+ continue;
9558
+ }
9559
+ if (depth !== 1) continue;
9560
+ if (c === ",") {
9561
+ expectKey = true;
9562
+ continue;
9563
+ }
9564
+ if (expectKey && /[A-Za-z_$]/.test(c ?? "")) {
9565
+ const m = /^([A-Za-z_$][\w$]*)\s*(:|,|\})/.exec(src.slice(i));
9566
+ if (m !== null && m[1] !== void 0) keys.push(m[1]);
9567
+ expectKey = false;
9568
+ }
9569
+ }
9570
+ return keys;
9571
+ }
9572
+ function callArgsAt(src, open) {
9573
+ const args = [];
9574
+ let depth = 0;
9575
+ let start = open + 1;
9576
+ for (let i = open; i < src.length; i++) {
9577
+ const c = src[i];
9578
+ if (c === '"' || c === "'" || c === "`") {
9579
+ let j = i + 1;
9580
+ while (j < src.length && src[j] !== c) j += src[j] === "\\" ? 2 : 1;
9581
+ i = j;
9582
+ continue;
9583
+ }
9584
+ if (c === "(" || c === "{" || c === "[") depth++;
9585
+ else if (c === ")" || c === "}" || c === "]") {
9586
+ depth--;
9587
+ if (depth === 0) {
9588
+ args.push(src.slice(start, i));
9589
+ return args;
9590
+ }
9591
+ } else if (c === "," && depth === 1) {
9592
+ args.push(src.slice(start, i));
9593
+ start = i + 1;
9594
+ }
9595
+ }
9596
+ return args;
9597
+ }
9598
+ function initialStateKeys(src, argStart, argText) {
9599
+ const lead = argText.length - argText.trimStart().length;
9600
+ let at = argStart + lead;
9601
+ let text = argText.trim();
9602
+ const lazy = /^\(\s*\)\s*=>\s*\(?\s*/.exec(text);
9603
+ if (lazy !== null) {
9604
+ at += lazy[0].length;
9605
+ text = text.slice(lazy[0].length);
9606
+ }
9607
+ if (text.startsWith("{")) return objectLiteralKeysAt(src, at) ?? [];
9608
+ const ident = /^([A-Za-z_$][\w$]*)\s*\)?\s*$/.exec(text);
9609
+ if (ident === null || ident[1] === void 0) return [];
9610
+ const decls = [...src.matchAll(new RegExp(`(?:const|let|var)\\s+${ident[1]}\\s*(?::[^=]{1,200})?=\\s*`, "g"))];
9611
+ const decl = decls.length === 1 ? decls[0] : void 0;
9612
+ if (decl === void 0) return [];
9613
+ return objectLiteralKeysAt(src, (decl.index ?? 0) + decl[0].length) ?? [];
9614
+ }
9454
9615
  function collectStateKeys(src) {
9455
9616
  const keys = /* @__PURE__ */ new Set();
9456
9617
  const varRe = /const\s*\[\s*(\w+)\s*,\s*(\w+)\s*\]\s*=\s*useState/g;
9457
9618
  for (const m of src.matchAll(varRe)) keys.add(m[1]);
9619
+ const contextRe = /const\s*\[\s*(\w+)\s*,\s*\w+\s*\]\s*=\s*useGguiContext\s*(?:<[^>]*>)?\s*\(\s*['"`](\w+)['"`]/g;
9620
+ for (const m of src.matchAll(contextRe)) {
9621
+ keys.add(m[1]);
9622
+ keys.add(m[2]);
9623
+ }
9458
9624
  const objRe = /useState(?:<[^>]*>)?\s*\(\s*\{([\s\S]*?)\}\s*\)/g;
9459
9625
  for (const m of src.matchAll(objRe)) {
9460
9626
  const keyRe = /(?:^|,)\s*(\w+)\s*:/g;
@@ -9465,6 +9631,16 @@ function collectStateKeys(src) {
9465
9631
  const keyRe = /(?:^|,)\s*(\w+)\s*:/g;
9466
9632
  for (const km of m[1].matchAll(keyRe)) keys.add(km[1]);
9467
9633
  }
9634
+ for (const m of src.matchAll(/\b(useState|useReducer)\s*(?:<[^>]*>)?\s*\(/g)) {
9635
+ const open = (m.index ?? 0) + m[0].length - 1;
9636
+ const args = callArgsAt(src, open);
9637
+ const which = m[1] === "useReducer" ? 1 : 0;
9638
+ const arg = args[which];
9639
+ if (arg === void 0) continue;
9640
+ let argStart = open + 1;
9641
+ for (let k = 0; k < which; k++) argStart += (args[k]?.length ?? 0) + 1;
9642
+ for (const key of initialStateKeys(src, argStart, arg)) keys.add(key);
9643
+ }
9468
9644
  return keys;
9469
9645
  }
9470
9646
  function mkIssue(subcategory, description, fix, result = "fail") {
@@ -10077,7 +10253,7 @@ function runNoHardcodedEntities(input) {
10077
10253
  mkIssue(
10078
10254
  "state.merge.no_hardcoded_entities",
10079
10255
  `Hardcoded entity array literal (multiple objects with "${idField}") in the component \u2014 entity data should come from state/props.`,
10080
- `Remove the literal array. Seed state from props.{entityProp} and merge updates via stream.`
10256
+ `Remove the literal array: this data comes from ${all.filter((e) => e.idField === idField).map((e) => `props.${e.name}`).join(" / ") || "props"}. Read it there (\`?? []\` for an absent value, never a literal default), seed state from it, and merge updates via stream.`
10081
10257
  )
10082
10258
  );
10083
10259
  }
@@ -12969,13 +13145,14 @@ MANDATORY:
12969
13145
  2. NEVER use CSS gradients with custom colors. If you need a gradient: \`linear-gradient(to bottom, var(--ggui-color-primary-500), var(--ggui-color-primary-700))\`.
12970
13146
  3. NEVER invent your own palette. The system provides primary, neutral, success, warning, error, and info \u2014 use ONLY these.
12971
13147
  4. NEVER include literal fallback values in ANY token reference \u2014 colors, spacing, typography, radius, shadows alike. Write \`var(--ggui-spacing-4)\` bare, never \`var(--ggui-spacing-4, 16px)\`; \`var(--ggui-color-primary-600)\` bare, never \`var(--ggui-color-primary-600, #0284c7)\`. The runtime injects every token; a literal fallback paints the wrong value exactly when the operator's theme matters most.
13148
+ 5. NEVER set an inline \`border\`, \`borderColor\`, \`borderWidth\` or \`borderStyle\` on a kit control \u2014 \`Button\`, \`Input\`, \`Select\`, \`TextArea\`, \`Checkbox\`, \`RadioGroup\`, \`Toggle\`, \`SearchField\`. The control draws its own boundary, and it clears 3:1 on its surface in every theme: an outline chip is \`<Button variant="outline">\` exactly as it comes (its edge is \`controlAccentOutline\`). Where you draw a control-like edge on your own element, use \`var(--ggui-color-controlOutline)\`. A ramp step such as \`primary-200\` as a control's border is nearly invisible (about 1.1:1).
12972
13149
 
12973
13150
  Token categories:
12974
13151
  - Brand fills: \`var(--ggui-color-primary-600)\`, \`var(--ggui-color-primary-50)\`
12975
13152
  - Accent text: \`var(--ggui-color-link)\` \u2014 eyebrows, taglines, links, labels; readable per mode (never a bare \`primary-<stop>\` as a text color)
12976
13153
  - Text: \`var(--ggui-color-onContainer)\`, \`var(--ggui-color-onSunken)\`
12977
13154
  - Backgrounds: \`var(--ggui-color-container)\`, \`var(--ggui-color-sunken)\`
12978
- - Borders: \`var(--ggui-color-outline)\`
13155
+ - Borders: \`var(--ggui-color-outline)\` \u2014 dividers and the edges of non-interactive boxes; a control's edge is the kit's (rule 5)
12979
13156
  - Spacing: \`var(--ggui-spacing-4)\`, \`var(--ggui-spacing-6)\`
12980
13157
  - Typography: \`var(--ggui-font-size-sm)\`, \`var(--ggui-font-weight-semibold)\`
12981
13158
  - Shadows: \`var(--ggui-shape-shadow-sm)\`, \`var(--ggui-shape-shadow-md)\`, \`var(--ggui-shape-shadow-lg)\`
@@ -12990,7 +13167,7 @@ Use the FULL primary palette throughout the component \u2014 NOT only on submit
12990
13167
  | Element | Token | Purpose |
12991
13168
  |---------|-------|---------|
12992
13169
  | Section headers, hero areas, highlight strips | \`primary-50\` / \`primary-100\` | Subtle branded backgrounds |
12993
- | Borders, dividers, focus rings, input focus | \`primary-200\` / \`primary-300\` | Branded structure |
13170
+ | Dividers, section rules, edges of non-interactive boxes | \`primary-200\` / \`primary-300\` | Branded structure \u2014 never a control's border (rule 5) |
12994
13171
  | Accent TEXT \u2014 eyebrows, taglines, links, labels, inline arrows | \`link\` (\`var(--ggui-color-link)\`) or \`<Text tone="emphasized">\` | The brand's READABLE ink, derived per mode to clear 4.5:1 \u2014 a bare \`primary-300\u2026600\` as a text color fails tier-0 |
12995
13172
  | Icon fills, badges, active indicators | \`primary-500\` / \`primary-600\` | Core accent fill |
12996
13173
  | Buttons, CTAs, filled interactive elements | \`primary-600\` / \`primary-700\` | Primary actions |
@@ -13663,6 +13840,7 @@ These tokens adapt automatically to any theme (light, dark, branded). **ALWAYS u
13663
13840
  | link | var(--ggui-color-link) | Accent TEXT \u2014 eyebrows, taglines, links, labels, inline arrows: the brand's readable ink, derived per mode to clear 4.5:1 on the container and the ground |
13664
13841
  | outline | var(--ggui-color-outline) | Borders, dividers |
13665
13842
  | outlineVariant | var(--ggui-color-outlineVariant) | Subtle borders |
13843
+ | controlOutline | var(--ggui-color-controlOutline) | A control's edge \u2014 clears 3:1 on its surface; kit controls draw it themselves |
13666
13844
 
13667
13845
  **Usage pattern:**
13668
13846
  - Page/section background \u2192 \`var(--ggui-color-ground)\`
@@ -13787,7 +13965,7 @@ Use \`var(--ggui-spacing-N)\` for all padding, gap, and margin values. **Never u
13787
13965
  ### Color usage guide
13788
13966
  - **Layering roles** (ground, container, elevated, sunken + their on* inks; outline): one kind of AREA each \u2014 use them for every background/text/border decision; they adapt to any theme
13789
13967
  - **primary-50/100**: Section backgrounds, highlight strips, card headers
13790
- - **primary-200/300**: Borders, dividers, focus rings, input outlines
13968
+ - **primary-200/300**: Dividers and section rules \u2014 never a control's border: kit controls draw their own edge (\`controlOutline\` / \`controlAccentOutline\`), so set no inline border on them
13791
13969
  - **primary-500/600**: Filled buttons, CTAs, badges, icon fills, active indicators \u2014 fills and borders, NOT a text color
13792
13970
  - **link**: Accent TEXT \u2014 eyebrows, taglines, links, labels, inline arrows; the brand's readable ink per mode (a bare \`primary-300\u2026600\` as a text color fails tier-0: \`universal.accent_text_ink\`)
13793
13971
  - **primary-700/800/900**: Headings and text on light primary backgrounds
@@ -16295,6 +16473,7 @@ async function assembleGenerationResult(input) {
16295
16473
  evalResult: telemetry.evalResult !== void 0 && telemetry.contractFeedback !== void 0 ? { ...telemetry.evalResult, contractFeedback: telemetry.contractFeedback } : telemetry.evalResult,
16296
16474
  // ggui#404 — present only when the same-exchange guard ended the run.
16297
16475
  ...telemetry.sameExchangeBreak !== void 0 ? { sameExchangeBreak: telemetry.sameExchangeBreak } : {},
16476
+ ...telemetry.sameFailureBreak !== void 0 ? { sameFailureBreak: telemetry.sameFailureBreak } : {},
16298
16477
  needsBackgroundImprovement,
16299
16478
  selfCheckPassed: telemetry.selfCheckPassed,
16300
16479
  timing,
@@ -17248,6 +17427,18 @@ Repeated error: ${errText}
17248
17427
  augmentedResultText += SAME_EXCHANGE_NUDGE;
17249
17428
  }
17250
17429
  }
17430
+ const isWrite = call.name === "apply_changes" || call.name === "rewrite";
17431
+ let repeatedFailure;
17432
+ if (ctx.dupeBreak && isWrite) {
17433
+ if (result.done) {
17434
+ clearWriteFailures(ctx.dupeBreak);
17435
+ } else {
17436
+ repeatedFailure = recordWriteFailure(ctx.dupeBreak, result.result);
17437
+ if (repeatedFailure?.repeats === SAME_FAILURE_NUDGE_AT) {
17438
+ augmentedResultText += SAME_FAILURE_NUDGE;
17439
+ }
17440
+ }
17441
+ }
17251
17442
  await codingAgent.sendToolResult([{
17252
17443
  callId: call.id,
17253
17444
  name: call.name,
@@ -17276,6 +17467,29 @@ Repeated error: ${errText}
17276
17467
  sameExchangeBreak: { tool: call.name, repeats: sameExchangeRepeats }
17277
17468
  };
17278
17469
  }
17470
+ if (repeatedFailure !== void 0 && repeatedFailure.repeats >= SAME_FAILURE_BREAK_AT) {
17471
+ const signature = repeatedFailure.signature.slice(0, 160);
17472
+ console.log(
17473
+ `[coding-agent] SAME_FAILURE_BREAK | tool=${call.name} | repeats=${repeatedFailure.repeats} | failure=${JSON.stringify(signature.slice(0, 80))} \u2014 terminating run (ggui#1791)`
17474
+ );
17475
+ return {
17476
+ control: "break",
17477
+ compiledCode: "",
17478
+ selfCheckPassed: false,
17479
+ outcome: void 0,
17480
+ phase: void 0,
17481
+ tokens,
17482
+ ...cacheTokens,
17483
+ llmMs,
17484
+ toolMs,
17485
+ lastResultText: augmentedResultText,
17486
+ lastDiffFailed: false,
17487
+ isEvalFeedback: false,
17488
+ iconNamesCache,
17489
+ preWarmedContext,
17490
+ sameFailureBreak: { signature, repeats: repeatedFailure.repeats }
17491
+ };
17492
+ }
17279
17493
  const turnDiffFailed = !!result.error && result.result.includes("DIFF");
17280
17494
  const isDiffFail = turnDiffFailed && !isPatchInvalid;
17281
17495
  const outcome = result.done ? "PASS" : isPatchInvalid ? "PATCH_INVALID" : isDiffFail ? "DIFF_FAIL" : "SELF_CHECK_FAIL";
@@ -18263,6 +18477,7 @@ function createTelemetry() {
18263
18477
  probeRepairTrigger: void 0,
18264
18478
  contractFeedback: void 0,
18265
18479
  sameExchangeBreak: void 0,
18480
+ sameFailureBreak: void 0,
18266
18481
  compiledCode: "",
18267
18482
  pairedSource: "",
18268
18483
  selfCheckPassed: false,
@@ -18318,6 +18533,7 @@ function absorbTurn(telemetry, turn) {
18318
18533
  else telemetry.counters.outcomes.selfCheckFail++;
18319
18534
  }
18320
18535
  if (turn.sameExchangeBreak !== void 0) telemetry.sameExchangeBreak = turn.sameExchangeBreak;
18536
+ if (turn.sameFailureBreak !== void 0) telemetry.sameFailureBreak = turn.sameFailureBreak;
18321
18537
  }
18322
18538
  function createGenerateTaskRunner(input) {
18323
18539
  const { session, params, classification, telemetry } = input;