@covibes/zeroshot 5.4.0 → 5.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -465
  3. package/cli/index.js +101 -5231
  4. package/package.json +16 -130
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -149
  7. package/cli/lib/first-run.js +0 -211
  8. package/cli/lib/update-checker.js +0 -281
  9. package/cli/message-formatter-utils.js +0 -75
  10. package/cli/message-formatters-normal.js +0 -275
  11. package/cli/message-formatters-watch.js +0 -181
  12. package/cluster-templates/base-templates/debug-workflow.json +0 -375
  13. package/cluster-templates/base-templates/full-workflow.json +0 -581
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -208
  16. package/cluster-templates/conductor-bootstrap.json +0 -124
  17. package/docker/zeroshot-cluster/Dockerfile +0 -152
  18. package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
  19. package/lib/completion.js +0 -174
  20. package/lib/docker-config.js +0 -220
  21. package/lib/git-remote-utils.js +0 -165
  22. package/lib/id-detector.js +0 -56
  23. package/lib/provider-defaults.js +0 -62
  24. package/lib/provider-detection.js +0 -59
  25. package/lib/provider-names.js +0 -57
  26. package/lib/settings/claude-auth.js +0 -78
  27. package/lib/settings.js +0 -503
  28. package/lib/stream-json-parser.js +0 -10
  29. package/scripts/fix-node-pty-permissions.js +0 -75
  30. package/scripts/record-demo.sh +0 -279
  31. package/scripts/setup-merge-queue.sh +0 -170
  32. package/scripts/test-install.sh +0 -40
  33. package/scripts/validate-templates.js +0 -100
  34. package/src/agent/agent-config.js +0 -246
  35. package/src/agent/agent-context-builder.js +0 -444
  36. package/src/agent/agent-hook-executor.js +0 -599
  37. package/src/agent/agent-lifecycle.js +0 -743
  38. package/src/agent/agent-stuck-detector.js +0 -256
  39. package/src/agent/agent-task-executor.js +0 -1690
  40. package/src/agent/agent-trigger-evaluator.js +0 -67
  41. package/src/agent/output-extraction.js +0 -217
  42. package/src/agent/output-reformatter.js +0 -175
  43. package/src/agent/schema-utils.js +0 -146
  44. package/src/agent-wrapper.js +0 -579
  45. package/src/agents/git-pusher-template.js +0 -285
  46. package/src/attach/attach-client.js +0 -438
  47. package/src/attach/attach-server.js +0 -543
  48. package/src/attach/index.js +0 -35
  49. package/src/attach/protocol.js +0 -220
  50. package/src/attach/ring-buffer.js +0 -121
  51. package/src/attach/socket-discovery.js +0 -267
  52. package/src/claude-task-runner.js +0 -574
  53. package/src/config-router.js +0 -80
  54. package/src/config-validator.js +0 -1925
  55. package/src/copy-worker.js +0 -43
  56. package/src/input-helpers.js +0 -65
  57. package/src/isolation-manager.js +0 -1471
  58. package/src/issue-providers/README.md +0 -305
  59. package/src/issue-providers/azure-devops-provider.js +0 -273
  60. package/src/issue-providers/base-provider.js +0 -232
  61. package/src/issue-providers/github-provider.js +0 -179
  62. package/src/issue-providers/gitlab-provider.js +0 -241
  63. package/src/issue-providers/index.js +0 -196
  64. package/src/issue-providers/jira-provider.js +0 -239
  65. package/src/ledger.js +0 -631
  66. package/src/lib/safe-exec.js +0 -88
  67. package/src/logic-engine.js +0 -223
  68. package/src/message-bus-bridge.js +0 -139
  69. package/src/message-bus.js +0 -249
  70. package/src/name-generator.js +0 -232
  71. package/src/orchestrator.js +0 -2815
  72. package/src/preflight.js +0 -639
  73. package/src/process-metrics.js +0 -608
  74. package/src/providers/anthropic/cli-builder.js +0 -73
  75. package/src/providers/anthropic/index.js +0 -204
  76. package/src/providers/anthropic/models.js +0 -23
  77. package/src/providers/anthropic/output-parser.js +0 -177
  78. package/src/providers/base-provider.js +0 -251
  79. package/src/providers/capabilities.js +0 -60
  80. package/src/providers/google/cli-builder.js +0 -55
  81. package/src/providers/google/index.js +0 -116
  82. package/src/providers/google/models.js +0 -24
  83. package/src/providers/google/output-parser.js +0 -101
  84. package/src/providers/index.js +0 -91
  85. package/src/providers/openai/cli-builder.js +0 -133
  86. package/src/providers/openai/index.js +0 -136
  87. package/src/providers/openai/models.js +0 -21
  88. package/src/providers/openai/output-parser.js +0 -143
  89. package/src/providers/opencode/cli-builder.js +0 -42
  90. package/src/providers/opencode/index.js +0 -103
  91. package/src/providers/opencode/models.js +0 -55
  92. package/src/providers/opencode/output-parser.js +0 -122
  93. package/src/schemas/sub-cluster.js +0 -185
  94. package/src/status-footer.js +0 -1026
  95. package/src/sub-cluster-wrapper.js +0 -601
  96. package/src/task-runner.js +0 -30
  97. package/src/template-resolver.js +0 -372
  98. package/src/tui/CHANGES.txt +0 -133
  99. package/src/tui/LAYOUT.md +0 -261
  100. package/src/tui/README.txt +0 -192
  101. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  102. package/src/tui/data-poller.js +0 -349
  103. package/src/tui/demo.js +0 -208
  104. package/src/tui/formatters.js +0 -129
  105. package/src/tui/index.js +0 -193
  106. package/src/tui/keybindings.js +0 -324
  107. package/src/tui/layout.js +0 -334
  108. package/src/tui/renderer.js +0 -184
  109. package/task-lib/attachable-watcher.js +0 -309
  110. package/task-lib/claude-recovery.js +0 -156
  111. package/task-lib/commands/clean.js +0 -50
  112. package/task-lib/commands/episodes.js +0 -105
  113. package/task-lib/commands/get-log-path.js +0 -23
  114. package/task-lib/commands/kill.js +0 -32
  115. package/task-lib/commands/list.js +0 -105
  116. package/task-lib/commands/logs.js +0 -453
  117. package/task-lib/commands/resume.js +0 -42
  118. package/task-lib/commands/run.js +0 -57
  119. package/task-lib/commands/schedule.js +0 -105
  120. package/task-lib/commands/scheduler-cmd.js +0 -96
  121. package/task-lib/commands/schedules.js +0 -148
  122. package/task-lib/commands/status.js +0 -44
  123. package/task-lib/commands/unschedule.js +0 -16
  124. package/task-lib/completion.js +0 -9
  125. package/task-lib/config.js +0 -8
  126. package/task-lib/name-generator.js +0 -230
  127. package/task-lib/package.json +0 -3
  128. package/task-lib/runner.js +0 -201
  129. package/task-lib/scheduler.js +0 -252
  130. package/task-lib/store.js +0 -529
  131. package/task-lib/tui/formatters.js +0 -215
  132. package/task-lib/tui/index.js +0 -197
  133. package/task-lib/tui/layout.js +0 -111
  134. package/task-lib/tui/renderer.js +0 -129
  135. package/task-lib/tui.js +0 -425
  136. package/task-lib/watcher.js +0 -226
@@ -1,67 +0,0 @@
1
- /**
2
- * AgentTriggerEvaluator - Trigger matching and logic evaluation
3
- *
4
- * Provides:
5
- * - Trigger matching based on message topics
6
- * - Logic evaluation via LogicEngine
7
- * - Trigger action determination
8
- */
9
-
10
- /**
11
- * Find trigger matching the message topic
12
- * @param {Object} params - Evaluation parameters
13
- * @param {Array} params.triggers - Agent triggers configuration
14
- * @param {Object} params.message - Message to match against
15
- * @returns {Object|null} Matching trigger or null
16
- */
17
- function findMatchingTrigger({ triggers, message }) {
18
- if (!triggers) {
19
- return null;
20
- }
21
-
22
- return triggers.find((trigger) => {
23
- // Match exact topic or wildcard
24
- if (trigger.topic === '*' || trigger.topic === message.topic) {
25
- return true;
26
- }
27
- // Match topic prefix (e.g., "VALIDATION_*")
28
- if (trigger.topic.endsWith('*')) {
29
- const prefix = trigger.topic.slice(0, -1);
30
- return message.topic.startsWith(prefix);
31
- }
32
- return false;
33
- });
34
- }
35
-
36
- /**
37
- * Evaluate trigger logic
38
- * @param {Object} params - Evaluation parameters
39
- * @param {Object} params.trigger - Trigger to evaluate
40
- * @param {Object} params.message - Triggering message
41
- * @param {Object} params.agent - Agent context (id, role, iteration, cluster_id)
42
- * @param {Object} params.logicEngine - LogicEngine instance
43
- * @returns {boolean} Whether trigger logic passed
44
- */
45
- function evaluateTrigger({ trigger, message, agent, logicEngine }) {
46
- if (!trigger.logic || !trigger.logic.script) {
47
- return true; // No logic = always true
48
- }
49
-
50
- // NO TRY/CATCH - let errors propagate and crash
51
- return logicEngine.evaluate(trigger.logic.script, agent, message);
52
- }
53
-
54
- /**
55
- * Get trigger action type
56
- * @param {Object} trigger - Trigger object
57
- * @returns {string} Action type ('execute_task' or 'stop_cluster')
58
- */
59
- function getTriggerAction(trigger) {
60
- return trigger.action || 'execute_task';
61
- }
62
-
63
- module.exports = {
64
- findMatchingTrigger,
65
- evaluateTrigger,
66
- getTriggerAction,
67
- };
@@ -1,217 +0,0 @@
1
- /**
2
- * Output Extraction Module - Multi-Provider JSON Extraction
3
- *
4
- * Clean extraction pipeline for structured JSON from AI provider outputs.
5
- * Each provider has different output formats - this module normalizes them.
6
- *
7
- * Provider formats:
8
- * - Claude: {"type":"result","result":{...}} or {"type":"result","structured_output":{...}}
9
- * - Codex: Raw text in item.created events, turn.completed has NO result field
10
- * - Gemini: Raw text in message events, result event may have NO result field
11
- *
12
- * Extraction priority (most specific → least specific):
13
- * 1. Result wrapper with content (type:result + result/structured_output field)
14
- * 2. Accumulated text from provider parser events
15
- * 3. Markdown code block extraction
16
- * 4. Direct JSON parse of entire output
17
- */
18
-
19
- const { getProvider, parseChunkWithProvider } = require('../providers');
20
-
21
- /**
22
- * Strip timestamp prefix from log lines.
23
- * Format: [epochMs]content or [epochMs]{json...}
24
- *
25
- * @param {string} line - Raw log line
26
- * @returns {string} Content without timestamp prefix
27
- */
28
- function stripTimestamp(line) {
29
- if (!line || typeof line !== 'string') return '';
30
- let trimmed = line.trim().replace(/\r$/, '');
31
- if (!trimmed) return '';
32
-
33
- const tsMatch = trimmed.match(/^\[(\d{13})\](.*)$/);
34
- if (tsMatch) trimmed = (tsMatch[2] || '').trimStart();
35
-
36
- // In cluster logs, lines are often prefixed like:
37
- // "validator | {json...}"
38
- // Strip the "<agent> | " prefix so we can JSON.parse the event line.
39
- if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {
40
- const pipeMatch = trimmed.match(/^[^|]{1,40}\|\s*(.*)$/);
41
- if (pipeMatch) {
42
- const afterPipe = (pipeMatch[1] || '').trimStart();
43
- if (afterPipe.startsWith('{') || afterPipe.startsWith('[')) return afterPipe;
44
- }
45
- }
46
-
47
- return trimmed;
48
- }
49
-
50
- /**
51
- * Strategy 1: Extract from result wrapper
52
- * Handles Claude CLI format: {"type":"result","result":{...}}
53
- *
54
- * @param {string} output - Raw output
55
- * @returns {object|null} Extracted JSON or null
56
- */
57
- function extractFromResultWrapper(output) {
58
- const lines = output.split('\n');
59
-
60
- for (const line of lines) {
61
- const content = stripTimestamp(line);
62
- if (!content.startsWith('{')) continue;
63
-
64
- try {
65
- const obj = JSON.parse(content);
66
- const extracted = extractResultContent(obj);
67
- if (extracted) return extracted;
68
- } catch {
69
- // Not valid JSON, continue to next line
70
- }
71
- }
72
-
73
- return null;
74
- }
75
-
76
- function extractResultContent(obj) {
77
- // Must be type:result WITH actual content
78
- if (obj?.type !== 'result') return null;
79
-
80
- // Check structured_output first (standard CLI format)
81
- if (obj.structured_output && typeof obj.structured_output === 'object') {
82
- return obj.structured_output;
83
- }
84
-
85
- // Check result field - can be object or string
86
- if (!obj.result) return null;
87
-
88
- if (typeof obj.result === 'object') {
89
- return obj.result;
90
- }
91
-
92
- if (typeof obj.result !== 'string') return null;
93
-
94
- // Result is string - might contain markdown-wrapped JSON
95
- return extractFromMarkdown(obj.result) || extractDirectJson(obj.result);
96
- }
97
-
98
- /**
99
- * Strategy 2: Extract from accumulated text events
100
- * Handles non-Claude providers where JSON is in text content
101
- *
102
- * @param {string} output - Raw output
103
- * @param {string} providerName - Provider name for parser selection
104
- * @returns {object|null} Extracted JSON or null
105
- */
106
- function extractFromTextEvents(output, providerName) {
107
- const provider = getProvider(providerName);
108
- const events = parseChunkWithProvider(provider, output);
109
-
110
- // Accumulate all text events
111
- const textContent = events
112
- .filter((e) => e.type === 'text')
113
- .map((e) => e.text)
114
- .join('');
115
-
116
- if (!textContent.trim()) return null;
117
-
118
- // Try parsing accumulated text as JSON
119
- return extractDirectJson(textContent) || extractFromMarkdown(textContent);
120
- }
121
-
122
- /**
123
- * Strategy 3: Extract JSON from markdown code block
124
- * Handles: ```json\n{...}\n```
125
- *
126
- * @param {string} text - Text that may contain markdown
127
- * @returns {object|null} Extracted JSON or null
128
- */
129
- function extractFromMarkdown(text) {
130
- if (!text) return null;
131
-
132
- // Match ```json ... ``` with any whitespace
133
- const match = text.match(/```json\s*([\s\S]*?)```/);
134
- if (!match) return null;
135
-
136
- try {
137
- const parsed = JSON.parse(match[1].trim());
138
- if (typeof parsed === 'object' && parsed !== null) {
139
- return parsed;
140
- }
141
- } catch {
142
- // Invalid JSON in markdown block
143
- }
144
-
145
- return null;
146
- }
147
-
148
- /**
149
- * Strategy 4: Direct JSON parse
150
- * Handles raw JSON output (single-line or multi-line)
151
- *
152
- * @param {string} text - Text to parse
153
- * @returns {object|null} Parsed JSON or null
154
- */
155
- function extractDirectJson(text) {
156
- if (!text) return null;
157
-
158
- const trimmed = text.trim();
159
- if (!trimmed) return null;
160
-
161
- try {
162
- const parsed = JSON.parse(trimmed);
163
- if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
164
- return parsed;
165
- }
166
- } catch {
167
- // Not valid JSON
168
- }
169
-
170
- return null;
171
- }
172
-
173
- /**
174
- * Main extraction function - tries all strategies in priority order
175
- *
176
- * @param {string} output - Raw output from AI provider CLI
177
- * @param {string} providerName - Provider name ('claude', 'codex', 'gemini')
178
- * @returns {object|null} Extracted JSON object or null if extraction failed
179
- */
180
- function extractJsonFromOutput(output, providerName = 'claude') {
181
- if (!output || typeof output !== 'string') return null;
182
-
183
- const trimmedOutput = output.trim();
184
- if (!trimmedOutput) return null;
185
-
186
- // Check for fatal error indicators
187
- if (trimmedOutput.includes('Task not found') || trimmedOutput.includes('Process terminated')) {
188
- return null;
189
- }
190
-
191
- // Strategy 1: Result wrapper (Claude format)
192
- const fromWrapper = extractFromResultWrapper(trimmedOutput);
193
- if (fromWrapper) return fromWrapper;
194
-
195
- // Strategy 2: Text events (non-Claude providers)
196
- const fromText = extractFromTextEvents(trimmedOutput, providerName);
197
- if (fromText) return fromText;
198
-
199
- // Strategy 3: Markdown extraction
200
- const fromMarkdown = extractFromMarkdown(trimmedOutput);
201
- if (fromMarkdown) return fromMarkdown;
202
-
203
- // Strategy 4: Direct JSON parse (raw output)
204
- const fromDirect = extractDirectJson(trimmedOutput);
205
- if (fromDirect) return fromDirect;
206
-
207
- return null;
208
- }
209
-
210
- module.exports = {
211
- extractJsonFromOutput,
212
- extractFromResultWrapper,
213
- extractFromTextEvents,
214
- extractFromMarkdown,
215
- extractDirectJson,
216
- stripTimestamp,
217
- };
@@ -1,175 +0,0 @@
1
- /**
2
- * Output Reformatter - Convert non-JSON output to valid JSON
3
- *
4
- * When an LLM outputs markdown/text instead of JSON despite schema instructions,
5
- * this module attempts to extract/reformat the content into valid JSON.
6
- *
7
- * STATUS: SDK NOT IMPLEMENTED - Reformatting is not available.
8
- * This module exists for future extension when SDK support is added.
9
- *
10
- * To enable reformatting:
11
- * 1. Implement SDK support in the provider (getSDKEnvVar, callSimple)
12
- * 2. The reformatOutput() function will then work automatically
13
- */
14
-
15
- const DEFAULT_MAX_ATTEMPTS = 3;
16
-
17
- /**
18
- * Build the reformatting prompt
19
- *
20
- * @param {string} rawOutput - The non-JSON output to reformat
21
- * @param {Object} schema - Target JSON schema
22
- * @param {string|null} previousError - Error from previous attempt (for feedback)
23
- * @returns {string} The prompt for the reformatting model
24
- */
25
- function buildReformatPrompt(rawOutput, schema, previousError = null) {
26
- const schemaStr = JSON.stringify(schema, null, 2);
27
- // Truncate long outputs to avoid context limits
28
- const truncatedOutput = rawOutput.length > 4000 ? rawOutput.slice(-4000) : rawOutput;
29
-
30
- let prompt = `Convert this text into a JSON object matching the schema.
31
-
32
- ## SCHEMA
33
- \`\`\`json
34
- ${schemaStr}
35
- \`\`\`
36
-
37
- ## TEXT TO CONVERT
38
- \`\`\`
39
- ${truncatedOutput}
40
- \`\`\`
41
-
42
- ## RULES
43
- - Output ONLY the JSON object
44
- - NO markdown code blocks
45
- - NO explanations
46
- - Start with { end with }
47
- - Match ALL required fields from schema`;
48
-
49
- if (previousError) {
50
- prompt += `
51
-
52
- ## PREVIOUS ATTEMPT FAILED
53
- Error: ${previousError}
54
- Fix this issue in your response.`;
55
- }
56
-
57
- return prompt;
58
- }
59
-
60
- /**
61
- * Attempt to reformat non-JSON output into valid JSON
62
- *
63
- * STATUS: SDK NOT IMPLEMENTED - This function always throws.
64
- * When SDK support is added to providers, this will work automatically.
65
- *
66
- * @param {Object} options
67
- * @param {string} options.rawOutput - The non-JSON output to reformat
68
- * @param {Object} options.schema - Target JSON schema
69
- * @param {string} options.providerName - Provider name (claude, codex, gemini, opencode)
70
- * @param {number} [options.maxAttempts=3] - Maximum reformatting attempts
71
- * @param {Function} [options.onAttempt] - Callback for each attempt (attempt, error)
72
- * @returns {Promise<Object>} The reformatted JSON object
73
- * @throws {Error} Always throws - SDK not implemented
74
- */
75
- function reformatOutput({
76
- rawOutput,
77
- schema: _schema,
78
- providerName,
79
- maxAttempts: _maxAttempts = DEFAULT_MAX_ATTEMPTS,
80
- onAttempt: _onAttempt,
81
- }) {
82
- // SDK not implemented - reformatting not available
83
- // When SDK support is added, uncomment the implementation below
84
- return Promise.reject(
85
- new Error(
86
- `Output reformatting not available: SDK not implemented for provider "${providerName}". ` +
87
- `Agent output must be valid JSON. Raw output (last 200 chars): ${(rawOutput || '').slice(-200)}`
88
- )
89
- );
90
-
91
- // FUTURE: When SDK support is added to providers, uncomment this:
92
- /*
93
- const { getProvider } = require('../providers');
94
- const provider = getProvider(providerName);
95
-
96
- let lastError = null;
97
-
98
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
99
- if (onAttempt) {
100
- onAttempt(attempt, lastError);
101
- }
102
-
103
- const prompt = buildReformatPrompt(rawOutput, schema, lastError);
104
-
105
- try {
106
- const result = await provider.callSimple(prompt, {
107
- level: 'level1',
108
- maxTokens: 2000,
109
- });
110
-
111
- if (!result?.success) {
112
- lastError = result?.error || 'API call failed';
113
- continue;
114
- }
115
-
116
- if (!result?.text) {
117
- lastError = 'Empty response from reformatting model';
118
- continue;
119
- }
120
-
121
- const parsed = extractJsonFromOutput(result.text, providerName);
122
-
123
- if (!parsed) {
124
- lastError = 'Could not extract JSON from reformatted output';
125
- continue;
126
- }
127
-
128
- const validationError = validateAgainstSchema(parsed, schema);
129
- if (validationError) {
130
- lastError = validationError;
131
- continue;
132
- }
133
-
134
- return parsed;
135
- } catch (err) {
136
- lastError = err.message;
137
- }
138
- }
139
-
140
- throw new Error(
141
- `Failed to reformat output after ${maxAttempts} attempts. Last error: ${lastError}`
142
- );
143
- */
144
- }
145
-
146
- /**
147
- * Validate parsed output against JSON schema
148
- *
149
- * @param {Object} parsed - Parsed JSON object
150
- * @param {Object} schema - JSON schema to validate against
151
- * @returns {string|null} Error message if validation failed, null if valid
152
- */
153
- function validateAgainstSchema(parsed, schema) {
154
- const Ajv = require('ajv');
155
- const ajv = new Ajv({ allErrors: true, strict: false });
156
- const validate = ajv.compile(schema);
157
- const valid = validate(parsed);
158
-
159
- if (!valid) {
160
- const errors = (validate.errors || [])
161
- .slice(0, 3)
162
- .map((e) => `${e.instancePath || '#'} ${e.message}`)
163
- .join('; ');
164
- return errors || 'Schema validation failed';
165
- }
166
-
167
- return null;
168
- }
169
-
170
- module.exports = {
171
- reformatOutput,
172
- buildReformatPrompt,
173
- validateAgainstSchema,
174
- DEFAULT_MAX_ATTEMPTS,
175
- };
@@ -1,146 +0,0 @@
1
- /**
2
- * Schema utilities for normalizing LLM output before validation.
3
- *
4
- * PROBLEM: LLMs (Claude, Gemini, Codex) via any interface (CLI, API) may return
5
- * enum values that don't exactly match the schema (e.g., "simple" vs "SIMPLE").
6
- *
7
- * SOLUTION: Normalize enum values BEFORE validation. Provider-agnostic.
8
- */
9
-
10
- const ENUM_VARIATIONS = {
11
- // taskType variations
12
- BUG: 'DEBUG',
13
- FIX: 'DEBUG',
14
- BUGFIX: 'DEBUG',
15
- BUG_FIX: 'DEBUG',
16
- INVESTIGATE: 'DEBUG',
17
- TROUBLESHOOT: 'DEBUG',
18
- IMPLEMENT: 'TASK',
19
- BUILD: 'TASK',
20
- CREATE: 'TASK',
21
- ADD: 'TASK',
22
- FEATURE: 'TASK',
23
- QUESTION: 'INQUIRY',
24
- ASK: 'INQUIRY',
25
- EXPLORE: 'INQUIRY',
26
- RESEARCH: 'INQUIRY',
27
- UNDERSTAND: 'INQUIRY',
28
- // complexity variations
29
- EASY: 'TRIVIAL',
30
- BASIC: 'SIMPLE',
31
- MINOR: 'SIMPLE',
32
- MODERATE: 'STANDARD',
33
- MEDIUM: 'STANDARD',
34
- NORMAL: 'STANDARD',
35
- HARD: 'STANDARD',
36
- COMPLEX: 'CRITICAL',
37
- RISKY: 'CRITICAL',
38
- HIGH_RISK: 'CRITICAL',
39
- DANGEROUS: 'CRITICAL',
40
- };
41
-
42
- /**
43
- * Normalize enum values in parsed JSON to match schema definitions.
44
- *
45
- * Handles:
46
- * - Case mismatches: "simple" → "SIMPLE"
47
- * - Whitespace: " SIMPLE " → "SIMPLE"
48
- * - Common variations: "bug" → "DEBUG", "fix" → "DEBUG"
49
- *
50
- * @param {Object} result - Parsed JSON result from LLM
51
- * @param {Object} schema - JSON schema with enum definitions
52
- * @returns {Object} Normalized result (mutates and returns same object)
53
- */
54
- function normalizeEnumValues(result, schema) {
55
- if (!result || typeof result !== 'object' || !schema?.properties) {
56
- return result;
57
- }
58
-
59
- for (const [key, propSchema] of Object.entries(schema.properties)) {
60
- const matched = normalizeEnumValue({
61
- result,
62
- key,
63
- propSchema,
64
- });
65
- if (matched) {
66
- continue;
67
- }
68
-
69
- // Recursively handle nested objects
70
- normalizeNestedValues(result, propSchema, key);
71
- }
72
-
73
- return result;
74
- }
75
-
76
- function normalizeEnumValue({ result, key, propSchema }) {
77
- if (!propSchema.enum || typeof result[key] !== 'string') {
78
- return false;
79
- }
80
-
81
- const rawValue = result[key];
82
- let value = rawValue.trim().toUpperCase();
83
- value = normalizeEnumCopyValue(value, propSchema.enum, key, rawValue);
84
-
85
- // Find exact match (case-insensitive)
86
- const match = findEnumMatch(propSchema.enum, value);
87
- if (match) {
88
- result[key] = match;
89
- return true;
90
- }
91
-
92
- // Common variations mapping
93
- const variation = ENUM_VARIATIONS[value];
94
- if (variation && propSchema.enum.includes(variation)) {
95
- result[key] = variation;
96
- }
97
-
98
- return false;
99
- }
100
-
101
- function normalizeEnumCopyValue(value, enumValues, key, rawValue) {
102
- // DETECT: Model copied the enum list instead of choosing (e.g., "TRIVIAL|SIMPLE|STANDARD")
103
- if (!value.includes('|')) {
104
- return value;
105
- }
106
-
107
- const parts = value.split('|').map((p) => p.trim());
108
- // Check if this looks like the enum list was copied verbatim
109
- const matchCount = parts.filter((p) => enumValues.includes(p)).length;
110
- if (matchCount < 2) {
111
- return value;
112
- }
113
-
114
- // Model copied the format - pick the first valid option and warn
115
- const firstValid = parts.find((p) => enumValues.includes(p));
116
- if (!firstValid) {
117
- return value;
118
- }
119
-
120
- console.warn(
121
- `⚠️ Model copied enum format instead of choosing. Field "${key}" had "${rawValue}", using "${firstValid}"`
122
- );
123
- return firstValid;
124
- }
125
-
126
- function findEnumMatch(enumValues, value) {
127
- return enumValues.find((entry) => entry.toUpperCase() === value);
128
- }
129
-
130
- function normalizeNestedValues(result, propSchema, key) {
131
- // Recursively handle nested objects
132
- if (propSchema.type === 'object' && propSchema.properties && result[key]) {
133
- normalizeEnumValues(result[key], propSchema);
134
- }
135
-
136
- // Handle arrays of objects
137
- if (propSchema.type === 'array' && propSchema.items?.properties && Array.isArray(result[key])) {
138
- for (const item of result[key]) {
139
- normalizeEnumValues(item, propSchema.items);
140
- }
141
- }
142
- }
143
-
144
- module.exports = {
145
- normalizeEnumValues,
146
- };