@perpscope/percolator-adapter 0.8.0 → 0.9.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/README.md +8 -0
- package/bin/perpscope.mjs +38 -1
- package/index.js +2 -0
- package/package.json +1 -1
- package/src/lib/percolator-adapter.js +96 -1
package/README.md
CHANGED
|
@@ -65,11 +65,17 @@ The full field-level contract is documented in `../../docs/field-compatibility-m
|
|
|
65
65
|
|
|
66
66
|
`buildCompatibilityRealityCheck(report, { input })` returns `perpscope.reality-check` with provenance, required/useful mapped counts, unknown fields, and alias counts. Use it when a terminal needs to show whether a capture is synthetic, real-backed candidate, or externally submitted.
|
|
67
67
|
|
|
68
|
+
`buildCompatibilityDoctor(report, { input })` returns `perpscope.compatibility-doctor` with pass/check status, shape, safety, required/useful mapped fields, unknown fields, alias suggestions, and next actions.
|
|
69
|
+
|
|
70
|
+
`buildCompatibilityBadge(reportOrDoctor)` returns `perpscope.compatibility-badge` with Markdown and JSON-friendly fields for READMEs, PRs, and capture handoffs.
|
|
71
|
+
|
|
68
72
|
## CLI
|
|
69
73
|
|
|
70
74
|
```bash
|
|
71
75
|
perpscope compat report capture.json
|
|
72
76
|
perpscope compat diff previous.json current.json
|
|
77
|
+
perpscope compat doctor capture.json
|
|
78
|
+
perpscope compat badge capture.json --json
|
|
73
79
|
```
|
|
74
80
|
|
|
75
81
|
Try it locally with:
|
|
@@ -80,6 +86,8 @@ perpscope compat diff ../../examples/fixture-pack-minimal-terminal.json ../../ex
|
|
|
80
86
|
|
|
81
87
|
For the real-backed candidate path, try `../../examples/fixture-pack-real-sanitized-rpc-shape.json`.
|
|
82
88
|
|
|
89
|
+
For a copy-paste starter shape, use `../../examples/capture-template.json`.
|
|
90
|
+
|
|
83
91
|
## DTO Example
|
|
84
92
|
|
|
85
93
|
```js
|
package/bin/perpscope.mjs
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import {
|
|
4
|
+
buildCompatibilityBadge,
|
|
5
|
+
buildCompatibilityDoctor,
|
|
6
|
+
buildReadOnlyRpcSnapshot,
|
|
4
7
|
buildPercolatorCompatibilityReport,
|
|
5
8
|
compareCompatibilityReports,
|
|
9
|
+
detectPercolatorInputShape,
|
|
6
10
|
exportCompatibilityReport,
|
|
7
11
|
normalizePercolatorSnapshot,
|
|
8
12
|
parsePercolatorJson
|
|
@@ -15,6 +19,8 @@ function usage() {
|
|
|
15
19
|
"Usage:",
|
|
16
20
|
" perpscope compat report <capture.json>",
|
|
17
21
|
" perpscope compat diff <previous.json> <current.json>",
|
|
22
|
+
" perpscope compat doctor <capture.json>",
|
|
23
|
+
" perpscope compat badge <capture.json> [--json|--markdown]",
|
|
18
24
|
"",
|
|
19
25
|
"Read-only only: the adapter rejects wallet, signer, transaction, instruction, order, private key, seed, mnemonic, and API key fields."
|
|
20
26
|
].join("\n");
|
|
@@ -26,10 +32,30 @@ function readCapture(path) {
|
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
function buildReport(input) {
|
|
29
|
-
const snapshot =
|
|
35
|
+
const snapshot = detectPercolatorInputShape(input) === "read-only-rpc-fetch"
|
|
36
|
+
? buildReadOnlyRpcSnapshot(input)
|
|
37
|
+
: normalizePercolatorSnapshot(input);
|
|
30
38
|
return buildPercolatorCompatibilityReport(input, snapshot);
|
|
31
39
|
}
|
|
32
40
|
|
|
41
|
+
function formatDoctor(doctor) {
|
|
42
|
+
const lines = [
|
|
43
|
+
`PerpScope compat doctor: ${doctor.pass ? "PASS" : "CHECK"}`,
|
|
44
|
+
`shape: ${doctor.shape}`,
|
|
45
|
+
`status: ${doctor.status} (${doctor.score}/100)`,
|
|
46
|
+
`safety: ${doctor.safety}`,
|
|
47
|
+
`required: ${doctor.required.label}`,
|
|
48
|
+
`useful: ${doctor.useful.label}`,
|
|
49
|
+
`unknown fields: ${doctor.unknownFields.length}`,
|
|
50
|
+
`alias suggestions: ${doctor.aliasSuggestions.length}`
|
|
51
|
+
];
|
|
52
|
+
if (doctor.nextActions.length) {
|
|
53
|
+
lines.push("next actions:");
|
|
54
|
+
for (const action of doctor.nextActions) lines.push(`- ${action}`);
|
|
55
|
+
}
|
|
56
|
+
return lines.join("\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
33
59
|
function main() {
|
|
34
60
|
const [scope, command, ...rest] = args;
|
|
35
61
|
if (!scope || scope === "--help" || scope === "-h") {
|
|
@@ -48,6 +74,17 @@ function main() {
|
|
|
48
74
|
console.log(JSON.stringify(compareCompatibilityReports(previous, current), null, 2));
|
|
49
75
|
return;
|
|
50
76
|
}
|
|
77
|
+
if (command === "doctor") {
|
|
78
|
+
const input = readCapture(rest[0]);
|
|
79
|
+
console.log(formatDoctor(buildCompatibilityDoctor(buildReport(input), { input })));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (command === "badge") {
|
|
83
|
+
const input = readCapture(rest[0]);
|
|
84
|
+
const badge = buildCompatibilityBadge(buildReport(input), { input });
|
|
85
|
+
console.log(rest.includes("--json") ? JSON.stringify(badge, null, 2) : badge.markdown);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
51
88
|
throw new Error(`Unknown compat command: ${command || ""}`.trim());
|
|
52
89
|
}
|
|
53
90
|
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { normalizeFundingSkewHistory } from "./funding-history.js";
|
|
2
2
|
|
|
3
|
-
export const PERPSCOPE_ADAPTER_VERSION = "0.
|
|
3
|
+
export const PERPSCOPE_ADAPTER_VERSION = "0.9.0";
|
|
4
4
|
|
|
5
5
|
const KEYPAIR_FIELD_PATTERN = /(^|_)(secret|private|keypair|mnemonic|seed|walletPath|wallet)(_|$)/i;
|
|
6
6
|
const HISTORY_COMMAND_KEYS = new Set([
|
|
@@ -285,6 +285,75 @@ export function buildCompatibilityRealityCheck(inputOrReport, options = {}) {
|
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
export function buildCompatibilityDoctor(inputOrReport, options = {}) {
|
|
289
|
+
const hasReportShape = inputOrReport && typeof inputOrReport === "object" && Array.isArray(inputOrReport.recognizedSections);
|
|
290
|
+
const report = hasReportShape ? normalizeCompatibilityReport(inputOrReport) : buildPercolatorCompatibilityReport(inputOrReport);
|
|
291
|
+
const reality = buildCompatibilityRealityCheck(report, {
|
|
292
|
+
input: hasReportShape ? options.input : inputOrReport,
|
|
293
|
+
generatedAt: options.generatedAt,
|
|
294
|
+
packageVersion: options.packageVersion
|
|
295
|
+
});
|
|
296
|
+
const requiredLane = reality.lanes.find((lane) => lane.label === "required") || {};
|
|
297
|
+
const usefulLane = reality.lanes.find((lane) => lane.label === "useful") || {};
|
|
298
|
+
const pass = report.status === "compatible" || (reality.gaps.dangerMissing === 0 && report.status === "partial");
|
|
299
|
+
const safety = report.status === "rejected" ? "rejected" : "read-only";
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
schema: "perpscope.compatibility-doctor",
|
|
303
|
+
version: 1,
|
|
304
|
+
package: {
|
|
305
|
+
name: "@perpscope/percolator-adapter",
|
|
306
|
+
version: options.packageVersion || PERPSCOPE_ADAPTER_VERSION
|
|
307
|
+
},
|
|
308
|
+
generatedAt: options.generatedAt || new Date().toISOString(),
|
|
309
|
+
pass,
|
|
310
|
+
tone: report.status === "rejected" || reality.gaps.dangerMissing ? "danger" : report.summary.ignoredCount || report.summary.suggestionCount ? "warning" : "good",
|
|
311
|
+
status: report.status,
|
|
312
|
+
shape: report.shape,
|
|
313
|
+
score: report.score,
|
|
314
|
+
safety,
|
|
315
|
+
source: report.source,
|
|
316
|
+
required: {
|
|
317
|
+
mapped: reality.mapped.requiredCount,
|
|
318
|
+
total: reality.mapped.requiredTotal,
|
|
319
|
+
label: requiredLane.value || `${reality.mapped.requiredCount}/${reality.mapped.requiredTotal}`
|
|
320
|
+
},
|
|
321
|
+
useful: {
|
|
322
|
+
mapped: reality.mapped.optionalCount,
|
|
323
|
+
total: reality.mapped.optionalTotal,
|
|
324
|
+
label: usefulLane.value || `${reality.mapped.optionalCount}/${reality.mapped.optionalTotal}`
|
|
325
|
+
},
|
|
326
|
+
unknownFields: report.ignoredFields || [],
|
|
327
|
+
aliasSuggestions: report.aliasSuggestions || [],
|
|
328
|
+
missingFields: report.missingFields || [],
|
|
329
|
+
nextActions: compatibilityDoctorActions(report, reality)
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function buildCompatibilityBadge(inputOrReport, options = {}) {
|
|
334
|
+
const doctor = inputOrReport?.schema === "perpscope.compatibility-doctor"
|
|
335
|
+
? inputOrReport
|
|
336
|
+
: buildCompatibilityDoctor(inputOrReport, options);
|
|
337
|
+
const label = options.label || "PerpScope compatible";
|
|
338
|
+
const summary = `${doctor.status}, ${doctor.score}/100, ${doctor.aliasSuggestions.length} alias suggestions`;
|
|
339
|
+
return {
|
|
340
|
+
schema: "perpscope.compatibility-badge",
|
|
341
|
+
version: 1,
|
|
342
|
+
package: doctor.package,
|
|
343
|
+
generatedAt: options.generatedAt || doctor.generatedAt || new Date().toISOString(),
|
|
344
|
+
label,
|
|
345
|
+
status: doctor.status,
|
|
346
|
+
score: doctor.score,
|
|
347
|
+
tone: doctor.tone,
|
|
348
|
+
aliasSuggestionCount: doctor.aliasSuggestions.length,
|
|
349
|
+
unknownFieldCount: doctor.unknownFields.length,
|
|
350
|
+
required: doctor.required,
|
|
351
|
+
useful: doctor.useful,
|
|
352
|
+
markdown: `**${label}:** ${summary}`,
|
|
353
|
+
text: `${label}: ${summary}`
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
288
357
|
const COMPATIBILITY_SECTION_SPECS = [
|
|
289
358
|
{
|
|
290
359
|
id: "safety",
|
|
@@ -737,6 +806,32 @@ function realityProvenance(input, report) {
|
|
|
737
806
|
};
|
|
738
807
|
}
|
|
739
808
|
|
|
809
|
+
function compatibilityDoctorActions(report, reality) {
|
|
810
|
+
const actions = [];
|
|
811
|
+
if (report.status === "rejected") {
|
|
812
|
+
actions.push("Remove wallet, signer, transaction, instruction, order, secret, private key, seed, mnemonic, or API key fields.");
|
|
813
|
+
return actions;
|
|
814
|
+
}
|
|
815
|
+
const dangerMissing = (report.missingFields || []).filter((field) => field.severity === "danger");
|
|
816
|
+
if (dangerMissing.length) {
|
|
817
|
+
actions.push(`Map required fields: ${dangerMissing.map((field) => field.field).join(", ")}.`);
|
|
818
|
+
}
|
|
819
|
+
const warningMissing = (report.missingFields || []).filter((field) => field.severity !== "danger").slice(0, 3);
|
|
820
|
+
if (warningMissing.length) {
|
|
821
|
+
actions.push(`Add useful trader fields: ${warningMissing.map((field) => field.field).join(", ")}.`);
|
|
822
|
+
}
|
|
823
|
+
if ((report.aliasSuggestions || []).length) {
|
|
824
|
+
actions.push(`Apply alias suggestions: ${(report.aliasSuggestions || []).slice(0, 3).map((suggestion) => `${suggestion.candidatePath || suggestion.action} -> ${suggestion.field}`).join(", ")}.`);
|
|
825
|
+
}
|
|
826
|
+
if ((report.ignoredFields || []).length) {
|
|
827
|
+
actions.push(`${report.ignoredFields.length} unknown field${report.ignoredFields.length === 1 ? "" : "s"} can be mapped or intentionally ignored.`);
|
|
828
|
+
}
|
|
829
|
+
if (!actions.length && reality.gaps.dangerMissing === 0) {
|
|
830
|
+
actions.push("Ready for read-only terminal display.");
|
|
831
|
+
}
|
|
832
|
+
return actions;
|
|
833
|
+
}
|
|
834
|
+
|
|
740
835
|
function differenceByField(left = [], right = []) {
|
|
741
836
|
const rightFields = new Set((right || []).map((entry) => entry.field));
|
|
742
837
|
return (left || []).filter((entry) => !rightFields.has(entry.field));
|