salmon-loop 0.3.2 → 0.5.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 (227) hide show
  1. package/dist/cli/authorization/non-interactive.js +9 -13
  2. package/dist/cli/authorization/provider.js +2 -10
  3. package/dist/cli/chat.js +12 -6
  4. package/dist/cli/commands/allowlist.js +1 -1
  5. package/dist/cli/commands/chat.js +13 -13
  6. package/dist/cli/commands/config.js +2 -2
  7. package/dist/cli/commands/mode.js +2 -2
  8. package/dist/cli/commands/parallel.js +1 -1
  9. package/dist/cli/commands/run/handler.js +9 -4
  10. package/dist/cli/commands/run/loop-params.js +2 -0
  11. package/dist/cli/commands/run/parse-options.js +14 -26
  12. package/dist/cli/commands/run/runtime-llm.js +15 -12
  13. package/dist/cli/commands/run/runtime-options.js +3 -1
  14. package/dist/cli/config.js +0 -8
  15. package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
  16. package/dist/cli/locales/en.js +2 -2
  17. package/dist/cli/reporters/standard.js +12 -3
  18. package/dist/cli/reporters/stream-json.js +2 -1
  19. package/dist/cli/slash/runtime.js +2 -2
  20. package/dist/cli/ui/hooks/useLoopEvents.js +1 -1
  21. package/dist/cli/ui/hooks/useLoopState.js +1 -1
  22. package/dist/core/adapters/fs/file-adapter.js +3 -1
  23. package/dist/core/adapters/git/git-adapter.js +6 -3
  24. package/dist/core/adapters/git/git-runner.js +5 -2
  25. package/dist/core/adapters/git/lock-manager.js +7 -4
  26. package/dist/core/ast/parser.js +18 -9
  27. package/dist/core/checkpoint-domain/manifest-store.js +21 -13
  28. package/dist/core/checkpoint-domain/service.js +3 -1
  29. package/dist/core/config/limits.js +1 -1
  30. package/dist/core/config/model-pricing.js +61 -0
  31. package/dist/core/config/schema.js +738 -0
  32. package/dist/core/config/validate.js +11 -922
  33. package/dist/core/context/ast/skeleton-extractor.js +225 -0
  34. package/dist/core/context/ast/source-outline.js +24 -1
  35. package/dist/core/context/budget/dynamic-adjuster.js +20 -5
  36. package/dist/core/context/builder.js +7 -3
  37. package/dist/core/context/cache/store-factory.js +3 -1
  38. package/dist/core/context/dependencies.js +2 -1
  39. package/dist/core/context/effectiveness/persistence.js +50 -0
  40. package/dist/core/context/effectiveness/tracker.js +24 -0
  41. package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
  42. package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
  43. package/dist/core/context/gatherers/ast-gatherer.js +34 -40
  44. package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
  45. package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
  46. package/dist/core/context/gatherers/knowledge-gatherer.js +21 -2
  47. package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
  48. package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
  49. package/dist/core/context/service.js +12 -2
  50. package/dist/core/context/steps/context-gather.js +14 -3
  51. package/dist/core/context/steps/context-targets.js +1 -0
  52. package/dist/core/context/targeting/target-resolver.js +29 -11
  53. package/dist/core/context/token/cache.js +5 -2
  54. package/dist/core/context/token/encoding-registry.js +7 -6
  55. package/dist/core/context/truncation/strategies/json.js +5 -2
  56. package/dist/core/context/truncation/type-detector.js +3 -1
  57. package/dist/core/extensions/index.js +48 -3
  58. package/dist/core/extensions/load.js +3 -2
  59. package/dist/core/extensions/merge.js +5 -1
  60. package/dist/core/extensions/paths.js +8 -2
  61. package/dist/core/extensions/schemas.js +21 -0
  62. package/dist/core/facades/cli-authorization-provider.js +1 -0
  63. package/dist/core/facades/cli-command-chat.js +2 -0
  64. package/dist/core/facades/cli-run-handler.js +1 -0
  65. package/dist/core/facades/cli-utils-serialize.js +2 -0
  66. package/dist/core/feedback/parsers.js +290 -1
  67. package/dist/core/grizzco/dsl/llm-strategy.js +4 -3
  68. package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
  69. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +30 -13
  70. package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
  71. package/dist/core/grizzco/engine/transaction/attempt-failure.js +49 -24
  72. package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
  73. package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
  74. package/dist/core/grizzco/execution/RejectionManager.js +7 -5
  75. package/dist/core/grizzco/runtime/apply-back-runtime.js +5 -2
  76. package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
  77. package/dist/core/grizzco/services/registry.js +18 -0
  78. package/dist/core/grizzco/steps/audit.js +20 -10
  79. package/dist/core/grizzco/steps/autopilot.js +21 -32
  80. package/dist/core/grizzco/steps/display-report.js +4 -11
  81. package/dist/core/grizzco/steps/explore.js +14 -4
  82. package/dist/core/grizzco/steps/generateReview.js +3 -1
  83. package/dist/core/grizzco/steps/patch/prompt-input.js +4 -1
  84. package/dist/core/grizzco/steps/patch.js +1 -0
  85. package/dist/core/grizzco/steps/plan.js +58 -49
  86. package/dist/core/grizzco/steps/research.js +3 -1
  87. package/dist/core/grizzco/steps/tool-runtime.js +3 -0
  88. package/dist/core/grizzco/steps/verify.js +7 -1
  89. package/dist/core/grizzco/validation/AstValidationService.js +3 -1
  90. package/dist/core/grizzco/workers/strata-sync-worker.js +2 -1
  91. package/dist/core/history/input-history.js +3 -1
  92. package/dist/core/intent/chat-intent.js +3 -1
  93. package/dist/core/llm/ai-sdk/message-mapper.js +37 -26
  94. package/dist/core/llm/ai-sdk/request-params.js +2 -6
  95. package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
  96. package/dist/core/llm/ai-sdk/retry-classifier.js +17 -7
  97. package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
  98. package/dist/core/llm/contracts/repair.js +16 -8
  99. package/dist/core/llm/errors.js +18 -14
  100. package/dist/core/llm/output-policy.js +8 -0
  101. package/dist/core/llm/redact.js +1 -3
  102. package/dist/core/llm/retry-utils.js +8 -2
  103. package/dist/core/llm/stream-utils.js +5 -3
  104. package/dist/core/llm/sub-agent-factory.js +51 -0
  105. package/dist/core/llm/tool-calling-stub.js +48 -0
  106. package/dist/core/llm/utils.js +17 -6
  107. package/dist/core/mcp/bridge/prompt-command-provider.js +4 -3
  108. package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
  109. package/dist/core/mcp/bridge/tool-bridge.js +5 -14
  110. package/dist/core/mcp/catalog/discovery.js +3 -1
  111. package/dist/core/mcp/client/connection-manager.js +7 -4
  112. package/dist/core/mcp/client/transport-factory.js +7 -3
  113. package/dist/core/mcp/host/sampling-provider.js +1 -1
  114. package/dist/core/mcp/schema/json-schema-to-zod.js +2 -1
  115. package/dist/core/memory/relevant-retrieval.js +6 -4
  116. package/dist/core/observability/audit-file.js +2 -1
  117. package/dist/core/observability/audit-trail.js +3 -1
  118. package/dist/core/observability/authorization-decisions.js +13 -12
  119. package/dist/core/observability/error-mapping.js +2 -1
  120. package/dist/core/observability/logger.js +2 -1
  121. package/dist/core/observability/monitor.js +24 -0
  122. package/dist/core/observability/run-outcome-reporter.js +1 -0
  123. package/dist/core/observability/token-usage.js +5 -4
  124. package/dist/core/permission-gate/default-gate.js +5 -8
  125. package/dist/core/plan/storage.js +7 -4
  126. package/dist/core/plugin/loader.js +8 -5
  127. package/dist/core/prompts/registry.js +12 -30
  128. package/dist/core/prompts/runtime.js +3 -1
  129. package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
  130. package/dist/core/protocols/a2a/sdk/executor.js +3 -1
  131. package/dist/core/protocols/a2a/sdk/server.js +5 -4
  132. package/dist/core/protocols/acp/acp-command-runner.js +7 -6
  133. package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
  134. package/dist/core/protocols/acp/formal-agent.js +13 -6
  135. package/dist/core/protocols/acp/permission-provider.js +3 -2
  136. package/dist/core/protocols/acp/stdio-server.js +6 -6
  137. package/dist/core/reflection/engine.js +114 -14
  138. package/dist/core/runtime/agent-server-runtime.js +3 -2
  139. package/dist/core/runtime/batch-runner.js +81 -0
  140. package/dist/core/runtime/initialize.js +71 -6
  141. package/dist/core/runtime/loop-finalize.js +3 -0
  142. package/dist/core/runtime/loop-session-runner.js +5 -0
  143. package/dist/core/runtime/loop.js +4 -0
  144. package/dist/core/runtime/paths.js +9 -6
  145. package/dist/core/runtime/spawn-interactive.js +5 -4
  146. package/dist/core/security/redaction.js +3 -2
  147. package/dist/core/session/compaction/index.js +4 -3
  148. package/dist/core/session/compression.js +3 -1
  149. package/dist/core/session/manager.js +26 -38
  150. package/dist/core/session/pruning-strategy.js +2 -1
  151. package/dist/core/session/token-tracker.js +27 -9
  152. package/dist/core/skills/parser.js +3 -2
  153. package/dist/core/skills/permissions.js +2 -2
  154. package/dist/core/skills/runtime/MicroTaskRunner.js +1 -1
  155. package/dist/core/skills/runtime/SkillRunner.js +5 -2
  156. package/dist/core/slash/steps/slash-execute.js +7 -5
  157. package/dist/core/slash/strategy.js +1 -1
  158. package/dist/core/strata/checkpoint/manager.js +16 -10
  159. package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
  160. package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
  161. package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
  162. package/dist/core/strata/interaction/file-system-provider.js +2 -1
  163. package/dist/core/strata/layers/file-state-resolver.js +9 -7
  164. package/dist/core/strata/layers/immutable-git-layer.js +3 -1
  165. package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
  166. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
  167. package/dist/core/strata/layers/worktree.js +9 -10
  168. package/dist/core/strata/runtime/environment.js +2 -1
  169. package/dist/core/strata/runtime/synchronizer.js +28 -26
  170. package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
  171. package/dist/core/structured-output/json-extract.js +3 -1
  172. package/dist/core/structured-output/json-schema-validator.js +1 -13
  173. package/dist/core/sub-agent/artifacts/store.js +2 -1
  174. package/dist/core/sub-agent/context-snapshot.js +12 -6
  175. package/dist/core/sub-agent/controller.js +70 -1
  176. package/dist/core/sub-agent/core/loop.js +25 -3
  177. package/dist/core/sub-agent/core/manager.js +343 -117
  178. package/dist/core/sub-agent/registry-defaults.js +12 -0
  179. package/dist/core/sub-agent/registry.js +8 -0
  180. package/dist/core/sub-agent/summary.js +96 -0
  181. package/dist/core/sub-agent/team.js +98 -0
  182. package/dist/core/sub-agent/tools/task-await.js +109 -0
  183. package/dist/core/sub-agent/tools/task-spawn.js +52 -7
  184. package/dist/core/sub-agent/tools/team.js +92 -0
  185. package/dist/core/sub-agent/types.js +11 -2
  186. package/dist/core/target-runtime/profile.js +3 -1
  187. package/dist/core/tools/audit.js +3 -2
  188. package/dist/core/tools/budget.js +7 -12
  189. package/dist/core/tools/builtin/ast.js +144 -0
  190. package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
  191. package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
  192. package/dist/core/tools/builtin/code-search/executor.js +46 -43
  193. package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
  194. package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
  195. package/dist/core/tools/builtin/fs.js +90 -7
  196. package/dist/core/tools/builtin/git.js +242 -0
  197. package/dist/core/tools/builtin/glob.js +79 -0
  198. package/dist/core/tools/builtin/index.js +53 -111
  199. package/dist/core/tools/builtin/interaction.js +13 -15
  200. package/dist/core/tools/builtin/knowledge.js +146 -4
  201. package/dist/core/tools/builtin/proposal.js +14 -3
  202. package/dist/core/tools/builtin/verify.js +35 -3
  203. package/dist/core/tools/capability/executor.js +5 -5
  204. package/dist/core/tools/headless-payload.js +1 -3
  205. package/dist/core/tools/mapper.js +8 -42
  206. package/dist/core/tools/parallel/persistence.js +17 -5
  207. package/dist/core/tools/parallel/scheduler.js +23 -21
  208. package/dist/core/tools/permissions/permission-rules.js +69 -115
  209. package/dist/core/tools/plugins/loader.js +4 -3
  210. package/dist/core/tools/router.js +112 -58
  211. package/dist/core/tools/session.js +64 -102
  212. package/dist/core/tools/streaming/ToolCallAccumulator.js +1 -3
  213. package/dist/core/tools/tool-visibility.js +2 -1
  214. package/dist/core/tools/types.js +10 -0
  215. package/dist/core/types/batch.js +2 -0
  216. package/dist/core/utils/error.js +79 -0
  217. package/dist/core/utils/sanitizer.js +5 -2
  218. package/dist/core/utils/serialize.js +66 -0
  219. package/dist/core/utils/zod.js +29 -0
  220. package/dist/core/verification/detect-runner.js +86 -0
  221. package/dist/core/verification/runner.js +76 -0
  222. package/dist/core/version.js +3 -1
  223. package/dist/core/workspace/capabilities.js +3 -2
  224. package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
  225. package/dist/languages/python/index.js +154 -0
  226. package/dist/locales/en.js +8 -1
  227. package/package.json +2 -1
@@ -21,4 +21,25 @@ export const SkillsConfigSchema = z
21
21
  discovery: skillDiscoverySchema.optional().default({}),
22
22
  })
23
23
  .strict();
24
+ export const AgentProfileConfigSchema = z.object({
25
+ id: z.string().min(1),
26
+ name: z.string().min(1),
27
+ role: z.string().min(1),
28
+ description: z.string(),
29
+ allowedTools: z.array(z.string()).optional(),
30
+ toolInheritance: z.enum(['none', 'safe', 'all']).optional(),
31
+ permissionMode: z.enum(['default', 'plan', 'bypassPermissions']).optional(),
32
+ systemPrompt: z.string().optional(),
33
+ readOnly: z.boolean().optional(),
34
+ stratagem: z.enum(['investigator', 'surgeon', 'janitor']).optional(),
35
+ maxTokens: z.number().positive().optional(),
36
+ maxAttempts: z.number().positive().optional(),
37
+ timeoutMs: z.number().positive().optional(),
38
+ model: z.string().optional(),
39
+ enabled: z.boolean().optional(),
40
+ });
41
+ export const AgentsConfigSchema = z.object({
42
+ version: z.literal(1),
43
+ agents: z.array(AgentProfileConfigSchema),
44
+ });
24
45
  //# sourceMappingURL=schemas.js.map
@@ -1,2 +1,3 @@
1
+ export { DEFAULT_TOOL_AUTH } from '../config/defaults.js';
1
2
  export { getLogger } from '../observability/logger.js';
2
3
  //# sourceMappingURL=cli-authorization-provider.js.map
@@ -1,9 +1,11 @@
1
1
  export { ConfigError, normalizePermissionMode, resolveConfig } from '../config/index.js';
2
2
  export { ExtensionConfigError, resolveExtensions } from '../extensions/index.js';
3
3
  export { createRuntimeLlm } from '../llm/factory.js';
4
+ export { createSubAgentLlmFactory } from '../llm/sub-agent-factory.js';
4
5
  export { getLogger } from '../observability/logger.js';
5
6
  export { PluginLoader } from '../plugin/loader.js';
6
7
  export { resolveExecutionProfile } from '../runtime/execution-profile.js';
7
8
  export { clearPluginRegistry, createPluginRegistry, setPluginRegistry, } from '../plugin/registry.js';
8
9
  export { clearPromptRegistry, createPromptRegistry, setPromptRegistry, } from '../prompts/registry.js';
10
+ export { getString } from '../utils/serialize.js';
9
11
  //# sourceMappingURL=cli-command-chat.js.map
@@ -1,4 +1,5 @@
1
1
  export { normalizePermissionMode } from '../config/index.js';
2
+ export { createSubAgentLlmFactory } from '../llm/sub-agent-factory.js';
2
3
  export { getLogger, PlainReporter, SilentReporter } from '../observability/logger.js';
3
4
  export { createPluginRegistry, setPluginRegistry, } from '../plugin/registry.js';
4
5
  export { createPromptRegistry, setPromptRegistry, } from '../prompts/registry.js';
@@ -0,0 +1,2 @@
1
+ export { isRecord } from '../utils/serialize.js';
2
+ //# sourceMappingURL=cli-utils-serialize.js.map
@@ -1,3 +1,4 @@
1
+ import { getLogger } from '../observability/logger.js';
1
2
  export function parseTscOutput(output) {
2
3
  const diagnostics = [];
3
4
  const lines = output.split('\n');
@@ -46,8 +47,296 @@ export function parsePythonError(output) {
46
47
  }
47
48
  return diagnostics;
48
49
  }
50
+ export function parsePytestOutput(output) {
51
+ const diagnostics = [];
52
+ const lines = output.split('\n');
53
+ // Pattern 1: FAILED lines (--tb=short -q format)
54
+ // e.g. FAILED tests/test_foo.py::test_bar - AssertionError: expected 5, got 3
55
+ // e.g. FAILED tests/test_foo.py::TestClass::test_method - ValueError: bad value
56
+ const failedRegex = /^FAILED\s+(\S+?)\s+-\s+(.+)$/;
57
+ // Pattern 2: Assertion detail lines (E prefix)
58
+ // e.g. E AssertionError: expected 5, got 3
59
+ const assertionRegex = /^\s*E\s{2,}(\w+(?:Error|Exception)?):\s*(.+)$/;
60
+ // Pattern 3: File:line reference in traceback
61
+ // e.g. tests/test_module.py:42: in test_function
62
+ const fileLineRegex = /^\s*(\S+\.py):(\d+): in /;
63
+ // Pattern 4: One-line format (--tb=line)
64
+ // e.g. tests/test_module.py:42: AssertionError
65
+ const lineFormatRegex = /^(\S+\.py):(\d+):\s+(\w+(?:Error|Exception)?)$/;
66
+ // Track current file context for enhancing FAILED diagnostics
67
+ let currentFile = null;
68
+ let currentLine = null;
69
+ // Map test_id -> { file, line, assertion } for merging
70
+ const assertionMap = new Map();
71
+ for (let i = 0; i < lines.length; i++) {
72
+ const line = lines[i];
73
+ // Collect file:line context from tracebacks
74
+ const fileMatch = line.match(fileLineRegex);
75
+ if (fileMatch) {
76
+ currentFile = fileMatch[1];
77
+ currentLine = parseInt(fileMatch[2]);
78
+ continue;
79
+ }
80
+ // Collect assertion details
81
+ const assertionMatch = line.match(assertionRegex);
82
+ if (assertionMatch && currentFile) {
83
+ // Associate with the most recent file context
84
+ const key = `${currentFile}:${currentLine}`;
85
+ if (!assertionMap.has(key)) {
86
+ assertionMap.set(key, {
87
+ file: currentFile,
88
+ line: currentLine ?? undefined,
89
+ assertion: `${assertionMatch[1]}: ${assertionMatch[2]}`,
90
+ });
91
+ }
92
+ continue;
93
+ }
94
+ // Parse FAILED lines — the primary signal
95
+ const failedMatch = line.match(failedRegex);
96
+ if (failedMatch) {
97
+ const testId = failedMatch[1]; // e.g. tests/test_foo.py::test_bar
98
+ const errorMessage = failedMatch[2];
99
+ // Extract file from test_id (everything before ::)
100
+ const testFile = testId.split('::')[0];
101
+ diagnostics.push({
102
+ file: testFile,
103
+ severity: 'error',
104
+ message: errorMessage,
105
+ source: 'pytest',
106
+ });
107
+ continue;
108
+ }
109
+ // Parse one-line format
110
+ const lineFormatMatch = line.match(lineFormatRegex);
111
+ if (lineFormatMatch) {
112
+ diagnostics.push({
113
+ file: lineFormatMatch[1],
114
+ line: parseInt(lineFormatMatch[2]),
115
+ severity: 'error',
116
+ message: lineFormatMatch[3],
117
+ source: 'pytest',
118
+ });
119
+ }
120
+ }
121
+ // Enhance FAILED diagnostics with file:line info from tracebacks
122
+ if (diagnostics.length > 0 && assertionMap.size > 0) {
123
+ for (const diag of diagnostics) {
124
+ if (diag.source === 'pytest' && !diag.line) {
125
+ // Find matching assertion detail by file
126
+ for (const [, info] of assertionMap) {
127
+ if (info.file === diag.file && info.line) {
128
+ diag.line = info.line;
129
+ if (info.assertion && diag.message.length < info.assertion.length) {
130
+ diag.message = info.assertion;
131
+ }
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ return diagnostics;
139
+ }
140
+ export function parseJestOutput(output) {
141
+ const diagnostics = [];
142
+ const lines = output.split('\n');
143
+ // Pattern 1: FAIL src/foo.test.ts
144
+ const suiteFailRegex = /^(?:FAIL|✗|✘)\s+(\S+)$/;
145
+ // Pattern 2: ● Test suite failed to run / ● test_name
146
+ const bulletRegex = /^\s*●\s+(.+)$/;
147
+ // Pattern 3: inline error: src/foo.ts:10:5 - error TS2322: ...
148
+ const inlineRegex = /^(\S+\.tsx?):(\d+):(\d+)\s+-\s+(error|warning)\s+(.+)$/;
149
+ let currentSuite = null;
150
+ for (const line of lines) {
151
+ const suiteMatch = line.match(suiteFailRegex);
152
+ if (suiteMatch) {
153
+ currentSuite = suiteMatch[1];
154
+ continue;
155
+ }
156
+ const inlineMatch = line.match(inlineRegex);
157
+ if (inlineMatch) {
158
+ diagnostics.push({
159
+ file: inlineMatch[1],
160
+ line: parseInt(inlineMatch[2]),
161
+ column: parseInt(inlineMatch[3]),
162
+ severity: inlineMatch[4],
163
+ message: inlineMatch[5],
164
+ source: 'jest',
165
+ });
166
+ continue;
167
+ }
168
+ const bulletMatch = line.match(bulletRegex);
169
+ if (bulletMatch && currentSuite) {
170
+ diagnostics.push({
171
+ file: currentSuite,
172
+ severity: 'error',
173
+ message: bulletMatch[1],
174
+ source: 'jest',
175
+ });
176
+ }
177
+ }
178
+ return diagnostics;
179
+ }
180
+ // ── Structured JSON parsers ──────────────────────────────────────────────────
181
+ /** Parse jest --json output (single JSON object). */
182
+ export function parseJestJson(output) {
183
+ const diagnostics = [];
184
+ let parsed;
185
+ try {
186
+ parsed = JSON.parse(output);
187
+ }
188
+ catch (error) {
189
+ getLogger().debug(`[Parsers] Failed to parse jest JSON output: ${error instanceof Error ? error.message : String(error)}`);
190
+ return [];
191
+ }
192
+ for (const suite of parsed.testResults ?? []) {
193
+ for (const assertion of suite.assertionResults ?? []) {
194
+ if (assertion.status === 'failed') {
195
+ diagnostics.push({
196
+ file: suite.name,
197
+ severity: 'error',
198
+ message: (assertion.failureMessages ?? []).join('\n') ||
199
+ `Test failed: ${(assertion.ancestorTitles ?? []).join(' > ')} > ${assertion.fullName ?? assertion.title}`,
200
+ source: 'jest',
201
+ });
202
+ }
203
+ }
204
+ }
205
+ return diagnostics;
206
+ }
207
+ /** Extract test summary from jest --json output. */
208
+ export function parseJestJsonSummary(output) {
209
+ try {
210
+ const parsed = JSON.parse(output);
211
+ if (typeof parsed.numTotalTests === 'number') {
212
+ const passed = parsed.numPassedTests ?? 0;
213
+ const failed = parsed.numFailedTests ?? 0;
214
+ const total = parsed.numTotalTests;
215
+ return { total, passed, failed, skipped: total - passed - failed };
216
+ }
217
+ }
218
+ catch (error) {
219
+ /* not JSON */
220
+ getLogger().debug(`[Parsers] Failed to parse jest JSON summary: ${error instanceof Error ? error.message : String(error)}`);
221
+ }
222
+ return undefined;
223
+ }
224
+ /** Parse eslint --format json output (JSON array). */
225
+ export function parseEslintJson(output) {
226
+ const diagnostics = [];
227
+ let parsed;
228
+ try {
229
+ parsed = JSON.parse(output);
230
+ }
231
+ catch (error) {
232
+ getLogger().debug(`[Parsers] Failed to parse eslint JSON output: ${error instanceof Error ? error.message : String(error)}`);
233
+ return [];
234
+ }
235
+ if (!Array.isArray(parsed))
236
+ return [];
237
+ for (const file of parsed) {
238
+ for (const msg of file.messages ?? []) {
239
+ diagnostics.push({
240
+ file: file.filePath,
241
+ line: msg.line,
242
+ column: msg.column,
243
+ severity: msg.severity === 2 ? 'error' : 'warning',
244
+ message: msg.message + (msg.ruleId ? ` (${msg.ruleId})` : ''),
245
+ source: 'eslint',
246
+ });
247
+ }
248
+ }
249
+ return diagnostics;
250
+ }
251
+ /** Parse bun test --json NDJSON output. */
252
+ export function parseBunTestNdjson(output) {
253
+ const diagnostics = [];
254
+ for (const line of output.split('\n')) {
255
+ const trimmed = line.trim();
256
+ if (!trimmed.startsWith('{'))
257
+ continue;
258
+ try {
259
+ const evt = JSON.parse(trimmed);
260
+ if (evt.type === 'test-fail' || evt.data?.status === 'fail') {
261
+ const data = evt.data ?? {};
262
+ diagnostics.push({
263
+ file: data.file ?? data.sourceFile ?? 'unknown',
264
+ severity: 'error',
265
+ message: data.error?.message ?? data.message ?? 'Test failed',
266
+ source: 'bun',
267
+ });
268
+ }
269
+ }
270
+ catch (error) {
271
+ /* skip non-JSON lines */
272
+ getLogger().debug(`[Parsers] Failed to parse bun test NDJSON line: ${error instanceof Error ? error.message : String(error)}`);
273
+ }
274
+ }
275
+ return diagnostics;
276
+ }
277
+ /** Parse go test -json NDJSON output. */
278
+ export function parseGoTestNdjson(output) {
279
+ const diagnostics = [];
280
+ for (const line of output.split('\n')) {
281
+ const trimmed = line.trim();
282
+ if (!trimmed.startsWith('{'))
283
+ continue;
284
+ try {
285
+ const evt = JSON.parse(trimmed);
286
+ if (evt.Action === 'fail' && evt.Test) {
287
+ diagnostics.push({
288
+ file: evt.Package ?? 'unknown',
289
+ severity: 'error',
290
+ message: `Test failed: ${evt.Test}`,
291
+ source: 'go',
292
+ });
293
+ }
294
+ }
295
+ catch (error) {
296
+ /* skip non-JSON lines */
297
+ getLogger().debug(`[Parsers] Failed to parse go test NDJSON line: ${error instanceof Error ? error.message : String(error)}`);
298
+ }
299
+ }
300
+ return diagnostics;
301
+ }
302
+ /** Dispatch to the correct structured parser based on runner type. */
303
+ export function parseRunnerOutput(output, runner) {
304
+ switch (runner) {
305
+ case 'jest':
306
+ case 'vitest':
307
+ return parseJestJson(output);
308
+ case 'eslint':
309
+ return parseEslintJson(output);
310
+ case 'bun':
311
+ return parseBunTestNdjson(output);
312
+ case 'go':
313
+ return parseGoTestNdjson(output);
314
+ case 'pytest':
315
+ case 'tsc':
316
+ case 'unknown':
317
+ default:
318
+ return parseGenericOutput(output);
319
+ }
320
+ }
321
+ /** Extract test summary from structured JSON output when available. */
322
+ export function parseStructuredSummary(output, runner) {
323
+ switch (runner) {
324
+ case 'jest':
325
+ case 'vitest':
326
+ return parseJestJsonSummary(output);
327
+ default:
328
+ return undefined;
329
+ }
330
+ }
331
+ // ── Text-based heuristic dispatcher (fallback) ──────────────────────────────
49
332
  export function parseGenericOutput(output) {
50
- // Fallback or combined parser
333
+ // Try pytest first (SWE-bench's primary test runner)
334
+ const pytest = parsePytestOutput(output);
335
+ if (pytest.length > 0)
336
+ return pytest;
337
+ const jest = parseJestOutput(output);
338
+ if (jest.length > 0)
339
+ return jest;
51
340
  const tsc = parseTscOutput(output);
52
341
  if (tsc.length > 0)
53
342
  return tsc;
@@ -1,12 +1,13 @@
1
1
  import { resolveLlmCapabilities } from '../../llm/capabilities.js';
2
2
  import { Phase } from '../../types/runtime.js';
3
+ import { FileStatus, OpType } from '../domain/grizzco-types.js';
3
4
  import { DecisionEngine, PlanBuilder } from './DecisionEngine.js';
4
5
  function defaultMaxRoundsForPhase(phase) {
5
6
  // Explore needs more rounds to navigate the codebase
6
7
  if (phase === Phase.EXPLORE)
7
8
  return 8;
8
9
  if (phase === Phase.AUTOPILOT)
9
- return 8;
10
+ return 12;
10
11
  if (phase === Phase.RESEARCH)
11
12
  return 8;
12
13
  if (phase === Phase.PLAN)
@@ -24,7 +25,7 @@ export function resolveLlmToolCallingPolicy(phase, llm) {
24
25
  repoRoot: '',
25
26
  file: {
26
27
  path: '',
27
- status: 0,
28
+ status: FileStatus.CLEAN,
28
29
  isBinary: false,
29
30
  isSymlink: false,
30
31
  isIgnored: false,
@@ -32,7 +33,7 @@ export function resolveLlmToolCallingPolicy(phase, llm) {
32
33
  size: 0,
33
34
  },
34
35
  operation: {
35
- type: 0,
36
+ type: OpType.PATCH,
36
37
  path: '',
37
38
  },
38
39
  options: {
@@ -1,4 +1,5 @@
1
1
  import { LIMITS } from '../../../config/limits.js';
2
+ import { getLogger } from '../../../observability/logger.js';
2
3
  export class LoopTelemetry {
3
4
  now;
4
5
  logs = [];
@@ -16,11 +17,13 @@ export class LoopTelemetry {
16
17
  try {
17
18
  return JSON.stringify(output);
18
19
  }
19
- catch {
20
+ catch (error) {
21
+ getLogger().debug(`[LoopTelemetry] JSON.stringify failed, falling back to String(): ${error instanceof Error ? error.message : String(error)}`);
20
22
  try {
21
23
  return String(output);
22
24
  }
23
- catch {
25
+ catch (stringError) {
26
+ getLogger().debug(`[LoopTelemetry] String() conversion also failed: ${stringError instanceof Error ? stringError.message : String(stringError)}`);
24
27
  return '[Unserializable]';
25
28
  }
26
29
  }
@@ -5,6 +5,7 @@ import { buildFailureEnvelope } from '../../../observability/error-envelope.js';
5
5
  import { getTokenUsageFromAuditTrail } from '../../../observability/token-usage.js';
6
6
  import { resolveExecutionProfile } from '../../../runtime/execution-profile.js';
7
7
  import { ErrorType, Phase } from '../../../types/runtime.js';
8
+ import { isRecord } from '../../../utils/serialize.js';
8
9
  const ROOT_CAUSE_CODES = [
9
10
  'LLM_RATE_LIMITED',
10
11
  'LLM_UPSTREAM_5XX',
@@ -22,6 +23,12 @@ function toRootCauseCode(code) {
22
23
  ? code
23
24
  : undefined;
24
25
  }
26
+ function applyPassthroughFields(result, options) {
27
+ if (options.tags)
28
+ result.tags = options.tags;
29
+ if (options.providerMeta)
30
+ result.providerMeta = options.providerMeta;
31
+ }
25
32
  export function buildLoopResultFromTransaction({ executionReport, flowMode, options, telemetry, auditPath, }) {
26
33
  const profile = resolveExecutionProfile(flowMode);
27
34
  const rootCause = toRootCauseCode(executionReport.lastErrorCode);
@@ -34,17 +41,19 @@ export function buildLoopResultFromTransaction({ executionReport, flowMode, opti
34
41
  : 'NON_RETRYABLE_FAILURE';
35
42
  const ctx = executionReport.lastContext ??
36
43
  executionReport.flowReport.data;
44
+ const ctxObj = isRecord(ctx) ? ctx : null;
37
45
  const contextHash = (() => {
38
- const hashFromBudget = ctx?.contextResult?.meta?.contextHash;
46
+ const contextResult = isRecord(ctxObj?.contextResult) ? ctxObj.contextResult : null;
47
+ const meta = isRecord(contextResult?.meta) ? contextResult.meta : null;
48
+ const hashFromBudget = meta?.contextHash;
39
49
  if (typeof hashFromBudget === 'string')
40
50
  return hashFromBudget;
41
- const hashFromContext = ctx && typeof ctx === 'object' && 'context' in ctx
42
- ? ctx.context?.contextHash
43
- : undefined;
51
+ const context = isRecord(ctxObj?.context) ? ctxObj.context : null;
52
+ const hashFromContext = context?.contextHash;
44
53
  return typeof hashFromContext === 'string' ? hashFromContext : undefined;
45
54
  })();
46
- const verifyArtifact = ctx && typeof ctx === 'object' && 'verifyArtifact' in ctx
47
- ? ctx.verifyArtifact
55
+ const verifyArtifact = ctxObj && typeof ctxObj.verifyArtifact !== 'undefined'
56
+ ? ctxObj.verifyArtifact
48
57
  : executionReport.lastVerifyArtifact;
49
58
  const artifactHints = (() => {
50
59
  const hints = {
@@ -71,12 +80,14 @@ export function buildLoopResultFromTransaction({ executionReport, flowMode, opti
71
80
  }
72
81
  return hints;
73
82
  })();
83
+ const report = isRecord(ctxObj?.report) ? ctxObj.report : null;
74
84
  const assistantMessage = ((flowMode === 'answer' || profile.driver === 'agent') &&
75
- ctx?.report?.summary?.trim?.()
76
- ? String(ctx.report.summary).trim()
85
+ typeof report?.summary === 'string' &&
86
+ report.summary.trim()
87
+ ? report.summary.trim()
77
88
  : undefined) ?? undefined;
78
- const finalPatch = ctx && typeof ctx === 'object' && 'diff' in ctx ? ctx.diff : undefined;
79
- const changedFiles = ctx && typeof ctx === 'object' && 'changedFiles' in ctx ? ctx.changedFiles : undefined;
89
+ const finalPatch = ctxObj && typeof ctxObj.diff === 'string' ? ctxObj.diff : undefined;
90
+ const changedFiles = Array.isArray(ctxObj?.changedFiles) ? ctxObj.changedFiles : undefined;
80
91
  const authorizationDecisions = (() => {
81
92
  if (!options.eventPayload?.includeAuthorizationDecisions)
82
93
  return undefined;
@@ -88,7 +99,7 @@ export function buildLoopResultFromTransaction({ executionReport, flowMode, opti
88
99
  const usage = getTokenUsageFromAuditTrail() ?? undefined;
89
100
  const budgetSummary = getBudgetRunSummary() ?? undefined;
90
101
  if (options.dryRun || profile.readOnly) {
91
- return {
102
+ const result = {
92
103
  success: true,
93
104
  reason: text.loop.operationCompleted,
94
105
  reasonCode: options.dryRun ? 'DRY_RUN' : 'SUCCESS',
@@ -110,8 +121,10 @@ export function buildLoopResultFromTransaction({ executionReport, flowMode, opti
110
121
  fsMode: executionReport.flowReport.fsMode ?? flowMode,
111
122
  budgetSummary,
112
123
  };
124
+ applyPassthroughFields(result, options);
125
+ return result;
113
126
  }
114
- return {
127
+ const result = {
115
128
  success: true,
116
129
  reason: text.loop.operationCompleted,
117
130
  reasonCode: 'SUCCESS',
@@ -133,6 +146,8 @@ export function buildLoopResultFromTransaction({ executionReport, flowMode, opti
133
146
  fsMode: executionReport.flowReport.fsMode ?? flowMode,
134
147
  budgetSummary,
135
148
  };
149
+ applyPassthroughFields(result, options);
150
+ return result;
136
151
  }
137
152
  const retryFailureReason = executionReport.history.at(-1)?.error ?? text.loop.loopExecutionFailed;
138
153
  const failureReason = executionReport.retryExhausted
@@ -159,7 +174,7 @@ export function buildLoopResultFromTransaction({ executionReport, flowMode, opti
159
174
  remediationSteps,
160
175
  fallbackMessage: failureReason,
161
176
  });
162
- return {
177
+ const result = {
163
178
  success: false,
164
179
  reason: resultReason,
165
180
  reasonCode,
@@ -190,6 +205,8 @@ export function buildLoopResultFromTransaction({ executionReport, flowMode, opti
190
205
  ? executionReport.terminalInputRequired
191
206
  : undefined,
192
207
  };
208
+ applyPassthroughFields(result, options);
209
+ return result;
193
210
  }
194
211
  export function buildLoopFailureResult({ message, flowMode, telemetry, auditPath, reasonCode, failurePhase, errorCode, }) {
195
212
  const usage = getTokenUsageFromAuditTrail() ?? undefined;