@pretorian-worx/runclaudia-core 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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +16 -0
  3. package/dist/adapters/nextjs.d.ts +27 -0
  4. package/dist/adapters/nextjs.js +457 -0
  5. package/dist/adapters/nextjs.js.map +1 -0
  6. package/dist/adapters/prisma.d.ts +22 -0
  7. package/dist/adapters/prisma.js +80 -0
  8. package/dist/adapters/prisma.js.map +1 -0
  9. package/dist/adapters/specs.d.ts +13 -0
  10. package/dist/adapters/specs.js +126 -0
  11. package/dist/adapters/specs.js.map +1 -0
  12. package/dist/adapters/terraform.d.ts +17 -0
  13. package/dist/adapters/terraform.js +82 -0
  14. package/dist/adapters/terraform.js.map +1 -0
  15. package/dist/diff.d.ts +13 -0
  16. package/dist/diff.js +187 -0
  17. package/dist/diff.js.map +1 -0
  18. package/dist/gating.d.ts +29 -0
  19. package/dist/gating.js +59 -0
  20. package/dist/gating.js.map +1 -0
  21. package/dist/index.d.ts +15 -0
  22. package/dist/index.js +12 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/llm.d.ts +24 -0
  25. package/dist/llm.js +135 -0
  26. package/dist/llm.js.map +1 -0
  27. package/dist/map.d.ts +9 -0
  28. package/dist/map.js +55 -0
  29. package/dist/map.js.map +1 -0
  30. package/dist/plan-format.d.ts +3 -0
  31. package/dist/plan-format.js +71 -0
  32. package/dist/plan-format.js.map +1 -0
  33. package/dist/plan.d.ts +21 -0
  34. package/dist/plan.js +44 -0
  35. package/dist/plan.js.map +1 -0
  36. package/dist/prompt.d.ts +40 -0
  37. package/dist/prompt.js +266 -0
  38. package/dist/prompt.js.map +1 -0
  39. package/dist/runner.d.ts +53 -0
  40. package/dist/runner.js +119 -0
  41. package/dist/runner.js.map +1 -0
  42. package/dist/select.d.ts +31 -0
  43. package/dist/select.js +108 -0
  44. package/dist/select.js.map +1 -0
  45. package/dist/types.d.ts +176 -0
  46. package/dist/types.js +17 -0
  47. package/dist/types.js.map +1 -0
  48. package/package.json +54 -0
package/dist/llm.js ADDED
@@ -0,0 +1,135 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ import { PlanSchema } from "./types.js";
3
+ import { SYSTEM_PROMPT } from "./prompt.js";
4
+ const WRAPPER_KEYS = ["plan", "result", "output", "data", "emit_plan"];
5
+ function unwrapCandidates(input) {
6
+ const out = [input];
7
+ if (input && typeof input === "object" && !Array.isArray(input)) {
8
+ const obj = input;
9
+ const keys = Object.keys(obj);
10
+ if (keys.length === 1) {
11
+ const only = obj[keys[0]];
12
+ if (only && typeof only === "object")
13
+ out.push(only);
14
+ }
15
+ for (const k of WRAPPER_KEYS) {
16
+ if (k in obj && obj[k] && typeof obj[k] === "object")
17
+ out.push(obj[k]);
18
+ }
19
+ }
20
+ return out;
21
+ }
22
+ export class PlannerError extends Error {
23
+ details;
24
+ constructor(message, details) {
25
+ super(message);
26
+ this.name = "PlannerError";
27
+ this.details = details;
28
+ }
29
+ }
30
+ const PLAN_TOOL = {
31
+ name: "emit_plan",
32
+ description: "Emit the structured test plan. Always call this exactly once. The tool input MUST be a flat object with the fields verdict, summary, flows, unmappedFiles, coverageGaps at the top level — DO NOT nest them under a 'plan' or other wrapper key.",
33
+ input_schema: {
34
+ type: "object",
35
+ required: ["verdict", "summary", "flows", "unmappedFiles", "coverageGaps"],
36
+ properties: {
37
+ verdict: { type: "string", enum: ["test", "skip"] },
38
+ skipReason: { type: "string" },
39
+ summary: { type: "string" },
40
+ flows: {
41
+ type: "array",
42
+ items: {
43
+ type: "object",
44
+ required: ["name", "routes", "risk", "reasoning", "suggestedChecks"],
45
+ properties: {
46
+ name: { type: "string" },
47
+ routes: { type: "array", items: { type: "string" } },
48
+ risk: { type: "string", enum: ["low", "medium", "high"] },
49
+ reasoning: { type: "string" },
50
+ suggestedChecks: { type: "array", items: { type: "string" } },
51
+ },
52
+ },
53
+ },
54
+ unmappedFiles: { type: "array", items: { type: "string" } },
55
+ coverageGaps: { type: "array", items: { type: "string" } },
56
+ },
57
+ },
58
+ };
59
+ export async function callPlanner(opts) {
60
+ const apiKey = opts.apiKey ?? process.env.ANTHROPIC_API_KEY;
61
+ if (!apiKey)
62
+ throw new Error("ANTHROPIC_API_KEY is not set");
63
+ const client = new Anthropic({ apiKey });
64
+ const model = opts.model ?? "claude-sonnet-4-6";
65
+ const response = await client.messages.create({
66
+ model,
67
+ max_tokens: opts.maxTokens ?? 4096,
68
+ system: [
69
+ {
70
+ type: "text",
71
+ text: opts.systemPrompt ?? SYSTEM_PROMPT,
72
+ cache_control: { type: "ephemeral" },
73
+ },
74
+ ],
75
+ tools: [PLAN_TOOL],
76
+ tool_choice: { type: "tool", name: "emit_plan" },
77
+ messages: [
78
+ {
79
+ role: "user",
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: opts.mapBlock,
84
+ cache_control: { type: "ephemeral" },
85
+ },
86
+ { type: "text", text: opts.diffBlock },
87
+ ],
88
+ },
89
+ ],
90
+ });
91
+ const toolUses = response.content.filter((b) => b.type === "tool_use" && b.name === "emit_plan");
92
+ if (toolUses.length === 0) {
93
+ const textPreview = response.content
94
+ .filter((b) => b.type === "text")
95
+ .map((b) => b.text)
96
+ .join("\n")
97
+ .slice(0, 2000);
98
+ throw new PlannerError("Model did not call emit_plan tool", {
99
+ stopReason: response.stop_reason,
100
+ contentTypes: response.content.map((b) => b.type),
101
+ textPreview,
102
+ });
103
+ }
104
+ const last = toolUses[toolUses.length - 1];
105
+ const candidates = unwrapCandidates(last.input);
106
+ let plan;
107
+ let lastIssues;
108
+ for (const c of candidates) {
109
+ const parsed = PlanSchema.safeParse(c);
110
+ if (parsed.success) {
111
+ plan = parsed.data;
112
+ break;
113
+ }
114
+ lastIssues = parsed.error.issues;
115
+ }
116
+ if (!plan) {
117
+ throw new PlannerError("emit_plan tool input did not match schema", {
118
+ stopReason: response.stop_reason,
119
+ rawInput: last.input,
120
+ zodIssues: lastIssues,
121
+ });
122
+ }
123
+ const usage = response.usage;
124
+ return {
125
+ plan,
126
+ model,
127
+ usage: {
128
+ inputTokens: usage.input_tokens,
129
+ outputTokens: usage.output_tokens,
130
+ cacheCreationTokens: usage.cache_creation_input_tokens ?? 0,
131
+ cacheReadTokens: usage.cache_read_input_tokens ?? 0,
132
+ },
133
+ };
134
+ }
135
+ //# sourceMappingURL=llm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm.js","sourceRoot":"","sources":["../src/llm.ts"],"names":[],"mappings":"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"}
package/dist/map.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { AppMap } from "./types.js";
2
+ export interface MapOptions {
3
+ rootDir: string;
4
+ cachePath?: string;
5
+ refresh?: boolean;
6
+ }
7
+ export declare function loadOrBuildMap(opts: MapOptions): AppMap;
8
+ export declare function readMap(path: string): AppMap | null;
9
+ export declare function writeMap(path: string, map: AppMap): void;
package/dist/map.js ADDED
@@ -0,0 +1,55 @@
1
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, statSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { buildNextMap } from "./adapters/nextjs.js";
4
+ export function loadOrBuildMap(opts) {
5
+ const rootDir = resolve(opts.rootDir);
6
+ const cachePath = opts.cachePath ?? join(rootDir, ".claudia", "map.json");
7
+ if (!opts.refresh && existsSync(cachePath)) {
8
+ const cached = readMap(cachePath);
9
+ // Force a rebuild if the cache predates the latest map schema (endpoints,
10
+ // fileToEndpoints, infra, fileToInfra).
11
+ const hasNewerFields = cached &&
12
+ Array.isArray(cached.endpoints) &&
13
+ Array.isArray(cached.infra) &&
14
+ Array.isArray(cached.dbModels) &&
15
+ Array.isArray(cached.specs) &&
16
+ cached.fileToEndpoints !== undefined &&
17
+ cached.fileToInfra !== undefined &&
18
+ cached.fileToTables !== undefined &&
19
+ cached.fileToSpecs !== undefined;
20
+ if (hasNewerFields && cached && isFresh(cached, rootDir))
21
+ return cached;
22
+ }
23
+ const map = buildNextMap({ rootDir });
24
+ writeMap(cachePath, map);
25
+ return map;
26
+ }
27
+ export function readMap(path) {
28
+ try {
29
+ const raw = readFileSync(path, "utf8");
30
+ return JSON.parse(raw);
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ export function writeMap(path, map) {
37
+ mkdirSync(dirname(path), { recursive: true });
38
+ writeFileSync(path, JSON.stringify(map, null, 2) + "\n", "utf8");
39
+ }
40
+ function isFresh(map, rootDir) {
41
+ const generatedAt = Date.parse(map.generatedAt);
42
+ if (Number.isNaN(generatedAt))
43
+ return false;
44
+ const tracked = new Set(Object.keys(map.fileToRoutes));
45
+ for (const file of tracked) {
46
+ const abs = join(rootDir, file);
47
+ if (!existsSync(abs))
48
+ return false;
49
+ const mtime = statSync(abs).mtimeMs;
50
+ if (mtime > generatedAt)
51
+ return false;
52
+ }
53
+ return true;
54
+ }
55
+ //# sourceMappingURL=map.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map.js","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { PlanRunResult } from "./plan.js";
2
+ export declare function formatPlanMarkdown(result: PlanRunResult): string;
3
+ export declare function formatPlanJson(result: PlanRunResult): string;
@@ -0,0 +1,71 @@
1
+ // Renderers live here, alongside the plan logic, so consumers (CLI,
2
+ // GitHub Action) can import them from @pretorian-worx/runclaudia-core without reaching into
3
+ // the CLI package's internals.
4
+ const RISK_BADGE = {
5
+ high: "🔴 high",
6
+ medium: "🟡 medium",
7
+ low: "🟢 low",
8
+ };
9
+ export function formatPlanMarkdown(result) {
10
+ const { plan, diff, usage, model, skipped } = result;
11
+ const lines = [];
12
+ lines.push("## claudia — test plan");
13
+ lines.push("");
14
+ lines.push(`Diff: \`${diff.base}..${diff.head}\` — ${diff.files.length} file${diff.files.length === 1 ? "" : "s"} changed.`);
15
+ lines.push("");
16
+ if (skipped || plan.verdict === "skip") {
17
+ lines.push(`**Skipped.** ${plan.skipReason ?? plan.summary}`);
18
+ return lines.join("\n");
19
+ }
20
+ lines.push(plan.summary);
21
+ lines.push("");
22
+ if (plan.flows.length === 0) {
23
+ lines.push("_No user-facing flows inferred._");
24
+ }
25
+ else {
26
+ lines.push("### Flows to verify");
27
+ for (const f of plan.flows) {
28
+ lines.push("");
29
+ lines.push(`**${f.name}** — ${RISK_BADGE[f.risk]} · ${f.routes.length > 0 ? f.routes.map((r) => `\`${r}\``).join(", ") : "_no route_"}`);
30
+ lines.push("");
31
+ lines.push(f.reasoning);
32
+ if (f.suggestedChecks.length > 0) {
33
+ lines.push("");
34
+ for (const c of f.suggestedChecks)
35
+ lines.push(`- [ ] ${c}`);
36
+ }
37
+ }
38
+ }
39
+ if (plan.unmappedFiles.length > 0) {
40
+ lines.push("");
41
+ lines.push("### Unmapped files");
42
+ for (const f of plan.unmappedFiles)
43
+ lines.push(`- \`${f}\``);
44
+ }
45
+ if (plan.coverageGaps.length > 0) {
46
+ lines.push("");
47
+ lines.push("### Coverage gaps");
48
+ for (const g of plan.coverageGaps)
49
+ lines.push(`- ${g}`);
50
+ }
51
+ if (usage && model) {
52
+ lines.push("");
53
+ lines.push(`<sub>${model} · in ${usage.inputTokens} (cache write ${usage.cacheCreationTokens} / cache read ${usage.cacheReadTokens}) · out ${usage.outputTokens}</sub>`);
54
+ }
55
+ lines.push("");
56
+ lines.push("---");
57
+ lines.push("");
58
+ lines.push("<sub>Was this plan useful? React with 👍 or 👎 on this comment. Other reactions are ignored. Run `claudia ratings` to aggregate over time.</sub>");
59
+ return lines.join("\n");
60
+ }
61
+ export function formatPlanJson(result) {
62
+ return JSON.stringify({
63
+ diff: { base: result.diff.base, head: result.diff.head, files: result.diff.files.map((f) => f.path) },
64
+ plan: result.plan,
65
+ skipped: result.skipped,
66
+ skipReason: result.skipReason,
67
+ usage: result.usage,
68
+ model: result.model,
69
+ }, null, 2);
70
+ }
71
+ //# sourceMappingURL=plan-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-format.js","sourceRoot":"","sources":["../src/plan-format.ts"],"names":[],"mappings":"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"}
package/dist/plan.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { type LlmResult } from "./llm.js";
2
+ import type { AppMap, Diff, Plan } from "./types.js";
3
+ export interface PlanRunOptions {
4
+ rootDir: string;
5
+ base: string;
6
+ head: string;
7
+ targetUrl?: string;
8
+ refreshMap?: boolean;
9
+ apiKey?: string;
10
+ model?: string;
11
+ }
12
+ export interface PlanRunResult {
13
+ diff: Diff;
14
+ map: AppMap;
15
+ plan: Plan;
16
+ skipped: boolean;
17
+ skipReason?: string;
18
+ usage?: LlmResult["usage"];
19
+ model?: string;
20
+ }
21
+ export declare function runPlan(opts: PlanRunOptions): Promise<PlanRunResult>;
package/dist/plan.js ADDED
@@ -0,0 +1,44 @@
1
+ import { classifySkip, readDiff } from "./diff.js";
2
+ import { loadOrBuildMap } from "./map.js";
3
+ import { buildUserMessage } from "./prompt.js";
4
+ import { callPlanner } from "./llm.js";
5
+ export async function runPlan(opts) {
6
+ const diff = readDiff({ base: opts.base, head: opts.head, cwd: opts.rootDir });
7
+ const map = loadOrBuildMap({ rootDir: opts.rootDir, refresh: opts.refreshMap });
8
+ const skip = classifySkip(diff);
9
+ if (skip.skip) {
10
+ return {
11
+ diff,
12
+ map,
13
+ plan: {
14
+ verdict: "skip",
15
+ skipReason: skip.reason,
16
+ summary: skip.reason ?? "No testable changes.",
17
+ flows: [],
18
+ unmappedFiles: [],
19
+ coverageGaps: [],
20
+ },
21
+ skipped: true,
22
+ skipReason: skip.reason,
23
+ };
24
+ }
25
+ const userMessage = buildUserMessage({ diff, map, targetUrl: opts.targetUrl });
26
+ const splitIdx = userMessage.indexOf("\n# Diff ");
27
+ const mapBlock = splitIdx >= 0 ? userMessage.slice(0, splitIdx) : userMessage;
28
+ const diffBlock = splitIdx >= 0 ? userMessage.slice(splitIdx + 1) : "";
29
+ const result = await callPlanner({
30
+ apiKey: opts.apiKey,
31
+ model: opts.model,
32
+ mapBlock,
33
+ diffBlock,
34
+ });
35
+ return {
36
+ diff,
37
+ map,
38
+ plan: result.plan,
39
+ skipped: false,
40
+ usage: result.usage,
41
+ model: result.model,
42
+ };
43
+ }
44
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAkB,MAAM,UAAU,CAAC;AAuBvD,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAoB;IAChD,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/E,MAAM,GAAG,GAAG,cAAc,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAEhF,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO;YACL,IAAI;YACJ,GAAG;YACH,IAAI,EAAE;gBACJ,OAAO,EAAE,MAAM;gBACf,UAAU,EAAE,IAAI,CAAC,MAAM;gBACvB,OAAO,EAAE,IAAI,CAAC,MAAM,IAAI,sBAAsB;gBAC9C,KAAK,EAAE,EAAE;gBACT,aAAa,EAAE,EAAE;gBACjB,YAAY,EAAE,EAAE;aACjB;YACD,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,IAAI,CAAC,MAAM;SACxB,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IAE/E,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;IAC9E,MAAM,SAAS,GAAG,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvE,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC;QAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ;QACR,SAAS;KACV,CAAC,CAAC;IAEH,OAAO;QACL,IAAI;QACJ,GAAG;QACH,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { AppMap, DbModelEntry, Diff, EndpointEntry, InfraEntry, RouteEntry, SpecEntry } from "./types.js";
2
+ export declare const SYSTEM_PROMPT = "You are claudia, a diff-aware test planner.\n\nYour job: read a code diff plus a map of the app's routes and API endpoints, then output a structured plan describing which user-facing flows a human tester should exercise to validate the change. You DO NOT execute tests. You produce a plan a reviewer or downstream tool will act on.\n\nRules:\n- Be specific. Cite changed file paths in your reasoning.\n- Map every changed file to the routes AND endpoints it reaches. If a file is not in the map, list it under unmappedFiles and explain why it might still matter.\n- Distinguish API changes from UI changes:\n - A changed page/component implies a flow on its route(s) \u2014 write checks as user actions.\n - A changed endpoint (route.ts) implies an API contract change \u2014 call out the method + path, the request body shape (json/formData/text/etc), and recommend exercising it via the UI flow that hits it OR directly (curl/API client) when no UI flow is implicated.\n - A changed component that *calls* an endpoint (statically detected \u2014 see \"Endpoints called by changed files\" below) implies a full-stack flow: the UI change AND the contract between UI and that endpoint. Verify the end-to-end roundtrip, not just the rendered output.\n- The flow.routes field can contain either page paths (\"/checkout\") or method-prefixed endpoint paths (\"POST /api/bugs/move\"). Use whichever fits the change.\n- Risk levels: \"high\" = auth, payments, data-mutation, schema changes, or many routes/endpoints affected; \"medium\" = single-route behavior change or additive endpoint; \"low\" = cosmetic, copy, isolated UI.\n- Suggested checks must be concrete user actions (\"complete checkout with a saved card\", not \"test the checkout flow\") or concrete API checks (\"POST /api/bugs/move with a valid payload; expect 200 + new bug ref\").\n- Set verdict to \"skip\" only if the diff genuinely cannot affect runtime behavior (already-filtered cases shouldn't reach you, so prefer \"test\").\n- coverageGaps captures *unmapped risk* \u2014 changes you can see have impact but no flow or endpoint in the map covers them.\n- Treat infrastructure changes as production-impact risk. If the diff includes Terraform/CDK resources and any endpoint in the diff touches the same service (per the endpoint's \"services\" annotation), call out the coordinated risk \u2014 e.g. \"S3 bucket policy changed AND POST /api/attachments writes to S3, verify the write still succeeds end-to-end.\"\n- Treat database-schema changes as high-risk by default. Endpoints carry a \"tables\" annotation listing the DB models they touch (e.g. a Prisma call like prisma.bug.create(...) maps to [\"Bug\"]). When the diff changes the schema for a model AND an endpoint in the diff (or called by the diff) touches that model, call out the read/write contract explicitly \u2014 \"the Bug model gained a non-null column; POST /api/bugs writes to Bug, verify the new column is populated.\"\n- When the prompt's \"Existing test coverage\" section lists specs that already cover the affected routes/endpoints, reference them by file:name in your suggestedChecks \u2014 e.g. \"Run e2e/checkout.spec.ts:'completes checkout' against the deploy.\" Recommending existing specs is cheaper for the team than writing new ones and is preferred when coverage exists.\n- coverageGaps should call out flows the diff implicates that have NO existing spec \u2014 that's a concrete signal to the team to add one.\n- Be terse. The output is read by humans on a PR.";
3
+ /**
4
+ * Reduce the map to only the routes that the diff actually touches.
5
+ * Massively cuts prompt token cost on large repos; the brain only needs the
6
+ * route information for places the diff implicates.
7
+ *
8
+ * A route is "implicated" if any of its tracked files appears in the diff
9
+ * (matching either the post-image path or, for renames, the pre-image path).
10
+ */
11
+ export interface EndpointCallSite {
12
+ endpoint: EndpointEntry;
13
+ /** Files in the diff that statically call this endpoint. */
14
+ callerFiles: string[];
15
+ }
16
+ export declare function filterMapForDiff(map: AppMap, diff: Diff): {
17
+ implicated: RouteEntry[];
18
+ omittedCount: number;
19
+ implicatedEndpoints: EndpointEntry[];
20
+ omittedEndpointCount: number;
21
+ /** Endpoints that aren't directly changed, but are called from files in the diff. */
22
+ endpointsCalledByDiff: EndpointCallSite[];
23
+ /** Infrastructure resources whose declaration file is in the diff. */
24
+ implicatedInfra: InfraEntry[];
25
+ omittedInfraCount: number;
26
+ /** Database models whose schema file is in the diff. */
27
+ implicatedDbModels: DbModelEntry[];
28
+ omittedDbModelCount: number;
29
+ /** Specs whose coverage intersects with implicated routes or endpoints. */
30
+ coveringSpecs: SpecEntry[];
31
+ /** Routes implicated by the diff that have NO covering spec. */
32
+ uncoveredRoutes: string[];
33
+ /** Endpoints implicated by the diff that have NO covering spec. */
34
+ uncoveredEndpoints: string[];
35
+ };
36
+ export declare function buildUserMessage(args: {
37
+ diff: Diff;
38
+ map: AppMap;
39
+ targetUrl?: string;
40
+ }): string;