salmon-loop 0.2.3 → 0.2.16
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/argv/headless-detection.js +27 -0
- package/dist/cli/chat-flow.js +11 -0
- package/dist/cli/chat.js +161 -24
- package/dist/cli/commands/chat.js +30 -24
- package/dist/cli/commands/context.js +15 -3
- package/dist/cli/commands/flow-mode.js +63 -0
- package/dist/cli/commands/help-format.js +12 -0
- package/dist/cli/commands/registry.js +6 -7
- package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
- package/dist/cli/commands/run/config-resolution.js +30 -24
- package/dist/cli/commands/run/early-errors.js +23 -0
- package/dist/cli/commands/run/handler.js +131 -44
- package/dist/cli/commands/run/headless-error-writer.js +8 -0
- package/dist/cli/commands/run/loop-params.js +3 -0
- package/dist/cli/commands/run/mode.js +2 -5
- package/dist/cli/commands/run/parse-options.js +18 -2
- package/dist/cli/commands/run/persist-session.js +10 -1
- package/dist/cli/commands/run/preflight.js +10 -0
- package/dist/cli/commands/run/reporter-factory.js +4 -0
- package/dist/cli/commands/run/runtime-llm.js +38 -11
- package/dist/cli/commands/run/runtime-options.js +2 -2
- package/dist/cli/commands/run/validate-options.js +0 -5
- package/dist/cli/commands/run/verbose.js +2 -7
- package/dist/cli/commands/serve.js +117 -90
- package/dist/cli/commands/tool-names.js +78 -78
- package/dist/cli/headless/anthropic-stream-normalized-encoder.js +6 -1
- package/dist/cli/headless/json-protocol.js +37 -0
- package/dist/cli/headless/native-stream-normalized-encoder.js +6 -1
- package/dist/cli/headless/protocol-metadata.js +22 -0
- package/dist/cli/headless/stream-json-protocol.js +34 -1
- package/dist/cli/index.js +6 -4
- package/dist/cli/locales/en.js +32 -6
- package/dist/cli/program-bootstrap.js +14 -4
- package/dist/cli/program-commands.js +9 -1
- package/dist/cli/program-options.js +1 -0
- package/dist/cli/reporters/anthropic-stream.js +7 -1
- package/dist/cli/reporters/json.js +4 -0
- package/dist/cli/reporters/stream-json.js +17 -2
- package/dist/cli/run-cli.js +5 -3
- package/dist/cli/slash/runtime.js +30 -15
- package/dist/cli/ui/components/CommandInput.js +7 -3
- package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
- package/dist/cli/utils/command-option-source.js +13 -0
- package/dist/cli/utils/output-format.js +6 -0
- package/dist/cli/utils/resolve-cli-config.js +98 -0
- package/dist/cli/utils/verbose-level.js +8 -0
- package/dist/cli/utils/verify-resolver.js +8 -4
- package/dist/cli/utils/worktree-prepare-resolver.js +7 -3
- package/dist/core/adapters/fs/file-adapter.js +6 -0
- package/dist/core/adapters/fs/filesystem.js +2 -1
- package/dist/core/adapters/git/git-adapter.js +78 -1
- package/dist/core/benchmark/patch-artifact.js +124 -0
- package/dist/core/benchmark/swe-bench.js +25 -0
- package/dist/core/config/load.js +39 -18
- package/dist/core/config/merge.js +27 -0
- package/dist/core/config/paths.js +24 -5
- package/dist/core/config/resolve-llm.js +12 -0
- package/dist/core/config/resolve.js +7 -5
- package/dist/core/config/resolvers/server.js +0 -6
- package/dist/core/config/validate.js +94 -21
- package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
- package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
- package/dist/core/context/keywords.js +18 -4
- package/dist/core/context/service-deps.js +2 -2
- package/dist/core/context/service.js +8 -0
- package/dist/core/context/steps/context-gather.js +38 -0
- package/dist/core/context/summarization/summarizer.js +55 -12
- package/dist/core/context/targeting/target-resolver.js +4 -4
- package/dist/core/extensions/index.js +23 -5
- package/dist/core/extensions/paths.js +31 -0
- package/dist/core/extensions/schemas.js +8 -5
- package/dist/core/facades/cli-chat.js +6 -2
- package/dist/core/facades/cli-command-chat.js +2 -1
- package/dist/core/facades/cli-command-tool-names.js +2 -0
- package/dist/core/facades/cli-context.js +1 -0
- package/dist/core/facades/cli-observability.js +1 -1
- package/dist/core/facades/cli-run-handler.js +4 -2
- package/dist/core/facades/cli-run-persist-session.js +1 -0
- package/dist/core/facades/cli-serve.js +2 -4
- package/dist/core/facades/cli-utils-worktree.js +1 -1
- package/dist/core/failure/diagnostics.js +53 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
- package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
- package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +173 -7
- package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
- package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
- package/dist/core/grizzco/steps/answer.js +13 -14
- package/dist/core/grizzco/steps/autopilot.js +396 -0
- package/dist/core/grizzco/steps/cache-sharing.js +29 -0
- package/dist/core/grizzco/steps/explore.js +37 -21
- package/dist/core/grizzco/steps/generateReview.js +2 -5
- package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
- package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
- package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
- package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
- package/dist/core/grizzco/steps/patch.js +105 -146
- package/dist/core/grizzco/steps/plan.js +101 -25
- package/dist/core/grizzco/steps/preflight.js +5 -3
- package/dist/core/grizzco/steps/request-assembly.js +78 -0
- package/dist/core/grizzco/steps/research.js +39 -36
- package/dist/core/grizzco/steps/tool-runtime.js +47 -0
- package/dist/core/grizzco/steps/verify-shared.js +23 -0
- package/dist/core/grizzco/steps/verify.js +13 -21
- package/dist/core/intent/chat-intent.js +0 -4
- package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
- package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
- package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
- package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
- package/dist/core/llm/ai-sdk/request-params.js +74 -1
- package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
- package/dist/core/llm/ai-sdk.js +112 -27
- package/dist/core/llm/capabilities.js +12 -0
- package/dist/core/llm/contracts/repair.js +36 -30
- package/dist/core/llm/errors.js +83 -2
- package/dist/core/llm/message-composition.js +7 -22
- package/dist/core/llm/phase-router.js +29 -10
- package/dist/core/llm/redact.js +28 -3
- package/dist/core/llm/registry.js +2 -0
- package/dist/core/llm/request-augmentation.js +55 -0
- package/dist/core/llm/request-envelope.js +334 -0
- package/dist/core/llm/shared-request-assembly.js +35 -0
- package/dist/core/llm/stream-utils.js +13 -4
- package/dist/core/llm/utils.js +18 -29
- package/dist/core/memory/relevant-retrieval.js +144 -0
- package/dist/core/observability/logger.js +11 -2
- package/dist/core/patch/diff.js +1 -0
- package/dist/core/prompts/registry.js +39 -2
- package/dist/core/prompts/runtime.js +50 -12
- package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
- package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
- package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
- package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
- package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
- package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
- package/dist/core/prompts/templates/system/main_system.hbs +4 -16
- package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
- package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
- package/dist/core/prompts/templates/system/research_system.hbs +2 -0
- package/dist/core/protocols/a2a/agent-card.js +3 -2
- package/dist/core/protocols/a2a/sdk/executor.js +8 -6
- package/dist/core/protocols/a2a/sdk/server.js +0 -1
- package/dist/core/protocols/acp/formal-agent.js +221 -55
- package/dist/core/protocols/acp/handlers.js +5 -1
- package/dist/core/protocols/acp/permission-provider.js +21 -1
- package/dist/core/protocols/shared/execution-request.js +24 -0
- package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
- package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
- package/dist/core/public-capabilities/projections.js +29 -0
- package/dist/core/public-capabilities/registry.js +26 -0
- package/dist/core/public-capabilities/types.js +2 -0
- package/dist/core/runtime/agent-server-runtime.js +47 -43
- package/dist/core/runtime/execution-profile.js +67 -0
- package/dist/core/session/artifact-state.js +160 -0
- package/dist/core/session/compaction/index.js +183 -0
- package/dist/core/session/compaction/microcompact.js +78 -0
- package/dist/core/session/compaction/tracking.js +48 -0
- package/dist/core/session/compaction/types.js +11 -0
- package/dist/core/session/compression.js +12 -4
- package/dist/core/session/manager.js +247 -10
- package/dist/core/session/pruning-strategy.js +55 -9
- package/dist/core/session/replacement-preview-provider.js +24 -0
- package/dist/core/session/replacement-state.js +131 -0
- package/dist/core/session/resume-repair/pipeline.js +79 -0
- package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
- package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
- package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
- package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
- package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
- package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
- package/dist/core/session/resume-repair/types.js +2 -0
- package/dist/core/session/summary-sync.js +164 -13
- package/dist/core/session/token-tracker.js +6 -0
- package/dist/core/skills/audit.js +34 -0
- package/dist/core/skills/bridge.js +84 -7
- package/dist/core/skills/discovery.js +94 -0
- package/dist/core/skills/feature-flags.js +52 -0
- package/dist/core/skills/index.js +1 -1
- package/dist/core/skills/loader.js +195 -20
- package/dist/core/skills/parser.js +296 -24
- package/dist/core/skills/permissions.js +117 -0
- package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
- package/dist/core/skills/runtime/SkillRunner.js +240 -61
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
- package/dist/core/strata/layers/worktree.js +70 -13
- package/dist/core/strata/runtime/synchronizer.js +29 -2
- package/dist/core/streaming/stream-assembler.js +75 -31
- package/dist/core/sub-agent/context-snapshot.js +156 -0
- package/dist/core/sub-agent/core/loop.js +1 -1
- package/dist/core/sub-agent/core/manager.js +119 -20
- package/dist/core/sub-agent/dispatch-policy.js +29 -0
- package/dist/core/sub-agent/prefix-consistency.js +48 -0
- package/dist/core/sub-agent/registry-defaults.js +4 -0
- package/dist/core/sub-agent/tools/task-spawn.js +79 -2
- package/dist/core/sub-agent/types.js +134 -5
- package/dist/core/tools/audit.js +13 -4
- package/dist/core/tools/builtin/ast-grep.js +1 -1
- package/dist/core/tools/builtin/ast.js +1 -1
- package/dist/core/tools/builtin/benchmark.js +360 -0
- package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
- package/dist/core/tools/builtin/code-search/executor.js +6 -1
- package/dist/core/tools/builtin/code-search/spec.js +26 -2
- package/dist/core/tools/builtin/fs.js +256 -23
- package/dist/core/tools/builtin/git.js +2 -2
- package/dist/core/tools/builtin/index.js +51 -2
- package/dist/core/tools/builtin/interaction.js +8 -1
- package/dist/core/tools/builtin/plan.js +37 -15
- package/dist/core/tools/builtin/shell.js +1 -1
- package/dist/core/tools/loader.js +39 -16
- package/dist/core/tools/mapper.js +17 -3
- package/dist/core/tools/parallel/scheduler.js +35 -4
- package/dist/core/tools/permissions/permission-rules.js +5 -10
- package/dist/core/tools/policy.js +6 -1
- package/dist/core/tools/recoverable-tool-errors.js +10 -0
- package/dist/core/tools/router.js +24 -6
- package/dist/core/tools/session.js +458 -48
- package/dist/core/tools/tool-visibility.js +62 -0
- package/dist/core/tools/types.js +9 -1
- package/dist/core/types/execution.js +4 -0
- package/dist/core/types/flow-mode.js +8 -0
- package/dist/core/utils/path.js +52 -0
- package/dist/core/verification/runner.js +4 -1
- package/dist/interfaces/cli/task-runner.js +4 -3
- package/dist/languages/typescript/index.js +4 -1
- package/dist/locales/en.js +87 -2
- package/dist/utils/eol.js +1 -1
- package/package.json +15 -8
- package/scripts/fix-es-abstract-compat.js +77 -0
- package/dist/core/runtime/fastify-server-bundle.js +0 -26
- package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
- package/dist/core/runtime/sidecar-paths.js +0 -47
- package/dist/core/runtime/sidecar-route-catalog.js +0 -103
package/dist/core/config/load.js
CHANGED
|
@@ -1,34 +1,55 @@
|
|
|
1
1
|
import { readFile } from '../adapters/fs/node-fs.js';
|
|
2
2
|
import { ConfigError } from './errors.js';
|
|
3
3
|
import { parseConfigText } from './file-format.js';
|
|
4
|
-
import { getDefaultRepoConfigPaths, resolveConfigPath } from './paths.js';
|
|
4
|
+
import { getDefaultRepoConfigPaths, getDefaultUserConfigPaths, resolveConfigPath, } from './paths.js';
|
|
5
5
|
import { validateConfigFileV1 } from './validate.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return null;
|
|
9
|
-
const candidatePaths = opts.configPath
|
|
10
|
-
? [resolveConfigPath(opts.repoRoot, opts.configPath)]
|
|
11
|
-
: getDefaultRepoConfigPaths(opts.repoRoot);
|
|
12
|
-
for (let i = 0; i < candidatePaths.length; i++) {
|
|
13
|
-
const absPath = candidatePaths[i];
|
|
6
|
+
async function loadFromCandidates(candidatePaths, required) {
|
|
7
|
+
const results = await Promise.all(candidatePaths.map(async (absPath) => {
|
|
14
8
|
try {
|
|
15
9
|
const raw = await readFile(absPath, 'utf8');
|
|
16
10
|
const parsed = parseConfigText(raw, absPath);
|
|
17
11
|
const config = validateConfigFileV1(parsed);
|
|
18
|
-
return { path: absPath, config };
|
|
12
|
+
return { ok: true, absPath, loaded: { path: absPath, config } };
|
|
19
13
|
}
|
|
20
14
|
catch (e) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
return { ok: false, absPath, error: e };
|
|
16
|
+
}
|
|
17
|
+
}));
|
|
18
|
+
for (const [i, result] of results.entries()) {
|
|
19
|
+
if (result.ok) {
|
|
20
|
+
return result.loaded;
|
|
21
|
+
}
|
|
22
|
+
const code = result.error && typeof result.error === 'object' && 'code' in result.error
|
|
23
|
+
? result.error.code
|
|
24
|
+
: undefined;
|
|
25
|
+
if (code === 'ENOENT') {
|
|
26
|
+
const isLast = i === results.length - 1;
|
|
27
|
+
if (required && isLast) {
|
|
28
|
+
throw new ConfigError('CONFIG_FILE_NOT_FOUND', { path: result.absPath });
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
+
continue;
|
|
30
31
|
}
|
|
32
|
+
throw result.error;
|
|
31
33
|
}
|
|
32
34
|
return null;
|
|
33
35
|
}
|
|
36
|
+
export async function tryLoadConfigFile(opts) {
|
|
37
|
+
if (!opts.enabled)
|
|
38
|
+
return null;
|
|
39
|
+
const candidatePaths = opts.configPath
|
|
40
|
+
? [resolveConfigPath(opts.repoRoot, opts.configPath)]
|
|
41
|
+
: getDefaultRepoConfigPaths(opts.repoRoot);
|
|
42
|
+
return loadFromCandidates(candidatePaths, opts.required);
|
|
43
|
+
}
|
|
44
|
+
export async function loadConfigStack(opts) {
|
|
45
|
+
if (!opts.enabled)
|
|
46
|
+
return {};
|
|
47
|
+
if (opts.configPath) {
|
|
48
|
+
const loaded = await tryLoadConfigFile(opts);
|
|
49
|
+
return loaded ? { repo: loaded } : {};
|
|
50
|
+
}
|
|
51
|
+
const repo = await loadFromCandidates(getDefaultRepoConfigPaths(opts.repoRoot), false);
|
|
52
|
+
const user = await loadFromCandidates(getDefaultUserConfigPaths(), false);
|
|
53
|
+
return { repo: repo ?? undefined, user: user ?? undefined };
|
|
54
|
+
}
|
|
34
55
|
//# sourceMappingURL=load.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function mergeValues(userValue, repoValue) {
|
|
5
|
+
if (repoValue === undefined)
|
|
6
|
+
return userValue;
|
|
7
|
+
if (userValue === undefined)
|
|
8
|
+
return repoValue;
|
|
9
|
+
if (isPlainObject(userValue) && isPlainObject(repoValue)) {
|
|
10
|
+
const merged = { ...userValue };
|
|
11
|
+
for (const [key, value] of Object.entries(repoValue)) {
|
|
12
|
+
merged[key] = mergeValues(userValue[key], value);
|
|
13
|
+
}
|
|
14
|
+
return merged;
|
|
15
|
+
}
|
|
16
|
+
return repoValue;
|
|
17
|
+
}
|
|
18
|
+
export function mergeConfigFiles(userConfig, repoConfig) {
|
|
19
|
+
if (!userConfig && !repoConfig)
|
|
20
|
+
return undefined;
|
|
21
|
+
if (!userConfig)
|
|
22
|
+
return repoConfig;
|
|
23
|
+
if (!repoConfig)
|
|
24
|
+
return userConfig;
|
|
25
|
+
return mergeValues(userConfig, repoConfig);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { defaultPathAdapter } from '../adapters/path/path-adapter.js';
|
|
3
|
+
function resolveUserConfigHome() {
|
|
4
|
+
const override = (process.env.SALMONLOOP_USER_CONFIG_HOME || '').trim();
|
|
5
|
+
if (override)
|
|
6
|
+
return defaultPathAdapter.resolve(override);
|
|
7
|
+
return homedir();
|
|
8
|
+
}
|
|
2
9
|
/**
|
|
3
10
|
* Repo-local configuration lives under ".salmonloop/" and is expected to be gitignored.
|
|
4
11
|
* Runtime state is stored under ".salmonloop/runtime/" (audit, rejections, tmp, locks).
|
|
@@ -7,14 +14,26 @@ export function getDefaultRepoConfigPath(repoRoot) {
|
|
|
7
14
|
return getDefaultRepoConfigPaths(repoRoot)[0];
|
|
8
15
|
}
|
|
9
16
|
export function getDefaultRepoConfigPaths(repoRoot) {
|
|
10
|
-
const base = join(resolve(repoRoot), '.salmonloop', 'config');
|
|
11
|
-
return [
|
|
17
|
+
const base = defaultPathAdapter.join(defaultPathAdapter.resolve(repoRoot), '.salmonloop', 'config');
|
|
18
|
+
return [
|
|
19
|
+
defaultPathAdapter.join(base, 'config.yaml'),
|
|
20
|
+
defaultPathAdapter.join(base, 'config.yml'),
|
|
21
|
+
defaultPathAdapter.join(base, 'config.json'),
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
export function getDefaultUserConfigPaths() {
|
|
25
|
+
const base = defaultPathAdapter.join(resolveUserConfigHome(), '.salmonloop', 'config');
|
|
26
|
+
return [
|
|
27
|
+
defaultPathAdapter.join(base, 'config.yaml'),
|
|
28
|
+
defaultPathAdapter.join(base, 'config.yml'),
|
|
29
|
+
defaultPathAdapter.join(base, 'config.json'),
|
|
30
|
+
];
|
|
12
31
|
}
|
|
13
32
|
export function resolveConfigPath(repoRoot, configPath) {
|
|
14
33
|
// Relative paths are resolved against the target repo root (not the CLI's cwd).
|
|
15
|
-
return resolve(repoRoot, configPath);
|
|
34
|
+
return defaultPathAdapter.resolve(repoRoot, configPath);
|
|
16
35
|
}
|
|
17
36
|
export function getDefaultIndexPath(repoRoot) {
|
|
18
|
-
return join(resolve(repoRoot), '.salmonloop', 'index');
|
|
37
|
+
return defaultPathAdapter.join(defaultPathAdapter.resolve(repoRoot), '.salmonloop', 'index');
|
|
19
38
|
}
|
|
20
39
|
//# sourceMappingURL=paths.js.map
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { resolveBaseUrl } from '../llm/base-url.js';
|
|
2
2
|
import { ConfigError } from './errors.js';
|
|
3
3
|
import { firstProviderRef, resolveApiKey, resolveModelId } from './resolve-env.js';
|
|
4
|
+
function mergeCapabilities(providerCapabilities, modelCapabilities) {
|
|
5
|
+
if (!providerCapabilities && !modelCapabilities)
|
|
6
|
+
return undefined;
|
|
7
|
+
return {
|
|
8
|
+
...providerCapabilities,
|
|
9
|
+
...modelCapabilities,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
4
12
|
export function resolveLlmFromConfig(raw) {
|
|
5
13
|
const llm = raw?.llm;
|
|
6
14
|
const providers = llm?.providers || {};
|
|
@@ -49,6 +57,7 @@ export function resolveLlmFromConfig(raw) {
|
|
|
49
57
|
const apiKeyResolution = resolveApiKey(provider.api?.apiKey);
|
|
50
58
|
const baseUrl = resolveBaseUrl(provider.api?.baseUrl);
|
|
51
59
|
const selectedModelId = resolveModelId(activeProfile.id);
|
|
60
|
+
const activeCapabilities = mergeCapabilities(provider.capabilities, activeProfile.capabilities);
|
|
52
61
|
const routing = llm?.routing;
|
|
53
62
|
const phaseToProviderModel = routing?.phaseToModel && typeof routing.phaseToModel === 'object'
|
|
54
63
|
? Object.fromEntries(Object.entries(routing.phaseToModel)
|
|
@@ -76,6 +85,7 @@ export function resolveLlmFromConfig(raw) {
|
|
|
76
85
|
});
|
|
77
86
|
}
|
|
78
87
|
const phaseKey = resolveApiKey(phaseProvider.api?.apiKey);
|
|
88
|
+
const capabilities = mergeCapabilities(phaseProvider.capabilities, profile.capabilities);
|
|
79
89
|
return [
|
|
80
90
|
phase,
|
|
81
91
|
{
|
|
@@ -93,6 +103,7 @@ export function resolveLlmFromConfig(raw) {
|
|
|
93
103
|
id: resolveModelId(profile.id),
|
|
94
104
|
slot: profileSlot,
|
|
95
105
|
},
|
|
106
|
+
capabilities,
|
|
96
107
|
},
|
|
97
108
|
];
|
|
98
109
|
})
|
|
@@ -124,6 +135,7 @@ export function resolveLlmFromConfig(raw) {
|
|
|
124
135
|
selectedModelId,
|
|
125
136
|
selectedModelSlot: activeModelSlot,
|
|
126
137
|
},
|
|
138
|
+
capabilities: activeCapabilities,
|
|
127
139
|
routing: resolvedRouting,
|
|
128
140
|
};
|
|
129
141
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveLlmOutputPolicy } from '../llm/output-policy.js';
|
|
2
|
-
import {
|
|
2
|
+
import { loadConfigStack } from './load.js';
|
|
3
|
+
import { mergeConfigFiles } from './merge.js';
|
|
3
4
|
import { getDefaultRepoConfigPath } from './paths.js';
|
|
4
5
|
import { resolveLlmFromConfig } from './resolve-llm.js';
|
|
5
6
|
import { resolveAstValidationStrictness } from './resolvers/ast-validation.js';
|
|
@@ -15,20 +16,21 @@ export async function resolveConfig(opts) {
|
|
|
15
16
|
const enabled = opts.enableConfigFile !== false;
|
|
16
17
|
const path = opts.configFilePath;
|
|
17
18
|
const required = Boolean(opts.configFilePath);
|
|
18
|
-
const loaded = await
|
|
19
|
+
const loaded = await loadConfigStack({
|
|
19
20
|
repoRoot: opts.repoRoot,
|
|
20
21
|
configPath: path,
|
|
21
22
|
enabled,
|
|
22
23
|
required,
|
|
23
24
|
});
|
|
24
|
-
const raw = loaded?.config;
|
|
25
|
+
const raw = mergeConfigFiles(loaded.user?.config, loaded.repo?.config);
|
|
25
26
|
const uiLogMode = resolveUiLogMode(raw);
|
|
26
27
|
const permissionMode = resolvePermissionMode(raw);
|
|
28
|
+
const sourcePath = loaded.repo?.path || loaded.user?.path || path || getDefaultRepoConfigPath(opts.repoRoot);
|
|
27
29
|
return {
|
|
28
30
|
source: {
|
|
29
31
|
enabled,
|
|
30
|
-
path:
|
|
31
|
-
used: Boolean(loaded),
|
|
32
|
+
path: sourcePath,
|
|
33
|
+
used: Boolean(loaded.repo || loaded.user),
|
|
32
34
|
},
|
|
33
35
|
raw,
|
|
34
36
|
permissionMode,
|
|
@@ -10,12 +10,6 @@ export function resolveServerConfig(raw) {
|
|
|
10
10
|
tokens: serverRaw.a2a.tokens,
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
if (serverRaw.sidecar) {
|
|
14
|
-
server.sidecar = {
|
|
15
|
-
socket: serverRaw.sidecar.socket,
|
|
16
|
-
allowConditional: serverRaw.sidecar.allowConditional,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
13
|
if (serverRaw.acp) {
|
|
20
14
|
server.acp = {
|
|
21
15
|
sessionStore: {
|
|
@@ -26,6 +26,59 @@ function isValidMarkdownTheme(value) {
|
|
|
26
26
|
function isValidMarkdownRenderMode(value) {
|
|
27
27
|
return typeof value === 'string' && MARKDOWN_RENDER_MODES.includes(value);
|
|
28
28
|
}
|
|
29
|
+
function validateLlmCapabilities(input, context) {
|
|
30
|
+
if (input === undefined)
|
|
31
|
+
return undefined;
|
|
32
|
+
if (!isRecord(input)) {
|
|
33
|
+
throw new ConfigError('CONFIG_INVALID_LLM_CAPABILITIES', {
|
|
34
|
+
...context,
|
|
35
|
+
expected: 'object',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const allowedKeys = new Set(['toolCalling', 'responseFormatJsonObject', 'streaming']);
|
|
39
|
+
for (const key of Object.keys(input)) {
|
|
40
|
+
if (!allowedKeys.has(key)) {
|
|
41
|
+
throw new ConfigError('CONFIG_INVALID_LLM_CAPABILITY', {
|
|
42
|
+
...context,
|
|
43
|
+
capability: key,
|
|
44
|
+
expected: 'toolCalling|responseFormatJsonObject|streaming',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const cfg = {};
|
|
49
|
+
for (const key of ['toolCalling', 'responseFormatJsonObject', 'streaming']) {
|
|
50
|
+
if (input[key] !== undefined && !isBoolean(input[key])) {
|
|
51
|
+
throw new ConfigError('CONFIG_INVALID_LLM_CAPABILITY', {
|
|
52
|
+
...context,
|
|
53
|
+
capability: key,
|
|
54
|
+
expected: 'boolean',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (input[key] !== undefined) {
|
|
58
|
+
cfg[key] = input[key];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return cfg;
|
|
62
|
+
}
|
|
63
|
+
function normalizeLlmModelParams(input, context) {
|
|
64
|
+
if (!isRecord(input))
|
|
65
|
+
return undefined;
|
|
66
|
+
for (const key of [
|
|
67
|
+
'capabilities',
|
|
68
|
+
'toolCalling',
|
|
69
|
+
'responseFormatJsonObject',
|
|
70
|
+
'streaming',
|
|
71
|
+
]) {
|
|
72
|
+
if (input[key] !== undefined) {
|
|
73
|
+
throw new ConfigError('CONFIG_INVALID_LLM_CAPABILITY_LOCATION', {
|
|
74
|
+
...context,
|
|
75
|
+
capability: key,
|
|
76
|
+
expected: 'model.capabilities',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return input;
|
|
81
|
+
}
|
|
29
82
|
export function validateConfigFileV1(input) {
|
|
30
83
|
if (!isRecord(input)) {
|
|
31
84
|
throw new ConfigError('CONFIG_INVALID_ROOT', { expected: 'object' });
|
|
@@ -164,6 +217,11 @@ export function validateConfigFileV1(input) {
|
|
|
164
217
|
if (!isRecord(serverRaw)) {
|
|
165
218
|
throw new ConfigError('CONFIG_INVALID_SERVER', { expected: 'object' });
|
|
166
219
|
}
|
|
220
|
+
for (const key of Object.keys(serverRaw)) {
|
|
221
|
+
if (key !== 'a2a' && key !== 'acp') {
|
|
222
|
+
throw new ConfigError('CONFIG_INVALID_SERVER_UNKNOWN_KEY', { key });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
167
225
|
const server = {};
|
|
168
226
|
if (serverRaw.a2a !== undefined) {
|
|
169
227
|
if (!isRecord(serverRaw.a2a)) {
|
|
@@ -187,24 +245,6 @@ export function validateConfigFileV1(input) {
|
|
|
187
245
|
tokens: a2aRaw.tokens,
|
|
188
246
|
};
|
|
189
247
|
}
|
|
190
|
-
if (serverRaw.sidecar !== undefined) {
|
|
191
|
-
if (!isRecord(serverRaw.sidecar)) {
|
|
192
|
-
throw new ConfigError('CONFIG_INVALID_SERVER_SIDECAR', { expected: 'object' });
|
|
193
|
-
}
|
|
194
|
-
const sidecarRaw = serverRaw.sidecar;
|
|
195
|
-
if (sidecarRaw.socket !== undefined && !isString(sidecarRaw.socket)) {
|
|
196
|
-
throw new ConfigError('CONFIG_INVALID_SERVER_SIDECAR_SOCKET', { expected: 'string' });
|
|
197
|
-
}
|
|
198
|
-
if (sidecarRaw.allowConditional !== undefined && !isBoolean(sidecarRaw.allowConditional)) {
|
|
199
|
-
throw new ConfigError('CONFIG_INVALID_SERVER_SIDECAR_ALLOW_CONDITIONAL', {
|
|
200
|
-
expected: 'boolean',
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
server.sidecar = {
|
|
204
|
-
socket: sidecarRaw.socket,
|
|
205
|
-
allowConditional: sidecarRaw.allowConditional,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
248
|
if (serverRaw.acp !== undefined) {
|
|
209
249
|
if (!isRecord(serverRaw.acp)) {
|
|
210
250
|
throw new ConfigError('CONFIG_INVALID_SERVER_ACP', { expected: 'object' });
|
|
@@ -471,6 +511,27 @@ export function validateConfigFileV1(input) {
|
|
|
471
511
|
}
|
|
472
512
|
if (input.llm.activeModel !== undefined)
|
|
473
513
|
cfg.llm.activeModel = input.llm.activeModel;
|
|
514
|
+
const llmAny = input.llm;
|
|
515
|
+
if (llmAny.simpleModel !== undefined && !isString(llmAny.simpleModel)) {
|
|
516
|
+
throw new ConfigError('CONFIG_INVALID_LLM_SIMPLE_MODEL', { expected: 'string' });
|
|
517
|
+
}
|
|
518
|
+
if (llmAny.simpleModel !== undefined)
|
|
519
|
+
cfg.llm.simpleModel = llmAny.simpleModel;
|
|
520
|
+
if (llmAny.mediumModel !== undefined && !isString(llmAny.mediumModel)) {
|
|
521
|
+
throw new ConfigError('CONFIG_INVALID_LLM_MEDIUM_MODEL', { expected: 'string' });
|
|
522
|
+
}
|
|
523
|
+
if (llmAny.mediumModel !== undefined)
|
|
524
|
+
cfg.llm.mediumModel = llmAny.mediumModel;
|
|
525
|
+
if (llmAny.complexModel !== undefined && !isString(llmAny.complexModel)) {
|
|
526
|
+
throw new ConfigError('CONFIG_INVALID_LLM_COMPLEX_MODEL', { expected: 'string' });
|
|
527
|
+
}
|
|
528
|
+
if (llmAny.complexModel !== undefined)
|
|
529
|
+
cfg.llm.complexModel = llmAny.complexModel;
|
|
530
|
+
if (llmAny.reasoningModel !== undefined && !isString(llmAny.reasoningModel)) {
|
|
531
|
+
throw new ConfigError('CONFIG_INVALID_LLM_REASONING_MODEL', { expected: 'string' });
|
|
532
|
+
}
|
|
533
|
+
if (llmAny.reasoningModel !== undefined)
|
|
534
|
+
cfg.llm.reasoningModel = llmAny.reasoningModel;
|
|
474
535
|
if (input.llm.providers !== undefined) {
|
|
475
536
|
if (!isRecord(input.llm.providers)) {
|
|
476
537
|
throw new ConfigError('CONFIG_INVALID_LLM_PROVIDERS', { expected: 'object' });
|
|
@@ -540,6 +601,12 @@ export function validateConfigFileV1(input) {
|
|
|
540
601
|
hint: 'use llm.models with provider references',
|
|
541
602
|
});
|
|
542
603
|
}
|
|
604
|
+
const providerCapabilities = validateLlmCapabilities(rawProvider.capabilities, {
|
|
605
|
+
provider: id,
|
|
606
|
+
});
|
|
607
|
+
if (providerCapabilities) {
|
|
608
|
+
p.capabilities = providerCapabilities;
|
|
609
|
+
}
|
|
543
610
|
cfg.llm.providers[id] = p;
|
|
544
611
|
}
|
|
545
612
|
}
|
|
@@ -569,12 +636,18 @@ export function validateConfigFileV1(input) {
|
|
|
569
636
|
expected: 'non_empty_string',
|
|
570
637
|
});
|
|
571
638
|
}
|
|
639
|
+
const params = normalizeLlmModelParams(rawModel.params, {
|
|
640
|
+
model: slot,
|
|
641
|
+
location: 'params',
|
|
642
|
+
});
|
|
643
|
+
const capabilities = validateLlmCapabilities(rawModel.capabilities, {
|
|
644
|
+
model: slot,
|
|
645
|
+
});
|
|
572
646
|
cfg.llm.models[slot] = {
|
|
573
647
|
provider: provider,
|
|
574
648
|
id: rawModel.id,
|
|
575
|
-
params
|
|
576
|
-
|
|
577
|
-
: undefined,
|
|
649
|
+
params,
|
|
650
|
+
capabilities,
|
|
578
651
|
};
|
|
579
652
|
}
|
|
580
653
|
}
|
|
@@ -1,8 +1,89 @@
|
|
|
1
|
+
import { FileAdapter } from '../../adapters/fs/file-adapter.js';
|
|
1
2
|
import { LIMITS } from '../../config/limits.js';
|
|
2
3
|
import { getLogger } from '../../observability/logger.js';
|
|
3
4
|
import { spawnCommand } from '../../runtime/process-runner.js';
|
|
4
|
-
import { normalizePath } from '../../utils/path.js';
|
|
5
|
+
import { ensureInSandbox, normalizePath, safeJoin, safeRelative } from '../../utils/path.js';
|
|
6
|
+
const FALLBACK_EXCLUDED_DIRS = new Set(['.git', 'node_modules']);
|
|
7
|
+
const FALLBACK_MAX_FILES = 2000;
|
|
8
|
+
const FALLBACK_MAX_FILE_BYTES = Math.max(LIMITS.largeFileThresholdBytes, 64 * 1024);
|
|
9
|
+
const fileAdapter = new FileAdapter();
|
|
10
|
+
function isHiddenPathSegment(name) {
|
|
11
|
+
return name.startsWith('.');
|
|
12
|
+
}
|
|
13
|
+
function isBinaryLike(content) {
|
|
14
|
+
return content.includes('\0');
|
|
15
|
+
}
|
|
5
16
|
export class RipgrepGatherer {
|
|
17
|
+
async searchFileSystem(query, cwd, signal) {
|
|
18
|
+
const needle = query.toLowerCase();
|
|
19
|
+
if (!needle)
|
|
20
|
+
return [];
|
|
21
|
+
const results = [];
|
|
22
|
+
let scannedFiles = 0;
|
|
23
|
+
const pending = ['.'];
|
|
24
|
+
while (pending.length > 0 && scannedFiles < FALLBACK_MAX_FILES) {
|
|
25
|
+
if (signal?.aborted)
|
|
26
|
+
throw new Error('Operation cancelled by user');
|
|
27
|
+
const current = pending.shift();
|
|
28
|
+
const absoluteCurrent = ensureInSandbox(cwd, safeJoin(cwd, current));
|
|
29
|
+
let entries;
|
|
30
|
+
try {
|
|
31
|
+
entries = await fileAdapter.readdirWithTypes(absoluteCurrent);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
if (signal?.aborted)
|
|
39
|
+
throw new Error('Operation cancelled by user');
|
|
40
|
+
if (isHiddenPathSegment(entry.name))
|
|
41
|
+
continue;
|
|
42
|
+
if (entry.isSymbolicLink())
|
|
43
|
+
continue;
|
|
44
|
+
const relativePath = normalizePath(safeJoin(current, entry.name)).replace(/^(\.\/|\/)+/, '');
|
|
45
|
+
if (!relativePath)
|
|
46
|
+
continue;
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
if (FALLBACK_EXCLUDED_DIRS.has(entry.name))
|
|
49
|
+
continue;
|
|
50
|
+
pending.push(relativePath);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (!entry.isFile())
|
|
54
|
+
continue;
|
|
55
|
+
scannedFiles += 1;
|
|
56
|
+
if (scannedFiles > FALLBACK_MAX_FILES)
|
|
57
|
+
break;
|
|
58
|
+
const absoluteFile = ensureInSandbox(cwd, safeJoin(cwd, relativePath));
|
|
59
|
+
try {
|
|
60
|
+
const stat = await fileAdapter.stat(absoluteFile);
|
|
61
|
+
if (!stat.isFile() || stat.size > FALLBACK_MAX_FILE_BYTES)
|
|
62
|
+
continue;
|
|
63
|
+
const content = await fileAdapter.readFile(absoluteFile, 'utf-8');
|
|
64
|
+
if (isBinaryLike(content))
|
|
65
|
+
continue;
|
|
66
|
+
const lines = content.split(/\r?\n/);
|
|
67
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
68
|
+
const line = lines[index] ?? '';
|
|
69
|
+
if (!line.toLowerCase().includes(needle))
|
|
70
|
+
continue;
|
|
71
|
+
results.push({
|
|
72
|
+
file: normalizePath(safeRelative(cwd, absoluteFile)),
|
|
73
|
+
line: index + 1,
|
|
74
|
+
content: line,
|
|
75
|
+
});
|
|
76
|
+
if (results.length >= LIMITS.defaultSearchMatches)
|
|
77
|
+
return results;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return results;
|
|
86
|
+
}
|
|
6
87
|
async runRipgrep(query, cwd, signal) {
|
|
7
88
|
getLogger().trace(` [RG] Searching for: "${query}" in ${cwd}`);
|
|
8
89
|
if (signal?.aborted) {
|
|
@@ -38,7 +119,8 @@ export class RipgrepGatherer {
|
|
|
38
119
|
getLogger().trace(` [RG] Process closed with code ${result.code}. Output length: ${output.length}`);
|
|
39
120
|
if (result.error) {
|
|
40
121
|
if (result.error.code === 'ENOENT') {
|
|
41
|
-
getLogger().error('Error: ripgrep (rg) not found in PATH.
|
|
122
|
+
getLogger().error('Error: ripgrep (rg) not found in PATH. Falling back to bounded filesystem search.');
|
|
123
|
+
return await this.searchFileSystem(query, cwd, signal);
|
|
42
124
|
}
|
|
43
125
|
else {
|
|
44
126
|
getLogger().error(`Error running ripgrep: ${result.error.message}`);
|
|
@@ -85,7 +85,7 @@ function extractPathLikeTokens(input) {
|
|
|
85
85
|
}
|
|
86
86
|
function extractBacktickedTokens(input) {
|
|
87
87
|
const matches = [];
|
|
88
|
-
const re =
|
|
88
|
+
const re = /(?<!`)`([^`\n]{1,64})`(?!`)/g;
|
|
89
89
|
let m;
|
|
90
90
|
while ((m = re.exec(input)) !== null) {
|
|
91
91
|
const val = m[1]?.trim();
|
|
@@ -106,7 +106,7 @@ function extractErrorLikeTokens(input) {
|
|
|
106
106
|
}
|
|
107
107
|
function extractIdentifierTokens(input) {
|
|
108
108
|
const matches = [];
|
|
109
|
-
const re = /\b[A-Za-z_][A-Za-z0-9_]{2,}\b/g;
|
|
109
|
+
const re = /\b(?:[A-Z][A-Z0-9_]{1,}\d+[A-Z0-9_]*|[A-Za-z_][A-Za-z0-9_]{2,})\b/g;
|
|
110
110
|
let m;
|
|
111
111
|
while ((m = re.exec(input)) !== null) {
|
|
112
112
|
if (m[0])
|
|
@@ -114,6 +114,17 @@ function extractIdentifierTokens(input) {
|
|
|
114
114
|
}
|
|
115
115
|
return matches;
|
|
116
116
|
}
|
|
117
|
+
function extractQuotedPhrases(input) {
|
|
118
|
+
const matches = [];
|
|
119
|
+
const re = /["“”]([^"“”\n]{8,96})["“”]/g;
|
|
120
|
+
let m;
|
|
121
|
+
while ((m = re.exec(input)) !== null) {
|
|
122
|
+
const val = m[1]?.trim();
|
|
123
|
+
if (val)
|
|
124
|
+
matches.push(val);
|
|
125
|
+
}
|
|
126
|
+
return matches;
|
|
127
|
+
}
|
|
117
128
|
function isCjk(char) {
|
|
118
129
|
return /\p{Script=Han}|\p{Script=Hiragana}|\p{Script=Katakana}|\p{Script=Hangul}/u.test(char);
|
|
119
130
|
}
|
|
@@ -143,9 +154,11 @@ export function extractKeywords(instruction) {
|
|
|
143
154
|
const backticked = extractBacktickedTokens(raw);
|
|
144
155
|
// Priority 3: Error-like tokens (strong signal)
|
|
145
156
|
const errorLike = extractErrorLikeTokens(raw);
|
|
146
|
-
// Priority 4:
|
|
157
|
+
// Priority 4: Quoted diagnostics and messages (strong signal in bug reports)
|
|
158
|
+
const quotedPhrases = extractQuotedPhrases(raw);
|
|
159
|
+
// Priority 5: Identifiers (code-related)
|
|
147
160
|
const identifiers = extractIdentifierTokens(raw);
|
|
148
|
-
// Priority
|
|
161
|
+
// Priority 6: Word tokens
|
|
149
162
|
const wordTokens = raw
|
|
150
163
|
.toLowerCase()
|
|
151
164
|
.split(/[^\p{L}\p{N}_-]+/u)
|
|
@@ -155,6 +168,7 @@ export function extractKeywords(instruction) {
|
|
|
155
168
|
...pathLike,
|
|
156
169
|
...backticked,
|
|
157
170
|
...errorLike,
|
|
171
|
+
...quotedPhrases,
|
|
158
172
|
...identifiers,
|
|
159
173
|
...wordTokens,
|
|
160
174
|
]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DefaultPromptAssembler } from './assembly/default-prompt-assembler.js';
|
|
2
|
-
import {
|
|
2
|
+
import { getPromptCachingManager } from './cache/prompt-caching.js';
|
|
3
3
|
import { ArchitectureGatherer } from './gatherers/architecture-gatherer.js';
|
|
4
4
|
import { ArtifactGatherer } from './gatherers/artifact-gatherer.js';
|
|
5
5
|
import { AstGatherer } from './gatherers/ast-gatherer.js';
|
|
@@ -26,7 +26,7 @@ export function defaultContextServiceDeps() {
|
|
|
26
26
|
ghostDependencyGatherer: new GhostDependencyGatherer(ripgrepGatherer),
|
|
27
27
|
targetResolver: new TargetResolver(),
|
|
28
28
|
assembler: new DefaultPromptAssembler(),
|
|
29
|
-
promptCachingManager:
|
|
29
|
+
promptCachingManager: getPromptCachingManager(),
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
//# sourceMappingURL=service-deps.js.map
|
|
@@ -147,6 +147,14 @@ export class ContextService {
|
|
|
147
147
|
if (target.path)
|
|
148
148
|
deduped.add(target.path);
|
|
149
149
|
}
|
|
150
|
+
for (const file of result.context.relatedFiles ?? []) {
|
|
151
|
+
if (file.path)
|
|
152
|
+
deduped.add(file.path);
|
|
153
|
+
}
|
|
154
|
+
for (const snippet of result.context.rgSnippets ?? []) {
|
|
155
|
+
if (snippet.file)
|
|
156
|
+
deduped.add(snippet.file);
|
|
157
|
+
}
|
|
150
158
|
return [...deduped].sort().slice(0, ContextService.MAX_CACHE_TRACKED_FILES);
|
|
151
159
|
}
|
|
152
160
|
async computeTrackedFilesSignature(repoPath, files) {
|
|
@@ -1,7 +1,27 @@
|
|
|
1
|
+
import { FileAdapter } from '../../adapters/fs/file-adapter.js';
|
|
2
|
+
import { LIMITS } from '../../config/limits.js';
|
|
3
|
+
import { ensureInSandbox, normalizePath, safeJoin } from '../../utils/path.js';
|
|
4
|
+
import { outlineSource } from '../ast/source-outline.js';
|
|
1
5
|
import { CONTEXT_AUDIT_ACTION, CONTEXT_AUDIT_PHASE } from '../audit-constants.js';
|
|
2
6
|
import { recordContextAuditEvent } from '../audit.js';
|
|
3
7
|
import { extractKeywords } from '../keywords.js';
|
|
4
8
|
import { assertNotAborted } from '../service-helpers.js';
|
|
9
|
+
const fileAdapter = new FileAdapter();
|
|
10
|
+
async function readMatchedFileContent(req, file) {
|
|
11
|
+
if (req.snapshotHash && req.checkpointManager) {
|
|
12
|
+
return req.checkpointManager.readSnapshotFile(req.repoPath, req.snapshotHash, file);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const fullPath = ensureInSandbox(req.repoPath, safeJoin(req.repoPath, file));
|
|
16
|
+
const stat = await fileAdapter.stat(fullPath);
|
|
17
|
+
if (!stat.isFile() || stat.size > LIMITS.largeFileThresholdBytes)
|
|
18
|
+
return null;
|
|
19
|
+
return await fileAdapter.readFile(fullPath, 'utf-8');
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
5
25
|
export function buildContextGatherStep(deps) {
|
|
6
26
|
return async ({ req, diffScope, primaryText }) => {
|
|
7
27
|
assertNotAborted(req.signal);
|
|
@@ -24,6 +44,24 @@ export function buildContextGatherStep(deps) {
|
|
|
24
44
|
if (ghostFiles.length > 0) {
|
|
25
45
|
astRes.relatedFiles.push(...ghostFiles);
|
|
26
46
|
}
|
|
47
|
+
const relatedSeen = new Set(astRes.relatedFiles.map((file) => file.path));
|
|
48
|
+
const primaryPath = req.primaryFile
|
|
49
|
+
? normalizePath(req.primaryFile).replace(/^(\.\/|\/)+/, '')
|
|
50
|
+
: undefined;
|
|
51
|
+
for (const snippet of rgSnippets) {
|
|
52
|
+
const file = normalizePath(snippet.file).replace(/^(\.\/|\/)+/, '');
|
|
53
|
+
if (!file || file === primaryPath || relatedSeen.has(file))
|
|
54
|
+
continue;
|
|
55
|
+
relatedSeen.add(file);
|
|
56
|
+
const content = await readMatchedFileContent(req, file);
|
|
57
|
+
astRes.relatedFiles.push({
|
|
58
|
+
path: file,
|
|
59
|
+
kind: 'dependency',
|
|
60
|
+
mode: content ? 'full' : 'outline',
|
|
61
|
+
content: content ?? `ripgrep match at line ${snippet.line}: ${snippet.content}`,
|
|
62
|
+
outline: content ? outlineSource(content) : undefined,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
27
65
|
recordContextAuditEvent(CONTEXT_AUDIT_ACTION.gatherCompleted, {
|
|
28
66
|
rgSnippets: rgSnippets.length,
|
|
29
67
|
includedFiles: diffRes.includedFiles.length,
|