@kaelio/ktx 0.10.0 → 0.12.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/assets/python/{kaelio_ktx-0.10.0-py3-none-any.whl → kaelio_ktx-0.12.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/admin.js +1 -1
- package/dist/clack.d.ts +16 -0
- package/dist/clack.js +37 -6
- package/dist/claude-code-prompt-caching.js +1 -1
- package/dist/cli-program.js +7 -3
- package/dist/cli-runtime.d.ts +2 -0
- package/dist/cli-runtime.js +14 -8
- package/dist/commands/connection-commands.js +1 -1
- package/dist/commands/ingest-commands.js +4 -4
- package/dist/commands/mcp-commands.js +12 -12
- package/dist/commands/runtime-commands.js +4 -4
- package/dist/commands/setup-commands.js +6 -5
- package/dist/commands/sl-commands.js +1 -1
- package/dist/commands/sql-commands.js +1 -1
- package/dist/commands/status-commands.js +1 -1
- package/dist/community-cta.d.ts +11 -0
- package/dist/community-cta.js +19 -0
- package/dist/connection.js +1 -1
- package/dist/connectors/clickhouse/connector.js +1 -1
- package/dist/connectors/mysql/connector.js +1 -1
- package/dist/connectors/snowflake/connector.d.ts +1 -1
- package/dist/connectors/sqlite/connector.js +2 -25
- package/dist/connectors/sqlserver/connector.js +3 -3
- package/dist/context/connections/connection-type.d.ts +1 -1
- package/dist/context/connections/read-only-sql.d.ts +1 -0
- package/dist/context/connections/read-only-sql.js +116 -2
- package/dist/context/core/git-env.d.ts +12 -1
- package/dist/context/core/git-env.js +17 -2
- package/dist/context/core/git.service.d.ts +23 -0
- package/dist/context/core/git.service.js +86 -15
- package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
- package/dist/context/ingest/adapters/looker/client.js +7 -2
- package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
- package/dist/context/ingest/adapters/looker/factory.js +9 -0
- package/dist/context/ingest/adapters/looker/mapping.js +1 -1
- package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.js +1 -1
- package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
- package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
- package/dist/context/ingest/artifact-gates.d.ts +2 -6
- package/dist/context/ingest/artifact-gates.js +5 -47
- package/dist/context/ingest/constrained-repair.d.ts +55 -0
- package/dist/context/ingest/constrained-repair.js +167 -0
- package/dist/context/ingest/final-gate-repair.d.ts +9 -11
- package/dist/context/ingest/final-gate-repair.js +40 -128
- package/dist/context/ingest/finalization-scope.d.ts +1 -1
- package/dist/context/ingest/finalization-scope.js +15 -15
- package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
- package/dist/context/ingest/ingest-bundle.runner.js +101 -67
- package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
- package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
- package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
- package/dist/context/ingest/local-bundle-runtime.js +9 -10
- package/dist/context/ingest/local-ingest.d.ts +2 -0
- package/dist/context/ingest/local-ingest.js +2 -0
- package/dist/context/ingest/memory-flow/view-model.js +1 -1
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
- package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
- package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
- package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
- package/dist/context/llm/ai-sdk-runtime.js +2 -2
- package/dist/context/llm/claude-code-runtime.js +1 -1
- package/dist/context/llm/local-config.js +1 -1
- package/dist/context/llm/runtime-tools.js +2 -2
- package/dist/context/mcp/context-tools.js +7 -7
- package/dist/context/mcp/local-project-ports.js +23 -54
- package/dist/context/memory/local-memory.js +4 -1
- package/dist/context/memory/memory-agent.service.js +1 -1
- package/dist/context/project/config.d.ts +11 -4
- package/dist/context/project/config.js +85 -30
- package/dist/context/project/driver-schemas.js +1 -1
- package/dist/context/project/mappings-yaml-schema.js +2 -2
- package/dist/context/project/project.js +12 -4
- package/dist/context/scan/description-generation.js +4 -4
- package/dist/context/scan/local-enrichment-artifacts.js +2 -1
- package/dist/context/scan/local-scan.js +2 -2
- package/dist/context/scan/local-structural-artifacts.js +5 -5
- package/dist/context/scan/relationship-benchmark-report.js +1 -1
- package/dist/context/scan/relationship-discovery.js +3 -3
- package/dist/context/scan/relationship-llm-proposal.js +3 -3
- package/dist/context/sl/local-query.js +3 -33
- package/dist/context/sl/local-sl.d.ts +0 -8
- package/dist/context/sl/local-sl.js +44 -69
- package/dist/context/sl/semantic-layer.service.d.ts +25 -8
- package/dist/context/sl/semantic-layer.service.js +109 -56
- package/dist/context/sl/source-files.d.ts +46 -0
- package/dist/context/sl/source-files.js +131 -0
- package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
- package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
- package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
- package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
- package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
- package/dist/context/sql-analysis/dialect.d.ts +2 -0
- package/dist/context/sql-analysis/dialect.js +20 -0
- package/dist/context/tools/base-tool.d.ts +6 -19
- package/dist/context/tools/base-tool.js +0 -14
- package/dist/context-build-view.js +5 -5
- package/dist/database-tree-picker.js +18 -3
- package/dist/demo-assets.js +0 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +31 -23
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +44 -0
- package/dist/ingest.d.ts +1 -1
- package/dist/ingest.js +8 -2
- package/dist/io/symbols.d.ts +2 -0
- package/dist/io/symbols.js +2 -0
- package/dist/io/tty.d.ts +17 -0
- package/dist/io/tty.js +21 -0
- package/dist/links.d.ts +1 -0
- package/dist/links.js +1 -0
- package/dist/llm/embedding-health.js +1 -1
- package/dist/llm/embedding-provider.js +3 -3
- package/dist/llm/model-provider.js +1 -1
- package/dist/local-adapters.d.ts +1 -0
- package/dist/local-adapters.js +2 -2
- package/dist/local-scan-connectors.js +1 -1
- package/dist/managed-local-embeddings.js +17 -8
- package/dist/managed-mcp-daemon.js +3 -3
- package/dist/managed-python-command.d.ts +7 -0
- package/dist/managed-python-command.js +34 -8
- package/dist/managed-python-daemon.js +2 -2
- package/dist/managed-python-http.js +3 -3
- package/dist/managed-python-runtime.d.ts +30 -1
- package/dist/managed-python-runtime.js +134 -18
- package/dist/managed-uv-release.d.ts +7 -0
- package/dist/managed-uv-release.js +11 -0
- package/dist/mcp-http-server.js +4 -4
- package/dist/mcp-server-factory.js +3 -3
- package/dist/mcp-stdio-server.js +1 -1
- package/dist/memory-flow-hud.js +2 -2
- package/dist/next-steps.js +2 -2
- package/dist/prompt-navigation.d.ts +17 -0
- package/dist/prompt-navigation.js +49 -3
- package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
- package/dist/prompts/memory_agent_external_ingest.md +2 -2
- package/dist/public-ingest-copy.js +1 -1
- package/dist/public-ingest.js +3 -3
- package/dist/release-version.js +1 -1
- package/dist/runtime-requirements.js +1 -1
- package/dist/runtime.js +9 -9
- package/dist/scan.js +1 -1
- package/dist/setup-agents.js +22 -35
- package/dist/setup-banner.d.ts +20 -0
- package/dist/setup-banner.js +39 -0
- package/dist/setup-context.js +24 -15
- package/dist/setup-databases.js +31 -59
- package/dist/setup-demo-tour.js +12 -8
- package/dist/setup-embeddings.js +9 -9
- package/dist/setup-interrupt.js +1 -1
- package/dist/setup-models.d.ts +4 -1
- package/dist/setup-models.js +54 -28
- package/dist/setup-project.js +29 -5
- package/dist/setup-prompts.js +16 -5
- package/dist/setup-ready-menu.js +1 -1
- package/dist/setup-sources.js +27 -7
- package/dist/setup.d.ts +25 -0
- package/dist/setup.js +90 -19
- package/dist/skills/analytics/SKILL.md +3 -3
- package/dist/skills/dbt_ingest/SKILL.md +3 -3
- package/dist/skills/looker_ingest/SKILL.md +3 -3
- package/dist/skills/lookml_ingest/SKILL.md +7 -7
- package/dist/skills/metabase_ingest/SKILL.md +4 -4
- package/dist/skills/metricflow_ingest/SKILL.md +15 -15
- package/dist/skills/notion_synthesize/SKILL.md +1 -1
- package/dist/skills/sl/SKILL.md +3 -3
- package/dist/skills/sl_capture/SKILL.md +1 -1
- package/dist/skills/wiki_capture/SKILL.md +1 -1
- package/dist/source-mapping.js +1 -1
- package/dist/startup-profile.js +1 -1
- package/dist/status-project.d.ts +0 -2
- package/dist/status-project.js +4 -6
- package/dist/telemetry/command-hook.d.ts +24 -0
- package/dist/telemetry/command-hook.js +37 -3
- package/dist/telemetry/events.d.ts +1 -1
- package/dist/telemetry/exception.js +14 -0
- package/dist/telemetry/index.d.ts +2 -2
- package/dist/telemetry/index.js +2 -2
- package/dist/text-ingest.js +1 -1
- package/dist/tree-picker-tui.d.ts +0 -1
- package/dist/tree-picker-tui.js +2 -3
- package/package.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AgentRunnerPort, KtxRuntimeToolSet } from '../../context/llm/runtime-port.js';
|
|
2
|
+
import type { IngestTraceWriter } from './ingest-trace.js';
|
|
3
|
+
/**
|
|
4
|
+
* Shared loop for the two integration-time repair agents (semantic gate
|
|
5
|
+
* repair, textual conflict resolution). Success is decided by re-running the
|
|
6
|
+
* failed check — `verify` — never by whether the agent edited files: an
|
|
7
|
+
* ineffective edit fails, and an explicit no-change declaration that verifies
|
|
8
|
+
* succeeds.
|
|
9
|
+
*/
|
|
10
|
+
export type RepairVerification = {
|
|
11
|
+
ok: true;
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
reason: string;
|
|
15
|
+
};
|
|
16
|
+
export type ConstrainedRepairResult = {
|
|
17
|
+
status: 'repaired';
|
|
18
|
+
attempts: number;
|
|
19
|
+
changedPaths: string[];
|
|
20
|
+
} | {
|
|
21
|
+
status: 'failed';
|
|
22
|
+
attempts: number;
|
|
23
|
+
reason: string;
|
|
24
|
+
};
|
|
25
|
+
export interface ConstrainedRepairToolContext {
|
|
26
|
+
workdir: string;
|
|
27
|
+
allowedPaths: ReadonlySet<string>;
|
|
28
|
+
editedPaths: Set<string>;
|
|
29
|
+
declareNoChange(reason: string): void;
|
|
30
|
+
}
|
|
31
|
+
export interface ConstrainedRepairLoopInput {
|
|
32
|
+
agentRunner: AgentRunnerPort;
|
|
33
|
+
workdir: string;
|
|
34
|
+
allowedPaths: string[];
|
|
35
|
+
trace: IngestTraceWriter;
|
|
36
|
+
tracePhase: string;
|
|
37
|
+
traceEventName: string;
|
|
38
|
+
traceData: Record<string, unknown>;
|
|
39
|
+
systemPrompt: string;
|
|
40
|
+
buildUserPrompt(input: {
|
|
41
|
+
attempt: number;
|
|
42
|
+
maxAttempts: number;
|
|
43
|
+
previousFailure: string | null;
|
|
44
|
+
}): string;
|
|
45
|
+
buildExtraTools?(context: ConstrainedRepairToolContext): KtxRuntimeToolSet;
|
|
46
|
+
verify(changedPaths: string[]): Promise<RepairVerification>;
|
|
47
|
+
/** Failure reason when an attempt neither edits nor declares no-change. */
|
|
48
|
+
noChangeFailureReason: string;
|
|
49
|
+
telemetryTags: Record<string, string>;
|
|
50
|
+
maxAttempts?: number;
|
|
51
|
+
stepBudget?: number;
|
|
52
|
+
abortSignal?: AbortSignal;
|
|
53
|
+
}
|
|
54
|
+
export declare function buildDeleteRepairFileTool(context: ConstrainedRepairToolContext): KtxRuntimeToolSet;
|
|
55
|
+
export declare function runConstrainedRepairLoop(input: ConstrainedRepairLoopInput): Promise<ConstrainedRepairResult>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { traceTimed } from './ingest-trace.js';
|
|
5
|
+
const readRepairFileSchema = z.object({
|
|
6
|
+
path: z.string().min(1),
|
|
7
|
+
});
|
|
8
|
+
const writeRepairFileSchema = z.object({
|
|
9
|
+
path: z.string().min(1),
|
|
10
|
+
content: z.string(),
|
|
11
|
+
});
|
|
12
|
+
function normalizeRepoPath(path) {
|
|
13
|
+
const normalized = path.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
14
|
+
const parts = normalized.split('/').filter((part) => part.length > 0);
|
|
15
|
+
if (parts.length === 0 || parts.some((part) => part === '.' || part === '..')) {
|
|
16
|
+
throw new Error(`repair path must be a repository-relative path: ${path}`);
|
|
17
|
+
}
|
|
18
|
+
return parts.join('/');
|
|
19
|
+
}
|
|
20
|
+
function assertAllowedPath(path, allowedPaths) {
|
|
21
|
+
const normalized = normalizeRepoPath(path);
|
|
22
|
+
if (!allowedPaths.has(normalized)) {
|
|
23
|
+
throw new Error(`repair path not allowed: ${normalized}`);
|
|
24
|
+
}
|
|
25
|
+
return normalized;
|
|
26
|
+
}
|
|
27
|
+
async function readOptionalFile(path) {
|
|
28
|
+
try {
|
|
29
|
+
return { exists: true, content: await readFile(path, 'utf-8') };
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
33
|
+
return { exists: false, content: '' };
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function buildRepairFileTools(context) {
|
|
39
|
+
return {
|
|
40
|
+
read_repair_file: {
|
|
41
|
+
name: 'read_repair_file',
|
|
42
|
+
description: 'Read one allowed file from the integration worktree.',
|
|
43
|
+
inputSchema: readRepairFileSchema,
|
|
44
|
+
execute: async ({ path }) => {
|
|
45
|
+
const normalized = assertAllowedPath(path, context.allowedPaths);
|
|
46
|
+
const file = await readOptionalFile(join(context.workdir, normalized));
|
|
47
|
+
return {
|
|
48
|
+
markdown: file.exists ? file.content : `(missing file: ${normalized})`,
|
|
49
|
+
structured: { path: normalized, exists: file.exists },
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
write_repair_file: {
|
|
54
|
+
name: 'write_repair_file',
|
|
55
|
+
description: 'Replace one allowed integration worktree file with repaired text content.',
|
|
56
|
+
inputSchema: writeRepairFileSchema,
|
|
57
|
+
execute: async ({ path, content }) => {
|
|
58
|
+
const normalized = assertAllowedPath(path, context.allowedPaths);
|
|
59
|
+
const fullPath = join(context.workdir, normalized);
|
|
60
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
61
|
+
await writeFile(fullPath, content, 'utf-8');
|
|
62
|
+
context.editedPaths.add(normalized);
|
|
63
|
+
return {
|
|
64
|
+
markdown: `Wrote ${normalized}`,
|
|
65
|
+
structured: { path: normalized, bytes: Buffer.byteLength(content) },
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function buildDeleteRepairFileTool(context) {
|
|
72
|
+
const deleteRepairFileSchema = z.object({
|
|
73
|
+
path: z.string().min(1),
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
delete_repair_file: {
|
|
77
|
+
name: 'delete_repair_file',
|
|
78
|
+
description: 'Delete one allowed integration worktree file when the failed patch proves the deletion is correct.',
|
|
79
|
+
inputSchema: deleteRepairFileSchema,
|
|
80
|
+
execute: async ({ path }) => {
|
|
81
|
+
const normalized = assertAllowedPath(path, context.allowedPaths);
|
|
82
|
+
await rm(join(context.workdir, normalized), { force: true });
|
|
83
|
+
context.editedPaths.add(normalized);
|
|
84
|
+
return {
|
|
85
|
+
markdown: `Deleted ${normalized}`,
|
|
86
|
+
structured: { path: normalized },
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export async function runConstrainedRepairLoop(input) {
|
|
93
|
+
const allowedPaths = new Set(input.allowedPaths.map(normalizeRepoPath));
|
|
94
|
+
const sortedAllowedPaths = [...allowedPaths].sort();
|
|
95
|
+
const maxAttempts = input.maxAttempts ?? 2;
|
|
96
|
+
const stepBudget = input.stepBudget ?? 16;
|
|
97
|
+
// Edits persist in the worktree across attempts, so the verified set and the
|
|
98
|
+
// reported changedPaths accumulate over the whole loop.
|
|
99
|
+
const editedPaths = new Set();
|
|
100
|
+
let lastFailure = 'repair did not run';
|
|
101
|
+
let previousFailure = null;
|
|
102
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
103
|
+
let noChangeDeclaration = null;
|
|
104
|
+
const toolContext = {
|
|
105
|
+
workdir: input.workdir,
|
|
106
|
+
allowedPaths,
|
|
107
|
+
editedPaths,
|
|
108
|
+
declareNoChange: (reason) => {
|
|
109
|
+
noChangeDeclaration = reason;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
const traceData = {
|
|
113
|
+
...input.traceData,
|
|
114
|
+
attempt,
|
|
115
|
+
maxAttempts,
|
|
116
|
+
allowedPaths: sortedAllowedPaths,
|
|
117
|
+
};
|
|
118
|
+
const result = await traceTimed(input.trace, input.tracePhase, input.traceEventName, traceData, async () => input.agentRunner.runLoop({
|
|
119
|
+
modelRole: 'repair',
|
|
120
|
+
systemPrompt: input.systemPrompt,
|
|
121
|
+
userPrompt: input.buildUserPrompt({ attempt, maxAttempts, previousFailure }),
|
|
122
|
+
toolSet: {
|
|
123
|
+
...buildRepairFileTools(toolContext),
|
|
124
|
+
...(input.buildExtraTools?.(toolContext) ?? {}),
|
|
125
|
+
},
|
|
126
|
+
stepBudget,
|
|
127
|
+
telemetryTags: input.telemetryTags,
|
|
128
|
+
abortSignal: input.abortSignal,
|
|
129
|
+
}));
|
|
130
|
+
if (result.stopReason === 'error') {
|
|
131
|
+
lastFailure = result.error?.message ?? 'repair agent loop errored';
|
|
132
|
+
previousFailure = lastFailure;
|
|
133
|
+
await input.trace.event('error', input.tracePhase, `${input.traceEventName}_failed`, traceData, result.error);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const changedPaths = [...editedPaths].sort();
|
|
137
|
+
if (changedPaths.length === 0 && noChangeDeclaration === null) {
|
|
138
|
+
// Nothing changed and nothing was claimed: the failed check would fail
|
|
139
|
+
// identically, so skip verification and retry.
|
|
140
|
+
lastFailure = input.noChangeFailureReason;
|
|
141
|
+
previousFailure = lastFailure;
|
|
142
|
+
await input.trace.event('error', input.tracePhase, `${input.traceEventName}_failed`, {
|
|
143
|
+
...traceData,
|
|
144
|
+
reason: lastFailure,
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const verification = await input.verify(changedPaths);
|
|
149
|
+
if (!verification.ok) {
|
|
150
|
+
lastFailure = verification.reason;
|
|
151
|
+
previousFailure = lastFailure;
|
|
152
|
+
await input.trace.event('error', input.tracePhase, `${input.traceEventName}_failed`, {
|
|
153
|
+
...traceData,
|
|
154
|
+
changedPaths,
|
|
155
|
+
reason: lastFailure,
|
|
156
|
+
});
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
await input.trace.event('debug', input.tracePhase, `${input.traceEventName}_repaired`, {
|
|
160
|
+
...traceData,
|
|
161
|
+
changedPaths,
|
|
162
|
+
...(noChangeDeclaration !== null ? { noChangeDeclaration } : {}),
|
|
163
|
+
});
|
|
164
|
+
return { status: 'repaired', attempts: attempt, changedPaths };
|
|
165
|
+
}
|
|
166
|
+
return { status: 'failed', attempts: maxAttempts, reason: lastFailure };
|
|
167
|
+
}
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import type { AgentRunnerPort } from '../../context/llm/runtime-port.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ConstrainedRepairResult, RepairVerification } from './constrained-repair.js';
|
|
3
3
|
import type { IngestTraceWriter } from './ingest-trace.js';
|
|
4
4
|
type FinalGateRepairKind = 'patch_semantic_gate' | 'final_artifact_gate';
|
|
5
|
-
export type FinalGateRepairResult =
|
|
6
|
-
status: 'repaired';
|
|
7
|
-
attempts: number;
|
|
8
|
-
changedPaths: string[];
|
|
9
|
-
} | {
|
|
10
|
-
status: 'failed';
|
|
11
|
-
attempts: number;
|
|
12
|
-
reason: string;
|
|
13
|
-
};
|
|
5
|
+
export type FinalGateRepairResult = ConstrainedRepairResult;
|
|
14
6
|
export interface RepairFinalGateFailureInput {
|
|
15
7
|
agentRunner: AgentRunnerPort;
|
|
16
8
|
workdir: string;
|
|
@@ -18,13 +10,19 @@ export interface RepairFinalGateFailureInput {
|
|
|
18
10
|
allowedPaths: string[];
|
|
19
11
|
trace: IngestTraceWriter;
|
|
20
12
|
repairKind: FinalGateRepairKind;
|
|
13
|
+
/**
|
|
14
|
+
* Re-runs the failed gate against the current worktree. The repair counts
|
|
15
|
+
* as successful only when this passes — editing files is not the success
|
|
16
|
+
* signal.
|
|
17
|
+
*/
|
|
18
|
+
verify(changedPaths: string[]): Promise<RepairVerification>;
|
|
21
19
|
maxAttempts?: number;
|
|
22
20
|
stepBudget?: number;
|
|
23
21
|
abortSignal?: AbortSignal;
|
|
24
22
|
}
|
|
25
23
|
export declare function finalGateRepairPaths(input: {
|
|
26
24
|
changedWikiPageKeys: string[];
|
|
27
|
-
|
|
25
|
+
touchedSlSourcePaths: string[];
|
|
28
26
|
}): string[];
|
|
29
27
|
export declare function repairFinalGateFailure(input: RepairFinalGateFailureInput): Promise<FinalGateRepairResult>;
|
|
30
28
|
export {};
|
|
@@ -1,43 +1,8 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
1
|
import { z } from 'zod';
|
|
4
|
-
import {
|
|
5
|
-
const readRepairFileSchema = z.object({
|
|
6
|
-
path: z.string().min(1),
|
|
7
|
-
});
|
|
8
|
-
const writeRepairFileSchema = z.object({
|
|
9
|
-
path: z.string().min(1),
|
|
10
|
-
content: z.string(),
|
|
11
|
-
});
|
|
12
|
-
function normalizeRepoPath(path) {
|
|
13
|
-
const normalized = path.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
14
|
-
const parts = normalized.split('/').filter((part) => part.length > 0);
|
|
15
|
-
if (parts.length === 0 || parts.some((part) => part === '.' || part === '..')) {
|
|
16
|
-
throw new Error(`gate repair path must be a repository-relative path: ${path}`);
|
|
17
|
-
}
|
|
18
|
-
return parts.join('/');
|
|
19
|
-
}
|
|
20
|
-
function assertAllowedPath(path, allowedPaths) {
|
|
21
|
-
const normalized = normalizeRepoPath(path);
|
|
22
|
-
if (!allowedPaths.has(normalized)) {
|
|
23
|
-
throw new Error(`gate repair path not allowed: ${normalized}`);
|
|
24
|
-
}
|
|
25
|
-
return normalized;
|
|
26
|
-
}
|
|
27
|
-
async function readOptionalFile(path) {
|
|
28
|
-
try {
|
|
29
|
-
return { exists: true, content: await readFile(path, 'utf-8') };
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
33
|
-
return { exists: false, content: '' };
|
|
34
|
-
}
|
|
35
|
-
throw error;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
2
|
+
import { runConstrainedRepairLoop } from './constrained-repair.js';
|
|
38
3
|
function buildGateRepairSystemPrompt() {
|
|
39
4
|
return `<role>
|
|
40
|
-
You repair one
|
|
5
|
+
You repair one ktx isolated-diff artifact gate failure inside the integration worktree.
|
|
41
6
|
</role>
|
|
42
7
|
|
|
43
8
|
<rules>
|
|
@@ -51,6 +16,9 @@ You repair one KTX isolated-diff artifact gate failure inside the integration wo
|
|
|
51
16
|
</rules>`;
|
|
52
17
|
}
|
|
53
18
|
function buildGateRepairUserPrompt(input) {
|
|
19
|
+
const previousFailureBlock = input.previousFailure
|
|
20
|
+
? `\nPrevious attempt did not pass the gate:\n${input.previousFailure}\n`
|
|
21
|
+
: '';
|
|
54
22
|
return `Repair isolated-diff artifact gates.
|
|
55
23
|
|
|
56
24
|
Repair kind: ${input.repairKind}
|
|
@@ -61,119 +29,63 @@ ${input.allowedPaths.map((path) => `- ${path}`).join('\n')}
|
|
|
61
29
|
|
|
62
30
|
Gate error:
|
|
63
31
|
${input.gateError}
|
|
64
|
-
|
|
32
|
+
${previousFailureBlock}
|
|
65
33
|
Use read_gate_error first. Then inspect only the allowed files, write the
|
|
66
34
|
minimal repaired content, and stop.`;
|
|
67
35
|
}
|
|
68
|
-
function
|
|
36
|
+
function buildReadGateErrorTool(gateError) {
|
|
69
37
|
return {
|
|
70
38
|
read_gate_error: {
|
|
71
39
|
name: 'read_gate_error',
|
|
72
40
|
description: 'Read the artifact gate failure that must be repaired.',
|
|
73
41
|
inputSchema: z.object({}),
|
|
74
42
|
execute: async () => ({
|
|
75
|
-
markdown:
|
|
76
|
-
structured: { gateError
|
|
43
|
+
markdown: gateError,
|
|
44
|
+
structured: { gateError },
|
|
77
45
|
}),
|
|
78
46
|
},
|
|
79
|
-
read_repair_file: {
|
|
80
|
-
name: 'read_repair_file',
|
|
81
|
-
description: 'Read one allowed file from the integration worktree.',
|
|
82
|
-
inputSchema: readRepairFileSchema,
|
|
83
|
-
execute: async ({ path }) => {
|
|
84
|
-
const normalized = assertAllowedPath(path, input.allowedPaths);
|
|
85
|
-
const file = await readOptionalFile(join(input.workdir, normalized));
|
|
86
|
-
return {
|
|
87
|
-
markdown: file.exists ? file.content : `(missing file: ${normalized})`,
|
|
88
|
-
structured: { path: normalized, exists: file.exists },
|
|
89
|
-
};
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
write_repair_file: {
|
|
93
|
-
name: 'write_repair_file',
|
|
94
|
-
description: 'Replace one allowed integration worktree file with repaired text content.',
|
|
95
|
-
inputSchema: writeRepairFileSchema,
|
|
96
|
-
execute: async ({ path, content }) => {
|
|
97
|
-
const normalized = assertAllowedPath(path, input.allowedPaths);
|
|
98
|
-
const fullPath = join(input.workdir, normalized);
|
|
99
|
-
await mkdir(dirname(fullPath), { recursive: true });
|
|
100
|
-
await writeFile(fullPath, content, 'utf-8');
|
|
101
|
-
input.editedPaths.add(normalized);
|
|
102
|
-
return {
|
|
103
|
-
markdown: `Wrote ${normalized}`,
|
|
104
|
-
structured: { path: normalized, bytes: Buffer.byteLength(content) },
|
|
105
|
-
};
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
47
|
};
|
|
109
48
|
}
|
|
110
49
|
export function finalGateRepairPaths(input) {
|
|
111
50
|
return [
|
|
112
51
|
...new Set([
|
|
113
|
-
...input.
|
|
52
|
+
...input.touchedSlSourcePaths,
|
|
114
53
|
...input.changedWikiPageKeys.map((pageKey) => `wiki/global/${pageKey}.md`),
|
|
115
54
|
]),
|
|
116
55
|
].sort();
|
|
117
56
|
}
|
|
118
57
|
export async function repairFinalGateFailure(input) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
58
|
+
return runConstrainedRepairLoop({
|
|
59
|
+
agentRunner: input.agentRunner,
|
|
60
|
+
workdir: input.workdir,
|
|
61
|
+
allowedPaths: input.allowedPaths,
|
|
62
|
+
trace: input.trace,
|
|
63
|
+
tracePhase: 'gate_repair',
|
|
64
|
+
traceEventName: 'gate_repair',
|
|
65
|
+
traceData: {
|
|
66
|
+
repairKind: input.repairKind,
|
|
67
|
+
gateError: input.gateError,
|
|
68
|
+
},
|
|
69
|
+
systemPrompt: buildGateRepairSystemPrompt(),
|
|
70
|
+
buildUserPrompt: ({ attempt, maxAttempts, previousFailure }) => buildGateRepairUserPrompt({
|
|
71
|
+
gateError: input.gateError,
|
|
72
|
+
allowedPaths: [...input.allowedPaths].sort(),
|
|
127
73
|
repairKind: input.repairKind,
|
|
128
74
|
attempt,
|
|
129
75
|
maxAttempts,
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
gateError: input.gateError,
|
|
146
|
-
allowedPaths,
|
|
147
|
-
editedPaths,
|
|
148
|
-
}),
|
|
149
|
-
stepBudget,
|
|
150
|
-
telemetryTags: {
|
|
151
|
-
operationName: 'ingest-isolated-diff-gate-repair',
|
|
152
|
-
source: input.trace.context.sourceKey,
|
|
153
|
-
jobId: input.trace.context.jobId,
|
|
154
|
-
repairKind: input.repairKind,
|
|
155
|
-
},
|
|
156
|
-
abortSignal: input.abortSignal,
|
|
157
|
-
}));
|
|
158
|
-
if (result.stopReason === 'error') {
|
|
159
|
-
lastFailure = result.error?.message ?? 'gate repair agent loop errored';
|
|
160
|
-
await input.trace.event('error', 'gate_repair', 'gate_repair_failed', traceData, result.error);
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
const changedPaths = [...editedPaths].sort();
|
|
164
|
-
if (changedPaths.length === 0) {
|
|
165
|
-
lastFailure = 'gate repair completed without editing an allowed path';
|
|
166
|
-
await input.trace.event('error', 'gate_repair', 'gate_repair_failed', {
|
|
167
|
-
...traceData,
|
|
168
|
-
reason: lastFailure,
|
|
169
|
-
});
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
await input.trace.event('debug', 'gate_repair', 'gate_repair_repaired', {
|
|
173
|
-
...traceData,
|
|
174
|
-
changedPaths,
|
|
175
|
-
});
|
|
176
|
-
return { status: 'repaired', attempts: attempt, changedPaths };
|
|
177
|
-
}
|
|
178
|
-
return { status: 'failed', attempts: maxAttempts, reason: lastFailure };
|
|
76
|
+
previousFailure,
|
|
77
|
+
}),
|
|
78
|
+
buildExtraTools: () => buildReadGateErrorTool(input.gateError),
|
|
79
|
+
verify: input.verify,
|
|
80
|
+
noChangeFailureReason: 'gate repair completed without editing an allowed path',
|
|
81
|
+
telemetryTags: {
|
|
82
|
+
operationName: 'ingest-isolated-diff-gate-repair',
|
|
83
|
+
source: input.trace.context.sourceKey,
|
|
84
|
+
jobId: input.trace.context.jobId,
|
|
85
|
+
repairKind: input.repairKind,
|
|
86
|
+
},
|
|
87
|
+
maxAttempts: input.maxAttempts,
|
|
88
|
+
stepBudget: input.stepBudget ?? 16,
|
|
89
|
+
abortSignal: input.abortSignal,
|
|
90
|
+
});
|
|
179
91
|
}
|
|
@@ -17,6 +17,6 @@ interface CompareFinalizationDeclarationsInput {
|
|
|
17
17
|
derivedChangedWikiPageKeys: string[];
|
|
18
18
|
}
|
|
19
19
|
export declare function deriveFinalizationWikiPageKeys(paths: string[]): string[];
|
|
20
|
-
export declare function deriveFinalizationTouchedSources(input: DeriveTouchedSourcesInput):
|
|
20
|
+
export declare function deriveFinalizationTouchedSources(input: DeriveTouchedSourcesInput): DeriveTouchedSourcesResult;
|
|
21
21
|
export declare function compareFinalizationDeclarations(input: CompareFinalizationDeclarationsInput): IngestReportFinalizationMismatch[];
|
|
22
22
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSlYamlPath } from '../../context/sl/source-files.js';
|
|
1
2
|
function uniqueSorted(values) {
|
|
2
3
|
return [...new Set(values.filter((value) => value.length > 0))].sort();
|
|
3
4
|
}
|
|
@@ -28,32 +29,31 @@ export function deriveFinalizationWikiPageKeys(paths) {
|
|
|
28
29
|
.filter((path) => !path.slice('wiki/global/'.length, -'.md'.length).includes('/'))
|
|
29
30
|
.map((path) => path.slice('wiki/global/'.length, -'.md'.length)));
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
+
// Source identity is the in-file `name:`; filenames are derived labels (see
|
|
33
|
+
// source-files.ts), so a changed path — manifest shard or standalone file —
|
|
34
|
+
// cannot be mapped to a source by parsing its filename. Instead, every changed
|
|
35
|
+
// semantic-layer file is attributed through the before/after diff of its
|
|
36
|
+
// connection's composed sources. A changed file whose connection diff is empty
|
|
37
|
+
// cannot be attributed to any source and is surfaced as unresolved.
|
|
38
|
+
export function deriveFinalizationTouchedSources(input) {
|
|
32
39
|
const touched = new Map();
|
|
33
40
|
const unresolvedPaths = [];
|
|
41
|
+
const pathsByConnection = new Map();
|
|
34
42
|
for (const path of input.changedPaths) {
|
|
35
|
-
if (!path.startsWith('semantic-layer/') || !(path
|
|
43
|
+
if (!path.startsWith('semantic-layer/') || !isSlYamlPath(path)) {
|
|
36
44
|
continue;
|
|
37
45
|
}
|
|
38
|
-
const
|
|
39
|
-
const connectionId = parts[1] ?? '';
|
|
46
|
+
const connectionId = path.split('/')[1] ?? '';
|
|
40
47
|
if (!connectionId) {
|
|
41
48
|
unresolvedPaths.push(path);
|
|
42
49
|
continue;
|
|
43
50
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (!sourceName) {
|
|
48
|
-
unresolvedPaths.push(path);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
touched.set(`${connectionId}:${sourceName}`, { connectionId, sourceName });
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
51
|
+
pathsByConnection.set(connectionId, [...(pathsByConnection.get(connectionId) ?? []), path]);
|
|
52
|
+
}
|
|
53
|
+
for (const [connectionId, paths] of pathsByConnection) {
|
|
54
54
|
const changedNames = changedSourceNames(input.beforeSourcesByConnection.get(connectionId) ?? [], input.afterSourcesByConnection.get(connectionId) ?? []);
|
|
55
55
|
if (changedNames.length === 0) {
|
|
56
|
-
unresolvedPaths.push(
|
|
56
|
+
unresolvedPaths.push(...paths);
|
|
57
57
|
continue;
|
|
58
58
|
}
|
|
59
59
|
for (const sourceName of changedNames) {
|
|
@@ -55,6 +55,7 @@ export declare class IngestBundleRunner {
|
|
|
55
55
|
private provenanceValidationTraceData;
|
|
56
56
|
private wikiPageKeysFromPaths;
|
|
57
57
|
private touchedSlSourcesFromPaths;
|
|
58
|
+
private touchedSlSourcePaths;
|
|
58
59
|
private touchedSlSourcesFromActions;
|
|
59
60
|
private wikiPageKeysFromActions;
|
|
60
61
|
private uniqueWikiPageKeys;
|