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
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Test runner detection and JSON flag injection.
3
+ *
4
+ * Detects which test runner a command invokes and, when the runner
5
+ * supports structured JSON output, rewrites the command to emit JSON
6
+ * so downstream parsers can consume machine-readable data instead of
7
+ * regex-matching human-friendly text.
8
+ */
9
+ // ── Detection ────────────────────────────────────────────────────────────────
10
+ /** Heuristic detection from the raw command string. */
11
+ export function detectRunner(command) {
12
+ const cmd = command.toLowerCase();
13
+ // Order matters: more specific patterns first
14
+ if (/\bvitest\b/.test(cmd))
15
+ return 'vitest';
16
+ if (/\bjest\b/.test(cmd))
17
+ return 'jest';
18
+ if (/\bpytest\b/.test(cmd) || /\bpy\.test\b/.test(cmd))
19
+ return 'pytest';
20
+ if (/\btsc\b/.test(cmd))
21
+ return 'tsc';
22
+ if (/\beslint\b/.test(cmd))
23
+ return 'eslint';
24
+ if (/\bbun\s+test\b/.test(cmd))
25
+ return 'bun';
26
+ if (/\bgo\s+test\b/.test(cmd))
27
+ return 'go';
28
+ // npm/pnpm/yarn script proxies — try to infer from script name
29
+ if (/\bnpm\s+run\s+/.test(cmd) || /\bpnpm\s+/.test(cmd) || /\byarn\s+/.test(cmd)) {
30
+ if (/test:unit|test:e2e|test:integration|test:full/.test(cmd))
31
+ return 'unknown';
32
+ if (/\btest\b/.test(cmd))
33
+ return 'unknown'; // could be anything
34
+ }
35
+ return 'unknown';
36
+ }
37
+ // ── JSON flag injection ──────────────────────────────────────────────────────
38
+ /** Returns true when the runner supports structured JSON output. */
39
+ export function supportsJsonOutput(runner) {
40
+ return (runner === 'jest' ||
41
+ runner === 'vitest' ||
42
+ runner === 'eslint' ||
43
+ runner === 'bun' ||
44
+ runner === 'go');
45
+ }
46
+ /**
47
+ * Rewrite the command to emit structured output.
48
+ *
49
+ * Only modifies commands for runners that support JSON.
50
+ * Returns the original command unchanged when the runner
51
+ * has no JSON mode (pytest, tsc) or is unknown.
52
+ */
53
+ export function injectJsonFlags(command, runner) {
54
+ switch (runner) {
55
+ case 'jest':
56
+ // jest --json --outputFile=/dev/null would suppress file write;
57
+ // but --json alone prints to stdout which is what we want.
58
+ // Avoid duplicating --json if already present.
59
+ if (command.includes('--json'))
60
+ return command;
61
+ return `${command} --json`;
62
+ case 'vitest':
63
+ // vitest --reporter=json --run (--run prevents watch mode)
64
+ if (command.includes('--reporter=json') || command.includes('--reporter json'))
65
+ return command;
66
+ return `${command} --reporter=json --run`;
67
+ case 'eslint':
68
+ // eslint --format json
69
+ if (command.includes('--format json') || command.includes('--format=json'))
70
+ return command;
71
+ return `${command} --format json`;
72
+ case 'bun':
73
+ // bun test --json (outputs NDJSON to stdout)
74
+ if (command.includes('--json'))
75
+ return command;
76
+ return `${command} --json`;
77
+ case 'go':
78
+ // go test -json
79
+ if (command.includes('-json'))
80
+ return command;
81
+ return `${command} -json`;
82
+ default:
83
+ return command;
84
+ }
85
+ }
86
+ //# sourceMappingURL=detect-runner.js.map
@@ -90,6 +90,82 @@ export function isRetryable(error) {
90
90
  return false;
91
91
  }
92
92
  }
93
+ /**
94
+ * Extract test summary counts from runner output.
95
+ * Supports jest/vitest, pytest, bun test, and go test formats.
96
+ */
97
+ export function parseTestSummary(output) {
98
+ const lower = output.toLowerCase();
99
+ let passed = 0;
100
+ let failed = 0;
101
+ let skipped = 0;
102
+ let found = false;
103
+ // jest/vitest: "Tests: 2 failed, 5 passed, 7 total"
104
+ const jestMatch = lower.match(/tests:\s+(?:(\d+)\s+failed,\s*)?(?:(\d+)\s+passed,\s*)?(\d+)\s+total/);
105
+ if (jestMatch) {
106
+ failed = parseInt(jestMatch[1] || '0');
107
+ passed = parseInt(jestMatch[2] || '0');
108
+ const total = parseInt(jestMatch[3]);
109
+ skipped = total - passed - failed;
110
+ found = true;
111
+ }
112
+ // jest/vitest: "Test Suites: 1 failed, 3 passed, 4 total"
113
+ if (!found) {
114
+ const suiteMatch = lower.match(/test suites:\s+(?:(\d+)\s+failed,\s*)?(?:(\d+)\s+passed,\s*)?(\d+)\s+total/);
115
+ if (suiteMatch) {
116
+ failed = parseInt(suiteMatch[1] || '0');
117
+ passed = parseInt(suiteMatch[2] || '0');
118
+ const total = parseInt(suiteMatch[3]);
119
+ skipped = total - passed - failed;
120
+ found = true;
121
+ }
122
+ }
123
+ // pytest: "2 failed, 5 passed"
124
+ if (!found) {
125
+ const pytestMatch = lower.match(/(?:(\d+)\s+failed,?\s*)?(?:(\d+)\s+passed,?\s*)?(?:(\d+)\s+skipped)?/);
126
+ if (pytestMatch && (pytestMatch[1] || pytestMatch[2])) {
127
+ failed = parseInt(pytestMatch[1] || '0');
128
+ passed = parseInt(pytestMatch[2] || '0');
129
+ skipped = parseInt(pytestMatch[3] || '0');
130
+ found = true;
131
+ }
132
+ }
133
+ // bun test: "5 pass | 1 fail"
134
+ if (!found) {
135
+ const bunMatch = lower.match(/(\d+)\s+pass\s*\|\s*(\d+)\s+fail/);
136
+ if (bunMatch) {
137
+ passed = parseInt(bunMatch[1]);
138
+ failed = parseInt(bunMatch[2]);
139
+ found = true;
140
+ }
141
+ }
142
+ // go test: "ok" / "FAIL" with counts
143
+ if (!found) {
144
+ const goMatch = lower.match(/(\d+)\s+of\s+(\d+)\s+tests?\s+passed/);
145
+ if (goMatch) {
146
+ passed = parseInt(goMatch[1]);
147
+ const total = parseInt(goMatch[2]);
148
+ failed = total - passed;
149
+ found = true;
150
+ }
151
+ }
152
+ // Generic: "N passed" / "N failed" / "N skipped"
153
+ if (!found) {
154
+ const passMatch = lower.match(/(\d+)\s+pass(?:ed|ing)/);
155
+ const failMatch = lower.match(/(\d+)\s+fail(?:ed|ing)/);
156
+ const skipMatch = lower.match(/(\d+)\s+skip(?:ped)/);
157
+ if (passMatch || failMatch) {
158
+ passed = passMatch ? parseInt(passMatch[1]) : 0;
159
+ failed = failMatch ? parseInt(failMatch[1]) : 0;
160
+ skipped = skipMatch ? parseInt(skipMatch[1]) : 0;
161
+ found = true;
162
+ }
163
+ }
164
+ if (!found)
165
+ return undefined;
166
+ const total = passed + failed + Math.max(skipped, 0);
167
+ return { total, passed, failed, skipped: Math.max(skipped, 0) };
168
+ }
93
169
  export async function runCommand(repoPath, command, timeoutMs, env, signal) {
94
170
  const shell = getPlatformShellInvocation(command);
95
171
  let output = '';
@@ -1,4 +1,5 @@
1
1
  import { createRequire } from 'module';
2
+ import { getLogger } from './observability/logger.js';
2
3
  const require = createRequire(import.meta.url);
3
4
  function readPackageVersion() {
4
5
  try {
@@ -7,8 +8,9 @@ function readPackageVersion() {
7
8
  return pkg.version;
8
9
  }
9
10
  }
10
- catch {
11
+ catch (error) {
11
12
  // Fall back for non-package runtime embeddings.
13
+ getLogger().debug(`[Version] Failed to read package.json: ${error instanceof Error ? error.message : String(error)}`);
12
14
  }
13
15
  return '0.0.0';
14
16
  }
@@ -1,6 +1,7 @@
1
1
  import { access, constants } from '../adapters/fs/node-fs.js';
2
2
  import { GitAdapter } from '../adapters/git/git-adapter.js';
3
3
  import { LIMITS } from '../config/limits.js';
4
+ import { errorMessage } from '../utils/error.js';
4
5
  const PROBE_LIMITS = { maxStdoutBytes: 4_096, maxStderrChars: 4_096 };
5
6
  function gitFailureReason(result) {
6
7
  if (result.error?.code === 'ENOENT')
@@ -23,7 +24,7 @@ async function detectFileSystemCapability(workspacePath) {
23
24
  return {
24
25
  readable: false,
25
26
  writable: false,
26
- reason: error instanceof Error ? error.message : String(error),
27
+ reason: errorMessage(error),
27
28
  };
28
29
  }
29
30
  try {
@@ -34,7 +35,7 @@ async function detectFileSystemCapability(workspacePath) {
34
35
  return {
35
36
  readable: true,
36
37
  writable: false,
37
- reason: error instanceof Error ? error.message : String(error),
38
+ reason: errorMessage(error),
38
39
  };
39
40
  }
40
41
  }
@@ -1,6 +1,7 @@
1
1
  import { readFile } from '../../core/adapters/fs/node-fs.js';
2
2
  import { recordAuditEvent } from '../../core/observability/audit-trail.js';
3
3
  import { getLogger } from '../../core/observability/logger.js';
4
+ import { isRecord } from '../../core/utils/serialize.js';
4
5
  import { text } from '../../locales/index.js';
5
6
  function nowIso() {
6
7
  return new Date().toISOString();
@@ -43,7 +44,7 @@ function buildPhaseDurations(traces) {
43
44
  return undefined;
44
45
  const out = {};
45
46
  for (const t of traces) {
46
- if (!t || typeof t !== 'object')
47
+ if (!isRecord(t))
47
48
  continue;
48
49
  const name = t.name;
49
50
  const duration = t.duration;
@@ -69,13 +70,13 @@ async function tryReadAuditJson(auditPath) {
69
70
  }
70
71
  function extractNetworkErrorCode(error) {
71
72
  const allow = (value) => typeof value === 'string' && /^[A-Z0-9_]{2,32}$/.test(value) ? value : undefined;
72
- if (error && typeof error === 'object') {
73
- const e = error;
74
- return (allow(e.code) ||
75
- allow(e.cause?.code) ||
76
- allow(e.cause?.errno) ||
77
- allow(e.errno) ||
78
- allow(e.cause?.name));
73
+ if (isRecord(error)) {
74
+ const cause = isRecord(error.cause) ? error.cause : undefined;
75
+ return (allow(error.code) ||
76
+ allow(cause?.code) ||
77
+ allow(cause?.errno) ||
78
+ allow(error.errno) ||
79
+ allow(cause?.name));
79
80
  }
80
81
  return undefined;
81
82
  }
@@ -0,0 +1,154 @@
1
+ import { createRequire } from 'module';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { syncFs as fs } from '../../core/adapters/fs/node-fs.js';
5
+ import { ErrorType } from '../../core/types/index.js';
6
+ const require = createRequire(import.meta.url);
7
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
8
+ const queries = {
9
+ definitions: `
10
+ (function_definition name: (identifier) @name) @def
11
+ (class_definition name: (identifier) @name) @def
12
+ (decorated_definition
13
+ definition: (function_definition name: (identifier) @name)) @def
14
+ (decorated_definition
15
+ definition: (class_definition name: (identifier) @name)) @def
16
+ `,
17
+ references: `
18
+ (call function: (identifier) @name) @ref
19
+ (attribute object: (identifier) @name) @ref
20
+ `,
21
+ };
22
+ export const pythonPlugin = {
23
+ meta: {
24
+ id: 'python',
25
+ name: 'Python',
26
+ extensions: ['.py', '.pyw', '.pyi'],
27
+ capabilities: {
28
+ levels: {
29
+ l1Parsing: true,
30
+ l2Symbols: true,
31
+ l3Flow: true,
32
+ },
33
+ ast: {
34
+ strictValidation: true,
35
+ },
36
+ },
37
+ },
38
+ detection: {
39
+ matches: async (repoPath) => {
40
+ const markers = ['pyproject.toml', 'setup.py', 'requirements.txt', 'Pipfile'];
41
+ return markers.some((m) => fs.existsSync(path.join(repoPath, m)));
42
+ },
43
+ },
44
+ parsing: {
45
+ getTreeSitterWasm: async () => {
46
+ const searchPaths = [path.resolve(moduleDir, '../../../bin', 'tree-sitter-python.wasm')];
47
+ try {
48
+ const pkgPath = path.dirname(require.resolve('tree-sitter-python/package.json'));
49
+ searchPaths.push(path.join(pkgPath, 'tree-sitter-python.wasm'));
50
+ }
51
+ catch (_e) {
52
+ // ignore
53
+ }
54
+ for (const p of searchPaths) {
55
+ if (fs.existsSync(p)) {
56
+ return p;
57
+ }
58
+ }
59
+ return searchPaths[0];
60
+ },
61
+ queries,
62
+ queryPack: {
63
+ version: '1.0.0',
64
+ symbols: {
65
+ calls: `
66
+ (call function: (identifier) @callee)
67
+ (call function: (attribute attribute: (identifier) @callee))
68
+ `,
69
+ },
70
+ flow: {
71
+ control: `
72
+ (if_statement) @branch
73
+ (elif_clause) @branch
74
+ (for_statement) @loop
75
+ (while_statement) @loop
76
+ (await) @async
77
+ `,
78
+ exceptions: `
79
+ (try_statement) @trycatch
80
+ (raise_statement) @throw
81
+ (except_clause) @catch
82
+ `,
83
+ },
84
+ },
85
+ },
86
+ dependency: {
87
+ extractImports: (content) => {
88
+ const dependencies = [];
89
+ // from .foo import bar / from ..foo import bar
90
+ const fromPattern = /from\s+(\.\.+[.\w/]*)\s+import/g;
91
+ // import .foo (rare but valid in some contexts)
92
+ const importPattern = /(?:^|\s)import\s+(\.\.+[.\w/]*)/gm;
93
+ let match;
94
+ while ((match = fromPattern.exec(content)) !== null) {
95
+ if (match[1])
96
+ dependencies.push(match[1]);
97
+ }
98
+ while ((match = importPattern.exec(content)) !== null) {
99
+ if (match[1])
100
+ dependencies.push(match[1]);
101
+ }
102
+ return dependencies;
103
+ },
104
+ resolvePath: (_basePath, importPath) => {
105
+ if (!importPath.endsWith('.py') && !importPath.endsWith('.pyi')) {
106
+ return importPath + '.py';
107
+ }
108
+ return importPath;
109
+ },
110
+ },
111
+ diagnostics: {
112
+ classifyError: (output) => {
113
+ const lower = output.toLowerCase();
114
+ // Dependency errors
115
+ if (lower.includes('modulenotfounderror') ||
116
+ lower.includes('importerror') ||
117
+ lower.includes('no module named') ||
118
+ lower.includes('pip install')) {
119
+ return ErrorType.DEPENDENCY_ERROR;
120
+ }
121
+ // Compilation / syntax errors
122
+ if (lower.includes('syntaxerror') ||
123
+ lower.includes('indentationerror') ||
124
+ lower.includes('taberror') ||
125
+ lower.includes('failed to compile') ||
126
+ lower.includes('py_compile')) {
127
+ return ErrorType.COMPILATION;
128
+ }
129
+ // Test errors
130
+ if (lower.includes('pytest') ||
131
+ lower.includes('unittest') ||
132
+ lower.includes('assertionerror') ||
133
+ lower.includes('assert ') ||
134
+ lower.includes('test failed') ||
135
+ lower.includes('tests failed') ||
136
+ lower.includes('failed tests') ||
137
+ lower.includes(' ERRORS') ||
138
+ lower.includes(' FAILURES')) {
139
+ return ErrorType.TEST;
140
+ }
141
+ // Lint errors
142
+ if (lower.includes('pylint') ||
143
+ lower.includes('flake8') ||
144
+ lower.includes('mypy') ||
145
+ lower.includes('ruff') ||
146
+ lower.includes('pycodestyle') ||
147
+ lower.includes('pyflakes')) {
148
+ return ErrorType.LINT;
149
+ }
150
+ return undefined;
151
+ },
152
+ },
153
+ };
154
+ //# sourceMappingURL=index.js.map
@@ -576,11 +576,17 @@ Please return the patch in PURE unified diff format:`;
576
576
  fsListDirectoryDescription: 'List directory entries (files and subdirectories) under a repository path',
577
577
  fsListFilesDescription: 'List files (excluding subdirectories) under a repository path',
578
578
  fsWriteFileDescription: 'Write a UTF-8 text file atomically (slash-only)',
579
+ fsEditFileDescription: 'Replace exact occurrences of a string in a file (slash-only). old_string must appear exactly once unless replace_all is set.',
579
580
  fsCreateDirectoryDescription: 'Create a directory under the repository root (slash-only)',
580
581
  fsDeleteFileDescription: 'Delete a file under the repository root (slash-only)',
581
582
  gitStatusDescription: 'Show the working tree status',
582
583
  gitCatDescription: 'Read file content from a specific git revision',
584
+ gitBlameDescription: 'Show line-by-line authorship for a file',
585
+ gitLogDescription: 'Show structured commit history',
586
+ gitShowDescription: 'Show commit details and diff',
587
+ globFindDescription: 'Find files matching a glob pattern (respects .gitignore)',
583
588
  codeAstDescription: 'Query AST definitions and references for symbols',
589
+ codeFindReferencesDescription: 'Find all references to a symbol across the codebase using ripgrep pre-filter + tree-sitter precise matching',
584
590
  testRunDescription: 'Run verification command (test/lint/build) and classify errors',
585
591
  shellExecDescription: 'Execute a shell command in an isolated workspace (slash-only)',
586
592
  artifactReadDescription: 'Read salmonloop (s8p) artifacts by handle',
@@ -770,7 +776,8 @@ Please return the patch in PURE unified diff format:`;
770
776
  missionFailedWithReason: (reason) => `Smallfry mission failed: ${reason}`,
771
777
  },
772
778
  ui: {
773
- spawnToolDescription: 'Delegate a concrete sub-task to a specialized sub-agent. This is not a no-argument action: always provide agent_ref and task. Use agent_ref="explorer" for read-only investigation, "reviewer" for audit, "surgeon" for implementation proposals, or "cleaner" for lint/format cleanup. Keep task self-contained with relevant files and the exact deliverable. Omit session_target unless shared context is explicitly required.',
779
+ spawnToolDescription: 'Delegate a concrete sub-task to a specialized sub-agent. This is not a no-argument action: always provide agent_ref and task. Use agent_ref="explorer" for read-only investigation, "reviewer" for audit, "surgeon" for implementation proposals, or "cleaner" for lint/format cleanup. Keep task self-contained with relevant files and the exact deliverable. Omit session_target unless shared context is explicitly required. Set async=true to get a handle immediately and use agent_await to collect the result later.',
780
+ awaitToolDescription: 'Wait for an async sub-agent (spawned with agent_dispatch async=true) to complete and return its result. Pass the agentId from the dispatch handle.',
774
781
  progressTitle: (id) => `[Smallfry: ${id}]`,
775
782
  },
776
783
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salmon-loop",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "A chat-first coding agent CLI for safe, reviewable repository changes",
5
5
  "type": "module",
6
6
  "bin": {
@@ -154,6 +154,7 @@
154
154
  "react": "^19.2.4",
155
155
  "tiktoken": "^1.0.22",
156
156
  "tree-sitter-javascript": "^0.25.0",
157
+ "tree-sitter-python": "^0.25.0",
157
158
  "web-tree-sitter": "0.26.3",
158
159
  "yaml": "^2.8.2",
159
160
  "zod": "^4.3.6",