salmon-loop 0.4.1 → 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 (146) hide show
  1. package/dist/cli/authorization/provider.js +2 -10
  2. package/dist/cli/commands/config.js +2 -2
  3. package/dist/cli/commands/mode.js +2 -2
  4. package/dist/cli/commands/run/handler.js +3 -1
  5. package/dist/cli/commands/run/loop-params.js +1 -0
  6. package/dist/cli/commands/run/runtime-options.js +3 -1
  7. package/dist/cli/config.js +0 -8
  8. package/dist/cli/locales/en.js +2 -2
  9. package/dist/cli/reporters/standard.js +10 -0
  10. package/dist/core/adapters/fs/file-adapter.js +3 -1
  11. package/dist/core/adapters/git/git-adapter.js +6 -3
  12. package/dist/core/adapters/git/git-runner.js +5 -2
  13. package/dist/core/adapters/git/lock-manager.js +7 -4
  14. package/dist/core/checkpoint-domain/manifest-store.js +21 -13
  15. package/dist/core/checkpoint-domain/service.js +3 -1
  16. package/dist/core/config/limits.js +1 -1
  17. package/dist/core/config/model-pricing.js +61 -0
  18. package/dist/core/context/ast/skeleton-extractor.js +225 -0
  19. package/dist/core/context/ast/source-outline.js +24 -1
  20. package/dist/core/context/budget/dynamic-adjuster.js +20 -5
  21. package/dist/core/context/builder.js +7 -3
  22. package/dist/core/context/cache/store-factory.js +3 -1
  23. package/dist/core/context/dependencies.js +2 -1
  24. package/dist/core/context/effectiveness/persistence.js +50 -0
  25. package/dist/core/context/effectiveness/tracker.js +24 -0
  26. package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
  27. package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
  28. package/dist/core/context/gatherers/ast-gatherer.js +30 -28
  29. package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
  30. package/dist/core/context/gatherers/knowledge-gatherer.js +18 -2
  31. package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
  32. package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
  33. package/dist/core/context/service.js +4 -2
  34. package/dist/core/context/steps/context-gather.js +14 -3
  35. package/dist/core/context/steps/context-targets.js +1 -0
  36. package/dist/core/context/targeting/target-resolver.js +29 -11
  37. package/dist/core/context/token/cache.js +5 -2
  38. package/dist/core/context/truncation/strategies/json.js +5 -2
  39. package/dist/core/context/truncation/type-detector.js +3 -1
  40. package/dist/core/extensions/paths.js +2 -2
  41. package/dist/core/facades/cli-authorization-provider.js +1 -0
  42. package/dist/core/feedback/parsers.js +290 -1
  43. package/dist/core/grizzco/dsl/llm-strategy.js +1 -1
  44. package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
  45. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +15 -3
  46. package/dist/core/grizzco/engine/transaction/attempt-failure.js +44 -20
  47. package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
  48. package/dist/core/grizzco/execution/RejectionManager.js +7 -5
  49. package/dist/core/grizzco/runtime/apply-back-runtime.js +3 -1
  50. package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
  51. package/dist/core/grizzco/steps/autopilot.js +21 -32
  52. package/dist/core/grizzco/steps/explore.js +5 -2
  53. package/dist/core/grizzco/steps/generateReview.js +3 -1
  54. package/dist/core/grizzco/steps/research.js +3 -1
  55. package/dist/core/grizzco/steps/verify.js +7 -1
  56. package/dist/core/grizzco/validation/AstValidationService.js +3 -1
  57. package/dist/core/history/input-history.js +3 -1
  58. package/dist/core/intent/chat-intent.js +3 -1
  59. package/dist/core/llm/ai-sdk/message-mapper.js +13 -8
  60. package/dist/core/llm/ai-sdk/request-params.js +1 -3
  61. package/dist/core/llm/ai-sdk/retry-classifier.js +12 -4
  62. package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
  63. package/dist/core/llm/errors.js +5 -4
  64. package/dist/core/llm/retry-utils.js +8 -2
  65. package/dist/core/llm/stream-utils.js +5 -3
  66. package/dist/core/llm/sub-agent-factory.js +3 -0
  67. package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
  68. package/dist/core/mcp/catalog/discovery.js +3 -1
  69. package/dist/core/mcp/client/connection-manager.js +4 -2
  70. package/dist/core/mcp/client/transport-factory.js +7 -3
  71. package/dist/core/observability/audit-file.js +2 -1
  72. package/dist/core/observability/audit-trail.js +3 -1
  73. package/dist/core/observability/logger.js +2 -1
  74. package/dist/core/observability/monitor.js +24 -0
  75. package/dist/core/observability/run-outcome-reporter.js +1 -0
  76. package/dist/core/permission-gate/default-gate.js +5 -8
  77. package/dist/core/plan/storage.js +7 -4
  78. package/dist/core/plugin/loader.js +3 -1
  79. package/dist/core/prompts/registry.js +1 -1
  80. package/dist/core/prompts/runtime.js +3 -1
  81. package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
  82. package/dist/core/protocols/a2a/sdk/executor.js +3 -1
  83. package/dist/core/protocols/a2a/sdk/server.js +3 -1
  84. package/dist/core/protocols/acp/acp-command-runner.js +7 -6
  85. package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
  86. package/dist/core/protocols/acp/formal-agent.js +3 -2
  87. package/dist/core/protocols/acp/permission-provider.js +3 -2
  88. package/dist/core/reflection/engine.js +114 -14
  89. package/dist/core/runtime/batch-runner.js +81 -0
  90. package/dist/core/runtime/initialize.js +2 -1
  91. package/dist/core/runtime/loop-finalize.js +3 -0
  92. package/dist/core/runtime/loop-session-runner.js +5 -0
  93. package/dist/core/runtime/loop.js +4 -0
  94. package/dist/core/runtime/paths.js +9 -6
  95. package/dist/core/runtime/spawn-interactive.js +5 -4
  96. package/dist/core/security/redaction.js +3 -2
  97. package/dist/core/session/compression.js +3 -1
  98. package/dist/core/session/manager.js +2 -1
  99. package/dist/core/session/pruning-strategy.js +2 -1
  100. package/dist/core/session/token-tracker.js +11 -4
  101. package/dist/core/skills/permissions.js +2 -2
  102. package/dist/core/strata/checkpoint/manager.js +16 -10
  103. package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
  104. package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
  105. package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
  106. package/dist/core/strata/interaction/file-system-provider.js +2 -1
  107. package/dist/core/strata/layers/file-state-resolver.js +9 -7
  108. package/dist/core/strata/layers/immutable-git-layer.js +3 -1
  109. package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
  110. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
  111. package/dist/core/strata/layers/worktree.js +2 -1
  112. package/dist/core/strata/runtime/environment.js +2 -1
  113. package/dist/core/strata/runtime/synchronizer.js +18 -17
  114. package/dist/core/structured-output/json-extract.js +3 -1
  115. package/dist/core/sub-agent/artifacts/store.js +2 -1
  116. package/dist/core/sub-agent/core/manager.js +24 -1
  117. package/dist/core/sub-agent/registry-defaults.js +2 -2
  118. package/dist/core/sub-agent/summary.js +96 -0
  119. package/dist/core/sub-agent/tools/task-spawn.js +7 -4
  120. package/dist/core/target-runtime/profile.js +3 -1
  121. package/dist/core/tools/audit.js +3 -2
  122. package/dist/core/tools/budget.js +3 -1
  123. package/dist/core/tools/builtin/ast.js +144 -0
  124. package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
  125. package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
  126. package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
  127. package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
  128. package/dist/core/tools/builtin/fs.js +76 -1
  129. package/dist/core/tools/builtin/git.js +242 -0
  130. package/dist/core/tools/builtin/glob.js +79 -0
  131. package/dist/core/tools/builtin/index.js +12 -4
  132. package/dist/core/tools/builtin/knowledge.js +146 -4
  133. package/dist/core/tools/builtin/proposal.js +3 -1
  134. package/dist/core/tools/builtin/verify.js +35 -3
  135. package/dist/core/tools/permissions/permission-rules.js +3 -1
  136. package/dist/core/tools/router.js +88 -5
  137. package/dist/core/tools/session.js +10 -5
  138. package/dist/core/types/batch.js +2 -0
  139. package/dist/core/utils/sanitizer.js +5 -2
  140. package/dist/core/utils/serialize.js +5 -2
  141. package/dist/core/verification/detect-runner.js +86 -0
  142. package/dist/core/verification/runner.js +76 -0
  143. package/dist/core/version.js +3 -1
  144. package/dist/languages/python/index.js +154 -0
  145. package/dist/locales/en.js +6 -0
  146. package/package.json +2 -1
@@ -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
  }
@@ -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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "salmon-loop",
3
- "version": "0.4.1",
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",