@wrongstack/cli 0.305.1 → 0.306.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.
@@ -37,23 +37,24 @@ import {
37
37
  writeWebuiSessionChildReady
38
38
  } from "./chunk-3JPTNADJ.js";
39
39
  import {
40
- resolveActiveApiKey
41
- } from "./chunk-SZ42FYPT.js";
42
- import {
43
- activeProfileConfigPath
44
- } from "./chunk-YMXXOOFN.js";
40
+ CLI_VERSION
41
+ } from "./chunk-XJXDOF63.js";
45
42
  import {
46
43
  terminalLink,
47
44
  terminalText
48
45
  } from "./chunk-KE7E7DPX.js";
49
46
  import {
50
- CLI_VERSION
51
- } from "./chunk-XJXDOF63.js";
47
+ resolveActiveApiKey
48
+ } from "./chunk-SZ42FYPT.js";
49
+ import {
50
+ activeProfileConfigPath
51
+ } from "./chunk-YMXXOOFN.js";
52
52
  import "./chunk-7OCVIDC7.js";
53
53
 
54
54
  // src/execution.ts
55
55
  import * as path13 from "node:path";
56
56
  import { effectiveFallbackChain as effectiveFallbackChain2, setQueuedMessagesSnapshot as setQueuedMessagesSnapshot2 } from "@wrongstack/core/agent";
57
+ import { updateReviewReportEvidence } from "@wrongstack/core/plugin";
57
58
  import { attachTodosCheckpoint as attachTodosCheckpoint2 } from "@wrongstack/core/storage";
58
59
  import { normalizeTokenSavingTier as normalizeTokenSavingTier2 } from "@wrongstack/core/types";
59
60
  import { mergeCustomModelDefs } from "@wrongstack/core/utils";
@@ -2746,6 +2747,128 @@ import * as fsp from "node:fs/promises";
2746
2747
  import * as path10 from "node:path";
2747
2748
  import { emitReviewIfChanged } from "@wrongstack/core/plugin";
2748
2749
 
2750
+ // src/chimera-cascade-evidence.ts
2751
+ import { spawn as spawn2 } from "node:child_process";
2752
+ var JSON_BLOCK_RE = /```json\s*([\s\S]*?)```/gi;
2753
+ function extractCascadeEvidence(text) {
2754
+ if (!text) return null;
2755
+ for (const match of text.matchAll(JSON_BLOCK_RE)) {
2756
+ const body = match[1]?.trim();
2757
+ if (!body) continue;
2758
+ let parsed;
2759
+ try {
2760
+ parsed = JSON.parse(body);
2761
+ } catch {
2762
+ continue;
2763
+ }
2764
+ const evidence = parseEvidenceObject(parsed);
2765
+ if (evidence) return evidence;
2766
+ }
2767
+ return null;
2768
+ }
2769
+ function parseEvidenceObject(parsed) {
2770
+ if (typeof parsed !== "object" || parsed === null) return null;
2771
+ const root = parsed;
2772
+ const raw = root.verification_evidence;
2773
+ if (typeof raw !== "object" || raw === null) return null;
2774
+ const checks = raw;
2775
+ const out = {};
2776
+ let found = false;
2777
+ for (const key of ["typecheck", "lint", "tests"]) {
2778
+ const check = parseSingleCheck(checks[key]);
2779
+ if (check) {
2780
+ out[key] = check;
2781
+ found = true;
2782
+ }
2783
+ }
2784
+ return found ? out : null;
2785
+ }
2786
+ function parseSingleCheck(raw) {
2787
+ if (typeof raw !== "object" || raw === null) return void 0;
2788
+ const check = raw;
2789
+ const command = check.command;
2790
+ const exitCode = check.exitCode;
2791
+ if (typeof command !== "string" || command.trim().length === 0) return void 0;
2792
+ if (typeof exitCode !== "number" || !Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255) {
2793
+ return void 0;
2794
+ }
2795
+ const output = typeof check.output === "string" ? truncateOutput(check.output) : void 0;
2796
+ return { command: command.trim(), exitCode, output };
2797
+ }
2798
+ var MAX_EVIDENCE_OUTPUT_CHARS = 2e3;
2799
+ function truncateOutput(output) {
2800
+ return output.length > MAX_EVIDENCE_OUTPUT_CHARS ? `${output.slice(0, MAX_EVIDENCE_OUTPUT_CHARS)}
2801
+ \u2026 (truncated)` : output;
2802
+ }
2803
+ var SAFE_EXECUTABLES = /* @__PURE__ */ new Set([
2804
+ "pnpm",
2805
+ "npm",
2806
+ "yarn",
2807
+ "npx",
2808
+ "bun",
2809
+ "node",
2810
+ "tsc",
2811
+ "biome",
2812
+ "eslint",
2813
+ "vitest",
2814
+ "jest"
2815
+ ]);
2816
+ var CASCADE_EVIDENCE_COMMAND_TIMEOUT_MS = 12e4;
2817
+ var CASCADE_EVIDENCE_UNSAFE_EXIT = 126;
2818
+ var CASCADE_EVIDENCE_RUN_ERROR_EXIT = 127;
2819
+ function isSafeCascadeCommand(command) {
2820
+ if (!command || command.length > 500) return false;
2821
+ const tokens = command.trim().split(/\s+/);
2822
+ if (tokens.length === 0) return false;
2823
+ if (!SAFE_EXECUTABLES.has(tokens[0])) return false;
2824
+ return tokens.every((token) => /^[A-Za-z0-9@/._:=~+*?\[\]-]+$/.test(token));
2825
+ }
2826
+ var runCascadeVerificationCommand = (command, cwd, timeoutMs) => {
2827
+ if (!isSafeCascadeCommand(command)) {
2828
+ return Promise.resolve({ exitCode: CASCADE_EVIDENCE_UNSAFE_EXIT });
2829
+ }
2830
+ const tokens = command.trim().split(/\s+/);
2831
+ const executable = tokens[0];
2832
+ const args = tokens.slice(1);
2833
+ return new Promise((resolve3) => {
2834
+ let child;
2835
+ try {
2836
+ child = spawn2(executable, args, {
2837
+ cwd,
2838
+ stdio: "ignore",
2839
+ windowsHide: true,
2840
+ signal: AbortSignal.timeout(timeoutMs)
2841
+ });
2842
+ } catch {
2843
+ resolve3({ exitCode: CASCADE_EVIDENCE_RUN_ERROR_EXIT });
2844
+ return;
2845
+ }
2846
+ child.on("error", () => resolve3({ exitCode: CASCADE_EVIDENCE_RUN_ERROR_EXIT }));
2847
+ child.on("close", (code) => resolve3({ exitCode: code ?? CASCADE_EVIDENCE_RUN_ERROR_EXIT }));
2848
+ });
2849
+ };
2850
+ async function verifyCascadeEvidence(evidence, cwd, runCommand = runCascadeVerificationCommand, timeoutMs = CASCADE_EVIDENCE_COMMAND_TIMEOUT_MS) {
2851
+ if (!evidence) return { status: "missing", checks: [] };
2852
+ if (!evidence.typecheck) return { status: "failed", checks: [] };
2853
+ const checks = [];
2854
+ for (const name of ["typecheck", "lint", "tests"]) {
2855
+ const claimed = evidence[name];
2856
+ if (!claimed) continue;
2857
+ const actual = await runCommand(claimed.command, cwd, timeoutMs);
2858
+ const ok = claimed.exitCode === 0 && actual.exitCode === claimed.exitCode;
2859
+ checks.push({
2860
+ name,
2861
+ command: claimed.command,
2862
+ claimedExitCode: claimed.exitCode,
2863
+ actualExitCode: actual.exitCode,
2864
+ ok
2865
+ });
2866
+ }
2867
+ if (checks.length === 0) return { status: "missing", checks };
2868
+ const status = checks.every((c) => c.ok) ? "verified" : "failed";
2869
+ return { status, checks };
2870
+ }
2871
+
2749
2872
  // src/chimera-review-task.ts
2750
2873
  import { parseReviewSeverity } from "@wrongstack/core/plugin";
2751
2874
  function truncateAtCodePointBoundary(text, maxCodeUnits) {
@@ -2846,6 +2969,34 @@ function buildChimeraReviewTaskDescription(p) {
2846
2969
  lines.push("structured review report.");
2847
2970
  return lines.join("\n");
2848
2971
  }
2972
+ var CASCADE_EVIDENCE_INSTRUCTIONS = [
2973
+ `After fixing, run the project's REAL verification commands on the code you changed`,
2974
+ `(typecheck, lint, and the tests covering the fixed code) and return a machine-evidence`,
2975
+ `block as the LAST section of your response. The block MUST be a fenced JSON block`,
2976
+ `(exactly \`\`\`json ... \`\`\`) with this shape:`,
2977
+ ``,
2978
+ "```json",
2979
+ "{",
2980
+ ' "verification_evidence": {',
2981
+ ' "typecheck": { "command": "pnpm typecheck", "exitCode": 0 },',
2982
+ ' "lint": { "command": "pnpm lint", "exitCode": 0 },',
2983
+ ' "tests": { "command": "pnpm test --filter affected", "exitCode": 0 }',
2984
+ " }",
2985
+ "}",
2986
+ "```",
2987
+ ``,
2988
+ `Rules:`,
2989
+ `- \`command\` must be the EXACT command you ran (plain executable + args, no shell`,
2990
+ ` chaining like \`&&\`/\`;\`/\`|\`, no redirection, no shell expansion). These commands are`,
2991
+ ` re-run verbatim by the orchestrator to verify your work.`,
2992
+ `- \`exitCode\` is the real process exit code you observed: 0 = passed, non-zero = failed.`,
2993
+ ` Never invent or omit an exit code. If a command cannot be run, omit that key entirely`,
2994
+ ` and explain why in your report text \u2014 an omitted key is honest, a fabricated 0 is not.`,
2995
+ `- If your fix does not pass a command, report the true non-zero exit code; the`,
2996
+ ` orchestrator will treat the fix as unverified and keep the finding open.`,
2997
+ `- At least \`typecheck\` must be present; include \`lint\` and \`tests\` when the project`,
2998
+ ` has them.`
2999
+ ].join("\n");
2849
3000
  function buildChimeraCascadeTaskDescription(agentKind, p) {
2850
3001
  const fileList = p.bundle.files.map((f) => `- ${f.path}`).join("\n");
2851
3002
  const reportSlice = truncateAtCodePointBoundary(p.reviewText, 12e3);
@@ -2866,8 +3017,9 @@ function buildChimeraCascadeTaskDescription(agentKind, p) {
2866
3017
  `Investigate the security findings above. Read the flagged files, confirm or refute`,
2867
3018
  `each finding, and **apply fixes** for confirmed vulnerabilities using the edit tool.`,
2868
3019
  `Use severity (Critical/High/Medium), file:line citations, and remediation steps.`,
2869
- `After fixing, run the project's typecheck and linter to verify. If a finding is a`,
2870
- `false positive, say so and do not modify the file.`
3020
+ `If a finding is a false positive, say so and do not modify the file.`,
3021
+ ``,
3022
+ CASCADE_EVIDENCE_INSTRUCTIONS
2871
3023
  ].join("\n");
2872
3024
  }
2873
3025
  return [
@@ -2884,9 +3036,10 @@ function buildChimeraCascadeTaskDescription(agentKind, p) {
2884
3036
  ``,
2885
3037
  `Hunt for the bugs flagged above. Read the affected files, trace each finding to its`,
2886
3038
  `root cause, and **apply minimal fixes** for confirmed bugs using the edit tool.`,
2887
- `Use severity (Critical/High/Medium), file:line citations. After fixing, run the`,
2888
- `project's typecheck and linter to verify. If a finding is a false positive, say so`,
2889
- `and do not modify the file.`
3039
+ `Use severity (Critical/High/Medium), file:line citations. If a finding is a false`,
3040
+ `positive, say so and do not modify the file.`,
3041
+ ``,
3042
+ CASCADE_EVIDENCE_INSTRUCTIONS
2890
3043
  ].join("\n");
2891
3044
  }
2892
3045
 
@@ -3003,7 +3156,9 @@ function installChimeraCascadeHandler({
3003
3156
  session,
3004
3157
  getPendingWork,
3005
3158
  trackWork,
3006
- buildLadder
3159
+ buildLadder,
3160
+ verifyEvidence = verifyCascadeEvidence,
3161
+ persistEvidence
3007
3162
  }) {
3008
3163
  events.onPattern("chimera.cascade_needed", (_event, payload) => {
3009
3164
  const p = payload;
@@ -3016,6 +3171,7 @@ function installChimeraCascadeHandler({
3016
3171
  await previousWork;
3017
3172
  } catch {
3018
3173
  }
3174
+ const agentEvidence = [];
3019
3175
  for (const agentKind of p.agents) {
3020
3176
  try {
3021
3177
  const taskDesc = buildChimeraCascadeTaskDescription(agentKind, p);
@@ -3059,6 +3215,10 @@ function installChimeraCascadeHandler({
3059
3215
  const result = outcome.result;
3060
3216
  if (result?.status === "success") {
3061
3217
  const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
3218
+ const claimed = extractCascadeEvidence(resultText);
3219
+ if (claimed) {
3220
+ agentEvidence.push({ agentKind, evidence: claimed });
3221
+ }
3062
3222
  await session.append({
3063
3223
  type: "llm_response",
3064
3224
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3089,7 +3249,21 @@ function installChimeraCascadeHandler({
3089
3249
  });
3090
3250
  }
3091
3251
  }
3092
- await maybeReReviewCascade({ events, session, bundle: p.bundle });
3252
+ const accumulatedEvidence = {};
3253
+ for (const { evidence } of agentEvidence) {
3254
+ for (const name of ["typecheck", "lint", "tests"]) {
3255
+ if (evidence[name]) accumulatedEvidence[name] = evidence[name];
3256
+ }
3257
+ }
3258
+ await maybeReReviewCascade({
3259
+ events,
3260
+ session,
3261
+ bundle: p.bundle,
3262
+ reportId: p.reportId,
3263
+ claimedEvidence: accumulatedEvidence,
3264
+ verifyEvidence,
3265
+ persistEvidence
3266
+ });
3093
3267
  })();
3094
3268
  trackWork(pendingWork);
3095
3269
  });
@@ -3097,16 +3271,71 @@ function installChimeraCascadeHandler({
3097
3271
  async function maybeReReviewCascade({
3098
3272
  events,
3099
3273
  session,
3100
- bundle
3274
+ bundle,
3275
+ reportId,
3276
+ claimedEvidence,
3277
+ verifyEvidence,
3278
+ persistEvidence
3101
3279
  }) {
3102
3280
  const maxDepth = bundle.maxCascadeDepth ?? 0;
3103
3281
  const currentDepth = bundle.cascadeDepth ?? 0;
3282
+ const hasClaimedEvidence = Object.keys(claimedEvidence).length > 0;
3283
+ const verification = hasClaimedEvidence ? await verifyEvidence(
3284
+ claimedEvidence,
3285
+ bundle.cwd,
3286
+ void 0,
3287
+ CASCADE_EVIDENCE_COMMAND_TIMEOUT_MS
3288
+ ) : { status: "missing", checks: [] };
3289
+ const evidenceBundle = {
3290
+ ...bundle,
3291
+ evidenceStatus: verification.status,
3292
+ evidenceChecks: verification.checks
3293
+ };
3294
+ if (reportId && persistEvidence) {
3295
+ try {
3296
+ await persistEvidence(reportId, verification.status, verification.checks);
3297
+ } catch (error) {
3298
+ await session.append({
3299
+ type: "error",
3300
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3301
+ message: `Chimera cascade evidence persistence failed: ${errorText2(error)}`,
3302
+ phase: "agent"
3303
+ });
3304
+ }
3305
+ }
3306
+ await session.append({
3307
+ type: "llm_response",
3308
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3309
+ content: [
3310
+ {
3311
+ type: "text",
3312
+ text: renderEvidenceSummary(verification.status, verification.checks)
3313
+ }
3314
+ ],
3315
+ stopReason: "end_turn",
3316
+ usage: { input: 0, output: 0 }
3317
+ });
3318
+ if (verification.status !== "verified") {
3319
+ await session.append({
3320
+ type: "llm_response",
3321
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3322
+ content: [
3323
+ {
3324
+ type: "text",
3325
+ text: `\u{1F982} Chimera cascade evidence ${verification.status} \u2014 fix not confirmed, skipping re-review. Findings remain open.`
3326
+ }
3327
+ ],
3328
+ stopReason: "end_turn",
3329
+ usage: { input: 0, output: 0 }
3330
+ });
3331
+ return;
3332
+ }
3104
3333
  if (maxDepth > 0 && currentDepth < maxDepth) {
3105
3334
  try {
3106
3335
  const reReadFiles = [];
3107
- for (const f of bundle.files) {
3336
+ for (const f of evidenceBundle.files) {
3108
3337
  try {
3109
- const absPath = path10.join(bundle.cwd, f.path);
3338
+ const absPath = path10.join(evidenceBundle.cwd, f.path);
3110
3339
  const content = await fsp.readFile(absPath, "utf8");
3111
3340
  reReadFiles.push({ path: f.path, status: "modified", content });
3112
3341
  } catch {
@@ -3114,7 +3343,7 @@ async function maybeReReviewCascade({
3114
3343
  }
3115
3344
  if (reReadFiles.length > 0) {
3116
3345
  const reReviewBundle = {
3117
- ...bundle,
3346
+ ...evidenceBundle,
3118
3347
  files: reReadFiles,
3119
3348
  cascadeDepth: currentDepth + 1
3120
3349
  };
@@ -3172,18 +3401,28 @@ async function maybeReReviewCascade({
3172
3401
  });
3173
3402
  }
3174
3403
  }
3404
+ function renderEvidenceSummary(status, checks) {
3405
+ if (status === "missing") return "no machine evidence supplied";
3406
+ const summary = checks.map((check) => `${check.name}:${check.ok ? "pass" : "fail"}(${check.actualExitCode})`).join(", ");
3407
+ return `${status}${summary ? ` \u2014 ${summary}` : ""}`;
3408
+ }
3409
+ function errorText2(error) {
3410
+ return error instanceof Error ? error.message : String(error);
3411
+ }
3175
3412
 
3176
3413
  // src/execution-chimera-review.ts
3177
3414
  import { randomUUID as randomUUID3 } from "node:crypto";
3178
3415
  import path11 from "node:path";
3179
- import { effectiveFallbackChain } from "@wrongstack/core/agent";
3416
+ import { effectiveFallbackChain, fallbackProfileChain as fallbackProfileChain2 } from "@wrongstack/core/agent";
3180
3417
  import {
3181
3418
  CHIMERA_REVIEW_PROMPT,
3419
+ classifyChimeraReviewSource,
3182
3420
  integrateFindings,
3183
3421
  maybeCompactReviewStores,
3184
3422
  parseChimeraReviewReport,
3185
3423
  persistReviewReport,
3186
- recordCompletedReview
3424
+ recordCompletedReview,
3425
+ verifyFindingsAgainstDisk
3187
3426
  } from "@wrongstack/core/plugin";
3188
3427
  function normalizeFileKeyForCitation(raw, cwd) {
3189
3428
  const forward = raw.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -3267,7 +3506,13 @@ function installChimeraReviewHandler({
3267
3506
  const rawModel = p.reviewFallbackModels ? p.config.model?.trim() || void 0 : tModel;
3268
3507
  const baseProvider = rawProvider || tProvider || config.provider;
3269
3508
  const baseModel = rawModel || tModel || config.model;
3270
- const baseFallbacks = p.reviewFallbackModels ? [...p.reviewFallbackModels] : resolveReviewerFallbackModels(void 0);
3509
+ const configFallbacks = [...p.config.fallbackModels ?? []];
3510
+ if (p.config.fallbackProfile) {
3511
+ configFallbacks.push(...fallbackProfileChain2(config, p.config.fallbackProfile));
3512
+ }
3513
+ const baseFallbacks = p.reviewFallbackModels ? [...p.reviewFallbackModels] : resolveReviewerFallbackModels(
3514
+ configFallbacks.length > 0 ? configFallbacks : void 0
3515
+ );
3271
3516
  const assigned = assignReviewerModels(
3272
3517
  baseProvider,
3273
3518
  baseModel,
@@ -3368,18 +3613,34 @@ function installChimeraReviewHandler({
3368
3613
  }
3369
3614
  const reviewText = typeof result.result === "string" ? result.result.trim() : JSON.stringify(result.result);
3370
3615
  const reportId = randomUUID3();
3616
+ const reviewSource = classifyChimeraReviewSource(p);
3617
+ const winningAttempt = outcome.wonAt ? ladder[outcome.wonAt - 1] : void 0;
3618
+ const parsedReport = parseChimeraReviewReport(reviewText, {
3619
+ sessionId: reviewSessionId,
3620
+ agentId: p.fileProvenance?.find((entry) => entry.agentId)?.agentId ?? "chimera-review",
3621
+ reviewerModel: winningAttempt ? attemptLabel(winningAttempt) : void 0,
3622
+ reviewType: reviewSource,
3623
+ reportId
3624
+ });
3625
+ const verifiedFindings = await verifyFindingsAgainstDisk(parsedReport.findings, {
3626
+ cwd: p.cwd
3627
+ });
3628
+ const verifiedParsedReport = {
3629
+ ...parsedReport,
3630
+ findings: verifiedFindings
3631
+ };
3371
3632
  await persistAndEmitCompletion({
3372
3633
  bundle: p,
3373
3634
  reviewText,
3374
3635
  status: "success",
3375
3636
  cwd: p.cwd,
3376
3637
  sessionId: reviewSessionId,
3377
- reportId
3638
+ reportId,
3639
+ parsedReport: verifiedParsedReport
3378
3640
  });
3379
3641
  if (reviewText) {
3380
3642
  const reviewHasFindings = !isChimeraAllClearReview(reviewText);
3381
- const parsedReview = parseChimeraReviewReport(reviewText);
3382
- const citedFindings = parsedReview.findings.filter((finding) => finding.location);
3643
+ const citedFindings = verifiedFindings.filter((finding) => finding.location);
3383
3644
  const citedPaths = new Set(
3384
3645
  p.files.map((file) => normalizeFileKeyForCitation(file.path, p.cwd))
3385
3646
  );
@@ -3391,14 +3652,28 @@ function installChimeraReviewHandler({
3391
3652
  if (bucket) bucket.push(normalized);
3392
3653
  else changedBasenameIndex.set(basename7, [normalized]);
3393
3654
  }
3394
- const droppedCitations = citedFindings.filter((finding) => {
3655
+ const inScopeCited = citedFindings.filter((finding) => {
3395
3656
  const citedKey = normalizeFileKeyForCitation(finding.location.file, p.cwd);
3396
- if (citedPaths.has(citedKey)) return false;
3657
+ if (citedPaths.has(citedKey)) return true;
3397
3658
  const basename7 = path11.basename(finding.location.file).toLowerCase();
3398
3659
  const bucket = changedBasenameIndex.get(basename7);
3399
- return !(bucket && bucket.length === 1);
3660
+ return !!(bucket && bucket.length === 1);
3400
3661
  });
3401
- const effectiveHasFindings = reviewHasFindings && (parsedReview.findings.length === 0 || parsedReview.findings.length > droppedCitations.length);
3662
+ const droppedCitations = citedFindings.filter(
3663
+ (finding) => !inScopeCited.includes(finding)
3664
+ );
3665
+ const actionableFindings = inScopeCited.filter(
3666
+ (finding) => finding.verification?.status === "verified"
3667
+ );
3668
+ const unverifiedInScope = inScopeCited.filter(
3669
+ (finding) => finding.verification?.status === "unverified"
3670
+ );
3671
+ const failedVerificationCount = inScopeCited.filter(
3672
+ (finding) => finding.verification?.status === "failed"
3673
+ ).length;
3674
+ const effectiveHasFindings = reviewHasFindings && actionableFindings.length > 0;
3675
+ const verifiedCount = actionableFindings.length;
3676
+ const unverifiedCount = unverifiedInScope.length;
3402
3677
  const hallucinationNote = droppedCitations.length > 0 ? `
3403
3678
 
3404
3679
  \u26A0\uFE0F Citation validation: ${droppedCitations.length} finding(s) cite files outside the changed-file set and should be verified before acting.` : "";
@@ -3441,7 +3716,7 @@ ${reviewBody}`;
3441
3716
  reviewLength: reviewText.length,
3442
3717
  messageId: reviewMailMessageId
3443
3718
  });
3444
- if (!effectiveHasFindings) {
3719
+ if (!reviewHasFindings) {
3445
3720
  try {
3446
3721
  await mailbox.ack({
3447
3722
  messageId: reviewMailMessageId,
@@ -3485,11 +3760,8 @@ ${reviewBody}`;
3485
3760
  phase: "agent"
3486
3761
  });
3487
3762
  }
3488
- const findingCount = Math.max(
3489
- 0,
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.`;
3763
+ const findingCount = actionableFindings.length;
3764
+ 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
3765
  events.emitCustom("chimera.report_available", {
3494
3766
  reportId,
3495
3767
  sessionId: reviewSessionId,
@@ -3711,7 +3983,7 @@ function parseSuggestionsFromOutput(finalText, todos) {
3711
3983
  setAutoSuggestions([]);
3712
3984
  return null;
3713
3985
  }
3714
- const { texts, autoTexts } = parseNextSteps(finalText, false);
3986
+ const { texts, autoTexts } = parseNextSteps(finalText);
3715
3987
  if (autoTexts.length > 0) {
3716
3988
  setAutoSuggestions(autoTexts);
3717
3989
  }
@@ -4909,6 +5181,9 @@ async function execute(deps) {
4909
5181
  session: { provider: config.provider, model: config.model }
4910
5182
  }),
4911
5183
  getPendingWork: () => chimeraWork.pending(),
5184
+ persistEvidence: async (reportId, status, checks) => {
5185
+ await updateReviewReportEvidence(reportId, wpaths.projectDir, status, checks);
5186
+ },
4912
5187
  trackWork: (work) => {
4913
5188
  chimeraWork.track(work);
4914
5189
  }
@@ -5347,4 +5622,4 @@ export {
5347
5622
  execute,
5348
5623
  resolveReviewerFallbackModels
5349
5624
  };
5350
- //# sourceMappingURL=execution-QYFWDD3Y.js.map
5625
+ //# sourceMappingURL=execution-TTE7R5UY.js.map
@@ -1,3 +1,5 @@
1
+ import type { CascadeEvidenceCheckResult, CascadeEvidenceStatus } from '@wrongstack/core/plugin';
2
+ import { verifyCascadeEvidence } from './chimera-cascade-evidence.js';
1
3
  import type { ReviewerAttempt } from './chimera-reviewer-policy.js';
2
4
  import type { ExecuteDeps } from './execute-deps.js';
3
5
  type Director = NonNullable<ExecuteDeps['fleet']['director']>;
@@ -14,7 +16,11 @@ export type InstallChimeraCascadeHandlerOptions = {
14
16
  * holds the live config. Omit to keep the single unpinned spawn.
15
17
  */
16
18
  buildLadder?: (() => ReviewerAttempt[]) | undefined;
19
+ /** Test seam; production defaults to the real safe command verifier. */
20
+ verifyEvidence?: typeof verifyCascadeEvidence | undefined;
21
+ /** Persist every evidence verdict on the source report, even when re-review stops. */
22
+ persistEvidence?: ((reportId: string, status: CascadeEvidenceStatus, checks: CascadeEvidenceCheckResult[]) => Promise<void>) | undefined;
17
23
  };
18
- export declare function installChimeraCascadeHandler({ events, director, session, getPendingWork, trackWork, buildLadder, }: InstallChimeraCascadeHandlerOptions): void;
24
+ export declare function installChimeraCascadeHandler({ events, director, session, getPendingWork, trackWork, buildLadder, verifyEvidence, persistEvidence, }: InstallChimeraCascadeHandlerOptions): void;
19
25
  export {};
20
26
  //# sourceMappingURL=execution-chimera-cascade.d.ts.map
@@ -1,9 +1,9 @@
1
- import {
2
- resolveAuditActor
3
- } from "./chunk-Q5GTM25S.js";
4
1
  import {
5
2
  parseTokenTtlValue
6
3
  } from "./chunk-BGFM3PD7.js";
4
+ import {
5
+ resolveAuditActor
6
+ } from "./chunk-Q5GTM25S.js";
7
7
  import "./chunk-7OCVIDC7.js";
8
8
 
9
9
  // src/subcommands/handlers/hq.ts
@@ -28,6 +28,10 @@ function resolveDataDir(deps) {
28
28
  }
29
29
  var hqCmd = async (args, deps) => {
30
30
  const sub = args[0];
31
+ if (deps.flags?.["help"] === true && (!sub || sub === "serve")) {
32
+ printHelp(deps);
33
+ return 0;
34
+ }
31
35
  if (!sub || sub === "serve") {
32
36
  return startServer(deps);
33
37
  }
@@ -160,6 +164,10 @@ HQ auth loaded from ${handle.firstRunSetup.dataDir}
160
164
  }
161
165
  async function hqTokenCmd(args, deps) {
162
166
  const action = args[0];
167
+ if (deps.flags?.["help"] === true || action === "help" || action === "--help") {
168
+ printTokenHelp(deps);
169
+ return 0;
170
+ }
163
171
  if (action === "create") {
164
172
  return tokenCreate(args.slice(1), deps);
165
173
  }
@@ -171,17 +179,21 @@ async function hqTokenCmd(args, deps) {
171
179
  }
172
180
  deps.renderer.writeError(`Unknown hq token subcommand: ${action ?? "(none)"}
173
181
  `);
174
- deps.renderer.write("Usage: wstack hq token <create|list|revoke>\n");
182
+ printTokenHelp(deps);
175
183
  return 1;
176
184
  }
177
185
  async function hqAuditCmd(args, deps) {
178
186
  const action = args[0];
187
+ if (deps.flags?.["help"] === true || action === "help" || action === "--help") {
188
+ printAuditHelp(deps);
189
+ return 0;
190
+ }
179
191
  if (action === "verify" || action === void 0) {
180
192
  return hqAuditVerify(deps);
181
193
  }
182
194
  deps.renderer.writeError(`Unknown hq audit subcommand: ${action ?? "(none)"}
183
195
  `);
184
- deps.renderer.write("Usage: wstack hq audit <verify>\n");
196
+ printAuditHelp(deps);
185
197
  return 1;
186
198
  }
187
199
  async function hqAuditVerify(deps) {
@@ -608,8 +620,85 @@ function printHelp(deps) {
608
620
  deps.renderer.write(`auth.json schema version: ${HQ_AUTH_FILE_VERSION}.
609
621
  `);
610
622
  }
623
+ function printTokenHelp(deps) {
624
+ deps.renderer.write(`Usage: wstack hq token <create | list | revoke>
625
+ `);
626
+ deps.renderer.write("\n");
627
+ deps.renderer.write(
628
+ ` wstack hq token create [label] [--ttl <dur>] Mint a browser token (enters token mode).
629
+ `
630
+ );
631
+ deps.renderer.write(
632
+ ` wstack hq token create --client [label] [--ttl <dur>] Mint a client token (/ws/client).
633
+ `
634
+ );
635
+ deps.renderer.write(` wstack hq token list List issued browser tokens (alias: ls).
636
+ `);
637
+ deps.renderer.write(` wstack hq token list --client List issued client tokens.
638
+ `);
639
+ deps.renderer.write(
640
+ ` wstack hq token revoke <id> Revoke a browser token (id prefix match).
641
+ `
642
+ );
643
+ deps.renderer.write(` wstack hq token revoke --client <id> Revoke a client token.
644
+ `);
645
+ deps.renderer.write("\n");
646
+ deps.renderer.write(`The token secret is printed once at create time and cannot be recovered
647
+ `);
648
+ deps.renderer.write(`from auth.json (only its SHA-256 verifier is persisted). Tokens live in
649
+ `);
650
+ deps.renderer.write(`<dataDir>/auth.json (default ~/.wrongstack/hq/auth.json).
651
+ `);
652
+ deps.renderer.write("\n");
653
+ deps.renderer.write(`Flags:
654
+ `);
655
+ deps.renderer.write(
656
+ ` --data-dir <path> Override HQ data directory (default ~/.wrongstack/hq).
657
+ `
658
+ );
659
+ deps.renderer.write(` --client, -c Operate on client tokens instead of browser tokens.
660
+ `);
661
+ deps.renderer.write(
662
+ ` --capabilities <csv> Comma-separated capability grants (browser: control.enqueue; client: telemetry.publish,control.execute).
663
+ `
664
+ );
665
+ deps.renderer.write(
666
+ ` --ttl <duration> Stamp an expiresAt on the token (e.g. --ttl 1h, --ttl 7d, --ttl 3600s).
667
+ `
668
+ );
669
+ deps.renderer.write("\n");
670
+ deps.renderer.write(`Run \`wstack hq --help\` for the full HQ command list.
671
+ `);
672
+ }
673
+ function printAuditHelp(deps) {
674
+ deps.renderer.write(`Usage: wstack hq audit <verify>
675
+ `);
676
+ deps.renderer.write("\n");
677
+ deps.renderer.write(
678
+ ` wstack hq audit verify Re-derive the SHA-256 contentHash from the current
679
+ `
680
+ );
681
+ deps.renderer.write(
682
+ ` auth.json and print it so an operator can compare
683
+ `
684
+ );
685
+ deps.renderer.write(
686
+ ` it against a contentHash field in an audit-log entry.
687
+ `
688
+ );
689
+ deps.renderer.write("\n");
690
+ deps.renderer.write(`Flags:
691
+ `);
692
+ deps.renderer.write(
693
+ ` --data-dir <path> Override HQ data directory (default ~/.wrongstack/hq).
694
+ `
695
+ );
696
+ deps.renderer.write("\n");
697
+ deps.renderer.write(`Run \`wstack hq --help\` for the full HQ command list.
698
+ `);
699
+ }
611
700
  export {
612
701
  hqCmd,
613
702
  resolveAuditActor
614
703
  };
615
- //# sourceMappingURL=hq-5G6D5YIU.js.map
704
+ //# sourceMappingURL=hq-ZK6PGN3W.js.map