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
|
@@ -6,6 +6,7 @@ export class KnowledgeGatherer {
|
|
|
6
6
|
static KNOWLEDGE_SUBDIR = 'knowledge';
|
|
7
7
|
static SNAPSHOT_FILE = 'snapshot.json';
|
|
8
8
|
static COMPACTION_THRESHOLD = 20; // Compact after 20 events
|
|
9
|
+
static MAX_DECISIONS = 50; // Keep only the most recent N decisions
|
|
9
10
|
fileAdapter = new FileAdapter();
|
|
10
11
|
async gather(req) {
|
|
11
12
|
const { repoPath } = req;
|
|
@@ -15,6 +16,7 @@ export class KnowledgeGatherer {
|
|
|
15
16
|
project_rules: undefined,
|
|
16
17
|
architectural_decisions: [],
|
|
17
18
|
user_preferences: undefined,
|
|
19
|
+
lessons_learned: [],
|
|
18
20
|
};
|
|
19
21
|
const allDeprecated = new Set();
|
|
20
22
|
try {
|
|
@@ -58,23 +60,36 @@ export class KnowledgeGatherer {
|
|
|
58
60
|
if (data.user_preferences) {
|
|
59
61
|
aggregated.user_preferences = data.user_preferences;
|
|
60
62
|
}
|
|
63
|
+
if (data.lessons_learned && Array.isArray(data.lessons_learned)) {
|
|
64
|
+
if (!Array.isArray(aggregated.lessons_learned)) {
|
|
65
|
+
aggregated.lessons_learned = [];
|
|
66
|
+
}
|
|
67
|
+
aggregated.lessons_learned.push(...data.lessons_learned);
|
|
68
|
+
}
|
|
61
69
|
}
|
|
62
|
-
catch {
|
|
70
|
+
catch (error) {
|
|
63
71
|
// Skip corrupted files
|
|
72
|
+
getLogger().debug(`[KnowledgeGatherer] failed to load event file ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
// Filter out deprecated rules from aggregated project_rules
|
|
67
76
|
if (aggregated.project_rules) {
|
|
68
77
|
aggregated.project_rules = aggregated.project_rules.filter((r) => !allDeprecated.has(r));
|
|
69
78
|
}
|
|
79
|
+
// Prune stale architectural decisions (keep only the most recent N)
|
|
80
|
+
if (aggregated.architectural_decisions &&
|
|
81
|
+
aggregated.architectural_decisions.length > KnowledgeGatherer.MAX_DECISIONS) {
|
|
82
|
+
aggregated.architectural_decisions = aggregated.architectural_decisions.slice(-KnowledgeGatherer.MAX_DECISIONS);
|
|
83
|
+
}
|
|
70
84
|
// 3. Optional Compaction
|
|
71
85
|
if (eventFiles.length >= KnowledgeGatherer.COMPACTION_THRESHOLD) {
|
|
72
86
|
// Run compaction in background (non-blocking)
|
|
73
87
|
this.compact(knowledgeDir, aggregated, eventFiles).catch((e) => getLogger().debug(`[KnowledgeGatherer] Compaction failed: ${e}`));
|
|
74
88
|
}
|
|
75
89
|
}
|
|
76
|
-
catch {
|
|
90
|
+
catch (error) {
|
|
77
91
|
// Directory missing or other read errors, return empty aggregated state
|
|
92
|
+
getLogger().debug(`[KnowledgeGatherer] knowledge directory read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
78
93
|
}
|
|
79
94
|
return {
|
|
80
95
|
project_rules: aggregated.project_rules,
|
|
@@ -82,6 +97,7 @@ export class KnowledgeGatherer {
|
|
|
82
97
|
? aggregated.architectural_decisions
|
|
83
98
|
: undefined,
|
|
84
99
|
user_preferences: aggregated.user_preferences,
|
|
100
|
+
lessons_learned: aggregated.lessons_learned?.length ? aggregated.lessons_learned : undefined,
|
|
85
101
|
};
|
|
86
102
|
}
|
|
87
103
|
async compact(knowledgeDir, aggregated, filesToMerge) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FileAdapter } from '../../adapters/fs/file-adapter.js';
|
|
2
|
+
import { getLogger } from '../../observability/logger.js';
|
|
2
3
|
import { safeJoin } from '../../utils/path.js';
|
|
3
4
|
export class MetadataGatherer {
|
|
4
5
|
fileAdapter = new FileAdapter();
|
|
@@ -10,16 +11,18 @@ export class MetadataGatherer {
|
|
|
10
11
|
const pkgRaw = await this.fileAdapter.readFile(safeJoin(repoPath, 'package.json'), 'utf-8');
|
|
11
12
|
metadata.packageJson = JSON.parse(pkgRaw);
|
|
12
13
|
}
|
|
13
|
-
catch {
|
|
14
|
-
// Ignored
|
|
14
|
+
catch (error) {
|
|
15
|
+
// Ignored - best-effort metadata
|
|
16
|
+
getLogger().debug(`[MetadataGatherer] package.json read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
15
17
|
}
|
|
16
18
|
// 2. README.md (first 1000 chars)
|
|
17
19
|
try {
|
|
18
20
|
const readmeRaw = await this.fileAdapter.readFile(safeJoin(repoPath, 'README.md'), 'utf-8');
|
|
19
21
|
metadata.readmeHeader = readmeRaw.slice(0, 1000);
|
|
20
22
|
}
|
|
21
|
-
catch {
|
|
22
|
-
// Ignored
|
|
23
|
+
catch (error) {
|
|
24
|
+
// Ignored - best-effort metadata
|
|
25
|
+
getLogger().debug(`[MetadataGatherer] README.md read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
23
26
|
}
|
|
24
27
|
// 3. AI Instructions (GEMINI.md, CLAUDE.md, ARCH.md)
|
|
25
28
|
const aiFiles = ['GEMINI.md', 'CLAUDE.md', 'ARCH.md', '.gemini/ARCH.md'];
|
|
@@ -28,8 +31,9 @@ export class MetadataGatherer {
|
|
|
28
31
|
const content = await this.fileAdapter.readFile(safeJoin(repoPath, file), 'utf-8');
|
|
29
32
|
metadata.aiInstructions = (metadata.aiInstructions || '') + `\n--- ${file} ---\n${content}`;
|
|
30
33
|
}
|
|
31
|
-
catch {
|
|
32
|
-
// Ignored
|
|
34
|
+
catch (error) {
|
|
35
|
+
// Ignored - best-effort metadata
|
|
36
|
+
getLogger().debug(`[MetadataGatherer] AI instruction file ${file} not found: ${error instanceof Error ? error.message : String(error)}`);
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
39
|
// 4. List common config files
|
|
@@ -50,8 +54,9 @@ export class MetadataGatherer {
|
|
|
50
54
|
await this.fileAdapter.readFile(safeJoin(repoPath, config), 'utf-8');
|
|
51
55
|
metadata.configFiles.push(config);
|
|
52
56
|
}
|
|
53
|
-
catch {
|
|
57
|
+
catch (error) {
|
|
54
58
|
// Ignored: config not found
|
|
59
|
+
getLogger().debug(`[MetadataGatherer] config file ${config} not found: ${error instanceof Error ? error.message : String(error)}`);
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
return metadata;
|
|
@@ -30,7 +30,8 @@ export class RipgrepGatherer {
|
|
|
30
30
|
try {
|
|
31
31
|
entries = await fileAdapter.readdirWithTypes(absoluteCurrent);
|
|
32
32
|
}
|
|
33
|
-
catch {
|
|
33
|
+
catch (error) {
|
|
34
|
+
getLogger().debug(`[RipgrepGatherer] readdir failed for ${absoluteCurrent}: ${error instanceof Error ? error.message : String(error)}`);
|
|
34
35
|
continue;
|
|
35
36
|
}
|
|
36
37
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -77,7 +78,8 @@ export class RipgrepGatherer {
|
|
|
77
78
|
return results;
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
|
-
catch {
|
|
81
|
+
catch (error) {
|
|
82
|
+
getLogger().debug(`[RipgrepGatherer] file read failed for ${absoluteFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
81
83
|
continue;
|
|
82
84
|
}
|
|
83
85
|
}
|
|
@@ -149,8 +151,9 @@ export class RipgrepGatherer {
|
|
|
149
151
|
});
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
|
-
catch {
|
|
154
|
+
catch (error) {
|
|
153
155
|
// Ignore malformed JSON.
|
|
156
|
+
getLogger().debug(`[RipgrepGatherer] JSON parse failed for ripgrep output line: ${error instanceof Error ? error.message : String(error)}`);
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
return results;
|
|
@@ -167,7 +167,8 @@ export class ContextService {
|
|
|
167
167
|
const stat = await this.fileAdapter.stat(absoluteFile);
|
|
168
168
|
parts.push(this.formatStatSignature(relativeFile, stat));
|
|
169
169
|
}
|
|
170
|
-
catch {
|
|
170
|
+
catch (error) {
|
|
171
|
+
getLogger().debug(`[ContextService] stat failed for ${relativeFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
171
172
|
parts.push(`${relativeFile}:missing`);
|
|
172
173
|
}
|
|
173
174
|
}
|
|
@@ -186,7 +187,8 @@ export class ContextService {
|
|
|
186
187
|
const stat = await this.fileAdapter.stat(gitPath);
|
|
187
188
|
parts.push(this.formatStatSignature(rel, stat));
|
|
188
189
|
}
|
|
189
|
-
catch {
|
|
190
|
+
catch (error) {
|
|
191
|
+
getLogger().debug(`[ContextService] stat failed for ${rel}: ${error instanceof Error ? error.message : String(error)}`);
|
|
190
192
|
parts.push(`${rel}:missing`);
|
|
191
193
|
}
|
|
192
194
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { FileAdapter } from '../../adapters/fs/file-adapter.js';
|
|
2
2
|
import { LIMITS } from '../../config/limits.js';
|
|
3
|
+
import { getLogger } from '../../observability/logger.js';
|
|
3
4
|
import { ensureInSandbox, normalizePath, safeJoin } from '../../utils/path.js';
|
|
4
|
-
import {
|
|
5
|
+
import { detectLang } from '../ast/skeleton-extractor.js';
|
|
6
|
+
import { outlineSourceAsync } from '../ast/source-outline.js';
|
|
5
7
|
import { CONTEXT_AUDIT_ACTION, CONTEXT_AUDIT_PHASE } from '../audit-constants.js';
|
|
6
8
|
import { recordContextAuditEvent } from '../audit.js';
|
|
9
|
+
import { getEffectivenessTracker } from '../effectiveness/tracker.js';
|
|
7
10
|
import { extractKeywords } from '../keywords.js';
|
|
8
11
|
import { assertNotAborted } from '../service-helpers.js';
|
|
9
12
|
const fileAdapter = new FileAdapter();
|
|
@@ -18,7 +21,8 @@ async function readMatchedFileContent(req, file) {
|
|
|
18
21
|
return null;
|
|
19
22
|
return await fileAdapter.readFile(fullPath, 'utf-8');
|
|
20
23
|
}
|
|
21
|
-
catch {
|
|
24
|
+
catch (error) {
|
|
25
|
+
getLogger().debug(`[ContextGather] readMatchedFileContent failed for ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
26
|
return null;
|
|
23
27
|
}
|
|
24
28
|
}
|
|
@@ -59,7 +63,7 @@ export function buildContextGatherStep(deps) {
|
|
|
59
63
|
kind: 'dependency',
|
|
60
64
|
mode: content ? 'full' : 'outline',
|
|
61
65
|
content: content ?? `ripgrep match at line ${snippet.line}: ${snippet.content}`,
|
|
62
|
-
outline: content ?
|
|
66
|
+
outline: content ? await outlineSourceAsync(content, detectLang(file)) : undefined,
|
|
63
67
|
});
|
|
64
68
|
}
|
|
65
69
|
recordContextAuditEvent(CONTEXT_AUDIT_ACTION.gatherCompleted, {
|
|
@@ -75,6 +79,13 @@ export function buildContextGatherStep(deps) {
|
|
|
75
79
|
hasKnowledgeBase: Boolean(knowledgeBase),
|
|
76
80
|
hasRuntimeArtifacts: Boolean(runtimeArtifacts),
|
|
77
81
|
}, { source: 'context', severity: 'low', scope: 'session', phase: CONTEXT_AUDIT_PHASE.gather });
|
|
82
|
+
// Record context usage for effectiveness tracking
|
|
83
|
+
const tracker = getEffectivenessTracker();
|
|
84
|
+
for (const file of astRes.relatedFiles) {
|
|
85
|
+
const tokens = file.content ? Math.ceil(file.content.length / 4) : 0;
|
|
86
|
+
const relevanceScore = file.mode === 'full' ? 80 : 40;
|
|
87
|
+
tracker.recordUsage(file.path, false, tokens, relevanceScore);
|
|
88
|
+
}
|
|
78
89
|
return {
|
|
79
90
|
req,
|
|
80
91
|
diffScope,
|
|
@@ -20,6 +20,7 @@ export function buildContextTargetsStep(deps) {
|
|
|
20
20
|
definitionMap: ast.definitionMap,
|
|
21
21
|
symbolMap: ast.symbolMap,
|
|
22
22
|
churnByFile: gitHistory?.churnByFile,
|
|
23
|
+
contextFiles: req.contextFiles,
|
|
23
24
|
});
|
|
24
25
|
assertNotAborted(req.signal);
|
|
25
26
|
recordContextAuditEvent(CONTEXT_AUDIT_ACTION.targetsResolved, {
|
|
@@ -7,6 +7,8 @@ function reasonRank(reason) {
|
|
|
7
7
|
switch (reason) {
|
|
8
8
|
case 'explicit_path':
|
|
9
9
|
return 100;
|
|
10
|
+
case 'context_file':
|
|
11
|
+
return 95;
|
|
10
12
|
case 'symbol_definition':
|
|
11
13
|
return 90;
|
|
12
14
|
case 'diff_included':
|
|
@@ -140,6 +142,19 @@ function buildExplicitTargets(req) {
|
|
|
140
142
|
evidence: 'instruction_path',
|
|
141
143
|
})));
|
|
142
144
|
}
|
|
145
|
+
function buildContextFileTargets(contextFiles) {
|
|
146
|
+
if (!contextFiles || contextFiles.length === 0)
|
|
147
|
+
return [];
|
|
148
|
+
return dedupeTargets(contextFiles
|
|
149
|
+
.map((f) => normalizePath(f).replace(/^(\.\/|\/)+/, ''))
|
|
150
|
+
.filter(Boolean)
|
|
151
|
+
.map((path) => ({
|
|
152
|
+
path,
|
|
153
|
+
reason: 'context_file',
|
|
154
|
+
confidence: 'high',
|
|
155
|
+
evidence: 'context_files_option',
|
|
156
|
+
})));
|
|
157
|
+
}
|
|
143
158
|
function buildDiffTargets(includedFiles) {
|
|
144
159
|
if (!includedFiles || includedFiles.length === 0)
|
|
145
160
|
return [];
|
|
@@ -382,7 +397,7 @@ export class TargetResolver {
|
|
|
382
397
|
};
|
|
383
398
|
}
|
|
384
399
|
async resolve(params) {
|
|
385
|
-
const { req, includedFiles, importRelatedFiles, rgHitFiles, definitionMap, symbolMap, diffusionDepth, maxDiffusionTargets, churnByFile, } = params;
|
|
400
|
+
const { req, includedFiles, importRelatedFiles, rgHitFiles, definitionMap, symbolMap, diffusionDepth, maxDiffusionTargets, churnByFile, contextFiles, } = params;
|
|
386
401
|
const runner = new MicroTaskRunner({
|
|
387
402
|
debugLabel: 'context-targeting',
|
|
388
403
|
maxRounds: 5,
|
|
@@ -390,7 +405,8 @@ export class TargetResolver {
|
|
|
390
405
|
if (key === 'explicitTargets') {
|
|
391
406
|
const primary = buildPrimaryTarget(ctx.primaryFile);
|
|
392
407
|
const explicit = buildExplicitTargets(req);
|
|
393
|
-
|
|
408
|
+
const contextFileTargets = buildContextFileTargets(contextFiles);
|
|
409
|
+
return dedupeTargets([...primary, ...explicit, ...contextFileTargets]);
|
|
394
410
|
}
|
|
395
411
|
if (key === 'diffTargets') {
|
|
396
412
|
const primary = buildPrimaryTarget(ctx.primaryFile);
|
|
@@ -433,34 +449,36 @@ export class TargetResolver {
|
|
|
433
449
|
return [];
|
|
434
450
|
},
|
|
435
451
|
strategy: (engine) => {
|
|
452
|
+
const hasExplicitSignal = (data, key) => (data?.[key] || []).some((t) => t.reason === 'explicit_path' || t.reason === 'context_file');
|
|
453
|
+
const hasSymbolTargets = (data) => (data?.symbolTargets || []).some((t) => t.reason === 'symbol_definition');
|
|
454
|
+
const hasDiffTargets = (data) => (data?.diffTargets || []).some((t) => t.reason === 'diff_included');
|
|
436
455
|
return engine
|
|
437
456
|
.phase('Dependencies')
|
|
438
457
|
.requireData(['explicitTargets', 'symbolTargets', 'diffTargets', 'defaultTargets'])
|
|
439
458
|
.phase('Selection')
|
|
440
|
-
.when((c) => (c.data
|
|
459
|
+
.when((c) => hasExplicitSignal(c.data, 'explicitTargets'), (p) => {
|
|
441
460
|
p.addAction('SET_TARGETS', {
|
|
442
461
|
strategy: 'explicit',
|
|
443
462
|
targets: engine.ctx.data.explicitTargets,
|
|
444
463
|
});
|
|
445
464
|
})
|
|
446
|
-
.when((c) => !(c.data
|
|
447
|
-
(c.data?.symbolTargets || []).some((t) => t.reason === 'symbol_definition'), (p) => {
|
|
465
|
+
.when((c) => !hasExplicitSignal(c.data, 'explicitTargets') && hasSymbolTargets(c.data), (p) => {
|
|
448
466
|
p.addAction('SET_TARGETS', {
|
|
449
467
|
strategy: 'symbol',
|
|
450
468
|
targets: engine.ctx.data.symbolTargets,
|
|
451
469
|
});
|
|
452
470
|
})
|
|
453
|
-
.when((c) => !(c.data
|
|
454
|
-
!(c.data
|
|
455
|
-
(c.data
|
|
471
|
+
.when((c) => !hasExplicitSignal(c.data, 'explicitTargets') &&
|
|
472
|
+
!hasSymbolTargets(c.data) &&
|
|
473
|
+
hasDiffTargets(c.data), (p) => {
|
|
456
474
|
p.addAction('SET_TARGETS', {
|
|
457
475
|
strategy: 'diff',
|
|
458
476
|
targets: engine.ctx.data.diffTargets,
|
|
459
477
|
});
|
|
460
478
|
})
|
|
461
|
-
.unless((c) => (c.data
|
|
462
|
-
(c.data
|
|
463
|
-
(c.data
|
|
479
|
+
.unless((c) => hasExplicitSignal(c.data, 'explicitTargets') ||
|
|
480
|
+
hasSymbolTargets(c.data) ||
|
|
481
|
+
hasDiffTargets(c.data), (p) => {
|
|
464
482
|
p.addAction('SET_TARGETS', {
|
|
465
483
|
strategy: 'default',
|
|
466
484
|
targets: engine.ctx.data.defaultTargets,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { createHash } from 'crypto';
|
|
8
8
|
import { FileAdapter } from '../../adapters/fs/file-adapter.js';
|
|
9
|
+
import { getLogger } from '../../observability/logger.js';
|
|
9
10
|
/**
|
|
10
11
|
* Two-level token cache for performance optimization.
|
|
11
12
|
*
|
|
@@ -82,8 +83,9 @@ export class TokenCache {
|
|
|
82
83
|
this.fileCache.set(filePath, entry);
|
|
83
84
|
return entry;
|
|
84
85
|
}
|
|
85
|
-
catch {
|
|
86
|
+
catch (error) {
|
|
86
87
|
// File doesn't exist or can't be accessed
|
|
88
|
+
getLogger().debug(`[TokenCache] file cache get failed for ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
89
|
this.fileCache.delete(filePath);
|
|
88
90
|
this.misses++;
|
|
89
91
|
return null;
|
|
@@ -108,8 +110,9 @@ export class TokenCache {
|
|
|
108
110
|
contentHash: this.hashContent(content),
|
|
109
111
|
});
|
|
110
112
|
}
|
|
111
|
-
catch {
|
|
113
|
+
catch (error) {
|
|
112
114
|
// Ignore if file can't be accessed
|
|
115
|
+
getLogger().debug(`[TokenCache] file cache set failed for ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
118
|
// ==================== Invalidation ====================
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Preserves JSON structure while truncating large arrays/objects.
|
|
5
5
|
* Keeps root structure and key names visible.
|
|
6
6
|
*/
|
|
7
|
+
import { getLogger } from '../../../observability/logger.js';
|
|
7
8
|
import { DEFAULT_TRUNCATION_CONFIG } from '../types.js';
|
|
8
9
|
/**
|
|
9
10
|
* JSON truncation strategy.
|
|
@@ -21,7 +22,8 @@ export class JsonStrategy {
|
|
|
21
22
|
JSON.parse(output);
|
|
22
23
|
return true;
|
|
23
24
|
}
|
|
24
|
-
catch {
|
|
25
|
+
catch (error) {
|
|
26
|
+
getLogger().debug(`[JsonStrategy] canHandle parse check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
25
27
|
return false;
|
|
26
28
|
}
|
|
27
29
|
}
|
|
@@ -40,8 +42,9 @@ export class JsonStrategy {
|
|
|
40
42
|
try {
|
|
41
43
|
parsed = JSON.parse(output);
|
|
42
44
|
}
|
|
43
|
-
catch {
|
|
45
|
+
catch (error) {
|
|
44
46
|
// Not valid JSON, fall back to simple truncation
|
|
47
|
+
getLogger().debug(`[JsonStrategy] JSON parse failed during truncation: ${error instanceof Error ? error.message : String(error)}`);
|
|
45
48
|
return this.simpleTruncate(output, budget);
|
|
46
49
|
}
|
|
47
50
|
// Truncate while preserving structure
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Analyzes output content to determine the most appropriate
|
|
5
5
|
* truncation strategy.
|
|
6
6
|
*/
|
|
7
|
+
import { getLogger } from '../../observability/logger.js';
|
|
7
8
|
/**
|
|
8
9
|
* Detection patterns for each output type.
|
|
9
10
|
*/
|
|
@@ -84,8 +85,9 @@ export function detectOutputType(output) {
|
|
|
84
85
|
try {
|
|
85
86
|
JSON.parse(output);
|
|
86
87
|
}
|
|
87
|
-
catch {
|
|
88
|
+
catch (error) {
|
|
88
89
|
// Not valid JSON, downgrade to generic
|
|
90
|
+
getLogger().debug(`[TypeDetector] JSON validation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
89
91
|
bestType = 'generic';
|
|
90
92
|
bestScore = 0;
|
|
91
93
|
}
|
|
@@ -66,11 +66,11 @@ export function isWithinRoot(candidate, root) {
|
|
|
66
66
|
const realRoot = realpathSync(resolvedRoot);
|
|
67
67
|
return realCandidate === realRoot || realCandidate.startsWith(realRoot + path.sep);
|
|
68
68
|
}
|
|
69
|
-
catch {
|
|
69
|
+
catch (error) {
|
|
70
70
|
// Candidate or root does not exist yet — fall back to lexical check.
|
|
71
71
|
// This allows pre-declaring paths that will be created later, while
|
|
72
72
|
// still catching obvious traversal sequences like `../../etc`.
|
|
73
|
-
tryGetLogger()?.debug(`isWithinRoot: path not on disk, using lexical check for "${candidate}" against root "${root}"`);
|
|
73
|
+
tryGetLogger()?.debug(`[Paths] isWithinRoot: path not on disk, using lexical check for "${candidate}" against root "${root}": ${error instanceof Error ? error.message : String(error)}`);
|
|
74
74
|
return (resolvedCandidate === resolvedRoot || resolvedCandidate.startsWith(resolvedRoot + path.sep));
|
|
75
75
|
}
|
|
76
76
|
}
|