salmon-loop 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/authorization/non-interactive.js +9 -13
- package/dist/cli/authorization/provider.js +2 -10
- package/dist/cli/chat.js +12 -6
- package/dist/cli/commands/allowlist.js +1 -1
- package/dist/cli/commands/chat.js +13 -13
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/mode.js +2 -2
- package/dist/cli/commands/parallel.js +1 -1
- package/dist/cli/commands/run/handler.js +9 -4
- package/dist/cli/commands/run/loop-params.js +2 -0
- package/dist/cli/commands/run/parse-options.js +14 -26
- package/dist/cli/commands/run/runtime-llm.js +15 -12
- package/dist/cli/commands/run/runtime-options.js +3 -1
- package/dist/cli/config.js +0 -8
- package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
- package/dist/cli/locales/en.js +2 -2
- package/dist/cli/reporters/standard.js +12 -3
- package/dist/cli/reporters/stream-json.js +2 -1
- package/dist/cli/slash/runtime.js +2 -2
- package/dist/cli/ui/hooks/useLoopEvents.js +1 -1
- package/dist/cli/ui/hooks/useLoopState.js +1 -1
- 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/ast/parser.js +18 -9
- 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/config/schema.js +738 -0
- package/dist/core/config/validate.js +11 -922
- 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 +34 -40
- package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
- package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +21 -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 +12 -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/token/encoding-registry.js +7 -6
- package/dist/core/context/truncation/strategies/json.js +5 -2
- package/dist/core/context/truncation/type-detector.js +3 -1
- package/dist/core/extensions/index.js +48 -3
- package/dist/core/extensions/load.js +3 -2
- package/dist/core/extensions/merge.js +5 -1
- package/dist/core/extensions/paths.js +8 -2
- package/dist/core/extensions/schemas.js +21 -0
- package/dist/core/facades/cli-authorization-provider.js +1 -0
- package/dist/core/facades/cli-command-chat.js +2 -0
- package/dist/core/facades/cli-run-handler.js +1 -0
- package/dist/core/facades/cli-utils-serialize.js +2 -0
- package/dist/core/feedback/parsers.js +290 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +4 -3
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +30 -13
- package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +49 -24
- package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
- 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 +5 -2
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
- package/dist/core/grizzco/services/registry.js +18 -0
- package/dist/core/grizzco/steps/audit.js +20 -10
- package/dist/core/grizzco/steps/autopilot.js +21 -32
- package/dist/core/grizzco/steps/display-report.js +4 -11
- package/dist/core/grizzco/steps/explore.js +14 -4
- package/dist/core/grizzco/steps/generateReview.js +3 -1
- package/dist/core/grizzco/steps/patch/prompt-input.js +4 -1
- package/dist/core/grizzco/steps/patch.js +1 -0
- package/dist/core/grizzco/steps/plan.js +58 -49
- package/dist/core/grizzco/steps/research.js +3 -1
- package/dist/core/grizzco/steps/tool-runtime.js +3 -0
- package/dist/core/grizzco/steps/verify.js +7 -1
- package/dist/core/grizzco/validation/AstValidationService.js +3 -1
- package/dist/core/grizzco/workers/strata-sync-worker.js +2 -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 +37 -26
- package/dist/core/llm/ai-sdk/request-params.js +2 -6
- package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
- package/dist/core/llm/ai-sdk/retry-classifier.js +17 -7
- package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
- package/dist/core/llm/contracts/repair.js +16 -8
- package/dist/core/llm/errors.js +18 -14
- package/dist/core/llm/output-policy.js +8 -0
- package/dist/core/llm/redact.js +1 -3
- 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 +51 -0
- package/dist/core/llm/tool-calling-stub.js +48 -0
- package/dist/core/llm/utils.js +17 -6
- package/dist/core/mcp/bridge/prompt-command-provider.js +4 -3
- package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
- package/dist/core/mcp/bridge/tool-bridge.js +5 -14
- package/dist/core/mcp/catalog/discovery.js +3 -1
- package/dist/core/mcp/client/connection-manager.js +7 -4
- package/dist/core/mcp/client/transport-factory.js +7 -3
- package/dist/core/mcp/host/sampling-provider.js +1 -1
- package/dist/core/mcp/schema/json-schema-to-zod.js +2 -1
- package/dist/core/memory/relevant-retrieval.js +6 -4
- package/dist/core/observability/audit-file.js +2 -1
- package/dist/core/observability/audit-trail.js +3 -1
- package/dist/core/observability/authorization-decisions.js +13 -12
- package/dist/core/observability/error-mapping.js +2 -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/observability/token-usage.js +5 -4
- package/dist/core/permission-gate/default-gate.js +5 -8
- package/dist/core/plan/storage.js +7 -4
- package/dist/core/plugin/loader.js +8 -5
- package/dist/core/prompts/registry.js +12 -30
- 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 +5 -4
- 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 +13 -6
- package/dist/core/protocols/acp/permission-provider.js +3 -2
- package/dist/core/protocols/acp/stdio-server.js +6 -6
- package/dist/core/reflection/engine.js +114 -14
- package/dist/core/runtime/agent-server-runtime.js +3 -2
- package/dist/core/runtime/batch-runner.js +81 -0
- package/dist/core/runtime/initialize.js +71 -6
- 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/compaction/index.js +4 -3
- package/dist/core/session/compression.js +3 -1
- package/dist/core/session/manager.js +26 -38
- package/dist/core/session/pruning-strategy.js +2 -1
- package/dist/core/session/token-tracker.js +27 -9
- package/dist/core/skills/parser.js +3 -2
- package/dist/core/skills/permissions.js +2 -2
- package/dist/core/skills/runtime/MicroTaskRunner.js +1 -1
- package/dist/core/skills/runtime/SkillRunner.js +5 -2
- package/dist/core/slash/steps/slash-execute.js +7 -5
- package/dist/core/slash/strategy.js +1 -1
- 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 +9 -10
- package/dist/core/strata/runtime/environment.js +2 -1
- package/dist/core/strata/runtime/synchronizer.js +28 -26
- package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
- package/dist/core/structured-output/json-extract.js +3 -1
- package/dist/core/structured-output/json-schema-validator.js +1 -13
- package/dist/core/sub-agent/artifacts/store.js +2 -1
- package/dist/core/sub-agent/context-snapshot.js +12 -6
- package/dist/core/sub-agent/controller.js +70 -1
- package/dist/core/sub-agent/core/loop.js +25 -3
- package/dist/core/sub-agent/core/manager.js +343 -117
- package/dist/core/sub-agent/registry-defaults.js +12 -0
- package/dist/core/sub-agent/registry.js +8 -0
- package/dist/core/sub-agent/summary.js +96 -0
- package/dist/core/sub-agent/team.js +98 -0
- package/dist/core/sub-agent/tools/task-await.js +109 -0
- package/dist/core/sub-agent/tools/task-spawn.js +52 -7
- package/dist/core/sub-agent/tools/team.js +92 -0
- package/dist/core/sub-agent/types.js +11 -2
- package/dist/core/target-runtime/profile.js +3 -1
- package/dist/core/tools/audit.js +3 -2
- package/dist/core/tools/budget.js +7 -12
- 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/executor.js +46 -43
- 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 +90 -7
- 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 +53 -111
- package/dist/core/tools/builtin/interaction.js +13 -15
- package/dist/core/tools/builtin/knowledge.js +146 -4
- package/dist/core/tools/builtin/proposal.js +14 -3
- package/dist/core/tools/builtin/verify.js +35 -3
- package/dist/core/tools/capability/executor.js +5 -5
- package/dist/core/tools/headless-payload.js +1 -3
- package/dist/core/tools/mapper.js +8 -42
- package/dist/core/tools/parallel/persistence.js +17 -5
- package/dist/core/tools/parallel/scheduler.js +23 -21
- package/dist/core/tools/permissions/permission-rules.js +69 -115
- package/dist/core/tools/plugins/loader.js +4 -3
- package/dist/core/tools/router.js +112 -58
- package/dist/core/tools/session.js +64 -102
- package/dist/core/tools/streaming/ToolCallAccumulator.js +1 -3
- package/dist/core/tools/tool-visibility.js +2 -1
- package/dist/core/tools/types.js +10 -0
- package/dist/core/types/batch.js +2 -0
- package/dist/core/utils/error.js +79 -0
- package/dist/core/utils/sanitizer.js +5 -2
- package/dist/core/utils/serialize.js +66 -0
- package/dist/core/utils/zod.js +29 -0
- 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/core/workspace/capabilities.js +3 -2
- package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
- package/dist/languages/python/index.js +154 -0
- package/dist/locales/en.js +8 -1
- 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 {
|
|
@@ -50,28 +52,44 @@ export class KnowledgeGatherer {
|
|
|
50
52
|
data.deprecated_rules.forEach((r) => allDeprecated.add(r));
|
|
51
53
|
}
|
|
52
54
|
if (data.architectural_decisions) {
|
|
55
|
+
if (!Array.isArray(aggregated.architectural_decisions)) {
|
|
56
|
+
aggregated.architectural_decisions = [];
|
|
57
|
+
}
|
|
53
58
|
aggregated.architectural_decisions.push(...data.architectural_decisions);
|
|
54
59
|
}
|
|
55
60
|
if (data.user_preferences) {
|
|
56
61
|
aggregated.user_preferences = data.user_preferences;
|
|
57
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
|
+
}
|
|
58
69
|
}
|
|
59
|
-
catch {
|
|
70
|
+
catch (error) {
|
|
60
71
|
// Skip corrupted files
|
|
72
|
+
getLogger().debug(`[KnowledgeGatherer] failed to load event file ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
61
73
|
}
|
|
62
74
|
}
|
|
63
75
|
// Filter out deprecated rules from aggregated project_rules
|
|
64
76
|
if (aggregated.project_rules) {
|
|
65
77
|
aggregated.project_rules = aggregated.project_rules.filter((r) => !allDeprecated.has(r));
|
|
66
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
|
+
}
|
|
67
84
|
// 3. Optional Compaction
|
|
68
85
|
if (eventFiles.length >= KnowledgeGatherer.COMPACTION_THRESHOLD) {
|
|
69
86
|
// Run compaction in background (non-blocking)
|
|
70
87
|
this.compact(knowledgeDir, aggregated, eventFiles).catch((e) => getLogger().debug(`[KnowledgeGatherer] Compaction failed: ${e}`));
|
|
71
88
|
}
|
|
72
89
|
}
|
|
73
|
-
catch {
|
|
90
|
+
catch (error) {
|
|
74
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)}`);
|
|
75
93
|
}
|
|
76
94
|
return {
|
|
77
95
|
project_rules: aggregated.project_rules,
|
|
@@ -79,6 +97,7 @@ export class KnowledgeGatherer {
|
|
|
79
97
|
? aggregated.architectural_decisions
|
|
80
98
|
: undefined,
|
|
81
99
|
user_preferences: aggregated.user_preferences,
|
|
100
|
+
lessons_learned: aggregated.lessons_learned?.length ? aggregated.lessons_learned : undefined,
|
|
82
101
|
};
|
|
83
102
|
}
|
|
84
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;
|
|
@@ -119,6 +119,7 @@ export class ContextService {
|
|
|
119
119
|
expectedTargetSetSignature;
|
|
120
120
|
if (recordedTargetSetSignature !== expectedTargetSetSignature) {
|
|
121
121
|
await this.cacheStore.delete(cacheKey);
|
|
122
|
+
this.deleteUpdater(cacheKey);
|
|
122
123
|
this.cacheMetrics.misses += 1;
|
|
123
124
|
return {
|
|
124
125
|
missReason: 'target_signature_mismatch',
|
|
@@ -128,6 +129,7 @@ export class ContextService {
|
|
|
128
129
|
const nextSignature = await this.computeTrackedFilesSignature(repoPath, entry.trackedFiles);
|
|
129
130
|
if (nextSignature !== entry.signature) {
|
|
130
131
|
await this.cacheStore.delete(cacheKey);
|
|
132
|
+
this.deleteUpdater(cacheKey);
|
|
131
133
|
this.cacheMetrics.misses += 1;
|
|
132
134
|
return { missReason: 'signature_mismatch', targetSetSignature: expectedTargetSetSignature };
|
|
133
135
|
}
|
|
@@ -165,7 +167,8 @@ export class ContextService {
|
|
|
165
167
|
const stat = await this.fileAdapter.stat(absoluteFile);
|
|
166
168
|
parts.push(this.formatStatSignature(relativeFile, stat));
|
|
167
169
|
}
|
|
168
|
-
catch {
|
|
170
|
+
catch (error) {
|
|
171
|
+
getLogger().debug(`[ContextService] stat failed for ${relativeFile}: ${error instanceof Error ? error.message : String(error)}`);
|
|
169
172
|
parts.push(`${relativeFile}:missing`);
|
|
170
173
|
}
|
|
171
174
|
}
|
|
@@ -184,7 +187,8 @@ export class ContextService {
|
|
|
184
187
|
const stat = await this.fileAdapter.stat(gitPath);
|
|
185
188
|
parts.push(this.formatStatSignature(rel, stat));
|
|
186
189
|
}
|
|
187
|
-
catch {
|
|
190
|
+
catch (error) {
|
|
191
|
+
getLogger().debug(`[ContextService] stat failed for ${rel}: ${error instanceof Error ? error.message : String(error)}`);
|
|
188
192
|
parts.push(`${rel}:missing`);
|
|
189
193
|
}
|
|
190
194
|
}
|
|
@@ -210,6 +214,7 @@ export class ContextService {
|
|
|
210
214
|
if (!last || Date.now() - last <= this.cacheTtlMs)
|
|
211
215
|
return false;
|
|
212
216
|
await this.cacheStore.delete(cacheKey);
|
|
217
|
+
this.deleteUpdater(cacheKey);
|
|
213
218
|
this.cacheMetrics.evictions += 1;
|
|
214
219
|
return true;
|
|
215
220
|
}
|
|
@@ -243,6 +248,7 @@ export class ContextService {
|
|
|
243
248
|
}
|
|
244
249
|
if (victimKey) {
|
|
245
250
|
await this.cacheStore.delete(victimKey);
|
|
251
|
+
this.deleteUpdater(victimKey);
|
|
246
252
|
this.cacheMetrics.evictions += 1;
|
|
247
253
|
}
|
|
248
254
|
}
|
|
@@ -253,6 +259,7 @@ export class ContextService {
|
|
|
253
259
|
const chunk = victims.slice(i, i + 10);
|
|
254
260
|
await Promise.all(chunk.map(async ([key]) => {
|
|
255
261
|
await this.cacheStore.delete(key);
|
|
262
|
+
this.deleteUpdater(key);
|
|
256
263
|
this.cacheMetrics.evictions += 1;
|
|
257
264
|
}));
|
|
258
265
|
}
|
|
@@ -285,6 +292,9 @@ export class ContextService {
|
|
|
285
292
|
}
|
|
286
293
|
return updater;
|
|
287
294
|
}
|
|
295
|
+
deleteUpdater(key) {
|
|
296
|
+
this.updaters.delete(key);
|
|
297
|
+
}
|
|
288
298
|
logDiff(key, diff) {
|
|
289
299
|
if (!diff.addedFiles.length && !diff.modifiedFiles.length && !diff.removedFiles.length) {
|
|
290
300
|
return;
|
|
@@ -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 ====================
|
|
@@ -24,13 +24,13 @@ class TiktokenEncoding {
|
|
|
24
24
|
this.initialized = true;
|
|
25
25
|
}
|
|
26
26
|
encode(text) {
|
|
27
|
-
this.
|
|
28
|
-
return Array.from(
|
|
27
|
+
const encoder = this.getEncoder();
|
|
28
|
+
return Array.from(encoder.encode(text));
|
|
29
29
|
}
|
|
30
30
|
decode(tokens) {
|
|
31
|
-
this.
|
|
31
|
+
const encoder = this.getEncoder();
|
|
32
32
|
const uint32Tokens = new Uint32Array(tokens);
|
|
33
|
-
const decoded =
|
|
33
|
+
const decoded = encoder.decode(uint32Tokens);
|
|
34
34
|
return new TextDecoder().decode(decoded);
|
|
35
35
|
}
|
|
36
36
|
count(text) {
|
|
@@ -43,10 +43,11 @@ class TiktokenEncoding {
|
|
|
43
43
|
}
|
|
44
44
|
this.initialized = false;
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
if (!this.
|
|
46
|
+
getEncoder() {
|
|
47
|
+
if (!this.encoder) {
|
|
48
48
|
throw new Error(`Encoding ${this.name} not initialized. Call initialize() first.`);
|
|
49
49
|
}
|
|
50
|
+
return this.encoder;
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
@@ -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
|
}
|
|
@@ -3,9 +3,9 @@ import { buildResolvedMcpServersV2 } from '../mcp/config/index.js';
|
|
|
3
3
|
import { getLogger } from '../observability/logger.js';
|
|
4
4
|
import { loadConfig } from './load.js';
|
|
5
5
|
import { mergeScopedEntries } from './merge.js';
|
|
6
|
-
import { expandHome, getRepoMcpConfigPath, getRepoSkillConfigPath, getRepoToolConfigPath, getUserMcpConfigPath, getUserSkillConfigPath, getUserToolConfigPath, isWithinRoot, resolveRepoRelative, resolveUserRelative, } from './paths.js';
|
|
6
|
+
import { expandHome, getRepoAgentsConfigPath, getRepoMcpConfigPath, getRepoSkillConfigPath, getRepoToolConfigPath, getUserAgentsConfigPath, getUserMcpConfigPath, getUserSkillConfigPath, getUserToolConfigPath, isWithinRoot, resolveRepoRelative, resolveUserRelative, } from './paths.js';
|
|
7
7
|
import { redactExtensions } from './redact.js';
|
|
8
|
-
import { McpConfigSchema, SkillsConfigSchema, ToolsConfigSchema } from './schemas.js';
|
|
8
|
+
import { AgentsConfigSchema, McpConfigSchema, SkillsConfigSchema, ToolsConfigSchema, } from './schemas.js';
|
|
9
9
|
function defaultEnabled(scope) {
|
|
10
10
|
return scope === 'repo';
|
|
11
11
|
}
|
|
@@ -29,6 +29,47 @@ function buildResolvedPlugins(entries, repoRoot) {
|
|
|
29
29
|
};
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
+
function buildResolvedAgentProfiles(user, repo) {
|
|
33
|
+
const seen = new Map();
|
|
34
|
+
// User profiles first (lower priority)
|
|
35
|
+
if (user) {
|
|
36
|
+
for (const agent of user.agents) {
|
|
37
|
+
if (agent.enabled === false)
|
|
38
|
+
continue;
|
|
39
|
+
seen.set(agent.id, toResolvedProfile(agent, 'user'));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Repo profiles override user profiles (higher priority)
|
|
43
|
+
if (repo) {
|
|
44
|
+
for (const agent of repo.agents) {
|
|
45
|
+
if (agent.enabled === false) {
|
|
46
|
+
seen.delete(agent.id);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
seen.set(agent.id, toResolvedProfile(agent, 'repo'));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return Array.from(seen.values());
|
|
53
|
+
}
|
|
54
|
+
function toResolvedProfile(raw, scope) {
|
|
55
|
+
return {
|
|
56
|
+
id: raw.id,
|
|
57
|
+
name: raw.name,
|
|
58
|
+
role: raw.role,
|
|
59
|
+
description: raw.description,
|
|
60
|
+
allowedTools: raw.allowedTools ?? ['code.search', 'fs.read'],
|
|
61
|
+
readOnly: raw.readOnly ?? false,
|
|
62
|
+
stratagem: raw.stratagem ?? 'investigator',
|
|
63
|
+
toolInheritance: raw.toolInheritance,
|
|
64
|
+
permissionMode: raw.permissionMode,
|
|
65
|
+
systemPrompt: raw.systemPrompt,
|
|
66
|
+
maxTokens: raw.maxTokens,
|
|
67
|
+
maxAttempts: raw.maxAttempts,
|
|
68
|
+
timeoutMs: raw.timeoutMs,
|
|
69
|
+
model: raw.model,
|
|
70
|
+
scope,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
32
73
|
function buildResolvedSkills(user, repo, repoRoot) {
|
|
33
74
|
const repoDiscovery = repo?.discovery;
|
|
34
75
|
const userDiscovery = user?.discovery;
|
|
@@ -73,13 +114,15 @@ function buildResolvedSkills(user, repo, repoRoot) {
|
|
|
73
114
|
}
|
|
74
115
|
export async function resolveExtensions(options) {
|
|
75
116
|
const { repoRoot } = options;
|
|
76
|
-
const [userMcp, repoMcp, userTools, repoTools, userSkills, repoSkills] = await Promise.all([
|
|
117
|
+
const [userMcp, repoMcp, userTools, repoTools, userSkills, repoSkills, userAgents, repoAgents] = await Promise.all([
|
|
77
118
|
loadConfig(getUserMcpConfigPath(), McpConfigSchema),
|
|
78
119
|
loadConfig(getRepoMcpConfigPath(repoRoot), McpConfigSchema),
|
|
79
120
|
loadConfig(getUserToolConfigPath(), ToolsConfigSchema),
|
|
80
121
|
loadConfig(getRepoToolConfigPath(repoRoot), ToolsConfigSchema),
|
|
81
122
|
loadConfig(getUserSkillConfigPath(), SkillsConfigSchema),
|
|
82
123
|
loadConfig(getRepoSkillConfigPath(repoRoot), SkillsConfigSchema),
|
|
124
|
+
loadConfig(getUserAgentsConfigPath(), AgentsConfigSchema),
|
|
125
|
+
loadConfig(getRepoAgentsConfigPath(repoRoot), AgentsConfigSchema),
|
|
83
126
|
]);
|
|
84
127
|
const mergedServers = mergeScopedEntries(userMcp?.config.servers, repoMcp?.config.servers);
|
|
85
128
|
const mergedPlugins = mergeScopedEntries(userTools?.config.plugins, repoTools?.config.plugins);
|
|
@@ -87,11 +130,13 @@ export async function resolveExtensions(options) {
|
|
|
87
130
|
mcpServers: buildResolvedMcpServersV2(mergedServers, repoRoot),
|
|
88
131
|
toolPlugins: buildResolvedPlugins(mergedPlugins, repoRoot),
|
|
89
132
|
skillDiscovery: buildResolvedSkills(userSkills?.config, repoSkills?.config, repoRoot),
|
|
133
|
+
agentProfiles: buildResolvedAgentProfiles(userAgents?.config, repoAgents?.config),
|
|
90
134
|
};
|
|
91
135
|
const rawEffective = {
|
|
92
136
|
mcp: repoMcp?.config ?? userMcp?.config ?? null,
|
|
93
137
|
tools: repoTools?.config ?? userTools?.config ?? null,
|
|
94
138
|
skills: repoSkills?.config ?? userSkills?.config ?? null,
|
|
139
|
+
agents: repoAgents?.config ?? userAgents?.config ?? null,
|
|
95
140
|
};
|
|
96
141
|
return {
|
|
97
142
|
resolved,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { syncFs as fs } from '../adapters/fs/node-fs.js';
|
|
2
|
+
import { errorMessage } from '../utils/error.js';
|
|
2
3
|
export class ExtensionConfigError extends Error {
|
|
3
4
|
path;
|
|
4
5
|
constructor(path, message) {
|
|
@@ -18,7 +19,7 @@ export async function tryLoadJsonFile(path) {
|
|
|
18
19
|
: undefined) === 'ENOENT') {
|
|
19
20
|
return { exists: false };
|
|
20
21
|
}
|
|
21
|
-
throw new ExtensionConfigError(path, (error
|
|
22
|
+
throw new ExtensionConfigError(path, errorMessage(error) || 'Unable to read file');
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
export async function loadConfig(path, schema) {
|
|
@@ -30,7 +31,7 @@ export async function loadConfig(path, schema) {
|
|
|
30
31
|
return { path, config };
|
|
31
32
|
}
|
|
32
33
|
catch (error) {
|
|
33
|
-
throw new ExtensionConfigError(path, (error
|
|
34
|
+
throw new ExtensionConfigError(path, errorMessage(error) || 'Schema validation failed');
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
//# sourceMappingURL=load.js.map
|
|
@@ -11,7 +11,10 @@ export function mergeScopedEntries(user, repo) {
|
|
|
11
11
|
if (previous) {
|
|
12
12
|
merged.set(key, {
|
|
13
13
|
key,
|
|
14
|
-
entry: {
|
|
14
|
+
entry: {
|
|
15
|
+
...previous.entry,
|
|
16
|
+
...entry,
|
|
17
|
+
},
|
|
15
18
|
scope: 'repo',
|
|
16
19
|
});
|
|
17
20
|
}
|
|
@@ -38,6 +41,7 @@ export function mergeResolvedExtensions(base, overlay) {
|
|
|
38
41
|
: base.skillDiscovery.scope,
|
|
39
42
|
paths: [...base.skillDiscovery.paths, ...overlay.skillDiscovery.paths],
|
|
40
43
|
},
|
|
44
|
+
agentProfiles: [...base.agentProfiles, ...overlay.agentProfiles],
|
|
41
45
|
};
|
|
42
46
|
}
|
|
43
47
|
//# sourceMappingURL=merge.js.map
|
|
@@ -39,6 +39,12 @@ export function getUserToolConfigPath() {
|
|
|
39
39
|
export function getUserSkillConfigPath() {
|
|
40
40
|
return path.join(USER_CONFIG_DIR, 'skills-user.json');
|
|
41
41
|
}
|
|
42
|
+
export function getRepoAgentsConfigPath(repoRoot) {
|
|
43
|
+
return path.join(repoRoot, REPO_CONFIG_DIR, 'agents.json');
|
|
44
|
+
}
|
|
45
|
+
export function getUserAgentsConfigPath() {
|
|
46
|
+
return path.join(USER_CONFIG_DIR, 'agents-user.json');
|
|
47
|
+
}
|
|
42
48
|
/**
|
|
43
49
|
* Check whether a candidate path resides within (or equals) a given root directory.
|
|
44
50
|
*
|
|
@@ -60,11 +66,11 @@ export function isWithinRoot(candidate, root) {
|
|
|
60
66
|
const realRoot = realpathSync(resolvedRoot);
|
|
61
67
|
return realCandidate === realRoot || realCandidate.startsWith(realRoot + path.sep);
|
|
62
68
|
}
|
|
63
|
-
catch {
|
|
69
|
+
catch (error) {
|
|
64
70
|
// Candidate or root does not exist yet — fall back to lexical check.
|
|
65
71
|
// This allows pre-declaring paths that will be created later, while
|
|
66
72
|
// still catching obvious traversal sequences like `../../etc`.
|
|
67
|
-
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)}`);
|
|
68
74
|
return (resolvedCandidate === resolvedRoot || resolvedCandidate.startsWith(resolvedRoot + path.sep));
|
|
69
75
|
}
|
|
70
76
|
}
|