sigrank 0.19.0 → 1.0.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/MCP_SERVER_DESCRIPTION.md +172 -0
  3. package/README.md +89 -41
  4. package/adapters/index.mjs +1100 -0
  5. package/adapters/tokenpull.mjs +568 -0
  6. package/adapters.mjs +1 -865
  7. package/analytics/cascade.mjs +338 -0
  8. package/bin/sigrank.mjs +137 -0
  9. package/cascade.mjs +1 -274
  10. package/cli.mjs +1 -1853
  11. package/connect.mjs +1 -13
  12. package/identity/connect.mjs +13 -0
  13. package/identity/keystore.mjs +231 -0
  14. package/identity/sign.mjs +106 -0
  15. package/index.mjs +47 -162
  16. package/keystore.mjs +1 -242
  17. package/lib/constants.mjs +137 -0
  18. package/lib/pkg-version.mjs +59 -0
  19. package/manifest.json +46 -0
  20. package/narrate.mjs +1 -77
  21. package/package.json +44 -18
  22. package/preflight.mjs +1 -191
  23. package/presentation/cli.mjs +1926 -0
  24. package/presentation/narrate.mjs +219 -0
  25. package/presentation/tui-themes.mjs +189 -0
  26. package/presentation/tui.mjs +3316 -0
  27. package/prompts/check-my-efficiency.md +1 -0
  28. package/prompts/compare-with-leader.md +1 -0
  29. package/prompts/simulate-improvement.md +1 -0
  30. package/proxy.mjs +492 -0
  31. package/resources/class-tiers.md +81 -0
  32. package/resources/data-policy.md +31 -0
  33. package/resources/install-guide.md +59 -0
  34. package/resources/privacy-model.md +44 -0
  35. package/resources/scoring-formula.md +24 -0
  36. package/review.mjs +263 -0
  37. package/sign.mjs +1 -106
  38. package/submit/index.mjs +297 -0
  39. package/submit/preflight.mjs +191 -0
  40. package/submit.mjs +1 -303
  41. package/tokenpull.mjs +1 -576
  42. package/tokscale_analytics.mjs +864 -0
  43. package/tools/_framing.mjs +217 -0
  44. package/tools/_helpers.mjs +253 -0
  45. package/tools/_peer-matching.mjs +361 -0
  46. package/tools/_report.mjs +262 -0
  47. package/tools/_schemas.mjs +492 -0
  48. package/tools/_verifiers.mjs +148 -0
  49. package/tools/compare-operators.mjs +80 -0
  50. package/tools/compare-self.mjs +124 -0
  51. package/tools/describe-power-user.mjs +78 -0
  52. package/tools/diagnose-cascade.mjs +192 -0
  53. package/tools/discover-peers.mjs +198 -0
  54. package/tools/enroll.mjs +119 -0
  55. package/tools/get-best-operator.mjs +59 -0
  56. package/tools/get-leaderboard.mjs +27 -0
  57. package/tools/get-operator.mjs +33 -0
  58. package/tools/index.mjs +233 -0
  59. package/tools/optimize-efficiency.mjs +170 -0
  60. package/tools/rank-paste.mjs +51 -0
  61. package/tools/rank-windows.mjs +120 -0
  62. package/tools/self-improve.mjs +423 -0
  63. package/tools/simulate-change.mjs +207 -0
  64. package/tools/standard-record.mjs +108 -0
  65. package/tools/submit-paste.mjs +121 -0
  66. package/tools/submit-verified.mjs +153 -0
  67. package/tools/suggest-improvements.mjs +168 -0
  68. package/tools/tokenpull-compare.mjs +123 -0
  69. package/tools/tokenpull-submit.mjs +235 -0
  70. package/tools/tokenpull.mjs +113 -0
  71. package/tools/tokscale-breakdown.mjs +119 -0
  72. package/tools/tokscale-competitive-intel.mjs +45 -0
  73. package/tools/tokscale-cost-analysis.mjs +31 -0
  74. package/tools/tokscale-developer-profile.mjs +36 -0
  75. package/tools/tokscale-device-profile.mjs +35 -0
  76. package/tools/tokscale-market-share.mjs +38 -0
  77. package/tools/tokscale-mcp-usage.mjs +33 -0
  78. package/tools/tokscale-model-trends.mjs +32 -0
  79. package/tools/watch-tokenpull.mjs +134 -0
  80. package/tools.mjs +1 -3897
  81. package/tui.mjs +4 -2650
  82. package/banner.svg +0 -7
@@ -0,0 +1,338 @@
1
+ /**
2
+ * cascade.mjs — SigRank yield cascade facade.
3
+ *
4
+ * The canonical cascade math (Υ Yield, SNR, Leverage, Velocity, 10xDEV, and the
5
+ * 24-stage RS05 class taxonomy) now lives in the `@sigrank/cascade` package.
6
+ * This file re-exports the canonical functions and adds local-only helpers that
7
+ * are presentation/MCP-specific and intentionally NOT part of the canonical
8
+ * math package:
9
+ * - CLASS_TIERS, SIGNAL_CLASSES, UNCLASSED (display taxonomy)
10
+ * - tierOf(), stageOf() (display helpers)
11
+ * - detectMode(), MODE_EXPECTED_YIELD, qualityScore() (mode/quality layer)
12
+ * - parsePillars() (text → 4 pillars extractor)
13
+ *
14
+ * The cascade() wrapper preserves the object-signature
15
+ * ({ input, output, cacheCreate, cacheRead }) used throughout this repo and
16
+ * re-attaches the `mode` field (detectMode) that the canonical package
17
+ * intentionally omits (mode is a presentation-layer concern, not canonical
18
+ * math).
19
+ *
20
+ * Canonical reference: MO§ES Υ 18436.98 from (1251211, 11296121, 128196310, 2555179769).
21
+ *
22
+ * Degenerate-input policy (inherited from the canonical package):
23
+ * - Any pillar that collapses a denominator (i=0, o=0, cw=0, cr=0) returns null for
24
+ * the affected metrics rather than Infinity/NaN.
25
+ * - A `warnings[]` array is attached when any metric is null so callers can surface the
26
+ * reason without silently corrupting downstream calculations.
27
+ * - The cascade is NEVER thrown away — even partial results are useful for review/storage.
28
+ * Callers that require a fully-formed result should check `warnings.length === 0`.
29
+ */
30
+
31
+ // ─── Canonical math (from @sigrank/cascade) ─────────────────────────────────
32
+ import {
33
+ cascade as cascadeCanonical,
34
+ round,
35
+ classify,
36
+ RS05_CLASS_THRESHOLDS,
37
+ fieldStats,
38
+ percentileOf,
39
+ rankOf,
40
+ operatorSignature,
41
+ evaluateOperator,
42
+ } from "@sigrank/cascade";
43
+
44
+ // Re-export the canonical functions so existing import paths keep working.
45
+ export {
46
+ round,
47
+ classify,
48
+ RS05_CLASS_THRESHOLDS,
49
+ fieldStats,
50
+ percentileOf,
51
+ rankOf,
52
+ operatorSignature,
53
+ evaluateOperator,
54
+ };
55
+
56
+ /**
57
+ * The four raw token pillars → the cascade.
58
+ *
59
+ * Object-signature wrapper around the canonical `@sigrank/cascade` cascade()
60
+ * (which takes positional args). Re-attaches the `mode` field via detectMode()
61
+ * so the 20+ call sites in this repo that read `result.mode` keep working.
62
+ */
63
+ export function cascade({ input, output, cacheCreate, cacheRead }) {
64
+ const result = cascadeCanonical(input, output, cacheCreate, cacheRead);
65
+ // Re-attach mode — the canonical package intentionally omits it (mode is a
66
+ // presentation-layer concern, not canonical math).
67
+ result.mode = detectMode({ input, output, cacheCreate, cacheRead });
68
+ return result;
69
+ }
70
+
71
+ // ─── Local-only display taxonomy (not in @sigrank/cascade) ───────────────────
72
+
73
+ /**
74
+ * CLASS_TIERS — the 8 base tier names (K.01–K.08) for display: glyph, color,
75
+ * meaning. SINGLE SOURCE OF TRUTH for tier-level display info.
76
+ *
77
+ * The permanent class is an EXPERIENCE ladder keyed on TOTAL TOKENS. Each tier
78
+ * has 3 sub-stages (I/II/III) — 24 stages total. The 24 thresholds live in
79
+ * RS05_CLASS_THRESHOLDS (re-exported from @sigrank/cascade above). classify()
80
+ * returns the full sub-stage string (e.g. "REFINER II"). Use tierOf() to
81
+ * extract the base tier name.
82
+ *
83
+ * TRANSMITTER is NOT on this ladder — it is a temporary peak badge (RS.08).
84
+ * The client does not evaluate the badge (owner is still calibrating it);
85
+ * the server does it on read.
86
+ *
87
+ * Mirrors the server's canon-ids.ts CLASS_TIERS (display). Order is descending
88
+ * (ARCH+ → IGNITER).
89
+ */
90
+ export const CLASS_TIERS = [
91
+ "ARCH+",
92
+ "ARCH",
93
+ "POWER",
94
+ "BASE",
95
+ "SEEKER",
96
+ "REFINER",
97
+ "BEARER",
98
+ "IGNITER",
99
+ ];
100
+
101
+ /**
102
+ * SIGNAL_CLASSES — the full 24 sub-stage names (8 tiers × 3 sub-stages I/II/III).
103
+ * Mirrors the server's SIGNAL_CLASSES set in lib/board/mappers.ts. The API
104
+ * returns class_tier as one of these 24 values. classify() returns one of these
105
+ * (or UNCLASSED). Use this for schema enums and validation.
106
+ */
107
+ export const SIGNAL_CLASSES = [
108
+ "ARCH+ I", "ARCH+ II", "ARCH+ III",
109
+ "ARCH I", "ARCH II", "ARCH III",
110
+ "POWER I", "POWER II", "POWER III",
111
+ "BASE I", "BASE II", "BASE III",
112
+ "SEEKER I", "SEEKER II", "SEEKER III",
113
+ "REFINER I", "REFINER II", "REFINER III",
114
+ "BEARER I", "BEARER II", "BEARER III",
115
+ "IGNITER I", "IGNITER II", "IGNITER III",
116
+ ];
117
+
118
+ /** The degenerate-case class returned when totalTokens is null/undefined
119
+ * (all-zero / empty session). Distinct from IGNITER III (which is a real
120
+ * low-experience tier) so callers can tell "no data" from "bottom tier". */
121
+ export const UNCLASSED = "UNCLASSED";
122
+
123
+ /** Extract the base tier name from a sub-stage string (e.g. "ARCH+ I" → "ARCH+").
124
+ * Mirrors the server's tierOf() in components/sigrank/types.ts. Returns the
125
+ * input as-is if it's not a sub-stage string (e.g. UNCLASSED). */
126
+ export function tierOf(cls) {
127
+ if (cls === UNCLASSED || cls == null) return cls;
128
+ const parts = String(cls).split(" ");
129
+ if (parts.length >= 2 && ["I", "II", "III"].includes(parts[parts.length - 1])) {
130
+ return parts.slice(0, -1).join(" ");
131
+ }
132
+ return cls;
133
+ }
134
+
135
+ /** Extract the sub-stage from a sub-stage string (e.g. "ARCH+ I" → "I").
136
+ * Mirrors the server's stageOf(). Returns null for non-sub-stage strings. */
137
+ export function stageOf(cls) {
138
+ if (cls === UNCLASSED || cls == null) return null;
139
+ const parts = String(cls).split(" ");
140
+ const stage = parts[parts.length - 1];
141
+ return stage === "I" || stage === "II" || stage === "III" ? stage : null;
142
+ }
143
+
144
+ // ─── Mode / quality layer (not in @sigrank/cascade) ──────────────────────────
145
+
146
+ /**
147
+ * detectMode — classify an operator's current working mode from 4 token pillars.
148
+ *
149
+ * Pure ratio math, first-match-wins (same pattern as classify() — descending
150
+ * cuts preserve edge semantics). MAINTAIN checked first (high leverage is
151
+ * strongest signal), then DEBUG (low velocity is distinctive), then EDIT,
152
+ * then BUILD as fallback.
153
+ *
154
+ * Modes:
155
+ * BUILD — high input, low/zero cacheRead, output rising (greenfield)
156
+ * EDIT — high input, low cacheRead, high output (polishing)
157
+ * DEBUG — high input, low output, low cacheRead (investigating)
158
+ * MAINTAIN — low input, high cacheRead, high output (compounding)
159
+ * IDLE — near-zero tokens (not working)
160
+ *
161
+ * Returns { mode, confidence }.
162
+ */
163
+ export function detectMode({ input, output, cacheCreate, cacheRead }) {
164
+ const i = Number(input),
165
+ o = Number(output),
166
+ cw = Number(cacheCreate),
167
+ cr = Number(cacheRead);
168
+ const total = i + o + cw + cr;
169
+
170
+ // IDLE: near-zero tokens
171
+ if (total < 1000) return { mode: "IDLE", confidence: 1.0 };
172
+
173
+ const leverage = i > 0 ? cr / i : 0;
174
+ const velocity = i > 0 ? o / i : 0;
175
+ const input_share = total > 0 ? i / total : 0;
176
+
177
+ // MAINTAIN: high leverage + high velocity (the cascade is compounding)
178
+ if (leverage > 10 && velocity > 1)
179
+ return { mode: "MAINTAIN", confidence: 0.9 };
180
+ if (leverage > 3 && velocity > 0.5)
181
+ return { mode: "MAINTAIN", confidence: 0.7 };
182
+
183
+ // DEBUG: low velocity + high input share (reading, not producing)
184
+ if (velocity < 0.3 && input_share > 0.5)
185
+ return { mode: "DEBUG", confidence: 0.8 };
186
+
187
+ // EDIT: high input share + high velocity (fresh input but producing)
188
+ if (input_share > 0.4 && velocity > 0.5)
189
+ return { mode: "EDIT", confidence: 0.7 };
190
+
191
+ // DEBUG (secondary): high input share + low velocity
192
+ if (input_share > 0.4 && velocity < 0.5)
193
+ return { mode: "DEBUG", confidence: 0.6 };
194
+
195
+ // BUILD: fallback (high input, no cache reuse yet)
196
+ return { mode: "BUILD", confidence: 0.6 };
197
+ }
198
+
199
+ /**
200
+ * Expected yield per mode — global defaults used before personal baselines
201
+ * exist (needs 7+ days of history). Used by the quality score computation.
202
+ */
203
+ export const MODE_EXPECTED_YIELD = {
204
+ BUILD: 15,
205
+ EDIT: 45,
206
+ DEBUG: 10,
207
+ MAINTAIN: 5000,
208
+ IDLE: 0,
209
+ };
210
+
211
+ /**
212
+ * qualityScore — actual yield relative to mode expectation.
213
+ * Fixes the "debug is bad" problem: a DEBUG session at 80% quality is good.
214
+ * Returns a number 0+ (can exceed 1.0 if outperforming the expected yield).
215
+ */
216
+ export function qualityScore(actualYield, mode) {
217
+ const expected = MODE_EXPECTED_YIELD[mode] ?? 1;
218
+ if (expected === 0) return actualYield === 0 ? 1.0 : 0;
219
+ return actualYield / expected;
220
+ }
221
+
222
+ // ─── Text parsing (not in @sigrank/cascade) ──────────────────────────────────
223
+
224
+ /**
225
+ * Extract the 4 pillars from pasted text: JSON object OR 4 whitespace numbers.
226
+ *
227
+ * Hardened parse policy:
228
+ * - JSON path: requires named keys and numeric values. Rejects strings/null.
229
+ * - Positional path: requires the input to be ONLY numeric tokens (whitespace/commas
230
+ * allowed as separators). If the text contains non-numeric words the positional
231
+ * extractor attaches a `_parseWarnings` flag so downstream can route it to a review
232
+ * channel instead of treating it as authoritative data.
233
+ * - Negative values are accepted (not thrown away) but flagged — could be valid in
234
+ * some edge-case accounting or a data error; the server is the authority on validity.
235
+ * - We NEVER silently corrupt: if we can't parse 4 pillars we throw. If we parse but
236
+ * something looks suspicious we surface it in `_parseWarnings` on the returned object.
237
+ */
238
+ export function parsePillars(text) {
239
+ const t = String(text || "").trim();
240
+ const pw = []; // parse warnings to attach
241
+
242
+ // ── JSON path ──────────────────────────────────────────────────────────────
243
+ try {
244
+ const j = JSON.parse(t);
245
+ if (j && typeof j === "object" && !Array.isArray(j)) {
246
+ const g = (...keys) => {
247
+ for (const k of keys) if (j[k] != null) return j[k];
248
+ return null;
249
+ };
250
+ const input = g(
251
+ "input",
252
+ "tokens_input_fresh",
253
+ "inputTokens",
254
+ "input_tokens",
255
+ );
256
+ const output = g(
257
+ "output",
258
+ "tokens_output",
259
+ "outputTokens",
260
+ "output_tokens",
261
+ );
262
+ const cacheCreate = g(
263
+ "cacheCreate",
264
+ "tokens_cache_creation",
265
+ "cache_creation_tokens",
266
+ );
267
+ const cacheRead = g(
268
+ "cacheRead",
269
+ "tokens_cache_read",
270
+ "cache_read_tokens",
271
+ );
272
+ if ([input, output, cacheCreate, cacheRead].every((v) => v != null)) {
273
+ const pillars = {
274
+ input: Number(input),
275
+ output: Number(output),
276
+ cacheCreate: Number(cacheCreate),
277
+ cacheRead: Number(cacheRead),
278
+ };
279
+ if (
280
+ [
281
+ pillars.input,
282
+ pillars.output,
283
+ pillars.cacheCreate,
284
+ pillars.cacheRead,
285
+ ].some((v) => !Number.isFinite(v))
286
+ )
287
+ throw new Error(
288
+ "Non-numeric pillar value in JSON (got string or non-finite number).",
289
+ );
290
+ if (
291
+ [
292
+ pillars.input,
293
+ pillars.output,
294
+ pillars.cacheCreate,
295
+ pillars.cacheRead,
296
+ ].some((v) => v < 0)
297
+ )
298
+ pw.push(
299
+ "negative_pillar: one or more pillars is negative — may be a data error",
300
+ );
301
+ if (pw.length > 0) pillars._parseWarnings = pw;
302
+ return pillars;
303
+ }
304
+ }
305
+ } catch (e) {
306
+ // JSON.parse syntax error — fall through to positional. Re-throw parse errors we raised ourselves.
307
+ if (e.message.startsWith("Non-numeric")) throw e;
308
+ }
309
+
310
+ // ── Positional path ────────────────────────────────────────────────────────
311
+ // Guard: if the text contains alphabetic words, the numeric extraction is unreliable.
312
+ // We still attempt it (don't throw away the data) but flag it for review.
313
+ if (/[a-zA-Z]/.test(t))
314
+ pw.push(
315
+ "positional_from_mixed_text: extracted numbers from text that contains alphabetic characters — verify these are the correct 4 pillars",
316
+ );
317
+
318
+ const nums = (t.match(/-?\d[\d,]*\.?\d*/g) || []).map((s) =>
319
+ Number(s.replace(/,/g, "")),
320
+ );
321
+ if (nums.length >= 4) {
322
+ const [input, output, cacheCreate, cacheRead] = nums;
323
+ if (nums.length > 4)
324
+ pw.push(
325
+ `positional_extra_numbers: found ${nums.length} numbers, using first 4 — inspect for positional order error`,
326
+ );
327
+ const pillars = { input, output, cacheCreate, cacheRead };
328
+ if ([input, output, cacheCreate, cacheRead].some((v) => v < 0))
329
+ pw.push(
330
+ "negative_pillar: one or more pillars is negative — may be a data error",
331
+ );
332
+ if (pw.length > 0) pillars._parseWarnings = pw;
333
+ return pillars;
334
+ }
335
+ throw new Error(
336
+ "Could not parse 4 token pillars (input, output, cacheCreate, cacheRead) from the input.",
337
+ );
338
+ }
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Thin command shim for SigRank Standard commands.
5
+ *
6
+ * All existing commands and MCP/TUI behavior are delegated to ../index.mjs.
7
+ * The shim exists so the standard commands can be added without coupling the
8
+ * large presentation CLI to the standard specification.
9
+ */
10
+
11
+ const args = process.argv.slice(2);
12
+ const cmd = args[0];
13
+
14
+ const SPEC = "sigrank/0.1-draft";
15
+ const STANDARD_URL = "https://signalaf.com/standard";
16
+ const SCHEMA_URL =
17
+ "https://signalaf.com/standard/sigrank-operator-record-v0.1.schema.json";
18
+
19
+ function flag(name, fallback = undefined) {
20
+ const eq = args.find((a) => a.startsWith(`--${name}=`));
21
+ if (eq) return eq.slice(name.length + 3);
22
+ const i = args.indexOf(`--${name}`);
23
+ if (i >= 0 && i + 1 < args.length && !args[i + 1].startsWith("--")) {
24
+ return args[i + 1];
25
+ }
26
+ return fallback;
27
+ }
28
+
29
+ function printStandard() {
30
+ const payload = {
31
+ spec: SPEC,
32
+ status: "proposed_open_standard",
33
+ scope: "human operation of generative AI systems",
34
+ telemetry: ["input", "output", "cache_write", "cache_read"],
35
+ metrics: {
36
+ yield: "(cache_read * output) / input^2",
37
+ leverage: "cache_read / input",
38
+ velocity: "output / input",
39
+ snr: "output / (input + output)",
40
+ dev10x: "log10(cache_read / input)",
41
+ },
42
+ privacy:
43
+ "Core metrics do not require prompt text, response text, source code, or repository contents.",
44
+ reference_math: "@sigrank/cascade",
45
+ reference_platform: "SignalAF",
46
+ standard_url: STANDARD_URL,
47
+ schema_url: SCHEMA_URL,
48
+ };
49
+
50
+ if (args.includes("--json")) {
51
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
52
+ return;
53
+ }
54
+
55
+ process.stdout.write(`SigRank Standard ${SPEC}\n`);
56
+ process.stdout.write(`The new standard in operator metrics.\n\n`);
57
+ process.stdout.write(`Scope: ${payload.scope}\n`);
58
+ process.stdout.write(`Primitives: input / output / cache_write / cache_read\n`);
59
+ process.stdout.write(`Core metrics: Yield / Leverage / Velocity / SNR / 10xDEV\n`);
60
+ process.stdout.write(`Reference math: @sigrank/cascade\n`);
61
+ process.stdout.write(`Reference platform: SignalAF\n`);
62
+ process.stdout.write(`Spec: ${STANDARD_URL}\n`);
63
+ process.stdout.write(`Schema: ${SCHEMA_URL}\n`);
64
+ }
65
+
66
+ async function exportStandardRecord() {
67
+ const { callTool } = await import("../tools.mjs");
68
+
69
+ const platform = flag("platform", "claude");
70
+ const window = flag("window", "30d");
71
+
72
+ const explicit = ["input", "output", "cache-write", "cache-read"].every(
73
+ (name) => flag(name) !== undefined,
74
+ );
75
+
76
+ let pillars;
77
+ let sourcePlatform = platform;
78
+
79
+ if (explicit) {
80
+ pillars = {
81
+ input: Number(flag("input")),
82
+ output: Number(flag("output")),
83
+ cache_write: Number(flag("cache-write")),
84
+ cache_read: Number(flag("cache-read")),
85
+ };
86
+ } else {
87
+ const pulled = await callTool("tokenpull", { platform });
88
+ const selected = (pulled?.windows || []).find(
89
+ (w) => w.window === window || (window === "all_time" && w.window === "all"),
90
+ );
91
+ if (!selected?.pillars) {
92
+ throw new Error(
93
+ `No ${window} telemetry found for ${platform}. Pass --platform/--window or explicit --input --output --cache-write --cache-read.`,
94
+ );
95
+ }
96
+ sourcePlatform = pulled.platform || platform;
97
+ pillars = {
98
+ input: selected.pillars.input,
99
+ output: selected.pillars.output,
100
+ cache_write: selected.pillars.cacheCreate,
101
+ cache_read: selected.pillars.cacheRead,
102
+ };
103
+ }
104
+
105
+ for (const [key, value] of Object.entries(pillars)) {
106
+ if (!Number.isFinite(value) || value < 0) {
107
+ throw new Error(`${key} must be a non-negative finite number.`);
108
+ }
109
+ }
110
+
111
+ const record = await callTool("get_sigrank_standard_record", {
112
+ ...pillars,
113
+ provider: flag("provider", "local"),
114
+ model: flag("model", "unknown"),
115
+ tool: flag("tool", sourcePlatform || "sigrank"),
116
+ timestamp: new Date().toISOString(),
117
+ });
118
+
119
+ record.context = {
120
+ ...(record.context || {}),
121
+ window,
122
+ source_platform: sourcePlatform,
123
+ };
124
+
125
+ process.stdout.write(`${JSON.stringify(record, null, 2)}\n`);
126
+ }
127
+
128
+ if (cmd === "standard") {
129
+ printStandard();
130
+ } else if (cmd === "export" && args.includes("--standard")) {
131
+ exportStandardRecord().catch((err) => {
132
+ process.stderr.write(`[sigrank] ${err?.message || err}\n`);
133
+ process.exitCode = 1;
134
+ });
135
+ } else {
136
+ await import("../index.mjs");
137
+ }