codeloop-mcp-server 0.1.51 → 0.1.53
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/auth/critical_floors.d.ts.map +1 -1
- package/dist/auth/critical_floors.js +8 -0
- package/dist/auth/critical_floors.js.map +1 -1
- package/dist/auth/update_check.d.ts.map +1 -1
- package/dist/auth/update_check.js +19 -1
- package/dist/auth/update_check.js.map +1 -1
- package/dist/evidence/anti_rationalisation.d.ts +34 -0
- package/dist/evidence/anti_rationalisation.d.ts.map +1 -0
- package/dist/evidence/anti_rationalisation.js +85 -0
- package/dist/evidence/anti_rationalisation.js.map +1 -0
- package/dist/evidence/change_coverage.d.ts +59 -0
- package/dist/evidence/change_coverage.d.ts.map +1 -0
- package/dist/evidence/change_coverage.js +422 -0
- package/dist/evidence/change_coverage.js.map +1 -0
- package/dist/evidence/change_manifest.d.ts +94 -0
- package/dist/evidence/change_manifest.d.ts.map +1 -0
- package/dist/evidence/change_manifest.js +830 -0
- package/dist/evidence/change_manifest.js.map +1 -0
- package/dist/evidence/evidence_freshness.d.ts +39 -0
- package/dist/evidence/evidence_freshness.d.ts.map +1 -0
- package/dist/evidence/evidence_freshness.js +231 -0
- package/dist/evidence/evidence_freshness.js.map +1 -0
- package/dist/evidence/screenshot_diff.d.ts.map +1 -1
- package/dist/evidence/screenshot_diff.js +30 -12
- package/dist/evidence/screenshot_diff.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +123 -3
- package/dist/index.js.map +1 -1
- package/dist/runners/empty_state_detector.d.ts +33 -0
- package/dist/runners/empty_state_detector.d.ts.map +1 -0
- package/dist/runners/empty_state_detector.js +304 -0
- package/dist/runners/empty_state_detector.js.map +1 -0
- package/dist/tools/c7_slug.d.ts +14 -0
- package/dist/tools/c7_slug.d.ts.map +1 -0
- package/dist/tools/c7_slug.js +21 -0
- package/dist/tools/c7_slug.js.map +1 -0
- package/dist/tools/design_compare.d.ts.map +1 -1
- package/dist/tools/design_compare.js +22 -3
- package/dist/tools/design_compare.js.map +1 -1
- package/dist/tools/gate_check.d.ts.map +1 -1
- package/dist/tools/gate_check.js +159 -14
- package/dist/tools/gate_check.js.map +1 -1
- package/dist/tools/plan_change_journey.d.ts +41 -0
- package/dist/tools/plan_change_journey.d.ts.map +1 -0
- package/dist/tools/plan_change_journey.js +131 -0
- package/dist/tools/plan_change_journey.js.map +1 -0
- package/dist/tools/verify.d.ts.map +1 -1
- package/dist/tools/verify.js +113 -1
- package/dist/tools/verify.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "fs";
|
|
6
|
+
import { slugForTargetChangeEntry } from "./tools/c7_slug.js";
|
|
6
7
|
function dirHasFile(dir, predicate) {
|
|
7
8
|
try {
|
|
8
9
|
if (!existsSync(dir))
|
|
@@ -514,6 +515,7 @@ Returns: structured report with pass/fail counts, artifact paths, and next-step
|
|
|
514
515
|
platform: z.enum(["flutter", "web", "mobile", "xcode", "android", "dotnet", "auto"]).default("auto"),
|
|
515
516
|
project_dir: z.string().optional().describe("Absolute path to the project root. Defaults to CODELOOP_PROJECT_DIR env var or auto-discovered project directory. MUST be an actual project folder — passing the user's home directory is rejected. If your IDE launches the MCP server from the wrong cwd (common on Windows where Cursor uses C:\\Users\\<name> as cwd), set CODELOOP_PROJECT_DIR or pass this param explicitly."),
|
|
516
517
|
workspace_root: z.string().optional().describe("[Alias for project_dir] Same semantics; accepted because many agents reach for this conventional name. Pass either `project_dir` OR `workspace_root` — they're equivalent."),
|
|
518
|
+
tasks_completed: z.array(z.string()).optional().describe("0.1.52 C5 — free-text titles of the tasks the agent claims to have completed in this code change. Cross-checked against the change manifest produced by C1: every claim should map to >= 1 manifest entry and every manifest entry should map to >= 1 claim. Mismatches surface as warnings in the verify response and feed the change_coverage_evidence gate (C3)."),
|
|
517
519
|
}, async (params) => {
|
|
518
520
|
const cwd = resolveCwd(params);
|
|
519
521
|
const explicitDir = params.project_dir || params.workspace_root;
|
|
@@ -523,6 +525,7 @@ Returns: structured report with pass/fail counts, artifact paths, and next-step
|
|
|
523
525
|
const input = {
|
|
524
526
|
scope: params.scope,
|
|
525
527
|
platform: params.platform,
|
|
528
|
+
tasks_completed: params.tasks_completed,
|
|
526
529
|
};
|
|
527
530
|
const output = await runVerify(input, cfg, cwd);
|
|
528
531
|
await trackUsage(apiKey, "verification_run");
|
|
@@ -668,6 +671,7 @@ Returns: pass/fail for each gate, overall confidence score, and recommendation.`
|
|
|
668
671
|
acceptance_path: z.string().default("docs/acceptance/_template.md").describe("Path to the acceptance criteria markdown (absolute or relative to project_dir). Defaults to the template `codeloop_init_project` writes. If neither exists the acceptance gate runs with empty content and uses other signals only."),
|
|
669
672
|
project_dir: z.string().optional().describe("Absolute path to the project root. Defaults to CODELOOP_PROJECT_DIR env var or auto-discovered project directory. MUST be an actual project folder — passing the user's home directory is rejected. If your IDE launches the MCP server from the wrong cwd (common on Windows where Cursor uses C:\\Users\\<name> as cwd), set CODELOOP_PROJECT_DIR or pass this param explicitly."),
|
|
670
673
|
workspace_root: z.string().optional().describe("[Alias for project_dir] Same semantics; accepted because many agents reach for this conventional name. Pass either `project_dir` OR `workspace_root` — they're equivalent."),
|
|
674
|
+
recent_thinking: z.string().optional().describe("0.1.52 C6 — optional dump of the agent's recent thinking / rationale (last few turns of the loop). When present, the gate scans it for anti-rationalisation phrases ('comprehensive verification confirms', 'further interaction would be redundant', 'grid is empty so can't test', etc.) and surfaces specific matches in the continue_fixing postscript so the agent stops repeating the rationalisation and acts on the per-gate next steps instead. Safe to omit — the canonical FORBIDDEN list still ships in the directive without a hit."),
|
|
671
675
|
}, async (params) => {
|
|
672
676
|
const result = await withAuth(async () => {
|
|
673
677
|
const { runGateCheck } = await import("./tools/gate_check.js");
|
|
@@ -718,6 +722,14 @@ Returns: pass/fail for each gate, overall confidence score, and recommendation.`
|
|
|
718
722
|
return `${i + 1}. ${g}${severity}: ${action}`;
|
|
719
723
|
})
|
|
720
724
|
.join("\n");
|
|
725
|
+
// 0.1.52 C6 — anti-rationalisation directive. Built before the
|
|
726
|
+
// loopDirective so we can splice the formatted text in.
|
|
727
|
+
const { scanRecentThinking, buildAntiRationalisationDirective } = await import("./evidence/anti_rationalisation.js");
|
|
728
|
+
const recentThinking = typeof params.recent_thinking === "string"
|
|
729
|
+
? params.recent_thinking
|
|
730
|
+
: undefined;
|
|
731
|
+
const rationalisationHits = scanRecentThinking(recentThinking);
|
|
732
|
+
const antiRationalisationBlock = buildAntiRationalisationDirective(rationalisationHits);
|
|
721
733
|
const loopDirective = [
|
|
722
734
|
"",
|
|
723
735
|
"",
|
|
@@ -752,6 +764,9 @@ Returns: pass/fail for each gate, overall confidence score, and recommendation.`
|
|
|
752
764
|
"MISSING SCREENSHOTS are NEVER a reason to stop — call codeloop_capture_screenshot for each named screen, THEN codeloop_design_compare with mode=\"all\", THEN re-gate.",
|
|
753
765
|
"MISSING VIDEO is NEVER a reason to stop — call codeloop_start_recording, drive the app with codeloop_interact (NOT raw osascript / PowerShell / xdotool), then codeloop_stop_recording, THEN re-gate.",
|
|
754
766
|
"INCOMPLETE CRUD ARC is NEVER a reason to stop — call codeloop_plan_user_journey, follow the returned per-entity script, re-record, THEN re-gate.",
|
|
767
|
+
"UNEXERCISED CHANGE-MANIFEST ENTRIES are NEVER a reason to stop — call codeloop_plan_change_journey, follow each step in priority order, pass target_change_entry verbatim on every codeloop_interact / codeloop_capture_screenshot call, THEN re-gate.",
|
|
768
|
+
"",
|
|
769
|
+
antiRationalisationBlock,
|
|
755
770
|
].join("\n");
|
|
756
771
|
return {
|
|
757
772
|
content: withInitHint([{ type: "text", text: resultJson + loopDirective }], resolveCwd(params)),
|
|
@@ -1316,6 +1331,7 @@ Returns: confirmation + the captured image as an MCP ImageContent block so you c
|
|
|
1316
1331
|
run_id: z.string().optional(),
|
|
1317
1332
|
project_dir: z.string().optional().describe("Absolute path to the project root. Defaults to CODELOOP_PROJECT_DIR env var or auto-discovered project directory. MUST be an actual project folder — passing the user's home directory is rejected. If your IDE launches the MCP server from the wrong cwd (common on Windows where Cursor uses C:\\Users\\<name> as cwd), set CODELOOP_PROJECT_DIR or pass this param explicitly."),
|
|
1318
1333
|
workspace_root: z.string().optional().describe("[Alias for project_dir] Same semantics; accepted because many agents reach for this conventional name. Pass either `project_dir` OR `workspace_root` — they're equivalent."),
|
|
1334
|
+
target_change_entry: z.string().optional().describe("0.1.52 C7 — verbatim display name of the change-manifest entry this screenshot exercises (e.g. 'datagrid_column: \"Product Code\"' or 'PhotometricConfigurations.ProductCode'). When present, the screenshot file is auto-anchored: the filename is prefixed with a slugged form of the entry so the change_coverage_evidence (C3) gate's screenshot scan can credit this evidence to the correct manifest entry without fuzzy matching. The value is also persisted alongside the screenshot path in the response so downstream tools (interaction_replay, gate_check) can use it."),
|
|
1319
1335
|
}, async (params) => {
|
|
1320
1336
|
const authResult = await withAuth(async () => {
|
|
1321
1337
|
const { captureScreenshot } = await import("./runners/screenshot.js");
|
|
@@ -1345,7 +1361,19 @@ Returns: confirmation + the captured image as an MCP ImageContent block so you c
|
|
|
1345
1361
|
const desktopApp = isDesktopAppProject(cwd);
|
|
1346
1362
|
const cfg = loadConfig(cwd);
|
|
1347
1363
|
const targetApp = params.app_name ?? cfg.evidence?.target_app;
|
|
1348
|
-
|
|
1364
|
+
// 0.1.52 C7 — auto-anchor the screenshot filename when
|
|
1365
|
+
// target_change_entry is set so the C3 gate's screenshot
|
|
1366
|
+
// scan can credit this evidence to the correct manifest entry
|
|
1367
|
+
// without fuzzy matching. The slug is appended (not prepended)
|
|
1368
|
+
// so existing screen_name semantics still drive the run / replay
|
|
1369
|
+
// tooling that keys off the prefix.
|
|
1370
|
+
const targetChangeEntry = typeof params.target_change_entry === "string"
|
|
1371
|
+
? params.target_change_entry
|
|
1372
|
+
: undefined;
|
|
1373
|
+
const finalScreenName = targetChangeEntry
|
|
1374
|
+
? `${params.screen_name}--c7-${slugForTargetChangeEntry(targetChangeEntry)}`
|
|
1375
|
+
: params.screen_name;
|
|
1376
|
+
const result = await captureScreenshot(screenshotsDir, finalScreenName, targetApp, undefined, { desktopAppMode: desktopApp });
|
|
1349
1377
|
// Photometry-DB E2E 8 follow-on: when we capture a desktop app
|
|
1350
1378
|
// window, also resolve its on-screen bounds so the agent can
|
|
1351
1379
|
// (a) compute window-relative coords from the returned image
|
|
@@ -1367,7 +1395,7 @@ Returns: confirmation + the captured image as an MCP ImageContent block so you c
|
|
|
1367
1395
|
catch { /* best-effort */ }
|
|
1368
1396
|
}
|
|
1369
1397
|
await trackUsage(apiKey, "visual_review");
|
|
1370
|
-
return { ...result, windowBounds };
|
|
1398
|
+
return { ...result, windowBounds, target_change_entry: targetChangeEntry ?? null };
|
|
1371
1399
|
}, { tool: "codeloop_capture_screenshot", cwd: resolveCwd(params), input: params });
|
|
1372
1400
|
if (typeof authResult === "object" && authResult !== null && "error" in authResult) {
|
|
1373
1401
|
return { content: [{ type: "text", text: JSON.stringify(authResult, null, 2) }] };
|
|
@@ -1381,6 +1409,11 @@ Returns: confirmation + the captured image as an MCP ImageContent block so you c
|
|
|
1381
1409
|
path: result.paths[0],
|
|
1382
1410
|
method: result.method,
|
|
1383
1411
|
};
|
|
1412
|
+
if (result.target_change_entry) {
|
|
1413
|
+
payload.target_change_entry = result.target_change_entry;
|
|
1414
|
+
payload.c7_anchor_note =
|
|
1415
|
+
"This screenshot is anchored to a change-manifest entry — the change_coverage_evidence (C3) gate will credit it to that entry without fuzzy matching.";
|
|
1416
|
+
}
|
|
1384
1417
|
if (result.windowBounds) {
|
|
1385
1418
|
payload.window_bounds = result.windowBounds;
|
|
1386
1419
|
payload.coordinate_hint =
|
|
@@ -1604,6 +1637,57 @@ ai_substantive_prompts, upload_actions, datagrid_edits }, advice, discovered_int
|
|
|
1604
1637
|
content: withInitHint([{ type: "text", text: JSON.stringify(result, null, 2) + driveDirective }]),
|
|
1605
1638
|
};
|
|
1606
1639
|
});
|
|
1640
|
+
server.tool("codeloop_plan_change_journey", TOOL_BOOTSTRAP + `Build a per-change-entry interaction plan from the most recent change manifest (produced by
|
|
1641
|
+
codeloop_verify, 0.1.52 C1+C2). Where codeloop_plan_user_journey enumerates entity-level
|
|
1642
|
+
CRUD arcs across the WHOLE app, plan_change_journey is narrower: it only enumerates the
|
|
1643
|
+
steps the agent must drive to satisfy the new change_coverage_evidence (C3) gate for the
|
|
1644
|
+
features that just shipped in this change.
|
|
1645
|
+
|
|
1646
|
+
When to call: AFTER codeloop_verify writes a change_manifest.json AND BEFORE the agent
|
|
1647
|
+
starts driving codeloop_interact. The plan is the agent's per-change recording script,
|
|
1648
|
+
NOT a substitute for plan_user_journey — call both: plan_user_journey for broad CRUD
|
|
1649
|
+
coverage, plan_change_journey for the per-change verification.
|
|
1650
|
+
|
|
1651
|
+
Each returned step carries:
|
|
1652
|
+
- target_change_entry: the exact display name from the manifest. Pass this VERBATIM
|
|
1653
|
+
as a codeloop_interact / codeloop_capture_screenshot argument so the C3 gate
|
|
1654
|
+
credits the correct manifest entry without fuzzy matching.
|
|
1655
|
+
- entry_kind: ui_element_added / property_added / method_added /
|
|
1656
|
+
migration_column_added / migration_table_added / layout_restructure.
|
|
1657
|
+
- action: a concrete codeloop_interact (or shell) call template — substitute
|
|
1658
|
+
realistic test data into the placeholders.
|
|
1659
|
+
- empty_state_directive: present when the target is a DataGrid or list — see
|
|
1660
|
+
the C4 seed-first directive.
|
|
1661
|
+
|
|
1662
|
+
The preamble explicitly forbids typing into empty grids / clicking buttons that
|
|
1663
|
+
empty-state UI hides; seed data first via plan_user_journey's Create arc, a fixture
|
|
1664
|
+
script, or a pre-populated artifact.
|
|
1665
|
+
|
|
1666
|
+
Returns: { ready, manifest_run_id, total_entries, steps: [...], preamble, message }.`, {
|
|
1667
|
+
project_dir: z.string().optional().describe("Absolute path to the project root. Defaults to CODELOOP_PROJECT_DIR env var or auto-discovered project directory. MUST be an actual project folder."),
|
|
1668
|
+
workspace_root: z.string().optional().describe("[Alias for project_dir]"),
|
|
1669
|
+
run_id: z.string().optional().describe("Preferred run_id whose change_manifest.json to read. Defaults to the most recent run with a manifest."),
|
|
1670
|
+
}, async (params) => {
|
|
1671
|
+
const result = await withAuth(async () => {
|
|
1672
|
+
const { planChangeJourney } = await import("./tools/plan_change_journey.js");
|
|
1673
|
+
return planChangeJourney(resolveCwd(params), params.run_id);
|
|
1674
|
+
}, { tool: "codeloop_plan_change_journey", cwd: resolveCwd(params), input: params });
|
|
1675
|
+
const driveDirective = [
|
|
1676
|
+
"",
|
|
1677
|
+
"",
|
|
1678
|
+
"⚠️ DRIVE THIS PLAN NOW — do not deliberate, do not ask the user ⚠️",
|
|
1679
|
+
"Each step above corresponds to a manifest entry the change_coverage_evidence (C3) gate WILL block on. The next 3 tool calls per step are non-negotiable:",
|
|
1680
|
+
" 1. codeloop_interact — drive the action template; substitute the placeholder with realistic data; ALWAYS pass target_change_entry verbatim from the step.",
|
|
1681
|
+
" 2. codeloop_capture_screenshot — pin the result with target_change_entry from the same step.",
|
|
1682
|
+
" 3. (If the action triggered a modal) codeloop_handle_modal before continuing.",
|
|
1683
|
+
"Empty grids/lists are NOT a valid reason to skip a step — seed data per the preamble first.",
|
|
1684
|
+
"Migration entries (migration_column_added, migration_table_added) MUST be verified via codeloop_interact action='shell' that dumps the live schema; the gate scans build/runtime logs for the column/table name.",
|
|
1685
|
+
"Method entries are implicit — they do NOT need a direct interaction; the gate credits them automatically when the property/column they operate on is exercised.",
|
|
1686
|
+
].join("\n");
|
|
1687
|
+
return {
|
|
1688
|
+
content: withInitHint([{ type: "text", text: JSON.stringify(result, null, 2) + driveDirective }]),
|
|
1689
|
+
};
|
|
1690
|
+
});
|
|
1607
1691
|
server.tool("codeloop_record_interaction", TOOL_BOOTSTRAP + `Record a fixed-duration video of the app window (blocking). Use for simple captures where no
|
|
1608
1692
|
interaction is needed during recording. The app is brought to front automatically and the
|
|
1609
1693
|
IDE is restored after recording completes.
|
|
@@ -2427,6 +2511,7 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2427
2511
|
screenshot_path: z.string().optional().describe("Absolute path to the screenshot PNG that x/y were computed against. Used with `coords: \"screenshot\"` to scale agent-supplied coords from the captured image dimensions to the window's actual pixel dimensions before applying the window origin and DPI factor. Pass the `path` field returned by codeloop_capture_screenshot."),
|
|
2428
2512
|
project_dir: z.string().optional().describe("Absolute path to project root."),
|
|
2429
2513
|
workspace_root: z.string().optional().describe("[Alias for project_dir] Pass either; they're equivalent."),
|
|
2514
|
+
target_change_entry: z.string().optional().describe("0.1.52 C3+C4+C7 — verbatim display name of the change-manifest entry this interaction is exercising (e.g. 'datagrid_column: \"Product Code\"' or 'PhotometricConfigurations.ProductCode'). Pass this whenever you're following a step from codeloop_plan_change_journey so the change_coverage_evidence gate can credit the correct manifest entry without fuzzy matching, AND so the empty-state risk detector can compare the target against the manifest before the action is taken."),
|
|
2430
2515
|
}, async (params) => {
|
|
2431
2516
|
const result = await withAuth(async () => {
|
|
2432
2517
|
const action = params.action;
|
|
@@ -3278,6 +3363,13 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
3278
3363
|
inputArgs.purpose = params.purpose;
|
|
3279
3364
|
if (params.step)
|
|
3280
3365
|
inputArgs.step = params.step;
|
|
3366
|
+
// 0.1.52 C3+C7 — preserve the verbatim manifest entry name on the
|
|
3367
|
+
// log so the change_coverage_evidence gate can credit the correct
|
|
3368
|
+
// entry without fuzzy matching.
|
|
3369
|
+
if (params.target_change_entry) {
|
|
3370
|
+
inputArgs.target_change_entry = params
|
|
3371
|
+
.target_change_entry;
|
|
3372
|
+
}
|
|
3281
3373
|
// Post-action verification readback. Persisted alongside the
|
|
3282
3374
|
// interaction so a downstream consumer (depth gate, dev report,
|
|
3283
3375
|
// the agent on the next turn) can confirm the action actually
|
|
@@ -3347,13 +3439,41 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
3347
3439
|
// typing / clicking — and the user_journey gate would later fail
|
|
3348
3440
|
// because half the journey didn't happen. The directive blocks
|
|
3349
3441
|
// that path.
|
|
3350
|
-
|
|
3442
|
+
let postscript = "\n\n[CodeLoop H11] After this interaction, a modal/dialog/overlay MAY have appeared (Save? / Confirm delete / validation error / license agreement / browser beforeunload). " +
|
|
3351
3443
|
"BEFORE the next codeloop_interact call you MUST: (1) take a fresh codeloop_capture_screenshot, " +
|
|
3352
3444
|
"(2) inspect the screenshot for any popup, dialog, sheet, alert, or full-screen overlay, " +
|
|
3353
3445
|
"(3) if one is present call codeloop_handle_modal with the appropriate `decision` " +
|
|
3354
3446
|
"(\"confirm\" to proceed / \"cancel\" to abort / \"dismiss\" to close), and " +
|
|
3355
3447
|
"(4) only then continue the planned journey. " +
|
|
3356
3448
|
"Do NOT skip modals \"to keep moving\" — an unhandled modal will block every subsequent click and the user_journey_evidence gate will block ready_for_review.";
|
|
3449
|
+
// 0.1.52 C4 — Empty-state seeding directive. Heuristic runs against
|
|
3450
|
+
// the manifest entry the agent claims to be exercising plus the
|
|
3451
|
+
// recent interaction log; when the call looks like a row/cell
|
|
3452
|
+
// action with no prior commit/seed, the postscript appends a HARD
|
|
3453
|
+
// seed-first instruction so the agent doesn't waste a recording
|
|
3454
|
+
// session typing into a non-existent row.
|
|
3455
|
+
try {
|
|
3456
|
+
const { detectEmptyStateRisk, buildEmptyStateDirective } = await import("./runners/empty_state_detector.js");
|
|
3457
|
+
const cwdForC4 = resolveCwd(params);
|
|
3458
|
+
const argsForC4 = params ?? {};
|
|
3459
|
+
const target_change_entry = typeof params.target_change_entry === "string"
|
|
3460
|
+
? params.target_change_entry
|
|
3461
|
+
: undefined;
|
|
3462
|
+
const verdict = detectEmptyStateRisk({
|
|
3463
|
+
cwd: cwdForC4,
|
|
3464
|
+
target_change_entry,
|
|
3465
|
+
action: typeof params.action === "string"
|
|
3466
|
+
? params.action
|
|
3467
|
+
: "",
|
|
3468
|
+
args: argsForC4,
|
|
3469
|
+
});
|
|
3470
|
+
const directive = buildEmptyStateDirective(verdict, target_change_entry);
|
|
3471
|
+
if (directive)
|
|
3472
|
+
postscript += directive;
|
|
3473
|
+
}
|
|
3474
|
+
catch {
|
|
3475
|
+
/* best-effort */
|
|
3476
|
+
}
|
|
3357
3477
|
return {
|
|
3358
3478
|
content: withInitHint([
|
|
3359
3479
|
{ type: "text", text: JSON.stringify(result, null, 2) + postscript },
|