@wrongstack/core 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.
Files changed (38) hide show
  1. package/dist/coordination/agents/index.js +3 -2
  2. package/dist/coordination/agents/types.d.ts +1 -1
  3. package/dist/coordination/index.js +3 -2
  4. package/dist/core/index.d.ts +2 -1
  5. package/dist/core/index.js +2764 -2632
  6. package/dist/core/system-prompt-blocks.d.ts +1 -1
  7. package/dist/core/system-prompt-builder.d.ts +7 -1
  8. package/dist/core/system-prompt-glossary.d.ts +0 -23
  9. package/dist/defaults/index.js +3 -2
  10. package/dist/execution/index.js +3 -2
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +875 -530
  13. package/dist/observability/index.js +7 -3
  14. package/dist/plugin/index.d.ts +4 -3
  15. package/dist/plugin/index.js +589 -143
  16. package/dist/plugins/auto-review-plugin.d.ts +14 -7
  17. package/dist/plugins/chimera-plugin.d.ts +15 -1
  18. package/dist/plugins/review-finding-integration.d.ts +15 -3
  19. package/dist/plugins/review-finding-parser.d.ts +36 -0
  20. package/dist/plugins/review-finding-types.d.ts +46 -0
  21. package/dist/plugins/review-finding-verification.d.ts +53 -0
  22. package/dist/plugins/review-report-integration.d.ts +1 -0
  23. package/dist/plugins/review-report-store.d.ts +7 -0
  24. package/dist/plugins/review-report-types.d.ts +14 -0
  25. package/dist/plugins/review-types.d.ts +74 -0
  26. package/dist/replay/replay-provider-runner.d.ts +5 -4
  27. package/dist/tools/fallback-manage-tool-options.d.ts +9 -0
  28. package/dist/tools/index.js +91 -38
  29. package/dist/tools/one-shot-llm-tool.d.ts +6 -0
  30. package/dist/types/blocks.d.ts +10 -0
  31. package/dist/utils/index.js +8 -9
  32. package/instructions/agents/browser.md +1 -0
  33. package/instructions/agents/e2e.md +2 -0
  34. package/instructions/llm/chimera-review.md +52 -1
  35. package/instructions/system-lite.md +17 -6
  36. package/instructions/system-pro.md +25 -20
  37. package/instructions/system.md +25 -12
  38. package/package.json +3 -3
@@ -671,7 +671,9 @@ var init_review_report_store = __esm({
671
671
  unparseableCount: input.unparseableCount,
672
672
  durationSeconds: input.durationSeconds ?? existing.durationSeconds,
673
673
  rawText: input.rawText || existing.rawText,
674
- files: input.files.length > 0 ? input.files : existing.files
674
+ files: input.files.length > 0 ? input.files : existing.files,
675
+ ...input.evidenceStatus !== void 0 ? { evidenceStatus: input.evidenceStatus } : {},
676
+ ...input.evidenceChecks !== void 0 ? { evidenceChecks: input.evidenceChecks } : {}
675
677
  };
676
678
  await fsp4.appendFile(this.filePath, JSON.stringify({ __report: 1, data: updated }) + NL2, {
677
679
  encoding: "utf8",
@@ -694,7 +696,9 @@ var init_review_report_store = __esm({
694
696
  unparseableCount: input.unparseableCount,
695
697
  durationSeconds: input.durationSeconds,
696
698
  rawText: input.rawText,
697
- ...input.cascadeDepth !== void 0 ? { cascadeDepth: input.cascadeDepth } : {}
699
+ ...input.cascadeDepth !== void 0 ? { cascadeDepth: input.cascadeDepth } : {},
700
+ ...input.evidenceStatus !== void 0 ? { evidenceStatus: input.evidenceStatus } : {},
701
+ ...input.evidenceChecks !== void 0 ? { evidenceChecks: input.evidenceChecks } : {}
698
702
  };
699
703
  const createdEvent = {
700
704
  id: randomUUID6(),
@@ -738,6 +742,24 @@ var init_review_report_store = __esm({
738
742
  return { ...entry.report };
739
743
  });
740
744
  }
745
+ async updateEvidence(reportId, status, checks) {
746
+ return withFileLock(this.filePath, async () => {
747
+ const all = await this._readAll();
748
+ const entry = all.find((candidate) => candidate.report.id === reportId);
749
+ if (!entry) throw new Error(`Review report not found: ${reportId}`);
750
+ const updated = {
751
+ ...this._materialize(entry),
752
+ evidenceStatus: status,
753
+ evidenceChecks: checks
754
+ };
755
+ await fsp4.appendFile(
756
+ this.filePath,
757
+ `${JSON.stringify({ __report: 1, data: updated })}${NL2}`,
758
+ { encoding: "utf8", mode: SECRET_FILE_MODE }
759
+ );
760
+ return updated;
761
+ });
762
+ }
741
763
  async addNote(reportId, actor, note) {
742
764
  return withFileLock(this.filePath, async () => {
743
765
  const all = await this._readAll();
@@ -1141,10 +1163,10 @@ function validateAgainstSchema(value, schema) {
1141
1163
  return { ok: errors.length === 0, errors };
1142
1164
  }
1143
1165
  var MAX_SCHEMA_DEPTH = 64;
1144
- function walk(value, schema, path32, errors, depth) {
1166
+ function walk(value, schema, path33, errors, depth) {
1145
1167
  if (depth > MAX_SCHEMA_DEPTH) {
1146
1168
  errors.push({
1147
- path: path32 || "<root>",
1169
+ path: path33 || "<root>",
1148
1170
  message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
1149
1171
  });
1150
1172
  return;
@@ -1152,7 +1174,7 @@ function walk(value, schema, path32, errors, depth) {
1152
1174
  if (schema.enum !== void 0) {
1153
1175
  if (!enumIncludes(schema.enum, value)) {
1154
1176
  errors.push({
1155
- path: path32 || "<root>",
1177
+ path: path33 || "<root>",
1156
1178
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
1157
1179
  });
1158
1180
  return;
@@ -1161,7 +1183,7 @@ function walk(value, schema, path32, errors, depth) {
1161
1183
  if (typeof schema.type === "string") {
1162
1184
  if (!checkType(value, schema.type)) {
1163
1185
  errors.push({
1164
- path: path32 || "<root>",
1186
+ path: path33 || "<root>",
1165
1187
  message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
1166
1188
  });
1167
1189
  return;
@@ -1173,7 +1195,7 @@ function walk(value, schema, path32, errors, depth) {
1173
1195
  if (!(req in obj)) {
1174
1196
  const expected = schema.properties?.[req]?.type;
1175
1197
  errors.push({
1176
- path: joinPath(path32, req),
1198
+ path: joinPath(path33, req),
1177
1199
  message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
1178
1200
  });
1179
1201
  }
@@ -1181,14 +1203,14 @@ function walk(value, schema, path32, errors, depth) {
1181
1203
  if (schema.properties) {
1182
1204
  for (const [key, subSchema] of Object.entries(schema.properties)) {
1183
1205
  if (key in obj) {
1184
- walk(obj[key], subSchema, joinPath(path32, key), errors, depth + 1);
1206
+ walk(obj[key], subSchema, joinPath(path33, key), errors, depth + 1);
1185
1207
  }
1186
1208
  }
1187
1209
  }
1188
1210
  }
1189
1211
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
1190
1212
  for (let i = 0; i < value.length; i++) {
1191
- walk(value[i], schema.items, `${path32}[${i}]`, errors, depth + 1);
1213
+ walk(value[i], schema.items, `${path33}[${i}]`, errors, depth + 1);
1192
1214
  }
1193
1215
  }
1194
1216
  }
@@ -1902,7 +1924,10 @@ var DefaultPluginAPI = class {
1902
1924
  }
1903
1925
  };
1904
1926
  this.tools = {
1905
- register: (t) => tr.register(t, owner),
1927
+ register: (t) => {
1928
+ tr.register(t, owner);
1929
+ tr.exposeToProvider(t.name);
1930
+ },
1906
1931
  unregister: (name) => {
1907
1932
  assertCanMutateTool(name, "unregister");
1908
1933
  return tr.unregister(name);
@@ -7956,6 +7981,15 @@ function resolveAutoReviewConfig(cfg, sessionConfig) {
7956
7981
  maxCascadeDepth: cfg.maxCascadeDepth ?? DEFAULT_MAX_CASCADE_DEPTH
7957
7982
  };
7958
7983
  }
7984
+ function severitiesFromFindings(findings) {
7985
+ const severities = { critical: 0, high: 0, medium: 0 };
7986
+ for (const finding of findings) {
7987
+ if (finding.severity === "critical") severities.critical++;
7988
+ else if (finding.severity === "high") severities.high++;
7989
+ else if (finding.severity === "medium") severities.medium++;
7990
+ }
7991
+ return severities;
7992
+ }
7959
7993
  function parseReviewSeverity(text) {
7960
7994
  const result = { critical: 0, high: 0, medium: 0 };
7961
7995
  if (!text) return result;
@@ -7972,9 +8006,20 @@ function parseReviewSeverity(text) {
7972
8006
  }
7973
8007
  return result;
7974
8008
  }
7975
- function decideCascadeAgents(text, severities) {
8009
+ function decideCascadeAgents(text, severities, findings) {
7976
8010
  const agents = /* @__PURE__ */ new Set();
7977
8011
  if (severities.critical > 0 || severities.high > 0) agents.add("bug-hunter");
8012
+ if (findings && findings.length > 0) {
8013
+ const highPlus = findings.filter(
8014
+ (finding) => finding.severity === "critical" || finding.severity === "high"
8015
+ );
8016
+ if (highPlus.some((finding) => finding.category === "security")) {
8017
+ agents.add("security-scanner");
8018
+ }
8019
+ if (highPlus.every((finding) => finding.category !== void 0)) {
8020
+ return [...agents];
8021
+ }
8022
+ }
7978
8023
  const securityKeywords = [
7979
8024
  "injection",
7980
8025
  "xss",
@@ -8347,7 +8392,9 @@ function createAutoReviewPlugin() {
8347
8392
  maxFiles: cfg.maxFilesPerBatch,
8348
8393
  autoFix: "off",
8349
8394
  cascadeOn: "off",
8350
- maxCascadeDepth: 0
8395
+ maxCascadeDepth: 0,
8396
+ fallbackModels: [...cfg.fallbackModels],
8397
+ fallbackProfile: void 0
8351
8398
  },
8352
8399
  files: filesWithContent,
8353
8400
  activeTodos: ctxTodos,
@@ -8452,7 +8499,9 @@ function createAutoReviewPlugin() {
8452
8499
  maxFiles: cfg.maxFilesPerBatch,
8453
8500
  autoFix: "off",
8454
8501
  cascadeOn: "off",
8455
- maxCascadeDepth: 0
8502
+ maxCascadeDepth: 0,
8503
+ fallbackModels: [...cfg.fallbackModels],
8504
+ fallbackProfile: void 0
8456
8505
  },
8457
8506
  files: filesWithContent,
8458
8507
  cascadeOn: cfg.cascadeOn,
@@ -8494,23 +8543,31 @@ function createAutoReviewPlugin() {
8494
8543
  if (!p.reviewText) return;
8495
8544
  const cascadeOn = p.bundle.cascadeOn ?? "off";
8496
8545
  if (cascadeOn === "off") return;
8497
- const severities = parseReviewSeverity(p.reviewText);
8546
+ const parsed = p.parsedReport;
8547
+ const verifiedFindings = parsed?.findings.filter((f) => f.verification?.status === "verified") ?? [];
8548
+ const severities = parsed ? severitiesFromFindings(verifiedFindings) : parseReviewSeverity(p.reviewText);
8498
8549
  const threshold = shouldCascade(cascadeOn, severities);
8499
8550
  if (!threshold) return;
8500
- const agents = decideCascadeAgents(p.reviewText, severities);
8551
+ const agents = decideCascadeAgents(
8552
+ p.reviewText,
8553
+ severities,
8554
+ parsed ? verifiedFindings : void 0
8555
+ );
8501
8556
  if (agents.length === 0) {
8502
8557
  return;
8503
8558
  }
8504
8559
  const cascadePayload = {
8505
8560
  bundle: p.bundle,
8561
+ ...p.reportId ? { reportId: p.reportId } : {},
8506
8562
  reviewText: p.reviewText,
8507
8563
  severities,
8508
8564
  threshold,
8509
- agents
8565
+ agents,
8566
+ ...parsed ? { verifiedFindings } : {}
8510
8567
  };
8511
8568
  api.emitCustom("chimera.cascade_needed", cascadePayload);
8512
8569
  api.log.info(
8513
- `[auto-review] cascade_needed emitted \u2014 ${severities.critical} critical, ${severities.high} high, ${severities.medium} medium; agents: ${agents.join(", ")}`
8570
+ `[auto-review] cascade_needed emitted \u2014 ${severities.critical} critical, ${severities.high} high, ${severities.medium} medium; agents: ${agents.join(", ")}${parsed ? ` (gated on ${verifiedFindings.length} verified finding(s))` : ""}`
8514
8571
  );
8515
8572
  } catch (err) {
8516
8573
  api.log.warn(
@@ -8542,12 +8599,88 @@ init_review_finding_store();
8542
8599
  // src/plugins/review-finding-parser.ts
8543
8600
  init_review_finding_types();
8544
8601
  import { randomUUID as randomUUID5 } from "node:crypto";
8602
+ var SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low"]);
8603
+ var CATEGORIES = /* @__PURE__ */ new Set([
8604
+ "bug",
8605
+ "security",
8606
+ "performance",
8607
+ "type",
8608
+ "contract",
8609
+ "test",
8610
+ "other"
8611
+ ]);
8612
+ var CONFIDENCES = /* @__PURE__ */ new Set(["high", "medium", "low"]);
8613
+ var FENCED_BLOCK = /```json[ \t]*\r?\n([\s\S]*?)\r?\n```/gi;
8614
+ function extractStructuredFindingsBlock(reportText) {
8615
+ if (!reportText) return null;
8616
+ let best = null;
8617
+ for (const match of reportText.matchAll(FENCED_BLOCK)) {
8618
+ const body = match[1];
8619
+ if (!body?.trim()) continue;
8620
+ let parsed;
8621
+ try {
8622
+ parsed = JSON.parse(body);
8623
+ } catch {
8624
+ continue;
8625
+ }
8626
+ if (typeof parsed !== "object" || parsed === null) continue;
8627
+ const findings = parsed.findings;
8628
+ if (!Array.isArray(findings)) continue;
8629
+ const items = [];
8630
+ for (const raw of findings) {
8631
+ const item = normalizeStructuredItem(raw);
8632
+ if (item) items.push(item);
8633
+ }
8634
+ if (findings.length > 0 && items.length === 0) continue;
8635
+ const durationRaw = parsed.durationSeconds;
8636
+ const durationSeconds = typeof durationRaw === "number" && Number.isFinite(durationRaw) && durationRaw > 0 ? Math.floor(durationRaw) : void 0;
8637
+ best = { findings: items, ...durationSeconds !== void 0 ? { durationSeconds } : {} };
8638
+ }
8639
+ return best;
8640
+ }
8641
+ function normalizeStructuredItem(raw) {
8642
+ if (typeof raw !== "object" || raw === null) return null;
8643
+ const item = raw;
8644
+ const severity = typeof item.severity === "string" ? item.severity.toLowerCase() : "";
8645
+ if (!SEVERITIES.has(severity)) return null;
8646
+ const title = typeof item.title === "string" ? item.title.trim() : "";
8647
+ if (title.length === 0) return null;
8648
+ const file = typeof item.file === "string" && item.file.trim().length > 0 ? item.file.trim() : void 0;
8649
+ const line = typeof item.line === "number" && Number.isInteger(item.line) && item.line >= 1 ? item.line : void 0;
8650
+ const categoryRaw = typeof item.category === "string" ? item.category.toLowerCase() : "";
8651
+ const category = CATEGORIES.has(categoryRaw) ? categoryRaw : void 0;
8652
+ const confidenceRaw = typeof item.confidence === "string" ? item.confidence.toLowerCase() : "";
8653
+ const confidence = CONFIDENCES.has(confidenceRaw) ? confidenceRaw : void 0;
8654
+ return {
8655
+ severity,
8656
+ ...file ? { file } : {},
8657
+ ...line !== void 0 ? { line } : {},
8658
+ ...category ? { category } : {},
8659
+ ...confidence ? { confidence } : {},
8660
+ title,
8661
+ ...typeof item.description === "string" && item.description.trim().length > 0 ? { description: item.description.trim() } : {},
8662
+ ...typeof item.suggestedFix === "string" && item.suggestedFix.trim().length > 0 ? { suggestedFix: item.suggestedFix.trim() } : {}
8663
+ };
8664
+ }
8545
8665
  var SUGGEST_LINE = /^\s*(?:→|->|=>)\s*(.+)$/;
8546
8666
  var DURATION_LINE = /^Duration:\s*(\d+)s\s*$/im;
8547
8667
  function parseChimeraReviewReport(reportText, context = {}) {
8548
8668
  if (!reportText || reportText.trim().length === 0) {
8549
8669
  return { findings: [], unparseableCount: 0 };
8550
8670
  }
8671
+ const structured = extractStructuredFindingsBlock(reportText);
8672
+ if (structured) {
8673
+ const reportId2 = context.reportId ?? randomUUID5();
8674
+ const findings2 = structured.findings.map(
8675
+ (item) => buildFindingFromStructuredItem(item, { ...context, reportId: reportId2 })
8676
+ );
8677
+ return {
8678
+ findings: findings2,
8679
+ unparseableCount: 0,
8680
+ ...structured.durationSeconds !== void 0 ? { durationSeconds: structured.durationSeconds } : {},
8681
+ structured: true
8682
+ };
8683
+ }
8551
8684
  const findings = [];
8552
8685
  const reportId = context.reportId ?? randomUUID5();
8553
8686
  let unparseableCount = 0;
@@ -8679,14 +8812,99 @@ function normalizeFindingSource(reviewType) {
8679
8812
  return "chimera";
8680
8813
  }
8681
8814
  }
8815
+ function buildFindingFromStructuredItem(item, context) {
8816
+ const file = item.file;
8817
+ const line = item.line;
8818
+ const title = item.title;
8819
+ const description = item.description ?? title;
8820
+ return {
8821
+ id: randomUUID5(),
8822
+ fingerprint: computeFindingFingerprint(file ?? "", line ?? null, title),
8823
+ severity: item.severity,
8824
+ source: normalizeFindingSource(context.reviewType),
8825
+ ...file ? { location: { file, ...line !== void 0 ? { line } : {} } } : {},
8826
+ ...item.category ? { category: item.category } : {},
8827
+ ...item.confidence ? { confidence: item.confidence } : {},
8828
+ title,
8829
+ description,
8830
+ ...item.suggestedFix ? { suggestedFix: item.suggestedFix } : {},
8831
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
8832
+ status: "active",
8833
+ originReport: {
8834
+ reportId: context.reportId ?? randomUUID5(),
8835
+ sessionId: context.sessionId ?? "",
8836
+ agentId: context.agentId ?? "",
8837
+ reviewerModel: context.reviewerModel ?? ""
8838
+ }
8839
+ };
8840
+ }
8682
8841
 
8683
8842
  // src/plugins/review-report-integration.ts
8684
8843
  init_review_finding_store();
8844
+
8845
+ // src/plugins/review-finding-integration.ts
8846
+ init_review_finding_store();
8847
+ function classifyChimeraReviewSource(bundle) {
8848
+ const cascadeDepth = bundle.cascadeDepth ?? 0;
8849
+ if (cascadeDepth > 0) return "cascade";
8850
+ const cascadeOn = bundle.cascadeOn;
8851
+ if (cascadeOn !== void 0 && cascadeOn !== "off") return "auto";
8852
+ return "chimera";
8853
+ }
8854
+ async function integrateFindings(payload, projectDir, reportId) {
8855
+ if ((!payload.reviewText || payload.reviewText.trim().length === 0) && !payload.parsedReport) {
8856
+ return { created: 0, relinked: 0, reopened: 0, totalFindings: 0, unparseableCount: 0 };
8857
+ }
8858
+ const store = new JsonlFindingStore(projectDir);
8859
+ const source = classifyChimeraReviewSource(payload.bundle);
8860
+ const agentId = payload.bundle.fileProvenance?.find((entry) => entry.agentId)?.agentId ?? "chimera-review";
8861
+ const sessionId = payload.sessionId ?? payload.cwd;
8862
+ const model = payload.bundle.config.model;
8863
+ const parsed = payload.parsedReport ?? parseChimeraReviewReport(payload.reviewText, {
8864
+ sessionId,
8865
+ agentId,
8866
+ reviewerModel: model,
8867
+ reviewType: source,
8868
+ reportId
8869
+ });
8870
+ if (parsed.findings.length === 0) {
8871
+ return {
8872
+ created: 0,
8873
+ relinked: 0,
8874
+ reopened: 0,
8875
+ totalFindings: 0,
8876
+ unparseableCount: parsed.unparseableCount
8877
+ };
8878
+ }
8879
+ const result = await store.upsert(parsed.findings, {
8880
+ sessionId,
8881
+ reportId,
8882
+ agentId,
8883
+ model
8884
+ });
8885
+ return {
8886
+ created: result.created,
8887
+ relinked: result.relinked,
8888
+ reopened: result.reopened,
8889
+ reportId,
8890
+ totalFindings: parsed.findings.length,
8891
+ unparseableCount: parsed.unparseableCount
8892
+ };
8893
+ }
8894
+
8895
+ // src/plugins/review-report-integration.ts
8685
8896
  init_review_report_store();
8897
+ async function updateReviewReportEvidence(reportId, projectDir, evidenceStatus, evidenceChecks) {
8898
+ await new JsonlReportStore(projectDir).updateEvidence(
8899
+ reportId,
8900
+ evidenceStatus,
8901
+ evidenceChecks
8902
+ );
8903
+ }
8686
8904
  async function persistReviewReport(payload, reportId, projectDir) {
8687
8905
  const store = new JsonlReportStore(projectDir);
8688
8906
  const existed = await store.get(reportId);
8689
- const source = classifySource(payload);
8907
+ const source = classifyChimeraReviewSource(payload.bundle);
8690
8908
  const agentId = payload.bundle.fileProvenance?.find((entry) => entry.agentId)?.agentId ?? "chimera-review";
8691
8909
  const sessionId = payload.sessionId ?? payload.cwd;
8692
8910
  const model = payload.bundle.config.model;
@@ -8696,7 +8914,13 @@ async function persistReviewReport(payload, reportId, projectDir) {
8696
8914
  status: f.status
8697
8915
  }));
8698
8916
  const reviewStatus = payload.status === "success" ? "success" : "failed";
8699
- const parsed = reviewStatus === "success" ? parseChimeraReviewReport(payload.reviewText, { reportId }) : { findings: [], unparseableCount: 0, durationSeconds: void 0 };
8917
+ const parsed = reviewStatus === "success" ? payload.parsedReport ?? parseChimeraReviewReport(payload.reviewText, {
8918
+ sessionId,
8919
+ agentId,
8920
+ reviewerModel: model,
8921
+ reviewType: source,
8922
+ reportId
8923
+ }) : { findings: [], unparseableCount: 0, durationSeconds: void 0 };
8700
8924
  const counts = { critical: 0, high: 0, medium: 0, low: 0 };
8701
8925
  for (const finding of parsed.findings) {
8702
8926
  counts[finding.severity]++;
@@ -8714,7 +8938,12 @@ async function persistReviewReport(payload, reportId, projectDir) {
8714
8938
  unparseableCount: parsed.unparseableCount,
8715
8939
  durationSeconds: parsed.durationSeconds,
8716
8940
  rawText: payload.reviewText,
8717
- ...cascadeDepth !== void 0 ? { cascadeDepth } : {}
8941
+ ...cascadeDepth !== void 0 ? { cascadeDepth } : {},
8942
+ // P0-3: carry the cascade evidence verification (status + per-check
8943
+ // comparisons) so the persisted report is auditable. Absent on initial
8944
+ // reviews — no cascade step produced evidence yet.
8945
+ ...payload.bundle.evidenceStatus !== void 0 ? { evidenceStatus: payload.bundle.evidenceStatus } : {},
8946
+ ...payload.bundle.evidenceChecks !== void 0 ? { evidenceChecks: payload.bundle.evidenceChecks } : {}
8718
8947
  };
8719
8948
  await store.persist(input);
8720
8949
  if (reviewStatus === "success" && parsed.findings.length === 0 && parsed.unparseableCount === 0 && isExplicitAllClearReview(payload.reviewText)) {
@@ -8777,13 +9006,6 @@ async function syncReportReopen(reportId, projectDir, actor, reason) {
8777
9006
  });
8778
9007
  return { reportId, reopened: true, previousLifecycle: report.lifecycle };
8779
9008
  }
8780
- function classifySource(payload) {
8781
- const cascadeDepth = payload.bundle.cascadeDepth ?? 0;
8782
- if (cascadeDepth > 0) return "cascade";
8783
- const cascadeOn = payload.bundle.cascadeOn;
8784
- if (cascadeOn !== void 0 && cascadeOn !== "off") return "auto";
8785
- return "chimera";
8786
- }
8787
9009
 
8788
9010
  // src/plugins/review-finding-commands.ts
8789
9011
  async function executeFindingCommand(args, ctx) {
@@ -9113,6 +9335,18 @@ async function showReport(id, ctx) {
9113
9335
  `**Review status:** ${report.reviewStatus}`,
9114
9336
  ...report.cascadeDepth !== void 0 ? [`**Cascade depth:** ${report.cascadeDepth}`] : [],
9115
9337
  ...report.durationSeconds !== void 0 ? [`**Duration:** ${report.durationSeconds}s`] : [],
9338
+ ...report.evidenceStatus !== void 0 ? [
9339
+ `**Evidence:** ${report.evidenceStatus === "verified" ? "\u2705 verified" : report.evidenceStatus === "failed" ? "\u274C failed" : "\u26A0\uFE0F missing"}`,
9340
+ ...report.evidenceChecks && report.evidenceChecks.length > 0 ? [
9341
+ "",
9342
+ ...report.evidenceChecks.map((check) => {
9343
+ const mark = check.ok ? "\u2713" : "\u2717";
9344
+ const claimed = check.claimedExitCode ?? "\u2014";
9345
+ const actual = check.actualExitCode ?? "\u2014";
9346
+ return ` ${mark} \`${check.name}\` \u2014 \`${check.command}\` (claimed ${claimed}, observed ${actual})`;
9347
+ })
9348
+ ] : []
9349
+ ] : [],
9116
9350
  "",
9117
9351
  "**Severity counts:**",
9118
9352
  ` \u{1F534} Critical: ${report.counts.critical}`,
@@ -9221,7 +9455,9 @@ function resolveChimeraConfig(cfg, sessionProvider, sessionModel) {
9221
9455
  maxFiles: cfg.maxFiles ?? DEFAULT_MAX_FILES,
9222
9456
  autoFix: cfg.autoFix ?? "off",
9223
9457
  cascadeOn: cfg.cascadeOn ?? DEFAULT_CASCADE_ON,
9224
- maxCascadeDepth: cfg.maxCascadeDepth ?? DEFAULT_MAX_CASCADE_DEPTH2
9458
+ maxCascadeDepth: cfg.maxCascadeDepth ?? DEFAULT_MAX_CASCADE_DEPTH2,
9459
+ fallbackModels: cfg.fallbackModels ? [...cfg.fallbackModels] : [],
9460
+ fallbackProfile: cfg.fallbackProfile
9225
9461
  };
9226
9462
  }
9227
9463
  var CHIMERA_REVIEW_PROMPT = readBundledInstructionText("llm/chimera-review.md");
@@ -10468,55 +10704,262 @@ function dim(s) {
10468
10704
  return `\x1B[2m${s}\x1B[0m`;
10469
10705
  }
10470
10706
 
10471
- // src/plugins/review-finding-integration.ts
10472
- init_review_finding_store();
10473
- async function integrateFindings(payload, projectDir, reportId) {
10474
- if (!payload.reviewText || payload.reviewText.trim().length === 0) {
10475
- return { created: 0, relinked: 0, reopened: 0, totalFindings: 0, unparseableCount: 0 };
10476
- }
10477
- const store = new JsonlFindingStore(projectDir);
10478
- const source = (payload.bundle.cascadeDepth ?? 0) > 0 ? "cascade" : payload.bundle.cascadeOn !== void 0 && payload.bundle.cascadeOn !== "off" ? "auto" : "chimera";
10479
- const agentId = payload.bundle.fileProvenance?.find((entry) => entry.agentId)?.agentId ?? "chimera-review";
10480
- const sessionId = payload.sessionId ?? payload.cwd;
10481
- const model = payload.bundle.config.model;
10482
- const parsed = parseChimeraReviewReport(payload.reviewText, {
10483
- sessionId,
10484
- agentId,
10485
- reviewerModel: model,
10486
- reviewType: source,
10487
- reportId
10488
- });
10489
- if (parsed.findings.length === 0) {
10490
- return {
10491
- created: 0,
10492
- relinked: 0,
10493
- reopened: 0,
10494
- totalFindings: 0,
10495
- unparseableCount: parsed.unparseableCount
10496
- };
10497
- }
10498
- const result = await store.upsert(parsed.findings, {
10499
- sessionId,
10500
- reportId,
10501
- agentId,
10502
- model
10503
- });
10504
- return {
10505
- created: result.created,
10506
- relinked: result.relinked,
10507
- reopened: result.reopened,
10508
- reportId,
10509
- totalFindings: parsed.findings.length,
10510
- unparseableCount: parsed.unparseableCount
10707
+ // src/plugins/review-finding-verification.ts
10708
+ import * as fsp6 from "node:fs/promises";
10709
+ import * as path22 from "node:path";
10710
+ var DEFAULT_ANCHOR_WINDOW = 2;
10711
+ var STOPWORDS = /* @__PURE__ */ new Set([
10712
+ "a",
10713
+ "about",
10714
+ "above",
10715
+ "after",
10716
+ "again",
10717
+ "against",
10718
+ "all",
10719
+ "an",
10720
+ "and",
10721
+ "any",
10722
+ "are",
10723
+ "as",
10724
+ "at",
10725
+ "be",
10726
+ "been",
10727
+ "before",
10728
+ "below",
10729
+ "between",
10730
+ "both",
10731
+ "but",
10732
+ "by",
10733
+ "can",
10734
+ "could",
10735
+ "did",
10736
+ "do",
10737
+ "does",
10738
+ "each",
10739
+ "few",
10740
+ "for",
10741
+ "from",
10742
+ "further",
10743
+ "get",
10744
+ "had",
10745
+ "has",
10746
+ "have",
10747
+ "he",
10748
+ "her",
10749
+ "here",
10750
+ "him",
10751
+ "his",
10752
+ "how",
10753
+ "if",
10754
+ "in",
10755
+ "into",
10756
+ "is",
10757
+ "it",
10758
+ "its",
10759
+ "just",
10760
+ "may",
10761
+ "me",
10762
+ "might",
10763
+ "more",
10764
+ "most",
10765
+ "much",
10766
+ "must",
10767
+ "my",
10768
+ "new",
10769
+ "no",
10770
+ "nor",
10771
+ "not",
10772
+ "now",
10773
+ "of",
10774
+ "off",
10775
+ "on",
10776
+ "once",
10777
+ "only",
10778
+ "or",
10779
+ "other",
10780
+ "our",
10781
+ "out",
10782
+ "own",
10783
+ "same",
10784
+ "she",
10785
+ "should",
10786
+ "so",
10787
+ "some",
10788
+ "such",
10789
+ "than",
10790
+ "that",
10791
+ "the",
10792
+ "their",
10793
+ "them",
10794
+ "then",
10795
+ "there",
10796
+ "these",
10797
+ "they",
10798
+ "this",
10799
+ "those",
10800
+ "through",
10801
+ "to",
10802
+ "too",
10803
+ "under",
10804
+ "until",
10805
+ "up",
10806
+ "us",
10807
+ "very",
10808
+ "was",
10809
+ "we",
10810
+ "were",
10811
+ "what",
10812
+ "when",
10813
+ "where",
10814
+ "which",
10815
+ "while",
10816
+ "who",
10817
+ "whom",
10818
+ "why",
10819
+ "will",
10820
+ "with",
10821
+ "would",
10822
+ "you",
10823
+ "your",
10824
+ // Finding-prose generics: common English nouns that are terrible anchors
10825
+ // because they match prose everywhere. Kept deliberately small.
10826
+ "area",
10827
+ "bug",
10828
+ "check",
10829
+ "code",
10830
+ "data",
10831
+ "endpoint",
10832
+ "error",
10833
+ "file",
10834
+ "fix",
10835
+ "issue",
10836
+ "line",
10837
+ "make",
10838
+ "may",
10839
+ "potential",
10840
+ "request",
10841
+ "response",
10842
+ "result",
10843
+ "use",
10844
+ "used",
10845
+ "user",
10846
+ "using",
10847
+ "value"
10848
+ ]);
10849
+ function extractFindingAnchor(title, description) {
10850
+ const text = `${title ?? ""} ${description ?? ""}`;
10851
+ const tokens = text.match(/[A-Za-z_$][A-Za-z0-9_$]*/g) ?? [];
10852
+ if (tokens.length === 0) return void 0;
10853
+ const meaningful = tokens.filter(
10854
+ (t) => t.length >= 3 && !STOPWORDS.has(t.toLowerCase())
10855
+ );
10856
+ if (meaningful.length === 0) return void 0;
10857
+ const codeLike = meaningful.filter((t) => /[a-z][A-Z]/.test(t) || t.includes("_") || t.endsWith("$"));
10858
+ if (codeLike.length > 0) {
10859
+ codeLike.sort((a, b) => b.length - a.length);
10860
+ return codeLike[0];
10861
+ }
10862
+ meaningful.sort((a, b) => b.length - a.length);
10863
+ return meaningful[0];
10864
+ }
10865
+ function resolveFindingPath(raw, cwd) {
10866
+ const pathMod = process.platform === "win32" ? path22.win32 : path22;
10867
+ const forward = raw.replace(/\\/g, "/").replace(/^\.\//, "");
10868
+ const isAbsolute3 = pathMod.isAbsolute(forward) || /^[a-zA-Z]:\//.test(forward);
10869
+ const relative5 = isAbsolute3 ? pathMod.relative(cwd, forward).replace(/\\/g, "/") : forward;
10870
+ if (relative5 === ".." || relative5.startsWith("../") || pathMod.isAbsolute(relative5)) return null;
10871
+ const resolved = pathMod.resolve(cwd, relative5);
10872
+ const rel = pathMod.relative(cwd, resolved);
10873
+ if (rel === ".." || rel.startsWith("..") || pathMod.isAbsolute(rel)) return null;
10874
+ return resolved;
10875
+ }
10876
+ async function verifyFindingsAgainstDisk(findings, opts) {
10877
+ const window = opts.anchorWindow ?? DEFAULT_ANCHOR_WINDOW;
10878
+ const cache = /* @__PURE__ */ new Map();
10879
+ const readFile20 = async (abs) => {
10880
+ const cached = cache.get(abs);
10881
+ if (cached) return cached;
10882
+ let result;
10883
+ try {
10884
+ const content = await fsp6.readFile(abs, "utf8");
10885
+ result = { lines: content.split("\n") };
10886
+ } catch (err) {
10887
+ result = { error: err.code === "ENOENT" ? "missing" : "unreadable" };
10888
+ }
10889
+ cache.set(abs, result);
10890
+ return result;
10511
10891
  };
10892
+ return Promise.all(
10893
+ findings.map(async (finding) => {
10894
+ if (!finding.location?.file) return finding;
10895
+ const abs = resolveFindingPath(finding.location.file, opts.cwd);
10896
+ if (abs === null) {
10897
+ return { ...finding, verification: { status: "failed", reason: "outside_workspace" } };
10898
+ }
10899
+ const file = await readFile20(abs);
10900
+ if ("error" in file) {
10901
+ return {
10902
+ ...finding,
10903
+ verification: {
10904
+ status: "failed",
10905
+ reason: file.error === "missing" ? "file_missing" : "unreadable"
10906
+ }
10907
+ };
10908
+ }
10909
+ const anchor = extractFindingAnchor(finding.title, finding.description);
10910
+ if (finding.location.line === void 0) {
10911
+ if (anchor && /[a-z][A-Z]/.test(anchor)) {
10912
+ const hit = file.lines.findIndex((l) => l.includes(anchor));
10913
+ if (hit !== -1) {
10914
+ return {
10915
+ ...finding,
10916
+ verification: {
10917
+ status: "verified",
10918
+ reason: "anchor_found",
10919
+ evidence: trimEvidence(file.lines[hit] ?? "")
10920
+ }
10921
+ };
10922
+ }
10923
+ }
10924
+ return { ...finding, verification: { status: "unverified", reason: "no_line" } };
10925
+ }
10926
+ const line = finding.location.line;
10927
+ if (line < 1 || line > file.lines.length) {
10928
+ return { ...finding, verification: { status: "failed", reason: "line_out_of_range" } };
10929
+ }
10930
+ if (!anchor) {
10931
+ return { ...finding, verification: { status: "unverified", reason: "no_anchor" } };
10932
+ }
10933
+ const from = Math.max(0, line - 1 - window);
10934
+ const to = Math.min(file.lines.length, line - 1 + window + 1);
10935
+ for (let i = from; i < to; i++) {
10936
+ const sourceLine = file.lines[i];
10937
+ if (sourceLine.includes(anchor)) {
10938
+ return {
10939
+ ...finding,
10940
+ verification: {
10941
+ status: "verified",
10942
+ reason: "anchor_found",
10943
+ evidence: trimEvidence(sourceLine)
10944
+ }
10945
+ };
10946
+ }
10947
+ }
10948
+ return { ...finding, verification: { status: "unverified", reason: "anchor_not_found" } };
10949
+ })
10950
+ );
10951
+ }
10952
+ function trimEvidence(line) {
10953
+ const trimmed = line.trim();
10954
+ return trimmed.length > 200 ? `${trimmed.slice(0, 197)}...` : trimmed;
10512
10955
  }
10513
10956
 
10514
10957
  // src/plugins/review-store-maintenance.ts
10515
10958
  init_atomic_write();
10516
10959
  init_review_finding_store();
10517
10960
  init_review_report_store();
10518
- import * as fsp6 from "node:fs/promises";
10519
- import * as path22 from "node:path";
10961
+ import * as fsp7 from "node:fs/promises";
10962
+ import * as path23 from "node:path";
10520
10963
  var REVIEW_STORE_COMPACTION_THRESHOLD_BYTES = 8 * 1024 * 1024;
10521
10964
  var REVIEW_STORE_COMPACTION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
10522
10965
  var REVIEW_STORE_MAINTENANCE_FILE = ".review-store-maintenance.json";
@@ -10524,7 +10967,7 @@ async function maybeCompactReviewStores(projectDir, opts = {}) {
10524
10967
  const thresholdBytes = opts.thresholdBytes ?? REVIEW_STORE_COMPACTION_THRESHOLD_BYTES;
10525
10968
  const intervalMs = opts.intervalMs ?? REVIEW_STORE_COMPACTION_INTERVAL_MS;
10526
10969
  const now = opts.now ?? Date.now();
10527
- const maintenancePath = path22.join(projectDir, REVIEW_STORE_MAINTENANCE_FILE);
10970
+ const maintenancePath = path23.join(projectDir, REVIEW_STORE_MAINTENANCE_FILE);
10528
10971
  return withFileLock(maintenancePath, async () => {
10529
10972
  const totalBytes = await fileSize(resolveReportStorePath(projectDir)) + await fileSize(resolveFindingStorePath(projectDir));
10530
10973
  if (totalBytes < thresholdBytes) {
@@ -10564,7 +11007,7 @@ function emptyResult(reason, totalBytes) {
10564
11007
  }
10565
11008
  async function fileSize(filePath) {
10566
11009
  try {
10567
- return (await fsp6.stat(filePath)).size;
11010
+ return (await fsp7.stat(filePath)).size;
10568
11011
  } catch (error) {
10569
11012
  if (error.code === "ENOENT") return 0;
10570
11013
  throw error;
@@ -10572,7 +11015,7 @@ async function fileSize(filePath) {
10572
11015
  }
10573
11016
  async function readMaintenanceState(filePath) {
10574
11017
  try {
10575
- const parsed = JSON.parse(await fsp6.readFile(filePath, "utf8"));
11018
+ const parsed = JSON.parse(await fsp7.readFile(filePath, "utf8"));
10576
11019
  return typeof parsed.compactedAt === "string" && Number.isFinite(parsed.totalBytesBefore) ? parsed : null;
10577
11020
  } catch (error) {
10578
11021
  if (error.code === "ENOENT" || error instanceof SyntaxError) {
@@ -10584,7 +11027,7 @@ async function readMaintenanceState(filePath) {
10584
11027
 
10585
11028
  // src/plugins/skills-plugin.ts
10586
11029
  import * as os7 from "node:os";
10587
- import * as path27 from "node:path";
11030
+ import * as path28 from "node:path";
10588
11031
 
10589
11032
  // src/skills/foreign-sources.ts
10590
11033
  var FOREIGN_SKILL_TOOLS = [
@@ -10767,7 +11210,7 @@ function numField(rec, key) {
10767
11210
  init_errors();
10768
11211
  import { spawn as spawn5 } from "node:child_process";
10769
11212
  import * as fs14 from "node:fs/promises";
10770
- import * as path23 from "node:path";
11213
+ import * as path24 from "node:path";
10771
11214
  async function validateSkillNameAvailable(name, loader) {
10772
11215
  const formatViolations = validateSkillName(name);
10773
11216
  const conflicts = loader ? (await loader.listEntries()).filter((e) => e.name === name) : [];
@@ -10845,7 +11288,7 @@ function extractSkillFromPrompt(prompt) {
10845
11288
  const description = paragraphs[0]?.replace(/^#{1,6}\s+/m, "").trim() ?? titleSource;
10846
11289
  const body = paragraphs.slice(1).join("\n\n");
10847
11290
  const quoted = [...text.matchAll(/"([^"]{2,40})"/g)].map((m) => m[1]).filter((q) => typeof q === "string").map((q) => q.toLowerCase());
10848
- const titleWords = titleSource.split(/\W+/).map((w) => w.toLowerCase()).filter((w) => w.length > 3 && !STOPWORDS.has(w));
11291
+ const titleWords = titleSource.split(/\W+/).map((w) => w.toLowerCase()).filter((w) => w.length > 3 && !STOPWORDS2.has(w));
10849
11292
  const triggerKeywords = dedupe([...quoted, ...titleWords]).slice(0, 8);
10850
11293
  return { suggestedName, description, body, triggerKeywords };
10851
11294
  }
@@ -10891,8 +11334,8 @@ async function writeSkeletonSkill(skillsDir, body, opts = {}) {
10891
11334
  subsystem: "general"
10892
11335
  });
10893
11336
  }
10894
- const skillDir = path23.join(skillsDir, name);
10895
- const skillFile = path23.join(skillDir, "SKILL.md");
11337
+ const skillDir = path24.join(skillsDir, name);
11338
+ const skillFile = path24.join(skillDir, "SKILL.md");
10896
11339
  if (!opts.overwrite) {
10897
11340
  try {
10898
11341
  await fs14.access(skillFile);
@@ -10914,7 +11357,7 @@ function bodyLineAdvisory(body) {
10914
11357
  const lines = body.split("\n").length;
10915
11358
  return { lines, over: lines > SKILL_LIMITS.SKILL_BODY_LINE_LIMIT };
10916
11359
  }
10917
- var STOPWORDS = /* @__PURE__ */ new Set([
11360
+ var STOPWORDS2 = /* @__PURE__ */ new Set([
10918
11361
  "the",
10919
11362
  "and",
10920
11363
  "for",
@@ -10951,14 +11394,14 @@ function defaultEditor() {
10951
11394
  init_errors();
10952
11395
  init_error();
10953
11396
  import * as fs17 from "node:fs/promises";
10954
- import * as path26 from "node:path";
11397
+ import * as path27 from "node:path";
10955
11398
 
10956
11399
  // src/skills/github-fetcher.ts
10957
11400
  init_errors();
10958
11401
  import { createWriteStream } from "node:fs";
10959
11402
  import * as fs15 from "node:fs/promises";
10960
11403
  import * as os6 from "node:os";
10961
- import * as path24 from "node:path";
11404
+ import * as path25 from "node:path";
10962
11405
  import { Readable } from "node:stream";
10963
11406
  import { pipeline } from "node:stream/promises";
10964
11407
  import { createGunzip } from "node:zlib";
@@ -11100,7 +11543,7 @@ async function downloadGitHubTarball(parsed) {
11100
11543
  }
11101
11544
  });
11102
11545
  }
11103
- const tempDir = await fs15.mkdtemp(path24.join(os6.tmpdir(), "wskill-"));
11546
+ const tempDir = await fs15.mkdtemp(path25.join(os6.tmpdir(), "wskill-"));
11104
11547
  try {
11105
11548
  if (!response.body) {
11106
11549
  throw new WrongStackError({
@@ -11111,7 +11554,7 @@ async function downloadGitHubTarball(parsed) {
11111
11554
  });
11112
11555
  }
11113
11556
  const nodeStream = Readable.fromWeb(response.body);
11114
- const tarPath = path24.join(tempDir, ".wrongstack-download.tar");
11557
+ const tarPath = path25.join(tempDir, ".wrongstack-download.tar");
11115
11558
  await writeBoundedGzipStream(nodeStream, tarPath, SKILL_LIMITS.MAX_UNCOMPRESSED_TARBALL_SIZE);
11116
11559
  const tarBuf = await fs15.readFile(tarPath);
11117
11560
  try {
@@ -11176,10 +11619,10 @@ async function extractTar(buf, destDir) {
11176
11619
  const fullPath = prefix ? `${prefix}/${name}` : name;
11177
11620
  const relPath = stripTopDir(fullPath);
11178
11621
  if (relPath && relPath !== "." && relPath !== "..") {
11179
- const destPath = path24.join(destDir, relPath);
11180
- const resolvedDest = path24.resolve(destPath);
11181
- const resolvedRoot = path24.resolve(destDir);
11182
- if (resolvedDest !== resolvedRoot && !resolvedDest.startsWith(resolvedRoot + path24.sep)) {
11622
+ const destPath = path25.join(destDir, relPath);
11623
+ const resolvedDest = path25.resolve(destPath);
11624
+ const resolvedRoot = path25.resolve(destDir);
11625
+ if (resolvedDest !== resolvedRoot && !resolvedDest.startsWith(resolvedRoot + path25.sep)) {
11183
11626
  offset += 512 + Math.ceil(size / 512) * 512;
11184
11627
  continue;
11185
11628
  }
@@ -11189,7 +11632,7 @@ async function extractTar(buf, destDir) {
11189
11632
  }
11190
11633
  }
11191
11634
  if ((typeflag === 48 || typeflag === 0 || typeflag === 0) && size > 0) {
11192
- const dir = path24.dirname(destPath);
11635
+ const dir = path25.dirname(destPath);
11193
11636
  await fs15.mkdir(dir, { recursive: true });
11194
11637
  const dataStart = offset + 512;
11195
11638
  const dataEnd = dataStart + size;
@@ -11204,7 +11647,7 @@ async function extractTar(buf, destDir) {
11204
11647
  // src/skills/manifest-store.ts
11205
11648
  init_atomic_write();
11206
11649
  import * as fs16 from "node:fs/promises";
11207
- import * as path25 from "node:path";
11650
+ import * as path26 from "node:path";
11208
11651
  var SkillManifestStore = class {
11209
11652
  manifestPath;
11210
11653
  cache;
@@ -11227,7 +11670,7 @@ var SkillManifestStore = class {
11227
11670
  return this.cache;
11228
11671
  }
11229
11672
  async write(data) {
11230
- const dir = path25.dirname(this.manifestPath);
11673
+ const dir = path26.dirname(this.manifestPath);
11231
11674
  await fs16.mkdir(dir, { recursive: true });
11232
11675
  await atomicWrite(this.manifestPath, JSON.stringify(data, null, 2) + "\n");
11233
11676
  this.cache = data;
@@ -11266,8 +11709,8 @@ var SkillManifestStore = class {
11266
11709
 
11267
11710
  // src/skills/skill-installer.ts
11268
11711
  function isInside(resolved, destDir) {
11269
- const root = path26.resolve(destDir);
11270
- return resolved === root || resolved.startsWith(root + path26.sep);
11712
+ const root = path27.resolve(destDir);
11713
+ return resolved === root || resolved.startsWith(root + path27.sep);
11271
11714
  }
11272
11715
  var MAX_SKILL_FILE_SIZE = SKILL_LIMITS.MAX_SKILL_FILE_SIZE;
11273
11716
  var SkillInstaller = class {
@@ -11322,13 +11765,13 @@ var SkillInstaller = class {
11322
11765
  this.opts.log?.(`Overwriting existing skill "${skill.name}" (${scope})...`);
11323
11766
  await this.removeSkillFiles(skill.name, scope);
11324
11767
  }
11325
- const destDir = path26.join(targetDir, skill.name);
11768
+ const destDir = path27.join(targetDir, skill.name);
11326
11769
  await fs17.mkdir(destDir, { recursive: true });
11327
11770
  const copiedFiles = [];
11328
11771
  for (const file of skill.files) {
11329
- const srcPath = path26.join(skill.baseDir, file);
11330
- const destPath = path26.join(destDir, file);
11331
- const resolved2 = path26.resolve(destPath);
11772
+ const srcPath = path27.join(skill.baseDir, file);
11773
+ const destPath = path27.join(destDir, file);
11774
+ const resolved2 = path27.resolve(destPath);
11332
11775
  if (!isInside(resolved2, destDir)) {
11333
11776
  throw new FsError({
11334
11777
  message: `Path traversal detected in skill file: ${file}`,
@@ -11346,7 +11789,7 @@ var SkillInstaller = class {
11346
11789
  context: { skillName: skill.name, fileSize: stat8.size, maxSize: MAX_SKILL_FILE_SIZE }
11347
11790
  });
11348
11791
  }
11349
- await fs17.mkdir(path26.dirname(destPath), { recursive: true });
11792
+ await fs17.mkdir(path27.dirname(destPath), { recursive: true });
11350
11793
  await fs17.copyFile(srcPath, destPath);
11351
11794
  copiedFiles.push(file);
11352
11795
  }
@@ -11404,7 +11847,7 @@ var SkillInstaller = class {
11404
11847
  const results = [];
11405
11848
  for (const e of entries) {
11406
11849
  if (!await entryIsDirectory(srcDir, e)) continue;
11407
- const skillMdPath = path26.join(srcDir, e.name, "SKILL.md");
11850
+ const skillMdPath = path27.join(srcDir, e.name, "SKILL.md");
11408
11851
  let content;
11409
11852
  try {
11410
11853
  content = await fs17.readFile(skillMdPath, "utf8");
@@ -11417,15 +11860,15 @@ var SkillInstaller = class {
11417
11860
  if (existing.find((x) => x.scope === scope)) {
11418
11861
  await this.removeSkillFiles(fm.name, scope);
11419
11862
  }
11420
- const destDir = path26.join(targetDir, fm.name);
11863
+ const destDir = path27.join(targetDir, fm.name);
11421
11864
  await fs17.mkdir(destDir, { recursive: true });
11422
- const srcSkillDir = path26.join(srcDir, e.name);
11865
+ const srcSkillDir = path27.join(srcDir, e.name);
11423
11866
  const files = await collectFiles(srcSkillDir, srcSkillDir);
11424
11867
  const copiedFiles = [];
11425
11868
  for (const file of files) {
11426
- const srcPath = path26.join(srcSkillDir, file);
11427
- const destPath = path26.join(destDir, file);
11428
- const resolved = path26.resolve(destPath);
11869
+ const srcPath = path27.join(srcSkillDir, file);
11870
+ const destPath = path27.join(destDir, file);
11871
+ const resolved = path27.resolve(destPath);
11429
11872
  if (!isInside(resolved, destDir)) {
11430
11873
  throw new FsError({
11431
11874
  message: `Path traversal detected in skill file: ${file}`,
@@ -11434,7 +11877,7 @@ var SkillInstaller = class {
11434
11877
  context: { reason: "path_traversal", skillName: fm.name }
11435
11878
  });
11436
11879
  }
11437
- await fs17.mkdir(path26.dirname(destPath), { recursive: true });
11880
+ await fs17.mkdir(path27.dirname(destPath), { recursive: true });
11438
11881
  if (opts?.link) {
11439
11882
  try {
11440
11883
  await fs17.symlink(srcPath, destPath);
@@ -11597,7 +12040,7 @@ var SkillInstaller = class {
11597
12040
  */
11598
12041
  async detectSkills(baseDir) {
11599
12042
  const results = [];
11600
- const rootSkillMd = path26.join(baseDir, "SKILL.md");
12043
+ const rootSkillMd = path27.join(baseDir, "SKILL.md");
11601
12044
  try {
11602
12045
  await fs17.access(rootSkillMd);
11603
12046
  const content = await fs17.readFile(rootSkillMd, "utf8");
@@ -11612,17 +12055,17 @@ var SkillInstaller = class {
11612
12055
  }
11613
12056
  } catch {
11614
12057
  }
11615
- const skillsDir = path26.join(baseDir, "skills");
12058
+ const skillsDir = path27.join(baseDir, "skills");
11616
12059
  try {
11617
12060
  const entries = await fs17.readdir(skillsDir, { withFileTypes: true });
11618
12061
  for (const entry of entries) {
11619
12062
  if (!entry.isDirectory()) continue;
11620
- const skillFile = path26.join(skillsDir, entry.name, "SKILL.md");
12063
+ const skillFile = path27.join(skillsDir, entry.name, "SKILL.md");
11621
12064
  try {
11622
12065
  const content = await fs17.readFile(skillFile, "utf8");
11623
12066
  const fm = parseSkillFrontmatter(content);
11624
12067
  if (fm.name && fm.description && isValidSkillNameFormat(fm.name)) {
11625
- const skillDir = path26.join(skillsDir, entry.name);
12068
+ const skillDir = path27.join(skillsDir, entry.name);
11626
12069
  const files = await collectFiles(skillDir, skillDir);
11627
12070
  results.push({
11628
12071
  name: fm.name,
@@ -11655,8 +12098,8 @@ var SkillInstaller = class {
11655
12098
  */
11656
12099
  async removeSkillFiles(name, scope) {
11657
12100
  const targetDir = scope === "project" ? this.opts.projectSkillsDir : this.opts.globalSkillsDir;
11658
- const root = path26.resolve(targetDir);
11659
- const skillDir = path26.resolve(path26.join(targetDir, name));
12101
+ const root = path27.resolve(targetDir);
12102
+ const skillDir = path27.resolve(path27.join(targetDir, name));
11660
12103
  if (skillDir === root || !isInside(skillDir, root)) {
11661
12104
  throw new FsError({
11662
12105
  message: `Refusing to delete skill files outside the skills directory: ${name}`,
@@ -11700,7 +12143,7 @@ async function entryIsDirectory(dir, entry) {
11700
12143
  if (entry.isDirectory()) return true;
11701
12144
  if (entry.isSymbolicLink()) {
11702
12145
  try {
11703
- return (await fs17.stat(path26.join(dir, entry.name))).isDirectory();
12146
+ return (await fs17.stat(path27.join(dir, entry.name))).isDirectory();
11704
12147
  } catch {
11705
12148
  return false;
11706
12149
  }
@@ -11711,8 +12154,8 @@ async function collectFiles(dir, baseDir) {
11711
12154
  const results = [];
11712
12155
  const entries = await fs17.readdir(dir, { withFileTypes: true });
11713
12156
  for (const entry of entries) {
11714
- const fullPath = path26.join(dir, entry.name);
11715
- const relPath = path26.relative(baseDir, fullPath);
12157
+ const fullPath = path27.join(dir, entry.name);
12158
+ const relPath = path27.relative(baseDir, fullPath);
11716
12159
  if (entry.isDirectory()) {
11717
12160
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
11718
12161
  results.push(...await collectFiles(fullPath, baseDir));
@@ -11791,7 +12234,7 @@ function createSkillsPlugin(opts) {
11791
12234
  function makeInstaller(skillLoader, projectRoot, registryAdapters) {
11792
12235
  const paths = resolveWstackPaths({ projectRoot });
11793
12236
  return new SkillInstaller({
11794
- manifestPath: path27.join(paths.configDir, "installed-skills.json"),
12237
+ manifestPath: path28.join(paths.configDir, "installed-skills.json"),
11795
12238
  projectSkillsDir: paths.inProjectSkills,
11796
12239
  globalSkillsDir: paths.globalSkills,
11797
12240
  projectHash: paths.projectHash,
@@ -12170,7 +12613,7 @@ function resolveImportSourceDir(tool, opts) {
12170
12613
  const entry = IMPORT_SOURCE_TOOLS.find((t) => t.id === tool);
12171
12614
  if (!entry) return void 0;
12172
12615
  const base = opts.global ? opts.homeDir ?? os7.homedir() : opts.projectRoot;
12173
- return path27.join(base, "." + entry.id, entry.subdir);
12616
+ return path28.join(base, "." + entry.id, entry.subdir);
12174
12617
  }
12175
12618
  function buildSkillImportCommand(skillLoader) {
12176
12619
  return {
@@ -12211,7 +12654,7 @@ function buildSkillImportCommand(skillLoader) {
12211
12654
  };
12212
12655
  }
12213
12656
  } else if (positional) {
12214
- srcDir = path27.resolve(ctx.projectRoot, positional);
12657
+ srcDir = path28.resolve(ctx.projectRoot, positional);
12215
12658
  } else {
12216
12659
  return {
12217
12660
  message: "Usage: /skill-import <src-dir> | --from <tool> | --from-claude [--global] [--link]"
@@ -12338,14 +12781,14 @@ function truncate(s, max) {
12338
12781
  }
12339
12782
 
12340
12783
  // src/plugins/sync-plugin.ts
12341
- import * as path29 from "node:path";
12784
+ import * as path30 from "node:path";
12342
12785
  init_error();
12343
12786
 
12344
12787
  // src/storage/cloud-sync.ts
12345
12788
  init_atomic_write();
12346
12789
  init_errors();
12347
12790
  import * as fs18 from "node:fs/promises";
12348
- import * as path28 from "node:path";
12791
+ import * as path29 from "node:path";
12349
12792
  import { createHash as createHash9 } from "node:crypto";
12350
12793
  var ALL_SYNC_CATEGORIES = ["settings", "skills", "prompts", "memory", "history"];
12351
12794
  var CloudSync = class {
@@ -12353,8 +12796,8 @@ var CloudSync = class {
12353
12796
  this.paths = paths;
12354
12797
  this.getConfig = getConfig;
12355
12798
  this.setConfig = setConfig;
12356
- this.statePath = path28.join(paths.configDir, "sync-state.json");
12357
- this.getSettingsConfigPath = getSettingsConfigPath ?? (() => path28.join(this.paths.configDir, "config.json"));
12799
+ this.statePath = path29.join(paths.configDir, "sync-state.json");
12800
+ this.getSettingsConfigPath = getSettingsConfigPath ?? (() => path29.join(this.paths.configDir, "config.json"));
12358
12801
  }
12359
12802
  paths;
12360
12803
  getConfig;
@@ -12640,7 +13083,7 @@ var CloudSync = class {
12640
13083
  const files = await this.walkDir(localPath, localPath);
12641
13084
  for (const file of files) {
12642
13085
  const content = await fs18.readFile(file, "utf8");
12643
- const rel = path28.relative(localPath, file).replace(/\\/g, "/");
13086
+ const rel = path29.relative(localPath, file).replace(/\\/g, "/");
12644
13087
  entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
12645
13088
  hashes.push(`${cat}/${rel}\0${content}`);
12646
13089
  }
@@ -12667,7 +13110,7 @@ var CloudSync = class {
12667
13110
  const files = await this.walkDir(localPath, localPath);
12668
13111
  for (const file of files) {
12669
13112
  const content = await fs18.readFile(file, "utf8");
12670
- const rel = path28.relative(localPath, file).replace(/\\/g, "/");
13113
+ const rel = path29.relative(localPath, file).replace(/\\/g, "/");
12671
13114
  hashes.push(`${cat}/${rel}\0${content}`);
12672
13115
  }
12673
13116
  } else {
@@ -12701,7 +13144,7 @@ var CloudSync = class {
12701
13144
  const entries = await fs18.readdir(dir, { withFileTypes: true });
12702
13145
  entries.sort((a, b) => a.name.localeCompare(b.name));
12703
13146
  for (const entry of entries) {
12704
- const full = path28.join(dir, entry.name);
13147
+ const full = path29.join(dir, entry.name);
12705
13148
  if (entry.isSymbolicLink()) continue;
12706
13149
  if (entry.isDirectory()) {
12707
13150
  results.push(...await this.walkDir(full, base));
@@ -12714,17 +13157,17 @@ var CloudSync = class {
12714
13157
  };
12715
13158
  async function preparePulledDestination(cat, localPath, destPath, remotePath) {
12716
13159
  const directoryBacked = cat === "skills" || cat === "prompts";
12717
- const rootPath = directoryBacked ? localPath : path28.dirname(localPath);
13160
+ const rootPath = directoryBacked ? localPath : path29.dirname(localPath);
12718
13161
  const rootStat = await lstatIfExists(rootPath);
12719
13162
  if (rootStat?.isSymbolicLink()) {
12720
13163
  throw unsafePulledSymlinkError(remotePath, rootPath);
12721
13164
  }
12722
13165
  if (directoryBacked) {
12723
- const relativeParent = path28.relative(rootPath, path28.dirname(destPath));
13166
+ const relativeParent = path29.relative(rootPath, path29.dirname(destPath));
12724
13167
  let cursor = rootPath;
12725
13168
  if (relativeParent) {
12726
- for (const segment of relativeParent.split(path28.sep)) {
12727
- cursor = path28.join(cursor, segment);
13169
+ for (const segment of relativeParent.split(path29.sep)) {
13170
+ cursor = path29.join(cursor, segment);
12728
13171
  const stat8 = await lstatIfExists(cursor);
12729
13172
  if (stat8?.isSymbolicLink()) throw unsafePulledSymlinkError(remotePath, cursor);
12730
13173
  }
@@ -12732,7 +13175,7 @@ async function preparePulledDestination(cat, localPath, destPath, remotePath) {
12732
13175
  }
12733
13176
  const destStat = await lstatIfExists(destPath);
12734
13177
  if (destStat?.isSymbolicLink()) throw unsafePulledSymlinkError(remotePath, destPath);
12735
- await fs18.mkdir(path28.dirname(destPath), { recursive: true });
13178
+ await fs18.mkdir(path29.dirname(destPath), { recursive: true });
12736
13179
  }
12737
13180
  async function lstatIfExists(filePath) {
12738
13181
  try {
@@ -12762,9 +13205,9 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
12762
13205
  return localPath;
12763
13206
  }
12764
13207
  if (!rel) return localPath;
12765
- const normalizedRel = path28.normalize(rel);
12766
- const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path28.sep}`);
12767
- if (path28.isAbsolute(normalizedRel) || traversesUp) {
13208
+ const normalizedRel = path29.normalize(rel);
13209
+ const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path29.sep}`);
13210
+ if (path29.isAbsolute(normalizedRel) || traversesUp) {
12768
13211
  throw new FsError({
12769
13212
  message: `Refusing CloudSync path traversal: ${remotePath}`,
12770
13213
  code: ERROR_CODES.FS_DELETE_FAILED,
@@ -12772,10 +13215,10 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
12772
13215
  context: { reason: "path_traversal", normalizedRel }
12773
13216
  });
12774
13217
  }
12775
- const dest = path28.resolve(localPath, normalizedRel);
12776
- const root = path28.resolve(localPath);
12777
- const relative5 = path28.relative(root, dest);
12778
- if (relative5.startsWith("..") || path28.isAbsolute(relative5)) {
13218
+ const dest = path29.resolve(localPath, normalizedRel);
13219
+ const root = path29.resolve(localPath);
13220
+ const relative5 = path29.relative(root, dest);
13221
+ if (relative5.startsWith("..") || path29.isAbsolute(relative5)) {
12779
13222
  throw new FsError({
12780
13223
  message: `Refusing CloudSync path outside category root: ${remotePath}`,
12781
13224
  code: ERROR_CODES.FS_DELETE_FAILED,
@@ -12818,7 +13261,7 @@ function createSyncPlugin(opts) {
12818
13261
  api.log.warn("[sync] paths, configStore, or configDir not available \u2014 /sync disabled");
12819
13262
  return;
12820
13263
  }
12821
- const syncConfigPath = paths.syncConfig ?? path29.join(paths.configDir, "sync.json");
13264
+ const syncConfigPath = paths.syncConfig ?? path30.join(paths.configDir, "sync.json");
12822
13265
  cloud = new CloudSync(
12823
13266
  paths,
12824
13267
  () => {
@@ -12829,7 +13272,7 @@ function createSyncPlugin(opts) {
12829
13272
  await persistSyncConfig(syncConfigPath, cfg, vault);
12830
13273
  configStore?.update({ sync: cfg });
12831
13274
  },
12832
- () => path29.join(paths.configDir, "config.json")
13275
+ () => path30.join(paths.configDir, "config.json")
12833
13276
  );
12834
13277
  void cloud.loadState();
12835
13278
  api.slashCommands.register(buildSyncCommand(cloud, configStore, vault, syncConfigPath));
@@ -13021,7 +13464,7 @@ Warning: sync completed, but metadata persistence failed: ${toErrorMessage(err)}
13021
13464
  }
13022
13465
 
13023
13466
  // src/plugins/cloud-config-sync-plugin.ts
13024
- import * as path31 from "node:path";
13467
+ import * as path32 from "node:path";
13025
13468
 
13026
13469
  // src/security/config-secrets.ts
13027
13470
  function decryptConfigSecrets(cfg, vault, opts) {
@@ -13070,7 +13513,7 @@ function isSecretField(name) {
13070
13513
  init_atomic_write();
13071
13514
  import { createHash as createHash10, randomUUID as nodeRandomUUID } from "node:crypto";
13072
13515
  import * as fs19 from "node:fs/promises";
13073
- import * as path30 from "node:path";
13516
+ import * as path31 from "node:path";
13074
13517
 
13075
13518
  // src/storage/cloud-config-sync/sanitize.ts
13076
13519
  var SAGE_TREE = {
@@ -13645,7 +14088,7 @@ var CloudConfigSync = class {
13645
14088
  }
13646
14089
  async saveState(state) {
13647
14090
  this.state = state;
13648
- await fs19.mkdir(path30.dirname(this.deps.statePath), { recursive: true });
14091
+ await fs19.mkdir(path31.dirname(this.deps.statePath), { recursive: true });
13649
14092
  await atomicWrite(this.deps.statePath, `${JSON.stringify(state, null, 2)}
13650
14093
  `, {
13651
14094
  mode: 384
@@ -13692,8 +14135,8 @@ function createCloudConfigSyncPlugin(opts) {
13692
14135
  );
13693
14136
  return;
13694
14137
  }
13695
- const profileConfigPath = path31.join(paths.configDir, "config.json");
13696
- const statePath = path31.join(paths.configDir, "cloud-sync-state.json");
14138
+ const profileConfigPath = path32.join(paths.configDir, "config.json");
14139
+ const statePath = path32.join(paths.configDir, "cloud-sync-state.json");
13697
14140
  const warn = (msg) => api.log.warn(msg);
13698
14141
  const readLocalConfig = async () => {
13699
14142
  const raw = await readJsonObjectFile(profileConfigPath).catch(() => ({}));
@@ -13870,6 +14313,7 @@ export {
13870
14313
  DefaultPluginAPI,
13871
14314
  KERNEL_API_VERSION,
13872
14315
  buildReviewerModelPool,
14316
+ classifyChimeraReviewSource,
13873
14317
  createAutoReviewPlugin,
13874
14318
  createChimeraPlugin,
13875
14319
  createCloudConfigSyncPlugin,
@@ -13894,6 +14338,8 @@ export {
13894
14338
  resolvePluginManifestConfig,
13895
14339
  selectRoundRobinReviewerAssignment,
13896
14340
  unloadPlugins,
13897
- validatePluginConfigMetadata
14341
+ updateReviewReportEvidence,
14342
+ validatePluginConfigMetadata,
14343
+ verifyFindingsAgainstDisk
13898
14344
  };
13899
14345
  //# sourceMappingURL=index.js.map