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.
- package/dist/cli/authorization/provider.js +2 -10
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/mode.js +2 -2
- package/dist/cli/commands/run/handler.js +3 -1
- package/dist/cli/commands/run/loop-params.js +1 -0
- package/dist/cli/commands/run/runtime-options.js +3 -1
- package/dist/cli/config.js +0 -8
- package/dist/cli/locales/en.js +2 -2
- package/dist/cli/reporters/standard.js +10 -0
- package/dist/core/adapters/fs/file-adapter.js +3 -1
- package/dist/core/adapters/git/git-adapter.js +6 -3
- package/dist/core/adapters/git/git-runner.js +5 -2
- package/dist/core/adapters/git/lock-manager.js +7 -4
- package/dist/core/checkpoint-domain/manifest-store.js +21 -13
- package/dist/core/checkpoint-domain/service.js +3 -1
- package/dist/core/config/limits.js +1 -1
- package/dist/core/config/model-pricing.js +61 -0
- package/dist/core/context/ast/skeleton-extractor.js +225 -0
- package/dist/core/context/ast/source-outline.js +24 -1
- package/dist/core/context/budget/dynamic-adjuster.js +20 -5
- package/dist/core/context/builder.js +7 -3
- package/dist/core/context/cache/store-factory.js +3 -1
- package/dist/core/context/dependencies.js +2 -1
- package/dist/core/context/effectiveness/persistence.js +50 -0
- package/dist/core/context/effectiveness/tracker.js +24 -0
- package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
- package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
- package/dist/core/context/gatherers/ast-gatherer.js +30 -28
- package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +18 -2
- package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
- package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
- package/dist/core/context/service.js +4 -2
- package/dist/core/context/steps/context-gather.js +14 -3
- package/dist/core/context/steps/context-targets.js +1 -0
- package/dist/core/context/targeting/target-resolver.js +29 -11
- package/dist/core/context/token/cache.js +5 -2
- package/dist/core/context/truncation/strategies/json.js +5 -2
- package/dist/core/context/truncation/type-detector.js +3 -1
- package/dist/core/extensions/paths.js +2 -2
- package/dist/core/facades/cli-authorization-provider.js +1 -0
- package/dist/core/feedback/parsers.js +290 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +1 -1
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +15 -3
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +44 -20
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
- package/dist/core/grizzco/execution/RejectionManager.js +7 -5
- package/dist/core/grizzco/runtime/apply-back-runtime.js +3 -1
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
- package/dist/core/grizzco/steps/autopilot.js +21 -32
- package/dist/core/grizzco/steps/explore.js +5 -2
- package/dist/core/grizzco/steps/generateReview.js +3 -1
- package/dist/core/grizzco/steps/research.js +3 -1
- package/dist/core/grizzco/steps/verify.js +7 -1
- package/dist/core/grizzco/validation/AstValidationService.js +3 -1
- package/dist/core/history/input-history.js +3 -1
- package/dist/core/intent/chat-intent.js +3 -1
- package/dist/core/llm/ai-sdk/message-mapper.js +13 -8
- package/dist/core/llm/ai-sdk/request-params.js +1 -3
- package/dist/core/llm/ai-sdk/retry-classifier.js +12 -4
- package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
- package/dist/core/llm/errors.js +5 -4
- package/dist/core/llm/retry-utils.js +8 -2
- package/dist/core/llm/stream-utils.js +5 -3
- package/dist/core/llm/sub-agent-factory.js +3 -0
- package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
- package/dist/core/mcp/catalog/discovery.js +3 -1
- package/dist/core/mcp/client/connection-manager.js +4 -2
- package/dist/core/mcp/client/transport-factory.js +7 -3
- package/dist/core/observability/audit-file.js +2 -1
- package/dist/core/observability/audit-trail.js +3 -1
- package/dist/core/observability/logger.js +2 -1
- package/dist/core/observability/monitor.js +24 -0
- package/dist/core/observability/run-outcome-reporter.js +1 -0
- package/dist/core/permission-gate/default-gate.js +5 -8
- package/dist/core/plan/storage.js +7 -4
- package/dist/core/plugin/loader.js +3 -1
- package/dist/core/prompts/registry.js +1 -1
- package/dist/core/prompts/runtime.js +3 -1
- package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
- package/dist/core/protocols/a2a/sdk/executor.js +3 -1
- package/dist/core/protocols/a2a/sdk/server.js +3 -1
- package/dist/core/protocols/acp/acp-command-runner.js +7 -6
- package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
- package/dist/core/protocols/acp/formal-agent.js +3 -2
- package/dist/core/protocols/acp/permission-provider.js +3 -2
- package/dist/core/reflection/engine.js +114 -14
- package/dist/core/runtime/batch-runner.js +81 -0
- package/dist/core/runtime/initialize.js +2 -1
- package/dist/core/runtime/loop-finalize.js +3 -0
- package/dist/core/runtime/loop-session-runner.js +5 -0
- package/dist/core/runtime/loop.js +4 -0
- package/dist/core/runtime/paths.js +9 -6
- package/dist/core/runtime/spawn-interactive.js +5 -4
- package/dist/core/security/redaction.js +3 -2
- package/dist/core/session/compression.js +3 -1
- package/dist/core/session/manager.js +2 -1
- package/dist/core/session/pruning-strategy.js +2 -1
- package/dist/core/session/token-tracker.js +11 -4
- package/dist/core/skills/permissions.js +2 -2
- package/dist/core/strata/checkpoint/manager.js +16 -10
- package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
- package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
- package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
- package/dist/core/strata/interaction/file-system-provider.js +2 -1
- package/dist/core/strata/layers/file-state-resolver.js +9 -7
- package/dist/core/strata/layers/immutable-git-layer.js +3 -1
- package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
- package/dist/core/strata/layers/worktree.js +2 -1
- package/dist/core/strata/runtime/environment.js +2 -1
- package/dist/core/strata/runtime/synchronizer.js +18 -17
- package/dist/core/structured-output/json-extract.js +3 -1
- package/dist/core/sub-agent/artifacts/store.js +2 -1
- package/dist/core/sub-agent/core/manager.js +24 -1
- package/dist/core/sub-agent/registry-defaults.js +2 -2
- package/dist/core/sub-agent/summary.js +96 -0
- package/dist/core/sub-agent/tools/task-spawn.js +7 -4
- package/dist/core/target-runtime/profile.js +3 -1
- package/dist/core/tools/audit.js +3 -2
- package/dist/core/tools/budget.js +3 -1
- package/dist/core/tools/builtin/ast.js +144 -0
- package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
- package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
- package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
- package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
- package/dist/core/tools/builtin/fs.js +76 -1
- package/dist/core/tools/builtin/git.js +242 -0
- package/dist/core/tools/builtin/glob.js +79 -0
- package/dist/core/tools/builtin/index.js +12 -4
- package/dist/core/tools/builtin/knowledge.js +146 -4
- package/dist/core/tools/builtin/proposal.js +3 -1
- package/dist/core/tools/builtin/verify.js +35 -3
- package/dist/core/tools/permissions/permission-rules.js +3 -1
- package/dist/core/tools/router.js +88 -5
- package/dist/core/tools/session.js +10 -5
- package/dist/core/types/batch.js +2 -0
- package/dist/core/utils/sanitizer.js +5 -2
- package/dist/core/utils/serialize.js +5 -2
- package/dist/core/verification/detect-runner.js +86 -0
- package/dist/core/verification/runner.js +76 -0
- package/dist/core/version.js +3 -1
- package/dist/languages/python/index.js +154 -0
- package/dist/locales/en.js +6 -0
- package/package.json +2 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { AstParser } from '../../ast/parser.js';
|
|
2
|
+
import { createLanguageSupportOrchestrator } from '../../language-support/orchestrator.js';
|
|
3
|
+
import { getLogger } from '../../observability/logger.js';
|
|
4
|
+
import { tryGetPluginRegistry } from '../../plugin/registry.js';
|
|
5
|
+
const MAX_SKELETON_LINES = 200;
|
|
6
|
+
/**
|
|
7
|
+
* Top-level declaration node types that should be included in the skeleton
|
|
8
|
+
* even if not captured by the definitions query. This handles imports, exports,
|
|
9
|
+
* and other top-level statements.
|
|
10
|
+
*/
|
|
11
|
+
const TOP_LEVEL_INCLUDE_TYPES = new Set([
|
|
12
|
+
// JS/TS
|
|
13
|
+
'import_statement',
|
|
14
|
+
'export_statement',
|
|
15
|
+
'import_declaration',
|
|
16
|
+
'export_declaration',
|
|
17
|
+
// Python
|
|
18
|
+
'import_from_statement',
|
|
19
|
+
// Rust
|
|
20
|
+
'use_declaration',
|
|
21
|
+
'extern_crate_declaration',
|
|
22
|
+
// Go
|
|
23
|
+
'import_declaration',
|
|
24
|
+
'package_clause',
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* Extract a cross-language code skeleton using tree-sitter AST.
|
|
28
|
+
*
|
|
29
|
+
* For each definition (function, class, method, type, etc.), the skeleton
|
|
30
|
+
* includes only the signature line — not the body. Imports and top-level
|
|
31
|
+
* declarations are preserved in full.
|
|
32
|
+
*
|
|
33
|
+
* Falls back to undefined if tree-sitter parsing fails (e.g., no grammar
|
|
34
|
+
* installed for the language). The caller should fall back to regex-based
|
|
35
|
+
* outlineSource in that case.
|
|
36
|
+
*/
|
|
37
|
+
export async function extractSkeleton(sourceCode, lang) {
|
|
38
|
+
const plugin = tryGetPluginRegistry()?.getByExtension(`.${lang}`) ?? tryGetPluginRegistry()?.getById(lang);
|
|
39
|
+
if (!plugin)
|
|
40
|
+
return undefined;
|
|
41
|
+
try {
|
|
42
|
+
const tree = await AstParser.parse(sourceCode, lang);
|
|
43
|
+
if (!tree?.rootNode)
|
|
44
|
+
return undefined;
|
|
45
|
+
const lines = sourceCode.split('\n');
|
|
46
|
+
const includeLines = new Set();
|
|
47
|
+
const signatureLines = new Map(); // startRow -> signatureEndRow
|
|
48
|
+
// 1. Collect definition nodes via the plugin's definitions query
|
|
49
|
+
const orchestrator = createLanguageSupportOrchestrator();
|
|
50
|
+
const queryStr = await orchestrator.getASTQuery(lang, 'definitions');
|
|
51
|
+
if (queryStr) {
|
|
52
|
+
const captures = await AstParser.queryCapturesFromQuery(tree, lang, queryStr);
|
|
53
|
+
for (const capture of captures) {
|
|
54
|
+
if (capture.name === 'def') {
|
|
55
|
+
const sigEnd = findSignatureEnd(lines, capture.line - 1);
|
|
56
|
+
signatureLines.set(capture.line - 1, sigEnd);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// 2. Walk top-level nodes for imports and declarations not caught by the query
|
|
61
|
+
for (const child of tree.rootNode.children ?? []) {
|
|
62
|
+
const nodeType = child.type;
|
|
63
|
+
const startRow = child.startPosition.row;
|
|
64
|
+
if (TOP_LEVEL_INCLUDE_TYPES.has(nodeType)) {
|
|
65
|
+
// Include import/export lines in full
|
|
66
|
+
for (let r = child.startPosition.row; r <= child.endPosition.row; r++) {
|
|
67
|
+
includeLines.add(r);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else if (signatureLines.has(startRow)) {
|
|
71
|
+
// Already captured by definitions query — include signature only
|
|
72
|
+
// (handled below)
|
|
73
|
+
}
|
|
74
|
+
else if (isLikelyDeclaration(nodeType)) {
|
|
75
|
+
// Top-level declaration not caught by definitions query
|
|
76
|
+
const sigEnd = findSignatureEnd(lines, startRow);
|
|
77
|
+
signatureLines.set(startRow, sigEnd);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// 3. Also handle nested definitions (class methods, etc.)
|
|
81
|
+
collectNestedDefinitions(tree.rootNode, lines, signatureLines, 0);
|
|
82
|
+
// 4. Build the skeleton
|
|
83
|
+
const out = [];
|
|
84
|
+
const includedRows = new Set();
|
|
85
|
+
// First pass: include full import/export lines
|
|
86
|
+
for (const row of includeLines) {
|
|
87
|
+
if (out.length >= MAX_SKELETON_LINES)
|
|
88
|
+
break;
|
|
89
|
+
if (!includedRows.has(row)) {
|
|
90
|
+
out.push(lines[row]);
|
|
91
|
+
includedRows.add(row);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Second pass: include signature lines from definitions
|
|
95
|
+
const sortedSigs = [...signatureLines.entries()].sort((a, b) => a[0] - b[0]);
|
|
96
|
+
for (const [startRow, sigEndRow] of sortedSigs) {
|
|
97
|
+
if (out.length >= MAX_SKELETON_LINES)
|
|
98
|
+
break;
|
|
99
|
+
for (let r = startRow; r <= sigEndRow; r++) {
|
|
100
|
+
if (out.length >= MAX_SKELETON_LINES)
|
|
101
|
+
break;
|
|
102
|
+
if (!includedRows.has(r)) {
|
|
103
|
+
out.push(lines[r]);
|
|
104
|
+
includedRows.add(r);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const result = out.join('\n').trim();
|
|
109
|
+
return result.length > 0 ? result : undefined;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
// Tree-sitter parsing failed (no grammar, parse error, etc.)
|
|
113
|
+
getLogger().debug(`[SkeletonExtractor] parse failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Recursively collect nested definitions (e.g., class methods) that may not
|
|
119
|
+
* be captured by the top-level definitions query.
|
|
120
|
+
*/
|
|
121
|
+
function collectNestedDefinitions(node, lines, signatureLines, depth) {
|
|
122
|
+
if (depth > 3)
|
|
123
|
+
return; // Prevent deep recursion
|
|
124
|
+
for (const child of node.children ?? []) {
|
|
125
|
+
const startRow = child.startPosition.row;
|
|
126
|
+
if (isDefinitionNode(child.type) && !signatureLines.has(startRow)) {
|
|
127
|
+
const sigEnd = findSignatureEnd(lines, startRow);
|
|
128
|
+
signatureLines.set(startRow, sigEnd);
|
|
129
|
+
}
|
|
130
|
+
// Recurse into class/struct/impl bodies to find methods
|
|
131
|
+
if (isContainerNode(child.type)) {
|
|
132
|
+
collectNestedDefinitions(child, lines, signatureLines, depth + 1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Find the last line of a definition's signature (before the body starts).
|
|
138
|
+
* For single-line definitions, returns startRow.
|
|
139
|
+
* For multi-line definitions, returns the line containing the body-starting token.
|
|
140
|
+
*/
|
|
141
|
+
function findSignatureEnd(lines, startRow) {
|
|
142
|
+
// Look up to 10 lines ahead for the body start
|
|
143
|
+
const maxLook = Math.min(startRow + 10, lines.length - 1);
|
|
144
|
+
for (let r = startRow; r <= maxLook; r++) {
|
|
145
|
+
const line = lines[r];
|
|
146
|
+
// C-family: body starts with '{'
|
|
147
|
+
if (line.includes('{'))
|
|
148
|
+
return r;
|
|
149
|
+
// Python: body starts with ':'
|
|
150
|
+
// Heuristic: the colon is at the end of a def/class/if/for/while line
|
|
151
|
+
if (/:\s*$/.test(line) && r > startRow)
|
|
152
|
+
return r;
|
|
153
|
+
// Single-line Python def: def foo(): pass
|
|
154
|
+
if (/:\s+\S/.test(line) && r === startRow)
|
|
155
|
+
return r;
|
|
156
|
+
// Rust: body starts with '{' or 'where' clause
|
|
157
|
+
if (line.includes('{'))
|
|
158
|
+
return r;
|
|
159
|
+
}
|
|
160
|
+
// Fallback: just the first line
|
|
161
|
+
return startRow;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Check if a node type is a definition-like node that should appear in skeleton.
|
|
165
|
+
*/
|
|
166
|
+
function isDefinitionNode(type) {
|
|
167
|
+
return (type.includes('function') ||
|
|
168
|
+
type.includes('method') ||
|
|
169
|
+
type.includes('class') ||
|
|
170
|
+
type.includes('interface') ||
|
|
171
|
+
type.includes('type_alias') ||
|
|
172
|
+
type.includes('type_definition') ||
|
|
173
|
+
type.includes('enum') ||
|
|
174
|
+
type.includes('struct') ||
|
|
175
|
+
type.includes('impl') ||
|
|
176
|
+
type.includes('trait') ||
|
|
177
|
+
type.includes('module') ||
|
|
178
|
+
type.includes('namespace') ||
|
|
179
|
+
type === 'decorated_definition' ||
|
|
180
|
+
type === 'function_definition' ||
|
|
181
|
+
type === 'async_function_definition' ||
|
|
182
|
+
type === 'class_definition');
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Check if a node type is a container that may hold nested definitions.
|
|
186
|
+
*/
|
|
187
|
+
function isContainerNode(type) {
|
|
188
|
+
return (type.includes('class_body') ||
|
|
189
|
+
type.includes('interface_body') ||
|
|
190
|
+
type.includes('struct_body') ||
|
|
191
|
+
type.includes('impl_body') ||
|
|
192
|
+
type.includes('declaration_list') ||
|
|
193
|
+
type === 'block' ||
|
|
194
|
+
type === 'statement_block' ||
|
|
195
|
+
type === 'class_definition' ||
|
|
196
|
+
type === 'impl_item');
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Check if a top-level node type looks like a declaration worth including.
|
|
200
|
+
*/
|
|
201
|
+
function isLikelyDeclaration(type) {
|
|
202
|
+
return (type.includes('declaration') ||
|
|
203
|
+
type.includes('definition') ||
|
|
204
|
+
type.includes('variable') ||
|
|
205
|
+
type.includes('const') ||
|
|
206
|
+
type.includes('let') ||
|
|
207
|
+
type.includes('type_alias') ||
|
|
208
|
+
type.includes('struct') ||
|
|
209
|
+
type.includes('enum') ||
|
|
210
|
+
type.includes('trait') ||
|
|
211
|
+
type.includes('impl'));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Detect the tree-sitter language ID from a file path.
|
|
215
|
+
* Returns undefined if no matching plugin is found.
|
|
216
|
+
*/
|
|
217
|
+
export function detectLang(filePath) {
|
|
218
|
+
const dotIndex = filePath.lastIndexOf('.');
|
|
219
|
+
if (dotIndex < 0)
|
|
220
|
+
return undefined;
|
|
221
|
+
const ext = filePath.slice(dotIndex).toLowerCase();
|
|
222
|
+
const plugin = tryGetPluginRegistry()?.getByExtension(ext);
|
|
223
|
+
return plugin?.meta.id;
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=skeleton-extractor.js.map
|
|
@@ -1,4 +1,25 @@
|
|
|
1
|
+
import { extractSkeleton } from './skeleton-extractor.js';
|
|
1
2
|
const MAX_OUTLINE_LINES = 200;
|
|
3
|
+
/**
|
|
4
|
+
* Generate an outline/skeleton of source code.
|
|
5
|
+
*
|
|
6
|
+
* When a language plugin with tree-sitter support is available, uses AST-based
|
|
7
|
+
* skeleton extraction (signature-only for definitions, full lines for imports).
|
|
8
|
+
* Falls back to regex-based line filtering for unsupported languages.
|
|
9
|
+
*/
|
|
10
|
+
export async function outlineSourceAsync(sourceCode, lang) {
|
|
11
|
+
if (lang) {
|
|
12
|
+
const skeleton = await extractSkeleton(sourceCode, lang);
|
|
13
|
+
if (skeleton)
|
|
14
|
+
return skeleton;
|
|
15
|
+
}
|
|
16
|
+
return outlineSource(sourceCode);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Regex-based outline generator (fallback for unsupported languages).
|
|
20
|
+
* Recognizes JS/TS-style keywords: function, class, interface, type, enum,
|
|
21
|
+
* const/let/var declarations, import/export statements.
|
|
22
|
+
*/
|
|
2
23
|
export function outlineSource(sourceCode) {
|
|
3
24
|
const lines = sourceCode.split('\n');
|
|
4
25
|
const out = [];
|
|
@@ -16,7 +37,9 @@ export function outlineSource(sourceCode) {
|
|
|
16
37
|
continue;
|
|
17
38
|
}
|
|
18
39
|
if (/^\s*(?:export\s+)?(?:declare\s+)?(interface|type|class|enum|function)\b/.test(line) ||
|
|
19
|
-
/^\s*(?:export\s+)?(?:const|let|var)\s+[A-Za-z0-9_$]+\s*[:=]/.test(line)
|
|
40
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+[A-Za-z0-9_$]+\s*[:=]/.test(line) ||
|
|
41
|
+
/^\s*(?:async\s+)?(?:def|class)\s+\w+/.test(line) ||
|
|
42
|
+
/^\s*(?:from|import)\s+\w+/.test(line)) {
|
|
20
43
|
include(line);
|
|
21
44
|
}
|
|
22
45
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Collects runtime metrics and adjusts budget based on actual usage patterns.
|
|
5
|
-
*/
|
|
1
|
+
import { getLogger } from '../../observability/logger.js';
|
|
2
|
+
import { getEffectivenessTracker } from '../effectiveness/tracker.js';
|
|
6
3
|
/**
|
|
7
4
|
* Budget adjustment strategy based on runtime feedback.
|
|
8
5
|
*/
|
|
@@ -76,6 +73,24 @@ export class DynamicBudgetAdjuster {
|
|
|
76
73
|
if (successRate > 0.7 && truncationRate < 0.3) {
|
|
77
74
|
return null; // Current budget is working well
|
|
78
75
|
}
|
|
76
|
+
// Strategy 5: Effectiveness-based adjustment
|
|
77
|
+
// If context effectiveness is low, increase budget to allow more targeted context
|
|
78
|
+
try {
|
|
79
|
+
const effectivenessTracker = getEffectivenessTracker();
|
|
80
|
+
const metrics = effectivenessTracker.getMetrics();
|
|
81
|
+
if (metrics.totalSessions > 3 && metrics.tokenEfficiency < 0.3) {
|
|
82
|
+
const newBudget = Math.min(currentBudget * (1 + this.adjustmentStep * 0.5), this.maxBudget);
|
|
83
|
+
return {
|
|
84
|
+
newBudget: Math.round(newBudget),
|
|
85
|
+
reason: `Low context token efficiency (${metrics.tokenEfficiency.toFixed(2)})`,
|
|
86
|
+
confidence: 0.5,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// Effectiveness tracker not available, skip
|
|
92
|
+
getLogger().debug(`[DynamicBudgetAdjuster] effectiveness tracker unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
93
|
+
}
|
|
79
94
|
return null;
|
|
80
95
|
}
|
|
81
96
|
/**
|
|
@@ -3,6 +3,7 @@ import { defaultPathAdapter } from '../adapters/path/path-adapter.js';
|
|
|
3
3
|
import { ConfigError } from '../config/errors.js';
|
|
4
4
|
import { LIMITS } from '../config/limits.js';
|
|
5
5
|
import { resolveConfig } from '../config/resolve.js';
|
|
6
|
+
import { getLogger } from '../observability/logger.js';
|
|
6
7
|
import { createDefaultPermissionGate } from '../permission-gate/default-gate.js';
|
|
7
8
|
import { ensureInSandbox, normalizePath } from '../utils/path.js';
|
|
8
9
|
/**
|
|
@@ -30,7 +31,8 @@ function getExtensionsPatternForPlugins(plugins) {
|
|
|
30
31
|
return Array.from(extensions).join('|');
|
|
31
32
|
}
|
|
32
33
|
const fileAdapter = new FileAdapter();
|
|
33
|
-
import {
|
|
34
|
+
import { detectLang } from './ast/skeleton-extractor.js';
|
|
35
|
+
import { outlineSourceAsync } from './ast/source-outline.js';
|
|
34
36
|
import { createContextCacheStore } from './cache/store-factory.js';
|
|
35
37
|
import { applySmartCompression } from './compression/smart-compress.js';
|
|
36
38
|
import { findFileDependencies } from './dependencies.js';
|
|
@@ -85,7 +87,8 @@ async function readRepoFileText(repoPath, relativePath) {
|
|
|
85
87
|
const fullPath = ensureInSandbox(repoPath, defaultPathAdapter.join(repoPath, normalized));
|
|
86
88
|
return await fileAdapter.readFile(fullPath, 'utf-8');
|
|
87
89
|
}
|
|
88
|
-
catch {
|
|
90
|
+
catch (error) {
|
|
91
|
+
getLogger().debug(`[ContextBuilder] readRepoFileText failed for ${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
89
92
|
return null;
|
|
90
93
|
}
|
|
91
94
|
}
|
|
@@ -134,6 +137,7 @@ export class ContextBuilder {
|
|
|
134
137
|
snapshotHash: options.snapshotHash,
|
|
135
138
|
checkpointManager: options.checkpointManager,
|
|
136
139
|
signal: options.signal,
|
|
140
|
+
contextFiles: options.contextFiles,
|
|
137
141
|
});
|
|
138
142
|
return result;
|
|
139
143
|
}
|
|
@@ -239,7 +243,7 @@ export class ContextBuilder {
|
|
|
239
243
|
if (content === null)
|
|
240
244
|
continue;
|
|
241
245
|
const isLarge = content.length > LIMITS.largeFileThresholdBytes;
|
|
242
|
-
const outline =
|
|
246
|
+
const outline = await outlineSourceAsync(content, detectLang(p));
|
|
243
247
|
newRelatedFiles.push({
|
|
244
248
|
path: p,
|
|
245
249
|
kind: normalizedFailed.includes(p) ? 'failed' : 'dependency',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FileAdapter } from '../../adapters/fs/file-adapter.js';
|
|
2
2
|
import { recordAuditEvent } from '../../observability/audit-trail.js';
|
|
3
|
+
import { getLogger } from '../../observability/logger.js';
|
|
3
4
|
import { resolveContextCachePath } from './path-resolver.js';
|
|
4
5
|
import { MemoryContextCacheStore, PersistentContextCacheStore, } from './store.js';
|
|
5
6
|
export async function createContextCacheStore(repoPath, rawConfig, options) {
|
|
@@ -24,8 +25,9 @@ export async function createContextCacheStore(repoPath, rawConfig, options) {
|
|
|
24
25
|
try {
|
|
25
26
|
await cleanupAdapter.deleteFile(details.filePath);
|
|
26
27
|
}
|
|
27
|
-
catch {
|
|
28
|
+
catch (error) {
|
|
28
29
|
// best-effort cleanup only
|
|
30
|
+
getLogger().debug(`[ContextCache] cleanup failed for ${details.filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
29
31
|
}
|
|
30
32
|
};
|
|
31
33
|
if (pathResolution.mode === 'persistent' && pathResolution.filePath) {
|
|
@@ -75,7 +75,8 @@ async function findDirectDependencies(filePath, repoPath, fsAdapter) {
|
|
|
75
75
|
}
|
|
76
76
|
return dependencies;
|
|
77
77
|
}
|
|
78
|
-
catch {
|
|
78
|
+
catch (error) {
|
|
79
|
+
getLogger().debug(`[Dependencies] failed to find direct dependencies for ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
80
|
return [];
|
|
80
81
|
}
|
|
81
82
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effectiveness data persistence.
|
|
3
|
+
*
|
|
4
|
+
* Saves and restores tracker state across sessions.
|
|
5
|
+
* Data is stored in `.salmonloop/runtime/effectiveness.json`.
|
|
6
|
+
*/
|
|
7
|
+
import { mkdir, readFile, writeFile } from '../../adapters/fs/node-fs.js';
|
|
8
|
+
import { defaultPathAdapter } from '../../adapters/path/path-adapter.js';
|
|
9
|
+
import { logIgnoredError } from '../../observability/ignored-error.js';
|
|
10
|
+
import { getLogger } from '../../observability/logger.js';
|
|
11
|
+
import { getEffectivenessTracker } from './tracker.js';
|
|
12
|
+
const EFFECTIVENESS_FILE = 'effectiveness.json';
|
|
13
|
+
function getEffectivenessPath(repoRoot) {
|
|
14
|
+
return defaultPathAdapter.join(repoRoot, '.salmonloop', 'runtime', EFFECTIVENESS_FILE);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Persist current tracker state to disk.
|
|
18
|
+
*/
|
|
19
|
+
export async function persistEffectiveness(repoRoot) {
|
|
20
|
+
try {
|
|
21
|
+
const tracker = getEffectivenessTracker();
|
|
22
|
+
const data = tracker.serialize();
|
|
23
|
+
const filePath = getEffectivenessPath(repoRoot);
|
|
24
|
+
await mkdir(defaultPathAdapter.join(repoRoot, '.salmonloop', 'runtime'), { recursive: true });
|
|
25
|
+
await writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
logIgnoredError('[Effectiveness] Failed to persist', error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Restore tracker state from disk.
|
|
33
|
+
* Silently returns if no saved data exists.
|
|
34
|
+
*/
|
|
35
|
+
export async function restoreEffectiveness(repoRoot) {
|
|
36
|
+
try {
|
|
37
|
+
const filePath = getEffectivenessPath(repoRoot);
|
|
38
|
+
const raw = await readFile(filePath, 'utf-8');
|
|
39
|
+
const data = JSON.parse(raw);
|
|
40
|
+
if (data && typeof data === 'object') {
|
|
41
|
+
const tracker = getEffectivenessTracker();
|
|
42
|
+
tracker.deserialize(data);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
// File missing or corrupted — start fresh
|
|
47
|
+
getLogger().debug(`[Effectiveness] restore failed, starting fresh: ${error instanceof Error ? error.message : String(error)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=persistence.js.map
|
|
@@ -176,6 +176,30 @@ export class ContextEffectivenessTracker {
|
|
|
176
176
|
this.totalExecutions = 0;
|
|
177
177
|
this.sessionCount = 0;
|
|
178
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Serialize tracker state for persistence.
|
|
181
|
+
*/
|
|
182
|
+
serialize() {
|
|
183
|
+
return {
|
|
184
|
+
usageRecords: this.usageRecords,
|
|
185
|
+
failureRecords: this.failureRecords,
|
|
186
|
+
totalTokensUsed: this.totalTokensUsed,
|
|
187
|
+
successfulExecutions: this.successfulExecutions,
|
|
188
|
+
totalExecutions: this.totalExecutions,
|
|
189
|
+
sessionCount: this.sessionCount,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Restore tracker state from serialized data.
|
|
194
|
+
*/
|
|
195
|
+
deserialize(data) {
|
|
196
|
+
this.usageRecords = data.usageRecords ?? [];
|
|
197
|
+
this.failureRecords = data.failureRecords ?? [];
|
|
198
|
+
this.totalTokensUsed = data.totalTokensUsed ?? 0;
|
|
199
|
+
this.successfulExecutions = data.successfulExecutions ?? 0;
|
|
200
|
+
this.totalExecutions = data.totalExecutions ?? 0;
|
|
201
|
+
this.sessionCount = data.sessionCount ?? 0;
|
|
202
|
+
}
|
|
179
203
|
/**
|
|
180
204
|
* Aggregate file statistics.
|
|
181
205
|
*/
|
|
@@ -18,8 +18,9 @@ export class ArchitectureGatherer {
|
|
|
18
18
|
return parsed;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
catch {
|
|
21
|
+
catch (error) {
|
|
22
22
|
// Cache miss or corrupted, proceed to scan
|
|
23
|
+
getLogger().debug(`[ArchitectureGatherer] cache read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
23
24
|
}
|
|
24
25
|
const topology = {
|
|
25
26
|
modules: [],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { FileAdapter } from '../../adapters/fs/file-adapter.js';
|
|
3
|
+
import { getLogger } from '../../observability/logger.js';
|
|
3
4
|
import { safeJoin } from '../../utils/path.js';
|
|
4
5
|
export class ArtifactGatherer {
|
|
5
6
|
static COMMON_BUILD_DIRS = ['dist', 'build', 'out', 'target', 'bin'];
|
|
@@ -25,8 +26,9 @@ export class ArtifactGatherer {
|
|
|
25
26
|
const rootEntries = await this.fileAdapter.readdir(repoPath);
|
|
26
27
|
artifacts.buildDirs = rootEntries.filter((e) => ArtifactGatherer.COMMON_BUILD_DIRS.includes(e));
|
|
27
28
|
}
|
|
28
|
-
catch {
|
|
29
|
-
/* Ignore */
|
|
29
|
+
catch (error) {
|
|
30
|
+
/* Ignore - best-effort build dir detection */
|
|
31
|
+
getLogger().debug(`[ArtifactGatherer] build dir scan failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
30
32
|
}
|
|
31
33
|
// 2. Lock Files & Hashes
|
|
32
34
|
for (const lock of ArtifactGatherer.CRITICAL_LOCK_FILES) {
|
|
@@ -40,8 +42,9 @@ export class ArtifactGatherer {
|
|
|
40
42
|
artifacts.lockFiles?.push({ path: lock, hash });
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
|
-
catch {
|
|
44
|
-
/* Ignore */
|
|
45
|
+
catch (error) {
|
|
46
|
+
/* Ignore - best-effort lock file detection */
|
|
47
|
+
getLogger().debug(`[ArtifactGatherer] lock file scan failed for ${lock}: ${error instanceof Error ? error.message : String(error)}`);
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
// 3. Selective Env Vars (Names only, no values for security)
|
|
@@ -8,7 +8,8 @@ import { getPluginRegistry } from '../../plugin/registry.js';
|
|
|
8
8
|
import { normalizePath, safeJoin } from '../../utils/path.js';
|
|
9
9
|
import { extractImportSpecifiers } from '../ast/import-extractor.js';
|
|
10
10
|
import { resolveImportCandidates } from '../ast/module-resolver.js';
|
|
11
|
-
import {
|
|
11
|
+
import { detectLang } from '../ast/skeleton-extractor.js';
|
|
12
|
+
import { outlineSourceAsync } from '../ast/source-outline.js';
|
|
12
13
|
const AST_DEEP_TRIGGER_PATTERN = /\b(refactor|rename|migrate|cross[- ]?file|across|dependency|dependencies|module|architecture|global)\b/i;
|
|
13
14
|
function getImportScanDepth(req) {
|
|
14
15
|
const instruction = req.instruction || '';
|
|
@@ -23,7 +24,7 @@ async function buildSymbolMap(symbols, primaryFile, callNames, relatedFiles) {
|
|
|
23
24
|
const nodes = [];
|
|
24
25
|
const edges = [];
|
|
25
26
|
const definitionNodeByName = new Map();
|
|
26
|
-
//
|
|
27
|
+
// Phase 1: Collect all definitions (primary file first, then imported files)
|
|
27
28
|
for (const def of definitions) {
|
|
28
29
|
const id = `def:${def.name}:${def.location.start.line}:${def.location.start.column}`;
|
|
29
30
|
definitionNodeByName.set(def.name, id);
|
|
@@ -35,27 +36,6 @@ async function buildSymbolMap(symbols, primaryFile, callNames, relatedFiles) {
|
|
|
35
36
|
location: def.location,
|
|
36
37
|
});
|
|
37
38
|
}
|
|
38
|
-
for (const ref of references) {
|
|
39
|
-
const refId = `ref:${ref.name}:${ref.location.start.line}:${ref.location.start.column}`;
|
|
40
|
-
nodes.push({
|
|
41
|
-
id: refId,
|
|
42
|
-
name: ref.name,
|
|
43
|
-
kind: 'reference',
|
|
44
|
-
path: primaryFile,
|
|
45
|
-
location: ref.location,
|
|
46
|
-
});
|
|
47
|
-
const targetDefId = definitionNodeByName.get(ref.name);
|
|
48
|
-
if (!targetDefId)
|
|
49
|
-
continue;
|
|
50
|
-
const edgeType = callNames.includes(ref.name) ? 'call' : 'reference';
|
|
51
|
-
edges.push({
|
|
52
|
-
from: refId,
|
|
53
|
-
to: targetDefId,
|
|
54
|
-
type: edgeType,
|
|
55
|
-
confidence: 'high',
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
// Process imported files
|
|
59
39
|
for (const related of relatedFiles) {
|
|
60
40
|
if (related.kind !== 'import' || related.mode !== 'full')
|
|
61
41
|
continue;
|
|
@@ -74,7 +54,6 @@ async function buildSymbolMap(symbols, primaryFile, callNames, relatedFiles) {
|
|
|
74
54
|
path: related.path,
|
|
75
55
|
location: def.location,
|
|
76
56
|
});
|
|
77
|
-
// Allow cross-file references to find these definitions
|
|
78
57
|
if (!definitionNodeByName.has(def.name)) {
|
|
79
58
|
definitionNodeByName.set(def.name, id);
|
|
80
59
|
}
|
|
@@ -84,6 +63,27 @@ async function buildSymbolMap(symbols, primaryFile, callNames, relatedFiles) {
|
|
|
84
63
|
getLogger().debug(` [CONTEXT] Failed to extract symbols from ${related.path}: ${e}`);
|
|
85
64
|
}
|
|
86
65
|
}
|
|
66
|
+
// Phase 2: Create reference nodes and edges (now all definitions are available)
|
|
67
|
+
for (const ref of references) {
|
|
68
|
+
const refId = `ref:${ref.name}:${ref.location.start.line}:${ref.location.start.column}`;
|
|
69
|
+
nodes.push({
|
|
70
|
+
id: refId,
|
|
71
|
+
name: ref.name,
|
|
72
|
+
kind: 'reference',
|
|
73
|
+
path: primaryFile,
|
|
74
|
+
location: ref.location,
|
|
75
|
+
});
|
|
76
|
+
const targetDefId = definitionNodeByName.get(ref.name);
|
|
77
|
+
if (!targetDefId)
|
|
78
|
+
continue;
|
|
79
|
+
const edgeType = callNames.includes(ref.name) ? 'call' : 'reference';
|
|
80
|
+
edges.push({
|
|
81
|
+
from: refId,
|
|
82
|
+
to: targetDefId,
|
|
83
|
+
type: edgeType,
|
|
84
|
+
confidence: 'high',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
87
|
return { symbolMap: { nodes, edges } };
|
|
88
88
|
}
|
|
89
89
|
function summarizeControlFlow(primaryText) {
|
|
@@ -298,7 +298,7 @@ export class AstGatherer {
|
|
|
298
298
|
continue;
|
|
299
299
|
if (!relatedSeen.has(normalized)) {
|
|
300
300
|
relatedSeen.add(normalized);
|
|
301
|
-
const outline =
|
|
301
|
+
const outline = await outlineSourceAsync(content, detectLang(normalized));
|
|
302
302
|
const isLarge = content.length > LIMITS.largeFileThresholdBytes;
|
|
303
303
|
related.push({
|
|
304
304
|
path: normalized,
|
|
@@ -339,8 +339,9 @@ export class AstGatherer {
|
|
|
339
339
|
await this.fileAdapter.readFile(safeJoin(req.repoPath, c), 'utf-8');
|
|
340
340
|
return c;
|
|
341
341
|
}
|
|
342
|
-
catch {
|
|
343
|
-
// continue
|
|
342
|
+
catch (error) {
|
|
343
|
+
// continue - file not found, try next candidate
|
|
344
|
+
getLogger().debug(`[AstGatherer] candidate file not found: ${c}: ${error instanceof Error ? error.message : String(error)}`);
|
|
344
345
|
}
|
|
345
346
|
}
|
|
346
347
|
return null;
|
|
@@ -354,7 +355,8 @@ export class AstGatherer {
|
|
|
354
355
|
try {
|
|
355
356
|
return await this.fileAdapter.readFile(safeJoin(req.repoPath, filePath), 'utf-8');
|
|
356
357
|
}
|
|
357
|
-
catch {
|
|
358
|
+
catch (error) {
|
|
359
|
+
getLogger().debug(`[AstGatherer] readRepoFile failed for ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
358
360
|
return null;
|
|
359
361
|
}
|
|
360
362
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GitAdapter } from '../../adapters/git/git-adapter.js';
|
|
2
|
+
import { getLogger } from '../../observability/logger.js';
|
|
2
3
|
import { normalizePath } from '../../utils/path.js';
|
|
3
4
|
function buildChurnIndex(logOutput) {
|
|
4
5
|
const churn = {};
|
|
@@ -48,8 +49,9 @@ export class GitHistoryGatherer {
|
|
|
48
49
|
churnByFile: Object.keys(churnByNumstat).length > 0 ? churnByNumstat : buildChurnIndex(churnLog),
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
|
-
catch {
|
|
52
|
+
catch (error) {
|
|
52
53
|
// Not a git repo or git not found
|
|
54
|
+
getLogger().debug(`[GitHistoryGatherer] git history gather failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
53
55
|
return {};
|
|
54
56
|
}
|
|
55
57
|
}
|