@wrongstack/cli 0.305.1 → 0.306.2
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-EPERLKYI.js → auth-L3WGSGDX.js} +8 -8
- package/dist/boot/short-circuit-flags.d.ts +7 -1
- package/dist/boot/system-prompt-menu.d.ts +151 -0
- package/dist/chimera-cascade-evidence.d.ts +102 -0
- package/dist/chimera-review-task.d.ts +7 -0
- package/dist/{chunk-5KFLLTSQ.js → chunk-HITHEXE3.js} +5 -5
- package/dist/{chunk-2H47YMCV.js → chunk-KXN5HZDL.js} +4 -4
- package/dist/{chunk-V3XH6XBJ.js → chunk-T6YAVFXA.js} +33 -2
- package/dist/{chunk-3IC4IEZC.js → chunk-VUVOMXWP.js} +34 -3
- package/dist/{cli-main-6YE423OH.js → cli-main-3PUWFKSL.js} +319 -274
- package/dist/execute-deps.d.ts +14 -0
- package/dist/{execution-QYFWDD3Y.js → execution-4IG5XKOL.js} +688 -37
- package/dist/execution-chimera-cascade.d.ts +7 -1
- package/dist/{hq-5G6D5YIU.js → hq-ZK6PGN3W.js} +95 -6
- package/dist/index.js +760 -547
- package/dist/{modeldiag-SIVUOX2Y.js → modeldiag-6VS5KDP5.js} +4 -4
- package/dist/provider-helpers.d.ts +1 -5
- package/dist/{providers-models-VV74TRQ2.js → providers-models-HQN3AT3K.js} +2 -2
- package/dist/services/run-git.d.ts +18 -0
- package/dist/slash-commands/git.d.ts +0 -5
- package/dist/slash-commands/theme.d.ts +32 -0
- package/dist/tui-resource-menus.d.ts +13 -0
- package/dist/wiring/management-tools.d.ts +8 -1
- package/package.json +24 -24
|
@@ -5,9 +5,10 @@ import {
|
|
|
5
5
|
normalizeTuiThinkingWord,
|
|
6
6
|
resolveActualTarget,
|
|
7
7
|
resolvePersistPath,
|
|
8
|
+
runGit,
|
|
8
9
|
setAutoSuggestions,
|
|
9
10
|
setSuggestions
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-T6YAVFXA.js";
|
|
11
12
|
import {
|
|
12
13
|
startCliHqConnection
|
|
13
14
|
} from "./chunk-FKWHSFX4.js";
|
|
@@ -37,23 +38,24 @@ import {
|
|
|
37
38
|
writeWebuiSessionChildReady
|
|
38
39
|
} from "./chunk-3JPTNADJ.js";
|
|
39
40
|
import {
|
|
40
|
-
|
|
41
|
-
} from "./chunk-
|
|
42
|
-
import {
|
|
43
|
-
activeProfileConfigPath
|
|
44
|
-
} from "./chunk-YMXXOOFN.js";
|
|
41
|
+
CLI_VERSION
|
|
42
|
+
} from "./chunk-XJXDOF63.js";
|
|
45
43
|
import {
|
|
46
44
|
terminalLink,
|
|
47
45
|
terminalText
|
|
48
46
|
} from "./chunk-KE7E7DPX.js";
|
|
49
47
|
import {
|
|
50
|
-
|
|
51
|
-
} from "./chunk-
|
|
48
|
+
resolveActiveApiKey
|
|
49
|
+
} from "./chunk-SZ42FYPT.js";
|
|
50
|
+
import {
|
|
51
|
+
activeProfileConfigPath
|
|
52
|
+
} from "./chunk-YMXXOOFN.js";
|
|
52
53
|
import "./chunk-7OCVIDC7.js";
|
|
53
54
|
|
|
54
55
|
// src/execution.ts
|
|
55
56
|
import * as path13 from "node:path";
|
|
56
57
|
import { effectiveFallbackChain as effectiveFallbackChain2, setQueuedMessagesSnapshot as setQueuedMessagesSnapshot2 } from "@wrongstack/core/agent";
|
|
58
|
+
import { updateReviewReportEvidence } from "@wrongstack/core/plugin";
|
|
57
59
|
import { attachTodosCheckpoint as attachTodosCheckpoint2 } from "@wrongstack/core/storage";
|
|
58
60
|
import { normalizeTokenSavingTier as normalizeTokenSavingTier2 } from "@wrongstack/core/types";
|
|
59
61
|
import { mergeCustomModelDefs } from "@wrongstack/core/utils";
|
|
@@ -2746,6 +2748,128 @@ import * as fsp from "node:fs/promises";
|
|
|
2746
2748
|
import * as path10 from "node:path";
|
|
2747
2749
|
import { emitReviewIfChanged } from "@wrongstack/core/plugin";
|
|
2748
2750
|
|
|
2751
|
+
// src/chimera-cascade-evidence.ts
|
|
2752
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2753
|
+
var JSON_BLOCK_RE = /```json\s*([\s\S]*?)```/gi;
|
|
2754
|
+
function extractCascadeEvidence(text) {
|
|
2755
|
+
if (!text) return null;
|
|
2756
|
+
for (const match of text.matchAll(JSON_BLOCK_RE)) {
|
|
2757
|
+
const body = match[1]?.trim();
|
|
2758
|
+
if (!body) continue;
|
|
2759
|
+
let parsed;
|
|
2760
|
+
try {
|
|
2761
|
+
parsed = JSON.parse(body);
|
|
2762
|
+
} catch {
|
|
2763
|
+
continue;
|
|
2764
|
+
}
|
|
2765
|
+
const evidence = parseEvidenceObject(parsed);
|
|
2766
|
+
if (evidence) return evidence;
|
|
2767
|
+
}
|
|
2768
|
+
return null;
|
|
2769
|
+
}
|
|
2770
|
+
function parseEvidenceObject(parsed) {
|
|
2771
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
2772
|
+
const root = parsed;
|
|
2773
|
+
const raw = root.verification_evidence;
|
|
2774
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
2775
|
+
const checks = raw;
|
|
2776
|
+
const out = {};
|
|
2777
|
+
let found = false;
|
|
2778
|
+
for (const key of ["typecheck", "lint", "tests"]) {
|
|
2779
|
+
const check = parseSingleCheck(checks[key]);
|
|
2780
|
+
if (check) {
|
|
2781
|
+
out[key] = check;
|
|
2782
|
+
found = true;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
return found ? out : null;
|
|
2786
|
+
}
|
|
2787
|
+
function parseSingleCheck(raw) {
|
|
2788
|
+
if (typeof raw !== "object" || raw === null) return void 0;
|
|
2789
|
+
const check = raw;
|
|
2790
|
+
const command = check.command;
|
|
2791
|
+
const exitCode = check.exitCode;
|
|
2792
|
+
if (typeof command !== "string" || command.trim().length === 0) return void 0;
|
|
2793
|
+
if (typeof exitCode !== "number" || !Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255) {
|
|
2794
|
+
return void 0;
|
|
2795
|
+
}
|
|
2796
|
+
const output = typeof check.output === "string" ? truncateOutput(check.output) : void 0;
|
|
2797
|
+
return { command: command.trim(), exitCode, output };
|
|
2798
|
+
}
|
|
2799
|
+
var MAX_EVIDENCE_OUTPUT_CHARS = 2e3;
|
|
2800
|
+
function truncateOutput(output) {
|
|
2801
|
+
return output.length > MAX_EVIDENCE_OUTPUT_CHARS ? `${output.slice(0, MAX_EVIDENCE_OUTPUT_CHARS)}
|
|
2802
|
+
\u2026 (truncated)` : output;
|
|
2803
|
+
}
|
|
2804
|
+
var SAFE_EXECUTABLES = /* @__PURE__ */ new Set([
|
|
2805
|
+
"pnpm",
|
|
2806
|
+
"npm",
|
|
2807
|
+
"yarn",
|
|
2808
|
+
"npx",
|
|
2809
|
+
"bun",
|
|
2810
|
+
"node",
|
|
2811
|
+
"tsc",
|
|
2812
|
+
"biome",
|
|
2813
|
+
"eslint",
|
|
2814
|
+
"vitest",
|
|
2815
|
+
"jest"
|
|
2816
|
+
]);
|
|
2817
|
+
var CASCADE_EVIDENCE_COMMAND_TIMEOUT_MS = 12e4;
|
|
2818
|
+
var CASCADE_EVIDENCE_UNSAFE_EXIT = 126;
|
|
2819
|
+
var CASCADE_EVIDENCE_RUN_ERROR_EXIT = 127;
|
|
2820
|
+
function isSafeCascadeCommand(command) {
|
|
2821
|
+
if (!command || command.length > 500) return false;
|
|
2822
|
+
const tokens = command.trim().split(/\s+/);
|
|
2823
|
+
if (tokens.length === 0) return false;
|
|
2824
|
+
if (!SAFE_EXECUTABLES.has(tokens[0])) return false;
|
|
2825
|
+
return tokens.every((token) => /^[A-Za-z0-9@/._:=~+*?\[\]-]+$/.test(token));
|
|
2826
|
+
}
|
|
2827
|
+
var runCascadeVerificationCommand = (command, cwd, timeoutMs) => {
|
|
2828
|
+
if (!isSafeCascadeCommand(command)) {
|
|
2829
|
+
return Promise.resolve({ exitCode: CASCADE_EVIDENCE_UNSAFE_EXIT });
|
|
2830
|
+
}
|
|
2831
|
+
const tokens = command.trim().split(/\s+/);
|
|
2832
|
+
const executable = tokens[0];
|
|
2833
|
+
const args = tokens.slice(1);
|
|
2834
|
+
return new Promise((resolve3) => {
|
|
2835
|
+
let child;
|
|
2836
|
+
try {
|
|
2837
|
+
child = spawn2(executable, args, {
|
|
2838
|
+
cwd,
|
|
2839
|
+
stdio: "ignore",
|
|
2840
|
+
windowsHide: true,
|
|
2841
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
2842
|
+
});
|
|
2843
|
+
} catch {
|
|
2844
|
+
resolve3({ exitCode: CASCADE_EVIDENCE_RUN_ERROR_EXIT });
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
child.on("error", () => resolve3({ exitCode: CASCADE_EVIDENCE_RUN_ERROR_EXIT }));
|
|
2848
|
+
child.on("close", (code) => resolve3({ exitCode: code ?? CASCADE_EVIDENCE_RUN_ERROR_EXIT }));
|
|
2849
|
+
});
|
|
2850
|
+
};
|
|
2851
|
+
async function verifyCascadeEvidence(evidence, cwd, runCommand = runCascadeVerificationCommand, timeoutMs = CASCADE_EVIDENCE_COMMAND_TIMEOUT_MS) {
|
|
2852
|
+
if (!evidence) return { status: "missing", checks: [] };
|
|
2853
|
+
if (!evidence.typecheck) return { status: "failed", checks: [] };
|
|
2854
|
+
const checks = [];
|
|
2855
|
+
for (const name of ["typecheck", "lint", "tests"]) {
|
|
2856
|
+
const claimed = evidence[name];
|
|
2857
|
+
if (!claimed) continue;
|
|
2858
|
+
const actual = await runCommand(claimed.command, cwd, timeoutMs);
|
|
2859
|
+
const ok = claimed.exitCode === 0 && actual.exitCode === claimed.exitCode;
|
|
2860
|
+
checks.push({
|
|
2861
|
+
name,
|
|
2862
|
+
command: claimed.command,
|
|
2863
|
+
claimedExitCode: claimed.exitCode,
|
|
2864
|
+
actualExitCode: actual.exitCode,
|
|
2865
|
+
ok
|
|
2866
|
+
});
|
|
2867
|
+
}
|
|
2868
|
+
if (checks.length === 0) return { status: "missing", checks };
|
|
2869
|
+
const status = checks.every((c) => c.ok) ? "verified" : "failed";
|
|
2870
|
+
return { status, checks };
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2749
2873
|
// src/chimera-review-task.ts
|
|
2750
2874
|
import { parseReviewSeverity } from "@wrongstack/core/plugin";
|
|
2751
2875
|
function truncateAtCodePointBoundary(text, maxCodeUnits) {
|
|
@@ -2846,6 +2970,34 @@ function buildChimeraReviewTaskDescription(p) {
|
|
|
2846
2970
|
lines.push("structured review report.");
|
|
2847
2971
|
return lines.join("\n");
|
|
2848
2972
|
}
|
|
2973
|
+
var CASCADE_EVIDENCE_INSTRUCTIONS = [
|
|
2974
|
+
`After fixing, run the project's REAL verification commands on the code you changed`,
|
|
2975
|
+
`(typecheck, lint, and the tests covering the fixed code) and return a machine-evidence`,
|
|
2976
|
+
`block as the LAST section of your response. The block MUST be a fenced JSON block`,
|
|
2977
|
+
`(exactly \`\`\`json ... \`\`\`) with this shape:`,
|
|
2978
|
+
``,
|
|
2979
|
+
"```json",
|
|
2980
|
+
"{",
|
|
2981
|
+
' "verification_evidence": {',
|
|
2982
|
+
' "typecheck": { "command": "pnpm typecheck", "exitCode": 0 },',
|
|
2983
|
+
' "lint": { "command": "pnpm lint", "exitCode": 0 },',
|
|
2984
|
+
' "tests": { "command": "pnpm test --filter affected", "exitCode": 0 }',
|
|
2985
|
+
" }",
|
|
2986
|
+
"}",
|
|
2987
|
+
"```",
|
|
2988
|
+
``,
|
|
2989
|
+
`Rules:`,
|
|
2990
|
+
`- \`command\` must be the EXACT command you ran (plain executable + args, no shell`,
|
|
2991
|
+
` chaining like \`&&\`/\`;\`/\`|\`, no redirection, no shell expansion). These commands are`,
|
|
2992
|
+
` re-run verbatim by the orchestrator to verify your work.`,
|
|
2993
|
+
`- \`exitCode\` is the real process exit code you observed: 0 = passed, non-zero = failed.`,
|
|
2994
|
+
` Never invent or omit an exit code. If a command cannot be run, omit that key entirely`,
|
|
2995
|
+
` and explain why in your report text \u2014 an omitted key is honest, a fabricated 0 is not.`,
|
|
2996
|
+
`- If your fix does not pass a command, report the true non-zero exit code; the`,
|
|
2997
|
+
` orchestrator will treat the fix as unverified and keep the finding open.`,
|
|
2998
|
+
`- At least \`typecheck\` must be present; include \`lint\` and \`tests\` when the project`,
|
|
2999
|
+
` has them.`
|
|
3000
|
+
].join("\n");
|
|
2849
3001
|
function buildChimeraCascadeTaskDescription(agentKind, p) {
|
|
2850
3002
|
const fileList = p.bundle.files.map((f) => `- ${f.path}`).join("\n");
|
|
2851
3003
|
const reportSlice = truncateAtCodePointBoundary(p.reviewText, 12e3);
|
|
@@ -2866,8 +3018,9 @@ function buildChimeraCascadeTaskDescription(agentKind, p) {
|
|
|
2866
3018
|
`Investigate the security findings above. Read the flagged files, confirm or refute`,
|
|
2867
3019
|
`each finding, and **apply fixes** for confirmed vulnerabilities using the edit tool.`,
|
|
2868
3020
|
`Use severity (Critical/High/Medium), file:line citations, and remediation steps.`,
|
|
2869
|
-
`
|
|
2870
|
-
|
|
3021
|
+
`If a finding is a false positive, say so and do not modify the file.`,
|
|
3022
|
+
``,
|
|
3023
|
+
CASCADE_EVIDENCE_INSTRUCTIONS
|
|
2871
3024
|
].join("\n");
|
|
2872
3025
|
}
|
|
2873
3026
|
return [
|
|
@@ -2884,9 +3037,10 @@ function buildChimeraCascadeTaskDescription(agentKind, p) {
|
|
|
2884
3037
|
``,
|
|
2885
3038
|
`Hunt for the bugs flagged above. Read the affected files, trace each finding to its`,
|
|
2886
3039
|
`root cause, and **apply minimal fixes** for confirmed bugs using the edit tool.`,
|
|
2887
|
-
`Use severity (Critical/High/Medium), file:line citations.
|
|
2888
|
-
`
|
|
2889
|
-
|
|
3040
|
+
`Use severity (Critical/High/Medium), file:line citations. If a finding is a false`,
|
|
3041
|
+
`positive, say so and do not modify the file.`,
|
|
3042
|
+
``,
|
|
3043
|
+
CASCADE_EVIDENCE_INSTRUCTIONS
|
|
2890
3044
|
].join("\n");
|
|
2891
3045
|
}
|
|
2892
3046
|
|
|
@@ -3003,7 +3157,9 @@ function installChimeraCascadeHandler({
|
|
|
3003
3157
|
session,
|
|
3004
3158
|
getPendingWork,
|
|
3005
3159
|
trackWork,
|
|
3006
|
-
buildLadder
|
|
3160
|
+
buildLadder,
|
|
3161
|
+
verifyEvidence = verifyCascadeEvidence,
|
|
3162
|
+
persistEvidence
|
|
3007
3163
|
}) {
|
|
3008
3164
|
events.onPattern("chimera.cascade_needed", (_event, payload) => {
|
|
3009
3165
|
const p = payload;
|
|
@@ -3016,6 +3172,7 @@ function installChimeraCascadeHandler({
|
|
|
3016
3172
|
await previousWork;
|
|
3017
3173
|
} catch {
|
|
3018
3174
|
}
|
|
3175
|
+
const agentEvidence = [];
|
|
3019
3176
|
for (const agentKind of p.agents) {
|
|
3020
3177
|
try {
|
|
3021
3178
|
const taskDesc = buildChimeraCascadeTaskDescription(agentKind, p);
|
|
@@ -3059,6 +3216,10 @@ function installChimeraCascadeHandler({
|
|
|
3059
3216
|
const result = outcome.result;
|
|
3060
3217
|
if (result?.status === "success") {
|
|
3061
3218
|
const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
3219
|
+
const claimed = extractCascadeEvidence(resultText);
|
|
3220
|
+
if (claimed) {
|
|
3221
|
+
agentEvidence.push({ agentKind, evidence: claimed });
|
|
3222
|
+
}
|
|
3062
3223
|
await session.append({
|
|
3063
3224
|
type: "llm_response",
|
|
3064
3225
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3089,7 +3250,21 @@ function installChimeraCascadeHandler({
|
|
|
3089
3250
|
});
|
|
3090
3251
|
}
|
|
3091
3252
|
}
|
|
3092
|
-
|
|
3253
|
+
const accumulatedEvidence = {};
|
|
3254
|
+
for (const { evidence } of agentEvidence) {
|
|
3255
|
+
for (const name of ["typecheck", "lint", "tests"]) {
|
|
3256
|
+
if (evidence[name]) accumulatedEvidence[name] = evidence[name];
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
await maybeReReviewCascade({
|
|
3260
|
+
events,
|
|
3261
|
+
session,
|
|
3262
|
+
bundle: p.bundle,
|
|
3263
|
+
reportId: p.reportId,
|
|
3264
|
+
claimedEvidence: accumulatedEvidence,
|
|
3265
|
+
verifyEvidence,
|
|
3266
|
+
persistEvidence
|
|
3267
|
+
});
|
|
3093
3268
|
})();
|
|
3094
3269
|
trackWork(pendingWork);
|
|
3095
3270
|
});
|
|
@@ -3097,16 +3272,71 @@ function installChimeraCascadeHandler({
|
|
|
3097
3272
|
async function maybeReReviewCascade({
|
|
3098
3273
|
events,
|
|
3099
3274
|
session,
|
|
3100
|
-
bundle
|
|
3275
|
+
bundle,
|
|
3276
|
+
reportId,
|
|
3277
|
+
claimedEvidence,
|
|
3278
|
+
verifyEvidence,
|
|
3279
|
+
persistEvidence
|
|
3101
3280
|
}) {
|
|
3102
3281
|
const maxDepth = bundle.maxCascadeDepth ?? 0;
|
|
3103
3282
|
const currentDepth = bundle.cascadeDepth ?? 0;
|
|
3283
|
+
const hasClaimedEvidence = Object.keys(claimedEvidence).length > 0;
|
|
3284
|
+
const verification = hasClaimedEvidence ? await verifyEvidence(
|
|
3285
|
+
claimedEvidence,
|
|
3286
|
+
bundle.cwd,
|
|
3287
|
+
void 0,
|
|
3288
|
+
CASCADE_EVIDENCE_COMMAND_TIMEOUT_MS
|
|
3289
|
+
) : { status: "missing", checks: [] };
|
|
3290
|
+
const evidenceBundle = {
|
|
3291
|
+
...bundle,
|
|
3292
|
+
evidenceStatus: verification.status,
|
|
3293
|
+
evidenceChecks: verification.checks
|
|
3294
|
+
};
|
|
3295
|
+
if (reportId && persistEvidence) {
|
|
3296
|
+
try {
|
|
3297
|
+
await persistEvidence(reportId, verification.status, verification.checks);
|
|
3298
|
+
} catch (error) {
|
|
3299
|
+
await session.append({
|
|
3300
|
+
type: "error",
|
|
3301
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3302
|
+
message: `Chimera cascade evidence persistence failed: ${errorText2(error)}`,
|
|
3303
|
+
phase: "agent"
|
|
3304
|
+
});
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
await session.append({
|
|
3308
|
+
type: "llm_response",
|
|
3309
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3310
|
+
content: [
|
|
3311
|
+
{
|
|
3312
|
+
type: "text",
|
|
3313
|
+
text: renderEvidenceSummary(verification.status, verification.checks)
|
|
3314
|
+
}
|
|
3315
|
+
],
|
|
3316
|
+
stopReason: "end_turn",
|
|
3317
|
+
usage: { input: 0, output: 0 }
|
|
3318
|
+
});
|
|
3319
|
+
if (verification.status !== "verified") {
|
|
3320
|
+
await session.append({
|
|
3321
|
+
type: "llm_response",
|
|
3322
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3323
|
+
content: [
|
|
3324
|
+
{
|
|
3325
|
+
type: "text",
|
|
3326
|
+
text: `\u{1F982} Chimera cascade evidence ${verification.status} \u2014 fix not confirmed, skipping re-review. Findings remain open.`
|
|
3327
|
+
}
|
|
3328
|
+
],
|
|
3329
|
+
stopReason: "end_turn",
|
|
3330
|
+
usage: { input: 0, output: 0 }
|
|
3331
|
+
});
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3104
3334
|
if (maxDepth > 0 && currentDepth < maxDepth) {
|
|
3105
3335
|
try {
|
|
3106
3336
|
const reReadFiles = [];
|
|
3107
|
-
for (const f of
|
|
3337
|
+
for (const f of evidenceBundle.files) {
|
|
3108
3338
|
try {
|
|
3109
|
-
const absPath = path10.join(
|
|
3339
|
+
const absPath = path10.join(evidenceBundle.cwd, f.path);
|
|
3110
3340
|
const content = await fsp.readFile(absPath, "utf8");
|
|
3111
3341
|
reReadFiles.push({ path: f.path, status: "modified", content });
|
|
3112
3342
|
} catch {
|
|
@@ -3114,7 +3344,7 @@ async function maybeReReviewCascade({
|
|
|
3114
3344
|
}
|
|
3115
3345
|
if (reReadFiles.length > 0) {
|
|
3116
3346
|
const reReviewBundle = {
|
|
3117
|
-
...
|
|
3347
|
+
...evidenceBundle,
|
|
3118
3348
|
files: reReadFiles,
|
|
3119
3349
|
cascadeDepth: currentDepth + 1
|
|
3120
3350
|
};
|
|
@@ -3172,18 +3402,28 @@ async function maybeReReviewCascade({
|
|
|
3172
3402
|
});
|
|
3173
3403
|
}
|
|
3174
3404
|
}
|
|
3405
|
+
function renderEvidenceSummary(status, checks) {
|
|
3406
|
+
if (status === "missing") return "no machine evidence supplied";
|
|
3407
|
+
const summary = checks.map((check) => `${check.name}:${check.ok ? "pass" : "fail"}(${check.actualExitCode})`).join(", ");
|
|
3408
|
+
return `${status}${summary ? ` \u2014 ${summary}` : ""}`;
|
|
3409
|
+
}
|
|
3410
|
+
function errorText2(error) {
|
|
3411
|
+
return error instanceof Error ? error.message : String(error);
|
|
3412
|
+
}
|
|
3175
3413
|
|
|
3176
3414
|
// src/execution-chimera-review.ts
|
|
3177
3415
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3178
3416
|
import path11 from "node:path";
|
|
3179
|
-
import { effectiveFallbackChain } from "@wrongstack/core/agent";
|
|
3417
|
+
import { effectiveFallbackChain, fallbackProfileChain as fallbackProfileChain2 } from "@wrongstack/core/agent";
|
|
3180
3418
|
import {
|
|
3181
3419
|
CHIMERA_REVIEW_PROMPT,
|
|
3420
|
+
classifyChimeraReviewSource,
|
|
3182
3421
|
integrateFindings,
|
|
3183
3422
|
maybeCompactReviewStores,
|
|
3184
3423
|
parseChimeraReviewReport,
|
|
3185
3424
|
persistReviewReport,
|
|
3186
|
-
recordCompletedReview
|
|
3425
|
+
recordCompletedReview,
|
|
3426
|
+
verifyFindingsAgainstDisk
|
|
3187
3427
|
} from "@wrongstack/core/plugin";
|
|
3188
3428
|
function normalizeFileKeyForCitation(raw, cwd) {
|
|
3189
3429
|
const forward = raw.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
@@ -3267,7 +3507,13 @@ function installChimeraReviewHandler({
|
|
|
3267
3507
|
const rawModel = p.reviewFallbackModels ? p.config.model?.trim() || void 0 : tModel;
|
|
3268
3508
|
const baseProvider = rawProvider || tProvider || config.provider;
|
|
3269
3509
|
const baseModel = rawModel || tModel || config.model;
|
|
3270
|
-
const
|
|
3510
|
+
const configFallbacks = [...p.config.fallbackModels ?? []];
|
|
3511
|
+
if (p.config.fallbackProfile) {
|
|
3512
|
+
configFallbacks.push(...fallbackProfileChain2(config, p.config.fallbackProfile));
|
|
3513
|
+
}
|
|
3514
|
+
const baseFallbacks = p.reviewFallbackModels ? [...p.reviewFallbackModels] : resolveReviewerFallbackModels(
|
|
3515
|
+
configFallbacks.length > 0 ? configFallbacks : void 0
|
|
3516
|
+
);
|
|
3271
3517
|
const assigned = assignReviewerModels(
|
|
3272
3518
|
baseProvider,
|
|
3273
3519
|
baseModel,
|
|
@@ -3368,18 +3614,34 @@ function installChimeraReviewHandler({
|
|
|
3368
3614
|
}
|
|
3369
3615
|
const reviewText = typeof result.result === "string" ? result.result.trim() : JSON.stringify(result.result);
|
|
3370
3616
|
const reportId = randomUUID3();
|
|
3617
|
+
const reviewSource = classifyChimeraReviewSource(p);
|
|
3618
|
+
const winningAttempt = outcome.wonAt ? ladder[outcome.wonAt - 1] : void 0;
|
|
3619
|
+
const parsedReport = parseChimeraReviewReport(reviewText, {
|
|
3620
|
+
sessionId: reviewSessionId,
|
|
3621
|
+
agentId: p.fileProvenance?.find((entry) => entry.agentId)?.agentId ?? "chimera-review",
|
|
3622
|
+
reviewerModel: winningAttempt ? attemptLabel(winningAttempt) : void 0,
|
|
3623
|
+
reviewType: reviewSource,
|
|
3624
|
+
reportId
|
|
3625
|
+
});
|
|
3626
|
+
const verifiedFindings = await verifyFindingsAgainstDisk(parsedReport.findings, {
|
|
3627
|
+
cwd: p.cwd
|
|
3628
|
+
});
|
|
3629
|
+
const verifiedParsedReport = {
|
|
3630
|
+
...parsedReport,
|
|
3631
|
+
findings: verifiedFindings
|
|
3632
|
+
};
|
|
3371
3633
|
await persistAndEmitCompletion({
|
|
3372
3634
|
bundle: p,
|
|
3373
3635
|
reviewText,
|
|
3374
3636
|
status: "success",
|
|
3375
3637
|
cwd: p.cwd,
|
|
3376
3638
|
sessionId: reviewSessionId,
|
|
3377
|
-
reportId
|
|
3639
|
+
reportId,
|
|
3640
|
+
parsedReport: verifiedParsedReport
|
|
3378
3641
|
});
|
|
3379
3642
|
if (reviewText) {
|
|
3380
3643
|
const reviewHasFindings = !isChimeraAllClearReview(reviewText);
|
|
3381
|
-
const
|
|
3382
|
-
const citedFindings = parsedReview.findings.filter((finding) => finding.location);
|
|
3644
|
+
const citedFindings = verifiedFindings.filter((finding) => finding.location);
|
|
3383
3645
|
const citedPaths = new Set(
|
|
3384
3646
|
p.files.map((file) => normalizeFileKeyForCitation(file.path, p.cwd))
|
|
3385
3647
|
);
|
|
@@ -3391,14 +3653,28 @@ function installChimeraReviewHandler({
|
|
|
3391
3653
|
if (bucket) bucket.push(normalized);
|
|
3392
3654
|
else changedBasenameIndex.set(basename7, [normalized]);
|
|
3393
3655
|
}
|
|
3394
|
-
const
|
|
3656
|
+
const inScopeCited = citedFindings.filter((finding) => {
|
|
3395
3657
|
const citedKey = normalizeFileKeyForCitation(finding.location.file, p.cwd);
|
|
3396
|
-
if (citedPaths.has(citedKey)) return
|
|
3658
|
+
if (citedPaths.has(citedKey)) return true;
|
|
3397
3659
|
const basename7 = path11.basename(finding.location.file).toLowerCase();
|
|
3398
3660
|
const bucket = changedBasenameIndex.get(basename7);
|
|
3399
|
-
return
|
|
3661
|
+
return !!(bucket && bucket.length === 1);
|
|
3400
3662
|
});
|
|
3401
|
-
const
|
|
3663
|
+
const droppedCitations = citedFindings.filter(
|
|
3664
|
+
(finding) => !inScopeCited.includes(finding)
|
|
3665
|
+
);
|
|
3666
|
+
const actionableFindings = inScopeCited.filter(
|
|
3667
|
+
(finding) => finding.verification?.status === "verified"
|
|
3668
|
+
);
|
|
3669
|
+
const unverifiedInScope = inScopeCited.filter(
|
|
3670
|
+
(finding) => finding.verification?.status === "unverified"
|
|
3671
|
+
);
|
|
3672
|
+
const failedVerificationCount = inScopeCited.filter(
|
|
3673
|
+
(finding) => finding.verification?.status === "failed"
|
|
3674
|
+
).length;
|
|
3675
|
+
const effectiveHasFindings = reviewHasFindings && actionableFindings.length > 0;
|
|
3676
|
+
const verifiedCount = actionableFindings.length;
|
|
3677
|
+
const unverifiedCount = unverifiedInScope.length;
|
|
3402
3678
|
const hallucinationNote = droppedCitations.length > 0 ? `
|
|
3403
3679
|
|
|
3404
3680
|
\u26A0\uFE0F Citation validation: ${droppedCitations.length} finding(s) cite files outside the changed-file set and should be verified before acting.` : "";
|
|
@@ -3441,7 +3717,7 @@ ${reviewBody}`;
|
|
|
3441
3717
|
reviewLength: reviewText.length,
|
|
3442
3718
|
messageId: reviewMailMessageId
|
|
3443
3719
|
});
|
|
3444
|
-
if (!
|
|
3720
|
+
if (!reviewHasFindings) {
|
|
3445
3721
|
try {
|
|
3446
3722
|
await mailbox.ack({
|
|
3447
3723
|
messageId: reviewMailMessageId,
|
|
@@ -3485,11 +3761,8 @@ ${reviewBody}`;
|
|
|
3485
3761
|
phase: "agent"
|
|
3486
3762
|
});
|
|
3487
3763
|
}
|
|
3488
|
-
const findingCount =
|
|
3489
|
-
|
|
3490
|
-
parsedReview.findings.length - droppedCitations.length
|
|
3491
|
-
);
|
|
3492
|
-
const message = effectiveHasFindings ? `\u{1F982} Chimera report ready \u2014 ${findingCount || "unparsed"} potential finding(s) across ${p.files.length} file(s). No follow-up started; open the mailbox and explicitly ask the leader to act if wanted.` : `\u{1F982} Chimera report ready \u2014 ${p.files.length} file(s) checked, no actionable findings. No follow-up started.`;
|
|
3764
|
+
const findingCount = actionableFindings.length;
|
|
3765
|
+
const message = effectiveHasFindings ? `\u{1F982} Chimera report ready \u2014 ${findingCount || "unparsed"} potential finding(s) (${verifiedCount} verified against disk, ${unverifiedCount} unverified, ${failedVerificationCount} failed) across ${p.files.length} file(s). No follow-up started; open the mailbox and explicitly ask the leader to act if wanted.` : `\u{1F982} Chimera report ready \u2014 ${p.files.length} file(s) checked, no actionable findings. No follow-up started.`;
|
|
3493
3766
|
events.emitCustom("chimera.report_available", {
|
|
3494
3767
|
reportId,
|
|
3495
3768
|
sessionId: reviewSessionId,
|
|
@@ -3711,7 +3984,7 @@ function parseSuggestionsFromOutput(finalText, todos) {
|
|
|
3711
3984
|
setAutoSuggestions([]);
|
|
3712
3985
|
return null;
|
|
3713
3986
|
}
|
|
3714
|
-
const { texts, autoTexts } = parseNextSteps(finalText
|
|
3987
|
+
const { texts, autoTexts } = parseNextSteps(finalText);
|
|
3715
3988
|
if (autoTexts.length > 0) {
|
|
3716
3989
|
setAutoSuggestions(autoTexts);
|
|
3717
3990
|
}
|
|
@@ -4752,6 +5025,373 @@ function createTuiNextStepCallbacks(input) {
|
|
|
4752
5025
|
};
|
|
4753
5026
|
}
|
|
4754
5027
|
|
|
5028
|
+
// src/tui-resource-menus.ts
|
|
5029
|
+
import { readdir } from "node:fs/promises";
|
|
5030
|
+
import { readJsonObjectFile } from "@wrongstack/core/utils";
|
|
5031
|
+
function createTuiResourceMenuGetter(ctx) {
|
|
5032
|
+
return async (id) => {
|
|
5033
|
+
switch (id) {
|
|
5034
|
+
case "fallback":
|
|
5035
|
+
return fallbackMenu(ctx.configStore);
|
|
5036
|
+
case "profile":
|
|
5037
|
+
return profileMenu(ctx.configStore, ctx.paths);
|
|
5038
|
+
case "provider-status":
|
|
5039
|
+
return providerStatusMenu(ctx.statusTracker);
|
|
5040
|
+
case "memory":
|
|
5041
|
+
return memoryMenu(ctx.memoryStore);
|
|
5042
|
+
case "worktree":
|
|
5043
|
+
return worktreeMenu(ctx.projectRoot);
|
|
5044
|
+
case "git":
|
|
5045
|
+
return gitMenu(ctx.projectRoot);
|
|
5046
|
+
}
|
|
5047
|
+
};
|
|
5048
|
+
}
|
|
5049
|
+
function fallbackMenu(store) {
|
|
5050
|
+
const config = store.get();
|
|
5051
|
+
const chain = config.fallbackModels ?? [];
|
|
5052
|
+
const profiles = config.fallbackProfiles ?? {};
|
|
5053
|
+
const favorites = config.favoriteModels ?? [];
|
|
5054
|
+
const auto = config.fallbackAuto !== false;
|
|
5055
|
+
const items = [
|
|
5056
|
+
{
|
|
5057
|
+
id: "leader",
|
|
5058
|
+
label: "Leader",
|
|
5059
|
+
status: "good",
|
|
5060
|
+
summary: `${config.provider}/${config.model}`,
|
|
5061
|
+
details: [
|
|
5062
|
+
{ label: "provider", value: config.provider },
|
|
5063
|
+
{ label: "model", value: config.model },
|
|
5064
|
+
{ label: "bridge", value: config.fallbackBridge || "disabled" }
|
|
5065
|
+
],
|
|
5066
|
+
body: "The active session model. The bridge, when configured, is tried before the ordered fallback chain."
|
|
5067
|
+
},
|
|
5068
|
+
{
|
|
5069
|
+
id: "auto",
|
|
5070
|
+
label: "Smart fallback",
|
|
5071
|
+
status: auto ? "good" : "muted",
|
|
5072
|
+
summary: auto ? "enabled" : "disabled",
|
|
5073
|
+
details: [
|
|
5074
|
+
{ label: "mode", value: auto ? "auto-derived when chain is empty" : "explicit chain only" },
|
|
5075
|
+
{ label: "favorites only", value: config.favoriteModelsOnly ? "yes" : "no" },
|
|
5076
|
+
{ label: "favorites", value: String(favorites.length) }
|
|
5077
|
+
],
|
|
5078
|
+
actions: [
|
|
5079
|
+
{
|
|
5080
|
+
key: "t",
|
|
5081
|
+
label: auto ? "turn off" : "turn on",
|
|
5082
|
+
command: `/fallback auto ${auto ? "off" : "on"}`
|
|
5083
|
+
}
|
|
5084
|
+
]
|
|
5085
|
+
},
|
|
5086
|
+
...chain.map((ref, index) => ({
|
|
5087
|
+
id: `chain:${index}`,
|
|
5088
|
+
label: `${index + 1}. ${ref}`,
|
|
5089
|
+
status: "warn",
|
|
5090
|
+
summary: "explicit fallback",
|
|
5091
|
+
details: [
|
|
5092
|
+
{ label: "position", value: String(index + 1) },
|
|
5093
|
+
{ label: "model ref", value: ref },
|
|
5094
|
+
{ label: "after", value: index === 0 ? "leader/bridge" : chain[index - 1] ?? "leader" }
|
|
5095
|
+
],
|
|
5096
|
+
actions: [
|
|
5097
|
+
{ key: "x", label: "remove", command: `/fallback remove ${index + 1}`, confirm: true }
|
|
5098
|
+
]
|
|
5099
|
+
})),
|
|
5100
|
+
...Object.entries(profiles).sort(([a], [b]) => a.localeCompare(b)).map(([name, refs]) => ({
|
|
5101
|
+
id: `profile:${name}`,
|
|
5102
|
+
label: `Profile: ${name}`,
|
|
5103
|
+
status: refs.length > 0 ? "good" : "muted",
|
|
5104
|
+
summary: `${refs.length} model${refs.length === 1 ? "" : "s"}`,
|
|
5105
|
+
details: [
|
|
5106
|
+
{ label: "name", value: name },
|
|
5107
|
+
{ label: "length", value: String(refs.length) }
|
|
5108
|
+
],
|
|
5109
|
+
body: refs.join(" \u2192 ") || "(empty profile)",
|
|
5110
|
+
actions: [
|
|
5111
|
+
{ key: "u", label: "use", command: `/fallback profile use ${name}`, confirm: true },
|
|
5112
|
+
{ key: "x", label: "delete", command: `/fallback profile remove ${name}`, confirm: true }
|
|
5113
|
+
]
|
|
5114
|
+
})),
|
|
5115
|
+
...favorites.map((ref, index) => ({
|
|
5116
|
+
id: `favorite:${index}`,
|
|
5117
|
+
label: `\u2605 ${ref}`,
|
|
5118
|
+
status: "good",
|
|
5119
|
+
summary: "favorite model",
|
|
5120
|
+
details: [{ label: "model ref", value: ref }],
|
|
5121
|
+
actions: [
|
|
5122
|
+
{
|
|
5123
|
+
key: "x",
|
|
5124
|
+
label: "unfavorite",
|
|
5125
|
+
command: `/fallback fav remove ${index + 1}`,
|
|
5126
|
+
confirm: true
|
|
5127
|
+
}
|
|
5128
|
+
]
|
|
5129
|
+
}))
|
|
5130
|
+
];
|
|
5131
|
+
return {
|
|
5132
|
+
id: "fallback",
|
|
5133
|
+
title: "Fallback routing",
|
|
5134
|
+
subtitle: `${chain.length} explicit \xB7 ${Object.keys(profiles).length} profiles \xB7 ${favorites.length} favorites`,
|
|
5135
|
+
items
|
|
5136
|
+
};
|
|
5137
|
+
}
|
|
5138
|
+
async function profileMenu(store, paths) {
|
|
5139
|
+
const active = store.get().activeProfile ?? paths.profileName ?? "default";
|
|
5140
|
+
let names = [];
|
|
5141
|
+
try {
|
|
5142
|
+
names = (await readdir(paths.profilesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
5143
|
+
} catch {
|
|
5144
|
+
}
|
|
5145
|
+
const items = await Promise.all(
|
|
5146
|
+
names.map(async (name) => {
|
|
5147
|
+
const path14 = paths.profileConfig(name);
|
|
5148
|
+
const data = await readJsonObjectFile(path14);
|
|
5149
|
+
const provider = typeof data["provider"] === "string" ? data["provider"] : "unset";
|
|
5150
|
+
const model = typeof data["model"] === "string" ? data["model"] : "unset";
|
|
5151
|
+
const fallbackCount = Array.isArray(data["fallbackModels"]) ? data["fallbackModels"].length : 0;
|
|
5152
|
+
const profileCount = isRecord(data["fallbackProfiles"]) ? Object.keys(data["fallbackProfiles"]).length : 0;
|
|
5153
|
+
const isActive = name === active;
|
|
5154
|
+
return {
|
|
5155
|
+
id: name,
|
|
5156
|
+
label: name,
|
|
5157
|
+
status: isActive ? "good" : "muted",
|
|
5158
|
+
summary: isActive ? "active" : `${provider}/${model}`,
|
|
5159
|
+
details: [
|
|
5160
|
+
{ label: "active", value: isActive ? "yes" : "no" },
|
|
5161
|
+
{ label: "provider", value: provider },
|
|
5162
|
+
{ label: "model", value: model },
|
|
5163
|
+
{
|
|
5164
|
+
label: "theme",
|
|
5165
|
+
value: typeof data["themePreset"] === "string" ? data["themePreset"] : "default"
|
|
5166
|
+
},
|
|
5167
|
+
{ label: "fallbacks", value: String(fallbackCount) },
|
|
5168
|
+
{ label: "fallback profiles", value: String(profileCount) },
|
|
5169
|
+
{ label: "config", value: path14 }
|
|
5170
|
+
],
|
|
5171
|
+
body: isActive ? "This profile owns the current session configuration." : "Switching profiles closes this session so every service restarts with the selected configuration.",
|
|
5172
|
+
actions: isActive ? [] : [{ key: "s", label: "switch", command: `/profile switch ${name}`, confirm: true }]
|
|
5173
|
+
};
|
|
5174
|
+
})
|
|
5175
|
+
);
|
|
5176
|
+
return {
|
|
5177
|
+
id: "profile",
|
|
5178
|
+
title: "Profiles",
|
|
5179
|
+
subtitle: `active: ${active}`,
|
|
5180
|
+
emptyText: "No profiles found.",
|
|
5181
|
+
items
|
|
5182
|
+
};
|
|
5183
|
+
}
|
|
5184
|
+
function providerStatusMenu(tracker) {
|
|
5185
|
+
const snapshot = tracker?.getSnapshot();
|
|
5186
|
+
const items = snapshot?.statuses.map((status) => ({
|
|
5187
|
+
id: `${status.providerId}/${status.model}`,
|
|
5188
|
+
label: `${status.providerId}/${status.model}`,
|
|
5189
|
+
status: status.state === "healthy" ? "good" : status.state === "degraded" ? "warn" : "bad",
|
|
5190
|
+
summary: status.state,
|
|
5191
|
+
details: [
|
|
5192
|
+
{ label: "state", value: status.state },
|
|
5193
|
+
{ label: "successes", value: String(status.totalSuccesses) },
|
|
5194
|
+
{ label: "failures", value: String(status.totalFailures) },
|
|
5195
|
+
{ label: "rate limits", value: String(status.rateLimitHits) },
|
|
5196
|
+
{ label: "consecutive failures", value: String(status.consecutiveFailures) },
|
|
5197
|
+
{
|
|
5198
|
+
label: "cooldown",
|
|
5199
|
+
value: status.stateExpiresAt ? new Date(status.stateExpiresAt).toLocaleString() : "none"
|
|
5200
|
+
},
|
|
5201
|
+
{ label: "last error kind", value: status.lastErrorKind ?? "none" }
|
|
5202
|
+
],
|
|
5203
|
+
body: status.lastErrorMessage ?? "No recorded error.",
|
|
5204
|
+
actions: [
|
|
5205
|
+
...status.state !== "healthy" ? [
|
|
5206
|
+
{
|
|
5207
|
+
key: "r",
|
|
5208
|
+
label: "retry now",
|
|
5209
|
+
command: `/provider-status retry ${status.providerId} ${status.model}`
|
|
5210
|
+
}
|
|
5211
|
+
] : [],
|
|
5212
|
+
{
|
|
5213
|
+
key: "x",
|
|
5214
|
+
label: "clear history",
|
|
5215
|
+
command: `/provider-status clear ${status.providerId} ${status.model}`,
|
|
5216
|
+
confirm: true
|
|
5217
|
+
}
|
|
5218
|
+
]
|
|
5219
|
+
})) ?? [];
|
|
5220
|
+
return {
|
|
5221
|
+
id: "provider-status",
|
|
5222
|
+
title: "Provider health",
|
|
5223
|
+
subtitle: snapshot ? `${snapshot.healthy} healthy \xB7 ${snapshot.degraded} degraded \xB7 ${snapshot.blocked} blocked` : "tracker unavailable",
|
|
5224
|
+
emptyText: "No provider/model activity has been recorded.",
|
|
5225
|
+
items
|
|
5226
|
+
};
|
|
5227
|
+
}
|
|
5228
|
+
async function memoryMenu(store) {
|
|
5229
|
+
if (!store)
|
|
5230
|
+
return {
|
|
5231
|
+
id: "memory",
|
|
5232
|
+
title: "Memory",
|
|
5233
|
+
emptyText: "Memory is disabled in this host.",
|
|
5234
|
+
items: []
|
|
5235
|
+
};
|
|
5236
|
+
const [health, entries] = await Promise.all([store.health(), store.list(void 0, 100)]);
|
|
5237
|
+
return {
|
|
5238
|
+
id: "memory",
|
|
5239
|
+
title: "Memory",
|
|
5240
|
+
subtitle: `${health.status} \xB7 ${health.backend} \xB7 newest 100`,
|
|
5241
|
+
emptyText: "No memories found.",
|
|
5242
|
+
items: entries.map((entry, index) => ({
|
|
5243
|
+
id: `${entry.ts}:${index}`,
|
|
5244
|
+
label: truncate(entry.text.replace(/\s+/g, " "), 52),
|
|
5245
|
+
status: entry.priority === "critical" || entry.priority === "high" ? "warn" : "muted",
|
|
5246
|
+
summary: `${entry.scope} \xB7 ${entry.type ?? "fact"}`,
|
|
5247
|
+
details: [
|
|
5248
|
+
{ label: "scope", value: entry.scope },
|
|
5249
|
+
{ label: "type", value: entry.type ?? "fact" },
|
|
5250
|
+
{ label: "priority", value: entry.priority ?? "medium" },
|
|
5251
|
+
{
|
|
5252
|
+
label: "confidence",
|
|
5253
|
+
value: entry.confidence === void 0 ? "unspecified" : entry.confidence.toFixed(2)
|
|
5254
|
+
},
|
|
5255
|
+
{ label: "created", value: entry.ts },
|
|
5256
|
+
{ label: "last accessed", value: entry.lastAccessed ?? "never" },
|
|
5257
|
+
{ label: "tags", value: entry.tags?.join(", ") || "none" },
|
|
5258
|
+
{ label: "source", value: entry.source ?? "unknown" }
|
|
5259
|
+
],
|
|
5260
|
+
body: entry.text,
|
|
5261
|
+
actions: entry.scope === "project-memory" ? [
|
|
5262
|
+
{
|
|
5263
|
+
key: "x",
|
|
5264
|
+
label: "forget matching text",
|
|
5265
|
+
command: `/memory forget ${entry.text.replace(/\s+/g, " ").trim()} --exact`,
|
|
5266
|
+
confirm: true
|
|
5267
|
+
}
|
|
5268
|
+
] : []
|
|
5269
|
+
}))
|
|
5270
|
+
};
|
|
5271
|
+
}
|
|
5272
|
+
async function gitMenu(projectRoot) {
|
|
5273
|
+
const [branch, head, status] = await Promise.all([
|
|
5274
|
+
runGit(["branch", "--show-current"], projectRoot),
|
|
5275
|
+
runGit(["rev-parse", "--short", "HEAD"], projectRoot),
|
|
5276
|
+
runGit(["status", "--porcelain=v1"], projectRoot)
|
|
5277
|
+
]);
|
|
5278
|
+
if (status.code !== 0)
|
|
5279
|
+
return { id: "git", title: "Git", emptyText: "Not a git repository.", items: [] };
|
|
5280
|
+
const lines = status.stdout.split(/\r?\n/).filter(Boolean);
|
|
5281
|
+
const items = [
|
|
5282
|
+
{
|
|
5283
|
+
id: "repo",
|
|
5284
|
+
label: branch.stdout.trim() || "(detached)",
|
|
5285
|
+
status: lines.length === 0 ? "good" : "warn",
|
|
5286
|
+
summary: `${head.stdout.trim() || "no HEAD"} \xB7 ${lines.length === 0 ? "clean" : `${lines.length} changes`}`,
|
|
5287
|
+
details: [
|
|
5288
|
+
{ label: "branch", value: branch.stdout.trim() || "(detached)" },
|
|
5289
|
+
{ label: "HEAD", value: head.stdout.trim() || "(unborn)" },
|
|
5290
|
+
{
|
|
5291
|
+
label: "working tree",
|
|
5292
|
+
value: lines.length === 0 ? "clean" : `${lines.length} changed paths`
|
|
5293
|
+
}
|
|
5294
|
+
],
|
|
5295
|
+
actions: [{ key: "c", label: "commit workflow", command: "/commit", confirm: true }]
|
|
5296
|
+
},
|
|
5297
|
+
...lines.map((line, index) => {
|
|
5298
|
+
const code = line.slice(0, 2);
|
|
5299
|
+
const path14 = line.slice(3);
|
|
5300
|
+
const staged = code[0] !== " " && code[0] !== "?";
|
|
5301
|
+
const unstaged = code[1] !== " " || code === "??";
|
|
5302
|
+
return {
|
|
5303
|
+
id: `change:${index}:${path14}`,
|
|
5304
|
+
label: path14,
|
|
5305
|
+
status: staged ? "good" : "warn",
|
|
5306
|
+
summary: `${code} \xB7 ${staged ? "staged" : "not staged"}${unstaged ? " \xB7 working tree" : ""}`,
|
|
5307
|
+
details: [
|
|
5308
|
+
{ label: "status", value: code },
|
|
5309
|
+
{ label: "staged", value: staged ? "yes" : "no" },
|
|
5310
|
+
{ label: "unstaged", value: unstaged ? "yes" : "no" },
|
|
5311
|
+
{ label: "path", value: path14 }
|
|
5312
|
+
],
|
|
5313
|
+
actions: [
|
|
5314
|
+
{ key: "d", label: "diff summary", command: staged ? "/git diff --staged" : "/git diff" }
|
|
5315
|
+
]
|
|
5316
|
+
};
|
|
5317
|
+
})
|
|
5318
|
+
];
|
|
5319
|
+
return { id: "git", title: "Git", subtitle: projectRoot, items };
|
|
5320
|
+
}
|
|
5321
|
+
async function worktreeMenu(projectRoot) {
|
|
5322
|
+
const result = await runGit(["worktree", "list", "--porcelain"], projectRoot);
|
|
5323
|
+
if (result.code !== 0) {
|
|
5324
|
+
return {
|
|
5325
|
+
id: "worktree",
|
|
5326
|
+
title: "Worktrees",
|
|
5327
|
+
emptyText: result.stderr || "Not a git repository.",
|
|
5328
|
+
items: []
|
|
5329
|
+
};
|
|
5330
|
+
}
|
|
5331
|
+
const records = result.stdout.trim().split(/\r?\n\r?\n/).map((block) => block.split(/\r?\n/).filter(Boolean)).filter((lines) => lines.length > 0);
|
|
5332
|
+
const rows = await Promise.all(
|
|
5333
|
+
records.map(async (lines, index) => {
|
|
5334
|
+
const fields = Object.fromEntries(
|
|
5335
|
+
lines.map((line) => {
|
|
5336
|
+
const split = line.indexOf(" ");
|
|
5337
|
+
return split < 0 ? [line, "yes"] : [line.slice(0, split), line.slice(split + 1)];
|
|
5338
|
+
})
|
|
5339
|
+
);
|
|
5340
|
+
const path14 = fields["worktree"] ?? "(unknown path)";
|
|
5341
|
+
const branchRef = fields["branch"];
|
|
5342
|
+
const branch = branchRef?.replace(/^refs\/heads\//, "") ?? (fields["detached"] ? "(detached)" : "(bare)");
|
|
5343
|
+
const status = path14 === "(unknown path)" || fields["prunable"] ? null : await runGit(["status", "--porcelain=v1"], path14);
|
|
5344
|
+
const changes = status?.code === 0 ? status.stdout.split(/\r?\n/).filter(Boolean) : [];
|
|
5345
|
+
const dirty = changes.length > 0;
|
|
5346
|
+
const isMain = index === 0;
|
|
5347
|
+
return {
|
|
5348
|
+
id: path14,
|
|
5349
|
+
label: branch,
|
|
5350
|
+
status: fields["prunable"] ? "bad" : dirty ? "warn" : "good",
|
|
5351
|
+
summary: `${isMain ? "main" : "linked"} \xB7 ${dirty ? `${changes.length} changes` : "clean"}`,
|
|
5352
|
+
details: [
|
|
5353
|
+
{ label: "path", value: path14 },
|
|
5354
|
+
{ label: "branch", value: branch },
|
|
5355
|
+
{ label: "HEAD", value: fields["HEAD"]?.slice(0, 12) ?? "unknown" },
|
|
5356
|
+
{ label: "working tree", value: dirty ? `${changes.length} changed paths` : "clean" },
|
|
5357
|
+
{ label: "locked", value: fields["locked"] ?? "no" },
|
|
5358
|
+
{ label: "prunable", value: fields["prunable"] ?? "no" }
|
|
5359
|
+
],
|
|
5360
|
+
body: changes.slice(0, 30).join("\n") || (isMain ? "Primary checkout." : "Linked checkout."),
|
|
5361
|
+
actions: isMain ? [
|
|
5362
|
+
{ key: "p", label: "prune stale metadata", command: "/worktree prune" },
|
|
5363
|
+
{
|
|
5364
|
+
key: "x",
|
|
5365
|
+
label: "clean managed worktrees",
|
|
5366
|
+
command: "/worktree clean --yes",
|
|
5367
|
+
confirm: true
|
|
5368
|
+
}
|
|
5369
|
+
] : branchRef ? [
|
|
5370
|
+
{
|
|
5371
|
+
key: "m",
|
|
5372
|
+
label: "squash merge",
|
|
5373
|
+
command: `/worktree merge ${branch} --yes`,
|
|
5374
|
+
confirm: true
|
|
5375
|
+
}
|
|
5376
|
+
] : []
|
|
5377
|
+
};
|
|
5378
|
+
})
|
|
5379
|
+
);
|
|
5380
|
+
return {
|
|
5381
|
+
id: "worktree",
|
|
5382
|
+
title: "Worktrees",
|
|
5383
|
+
subtitle: `${rows.length} checkout${rows.length === 1 ? "" : "s"}`,
|
|
5384
|
+
emptyText: "No worktrees found.",
|
|
5385
|
+
items: rows
|
|
5386
|
+
};
|
|
5387
|
+
}
|
|
5388
|
+
function isRecord(value) {
|
|
5389
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
5390
|
+
}
|
|
5391
|
+
function truncate(value, max) {
|
|
5392
|
+
return value.length <= max ? value : `${value.slice(0, max - 1)}\u2026`;
|
|
5393
|
+
}
|
|
5394
|
+
|
|
4755
5395
|
// src/execution.ts
|
|
4756
5396
|
async function execute(deps) {
|
|
4757
5397
|
const {
|
|
@@ -4909,6 +5549,9 @@ async function execute(deps) {
|
|
|
4909
5549
|
session: { provider: config.provider, model: config.model }
|
|
4910
5550
|
}),
|
|
4911
5551
|
getPendingWork: () => chimeraWork.pending(),
|
|
5552
|
+
persistEvidence: async (reportId, status, checks) => {
|
|
5553
|
+
await updateReviewReportEvidence(reportId, wpaths.projectDir, status, checks);
|
|
5554
|
+
},
|
|
4912
5555
|
trackWork: (work) => {
|
|
4913
5556
|
chimeraWork.track(work);
|
|
4914
5557
|
}
|
|
@@ -5031,6 +5674,14 @@ async function execute(deps) {
|
|
|
5031
5674
|
agent,
|
|
5032
5675
|
events,
|
|
5033
5676
|
slashRegistry,
|
|
5677
|
+
skillLoader,
|
|
5678
|
+
getResourceMenu: createTuiResourceMenuGetter({
|
|
5679
|
+
configStore,
|
|
5680
|
+
paths: wpaths,
|
|
5681
|
+
memoryStore,
|
|
5682
|
+
statusTracker,
|
|
5683
|
+
projectRoot
|
|
5684
|
+
}),
|
|
5034
5685
|
secretInputController,
|
|
5035
5686
|
attachments,
|
|
5036
5687
|
tokenCounter,
|
|
@@ -5347,4 +5998,4 @@ export {
|
|
|
5347
5998
|
execute,
|
|
5348
5999
|
resolveReviewerFallbackModels
|
|
5349
6000
|
};
|
|
5350
|
-
//# sourceMappingURL=execution-
|
|
6001
|
+
//# sourceMappingURL=execution-4IG5XKOL.js.map
|