@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.
- package/dist/coordination/agents/index.js +3 -2
- package/dist/coordination/agents/types.d.ts +1 -1
- package/dist/coordination/index.js +3 -2
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +2764 -2632
- 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 +3 -2
- package/dist/execution/index.js +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +875 -530
- package/dist/observability/index.js +7 -3
- package/dist/plugin/index.d.ts +4 -3
- package/dist/plugin/index.js +589 -143
- 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/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.js +8 -9
- 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
|
@@ -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,
|
|
1166
|
+
function walk(value, schema, path33, errors, depth) {
|
|
1145
1167
|
if (depth > MAX_SCHEMA_DEPTH) {
|
|
1146
1168
|
errors.push({
|
|
1147
|
-
path:
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
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, `${
|
|
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) =>
|
|
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
|
|
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(
|
|
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 =
|
|
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, {
|
|
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-
|
|
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
|
-
|
|
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
|
|
10519
|
-
import * as
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 && !
|
|
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 =
|
|
10895
|
-
const skillFile =
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
11180
|
-
const resolvedDest =
|
|
11181
|
-
const resolvedRoot =
|
|
11182
|
-
if (resolvedDest !== resolvedRoot && !resolvedDest.startsWith(resolvedRoot +
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
11270
|
-
return resolved === root || resolved.startsWith(root +
|
|
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 =
|
|
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 =
|
|
11330
|
-
const destPath =
|
|
11331
|
-
const resolved2 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
11863
|
+
const destDir = path27.join(targetDir, fm.name);
|
|
11421
11864
|
await fs17.mkdir(destDir, { recursive: true });
|
|
11422
|
-
const srcSkillDir =
|
|
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 =
|
|
11427
|
-
const destPath =
|
|
11428
|
-
const resolved =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
11659
|
-
const skillDir =
|
|
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(
|
|
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 =
|
|
11715
|
-
const relPath =
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
12357
|
-
this.getSettingsConfigPath = getSettingsConfigPath ?? (() =>
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 :
|
|
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 =
|
|
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(
|
|
12727
|
-
cursor =
|
|
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(
|
|
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 =
|
|
12766
|
-
const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${
|
|
12767
|
-
if (
|
|
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 =
|
|
12776
|
-
const root =
|
|
12777
|
-
const relative5 =
|
|
12778
|
-
if (relative5.startsWith("..") ||
|
|
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 ??
|
|
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
|
-
() =>
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
13696
|
-
const statePath =
|
|
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
|
-
|
|
14341
|
+
updateReviewReportEvidence,
|
|
14342
|
+
validatePluginConfigMetadata,
|
|
14343
|
+
verifyFindingsAgainstDisk
|
|
13898
14344
|
};
|
|
13899
14345
|
//# sourceMappingURL=index.js.map
|