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