@git-stunts/git-warp 10.8.0 → 11.3.3
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 +53 -32
- package/SECURITY.md +64 -0
- package/bin/cli/commands/check.js +168 -0
- package/bin/cli/commands/doctor/checks.js +422 -0
- package/bin/cli/commands/doctor/codes.js +46 -0
- package/bin/cli/commands/doctor/index.js +239 -0
- package/bin/cli/commands/doctor/types.js +89 -0
- package/bin/cli/commands/history.js +80 -0
- package/bin/cli/commands/info.js +139 -0
- package/bin/cli/commands/install-hooks.js +128 -0
- package/bin/cli/commands/materialize.js +99 -0
- package/bin/cli/commands/patch.js +142 -0
- package/bin/cli/commands/path.js +88 -0
- package/bin/cli/commands/query.js +235 -0
- package/bin/cli/commands/registry.js +32 -0
- package/bin/cli/commands/seek.js +598 -0
- package/bin/cli/commands/tree.js +230 -0
- package/bin/cli/commands/trust.js +154 -0
- package/bin/cli/commands/verify-audit.js +114 -0
- package/bin/cli/commands/view.js +46 -0
- package/bin/cli/infrastructure.js +350 -0
- package/bin/cli/schemas.js +177 -0
- package/bin/cli/shared.js +244 -0
- package/bin/cli/types.js +96 -0
- package/bin/presenters/index.js +41 -9
- package/bin/presenters/json.js +14 -12
- package/bin/presenters/text.js +286 -28
- package/bin/warp-graph.js +5 -2346
- package/index.d.ts +111 -21
- package/index.js +2 -0
- package/package.json +10 -8
- package/src/domain/WarpGraph.js +109 -3252
- package/src/domain/crdt/ORSet.js +8 -8
- package/src/domain/errors/EmptyMessageError.js +2 -2
- package/src/domain/errors/ForkError.js +1 -1
- package/src/domain/errors/IndexError.js +1 -1
- package/src/domain/errors/OperationAbortedError.js +1 -1
- package/src/domain/errors/QueryError.js +3 -3
- package/src/domain/errors/SchemaUnsupportedError.js +1 -1
- package/src/domain/errors/ShardCorruptionError.js +2 -2
- package/src/domain/errors/ShardLoadError.js +2 -2
- package/src/domain/errors/ShardValidationError.js +4 -4
- package/src/domain/errors/StorageError.js +2 -2
- package/src/domain/errors/SyncError.js +1 -1
- package/src/domain/errors/TraversalError.js +1 -1
- package/src/domain/errors/TrustError.js +29 -0
- package/src/domain/errors/WarpError.js +2 -2
- package/src/domain/errors/WormholeError.js +1 -1
- package/src/domain/errors/index.js +1 -0
- package/src/domain/services/AuditMessageCodec.js +137 -0
- package/src/domain/services/AuditReceiptService.js +471 -0
- package/src/domain/services/AuditVerifierService.js +707 -0
- package/src/domain/services/BitmapIndexBuilder.js +3 -3
- package/src/domain/services/BitmapIndexReader.js +28 -19
- package/src/domain/services/BoundaryTransitionRecord.js +18 -17
- package/src/domain/services/CheckpointSerializerV5.js +17 -16
- package/src/domain/services/CheckpointService.js +2 -2
- package/src/domain/services/CommitDagTraversalService.js +13 -13
- package/src/domain/services/DagPathFinding.js +7 -7
- package/src/domain/services/DagTopology.js +1 -1
- package/src/domain/services/DagTraversal.js +1 -1
- package/src/domain/services/HealthCheckService.js +1 -1
- package/src/domain/services/HookInstaller.js +1 -1
- package/src/domain/services/HttpSyncServer.js +120 -55
- package/src/domain/services/IndexRebuildService.js +7 -7
- package/src/domain/services/IndexStalenessChecker.js +4 -3
- package/src/domain/services/JoinReducer.js +11 -11
- package/src/domain/services/LogicalTraversal.js +1 -1
- package/src/domain/services/MessageCodecInternal.js +4 -1
- package/src/domain/services/MessageSchemaDetector.js +2 -2
- package/src/domain/services/MigrationService.js +1 -1
- package/src/domain/services/ObserverView.js +8 -8
- package/src/domain/services/PatchBuilderV2.js +42 -26
- package/src/domain/services/ProvenanceIndex.js +1 -1
- package/src/domain/services/ProvenancePayload.js +1 -1
- package/src/domain/services/QueryBuilder.js +3 -3
- package/src/domain/services/StateDiff.js +14 -11
- package/src/domain/services/StateSerializerV5.js +2 -2
- package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
- package/src/domain/services/SyncAuthService.js +71 -4
- package/src/domain/services/SyncProtocol.js +25 -11
- package/src/domain/services/TemporalQuery.js +9 -6
- package/src/domain/services/TranslationCost.js +7 -5
- package/src/domain/services/WarpMessageCodec.js +4 -1
- package/src/domain/services/WormholeService.js +16 -7
- package/src/domain/trust/TrustCanonical.js +42 -0
- package/src/domain/trust/TrustCrypto.js +111 -0
- package/src/domain/trust/TrustEvaluator.js +195 -0
- package/src/domain/trust/TrustRecordService.js +281 -0
- package/src/domain/trust/TrustStateBuilder.js +222 -0
- package/src/domain/trust/canonical.js +68 -0
- package/src/domain/trust/reasonCodes.js +64 -0
- package/src/domain/trust/schemas.js +160 -0
- package/src/domain/trust/verdict.js +42 -0
- package/src/domain/types/TickReceipt.js +1 -1
- package/src/domain/types/WarpErrors.js +45 -0
- package/src/domain/types/WarpOptions.js +29 -0
- package/src/domain/types/WarpPersistence.js +41 -0
- package/src/domain/types/WarpTypes.js +2 -2
- package/src/domain/types/WarpTypesV2.js +2 -2
- package/src/domain/types/git-cas.d.ts +20 -0
- package/src/domain/utils/MinHeap.js +6 -5
- package/src/domain/utils/RefLayout.js +59 -0
- package/src/domain/utils/canonicalStringify.js +5 -4
- package/src/domain/utils/roaring.js +31 -5
- package/src/domain/warp/PatchSession.js +26 -17
- package/src/domain/warp/Writer.js +18 -3
- package/src/domain/warp/_internal.js +26 -0
- package/src/domain/warp/_wire.js +58 -0
- package/src/domain/warp/_wiredMethods.d.ts +254 -0
- package/src/domain/warp/checkpoint.methods.js +401 -0
- package/src/domain/warp/fork.methods.js +323 -0
- package/src/domain/warp/materialize.methods.js +238 -0
- package/src/domain/warp/materializeAdvanced.methods.js +350 -0
- package/src/domain/warp/patch.methods.js +554 -0
- package/src/domain/warp/provenance.methods.js +286 -0
- package/src/domain/warp/query.methods.js +280 -0
- package/src/domain/warp/subscribe.methods.js +272 -0
- package/src/domain/warp/sync.methods.js +554 -0
- package/src/globals.d.ts +64 -0
- package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
- package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
- package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
- package/src/infrastructure/adapters/GitGraphAdapter.js +79 -11
- package/src/infrastructure/adapters/InMemoryGraphAdapter.js +36 -0
- package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
- package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
- package/src/ports/CommitPort.js +10 -0
- package/src/ports/RefPort.js +17 -0
- package/src/visualization/layouts/converters.js +2 -2
- package/src/visualization/layouts/elkAdapter.js +1 -1
- package/src/visualization/layouts/elkLayout.js +10 -7
- package/src/visualization/layouts/index.js +1 -1
- package/src/visualization/renderers/ascii/seek.js +16 -6
- package/src/visualization/renderers/svg/index.js +1 -1
- package/src/hooks/post-merge.sh +0 -60
package/bin/presenters/text.js
CHANGED
|
@@ -7,6 +7,28 @@
|
|
|
7
7
|
|
|
8
8
|
import { formatStructuralDiff } from '../../src/visualization/renderers/ascii/seek.js';
|
|
9
9
|
|
|
10
|
+
// ── Payload typedefs ────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {{ installed: boolean, foreign?: boolean, current?: boolean, version?: string }} HookStatus
|
|
14
|
+
* @typedef {{ repo: string, graphs: Array<{ name: string, writers?: { count: number } | null, checkpoint?: { sha: string } | null, coverage?: { sha: string } | null, cursor?: { active: boolean, tick: number, mode: string } | null }> }} InfoPayload
|
|
15
|
+
* @typedef {{ graph: string, stateHash?: string, nodes: Array<{ id?: string, props?: Record<string, unknown>, edges?: NodeEdges }>, _renderedAscii?: string, _renderedSvg?: string }} QueryPayload
|
|
16
|
+
* @typedef {{ outgoing?: Array<{ label: string, to: string }>, incoming?: Array<{ label: string, from: string }> }} NodeEdges
|
|
17
|
+
* @typedef {{ graph: string, from: string, to: string, found: boolean, length?: number, path?: string[] }} PathPayload
|
|
18
|
+
* @typedef {{ graph: string, health: { status: string }, checkpoint?: { sha: string, ageSeconds: number | null } | null, writers: { count: number, heads: Array<{ writerId: string, sha: string }> }, coverage?: { sha: string, missingWriters: string[] } | null, gc?: { totalTombstones: number, tombstoneRatio: number } | null, hook?: HookStatus | null, status?: { cachedState: string, patchesSinceCheckpoint: number, tombstoneRatio: number, writers: number } | null }} CheckPayload
|
|
19
|
+
* @typedef {{ graph: string, writer: string, nodeFilter?: string | null, entries: Array<{ sha: string, lamport: number, opCount: number }> }} HistoryPayload
|
|
20
|
+
* @typedef {{ error: { message: string } }} ErrorPayload
|
|
21
|
+
* @typedef {{ graphs: Array<{ graph: string, nodes?: number, edges?: number, checkpoint?: string, error?: string }> }} MaterializePayload
|
|
22
|
+
* @typedef {{ action: string, hookPath?: string, version?: string, backupPath?: string, name?: string }} InstallHooksPayload
|
|
23
|
+
* @typedef {{ graph?: string, action: string, tick?: number, maxTick?: number, ticks?: number[], nodes?: number, edges?: number, patchCount?: number, perWriter?: Record<string, unknown>, diff?: { nodes?: number, edges?: number } | null, tickReceipt?: Record<string, unknown>, structuralDiff?: import('../../src/domain/services/StateDiff.js').StateDiffResult | null, cursor?: { active: boolean, tick?: number }, message?: string, cursors?: Array<{ name: string, tick: number }>, activeTick?: number | null, name?: string, diffBaseline?: string, baselineTick?: number | null, truncated?: boolean, totalChanges?: number, shownChanges?: number }} SeekPayload
|
|
24
|
+
* @typedef {{ graph: string, health: string, checkedAt: string, summary: { checksRun: number, findingsTotal: number, ok: number, warn: number, fail: number, priorityActions: string[] }, findings: Array<{ status: string, id: string, message: string, fix?: string }> }} DoctorPayload
|
|
25
|
+
* @typedef {{ graph: string, verifiedAt: string, summary: { total: number, valid: number, partial: number, invalid: number }, chains: Array<{ writerId: string, status: string, receiptsVerified: number, since?: string, errors: Array<{ code: string, message: string }>, warnings: Array<{ code: string, message: string }> }>, trustWarning?: { message: string } }} VerifyAuditPayload
|
|
26
|
+
* @typedef {{ graph: string, trustVerdict: string, mode: string, trust: { source: string, evidenceSummary: { activeKeys: number, revokedKeys: number, activeBindings: number }, explanations: Array<{ trusted: boolean, writerId: string, reasonCode: string, reason: string }>, untrustedWriters: string[] } }} TrustPayload
|
|
27
|
+
* @typedef {{ type: string, node?: string, from?: string, to?: string, label?: string, key?: string, value?: unknown }} PatchOp
|
|
28
|
+
* @typedef {{ graph: string, sha: string, writer: string, lamport: number, schema?: number, ops: PatchOp[] }} PatchShowPayload
|
|
29
|
+
* @typedef {{ graph: string, total: number, showing: number, writerFilter?: string | null, entries: Array<{ sha: string, writer: string, lamport: number, opCount: number, nodeIds: string[] }> }} PatchListPayload
|
|
30
|
+
*/
|
|
31
|
+
|
|
10
32
|
// ── ANSI helpers ─────────────────────────────────────────────────────────────
|
|
11
33
|
|
|
12
34
|
const ANSI_GREEN = '\x1b[32m';
|
|
@@ -26,7 +48,7 @@ function colorCachedState(state) {
|
|
|
26
48
|
return `${ANSI_RED}${ANSI_DIM}${state}${ANSI_RESET}`;
|
|
27
49
|
}
|
|
28
50
|
|
|
29
|
-
/** @param {
|
|
51
|
+
/** @param {HookStatus} hook */
|
|
30
52
|
function formatHookStatusLine(hook) {
|
|
31
53
|
if (!hook.installed && hook.foreign) {
|
|
32
54
|
return "Hook: foreign hook present — run 'git warp install-hooks'";
|
|
@@ -42,7 +64,7 @@ function formatHookStatusLine(hook) {
|
|
|
42
64
|
|
|
43
65
|
// ── Simple renderers ─────────────────────────────────────────────────────────
|
|
44
66
|
|
|
45
|
-
/** @param {
|
|
67
|
+
/** @param {InfoPayload} payload */
|
|
46
68
|
export function renderInfo(payload) {
|
|
47
69
|
const lines = [`Repo: ${payload.repo}`];
|
|
48
70
|
lines.push(`Graphs: ${payload.graphs.length}`);
|
|
@@ -62,7 +84,25 @@ export function renderInfo(payload) {
|
|
|
62
84
|
return `${lines.join('\n')}\n`;
|
|
63
85
|
}
|
|
64
86
|
|
|
65
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* Appends edge lines for a single node to the output array.
|
|
89
|
+
* @param {string[]} lines
|
|
90
|
+
* @param {NodeEdges} edges
|
|
91
|
+
*/
|
|
92
|
+
function appendNodeEdges(lines, edges) {
|
|
93
|
+
if (edges.outgoing && edges.outgoing.length > 0) {
|
|
94
|
+
for (const e of edges.outgoing) {
|
|
95
|
+
lines.push(` -> ${e.label} -> ${e.to}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (edges.incoming && edges.incoming.length > 0) {
|
|
99
|
+
for (const e of edges.incoming) {
|
|
100
|
+
lines.push(` <- ${e.label} <- ${e.from}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** @param {QueryPayload} payload */
|
|
66
106
|
export function renderQuery(payload) {
|
|
67
107
|
const lines = [
|
|
68
108
|
`Graph: ${payload.graph}`,
|
|
@@ -76,21 +116,27 @@ export function renderQuery(payload) {
|
|
|
76
116
|
if (node.props && Object.keys(node.props).length > 0) {
|
|
77
117
|
lines.push(` props: ${JSON.stringify(node.props)}`);
|
|
78
118
|
}
|
|
119
|
+
if (node.edges) {
|
|
120
|
+
appendNodeEdges(lines, node.edges);
|
|
121
|
+
}
|
|
79
122
|
}
|
|
80
123
|
|
|
81
124
|
return `${lines.join('\n')}\n`;
|
|
82
125
|
}
|
|
83
126
|
|
|
84
|
-
/** @param {
|
|
127
|
+
/** @param {PathPayload} payload */
|
|
85
128
|
export function renderPath(payload) {
|
|
86
129
|
const lines = [
|
|
87
130
|
`Graph: ${payload.graph}`,
|
|
88
131
|
`From: ${payload.from}`,
|
|
89
132
|
`To: ${payload.to}`,
|
|
90
133
|
`Found: ${payload.found ? 'yes' : 'no'}`,
|
|
91
|
-
`Length: ${payload.length}`,
|
|
92
134
|
];
|
|
93
135
|
|
|
136
|
+
if (payload.found) {
|
|
137
|
+
lines.push(`Length: ${payload.length}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
94
140
|
if (payload.path && payload.path.length > 0) {
|
|
95
141
|
lines.push(`Path: ${payload.path.join(' -> ')}`);
|
|
96
142
|
}
|
|
@@ -101,7 +147,7 @@ export function renderPath(payload) {
|
|
|
101
147
|
/**
|
|
102
148
|
* Appends checkpoint and writer lines to check output.
|
|
103
149
|
* @param {string[]} lines
|
|
104
|
-
* @param {
|
|
150
|
+
* @param {CheckPayload} payload
|
|
105
151
|
*/
|
|
106
152
|
function appendCheckpointAndWriters(lines, payload) {
|
|
107
153
|
if (payload.checkpoint?.sha) {
|
|
@@ -124,7 +170,7 @@ function appendCheckpointAndWriters(lines, payload) {
|
|
|
124
170
|
/**
|
|
125
171
|
* Appends coverage, gc, and hook lines to check output.
|
|
126
172
|
* @param {string[]} lines
|
|
127
|
-
* @param {
|
|
173
|
+
* @param {CheckPayload} payload
|
|
128
174
|
*/
|
|
129
175
|
function appendCoverageAndExtras(lines, payload) {
|
|
130
176
|
if (payload.coverage?.sha) {
|
|
@@ -146,7 +192,7 @@ function appendCoverageAndExtras(lines, payload) {
|
|
|
146
192
|
}
|
|
147
193
|
}
|
|
148
194
|
|
|
149
|
-
/** @param {
|
|
195
|
+
/** @param {CheckPayload} payload */
|
|
150
196
|
export function renderCheck(payload) {
|
|
151
197
|
const lines = [
|
|
152
198
|
`Graph: ${payload.graph}`,
|
|
@@ -165,7 +211,7 @@ export function renderCheck(payload) {
|
|
|
165
211
|
return `${lines.join('\n')}\n`;
|
|
166
212
|
}
|
|
167
213
|
|
|
168
|
-
/** @param {
|
|
214
|
+
/** @param {HistoryPayload} payload */
|
|
169
215
|
export function renderHistory(payload) {
|
|
170
216
|
const lines = [
|
|
171
217
|
`Graph: ${payload.graph}`,
|
|
@@ -184,12 +230,12 @@ export function renderHistory(payload) {
|
|
|
184
230
|
return `${lines.join('\n')}\n`;
|
|
185
231
|
}
|
|
186
232
|
|
|
187
|
-
/** @param {
|
|
233
|
+
/** @param {ErrorPayload} payload */
|
|
188
234
|
export function renderError(payload) {
|
|
189
235
|
return `Error: ${payload.error.message}\n`;
|
|
190
236
|
}
|
|
191
237
|
|
|
192
|
-
/** @param {
|
|
238
|
+
/** @param {MaterializePayload} payload */
|
|
193
239
|
export function renderMaterialize(payload) {
|
|
194
240
|
if (payload.graphs.length === 0) {
|
|
195
241
|
return 'No graphs found in repo.\n';
|
|
@@ -206,7 +252,7 @@ export function renderMaterialize(payload) {
|
|
|
206
252
|
return `${lines.join('\n')}\n`;
|
|
207
253
|
}
|
|
208
254
|
|
|
209
|
-
/** @param {
|
|
255
|
+
/** @param {InstallHooksPayload} payload */
|
|
210
256
|
export function renderInstallHooks(payload) {
|
|
211
257
|
if (payload.action === 'up-to-date') {
|
|
212
258
|
return `Hook: already up to date (v${payload.version}) at ${payload.hookPath}\n`;
|
|
@@ -225,10 +271,10 @@ export function renderInstallHooks(payload) {
|
|
|
225
271
|
|
|
226
272
|
/**
|
|
227
273
|
* Formats a numeric delta as " (+N)" or " (-N)", or empty string for zero/non-finite.
|
|
228
|
-
* @param {
|
|
274
|
+
* @param {unknown} n
|
|
229
275
|
* @returns {string}
|
|
230
276
|
*/
|
|
231
|
-
function formatDelta(n) {
|
|
277
|
+
function formatDelta(n) {
|
|
232
278
|
if (typeof n !== 'number' || !Number.isFinite(n) || n === 0) {
|
|
233
279
|
return '';
|
|
234
280
|
}
|
|
@@ -238,10 +284,10 @@ function formatDelta(n) { // TODO(ts-cleanup): type CLI payload
|
|
|
238
284
|
|
|
239
285
|
/**
|
|
240
286
|
* Formats an operation summary object as a compact plain-text string.
|
|
241
|
-
* @param {
|
|
287
|
+
* @param {Record<string, number> | null | undefined} summary
|
|
242
288
|
* @returns {string}
|
|
243
289
|
*/
|
|
244
|
-
function formatOpSummaryPlain(summary) {
|
|
290
|
+
function formatOpSummaryPlain(summary) {
|
|
245
291
|
const order = [
|
|
246
292
|
['NodeAdd', '+', 'node'],
|
|
247
293
|
['EdgeAdd', '+', 'edge'],
|
|
@@ -264,7 +310,7 @@ function formatOpSummaryPlain(summary) { // TODO(ts-cleanup): type CLI payload
|
|
|
264
310
|
/**
|
|
265
311
|
* Appends a per-writer tick receipt summary below a base line.
|
|
266
312
|
* @param {string} baseLine
|
|
267
|
-
* @param {
|
|
313
|
+
* @param {SeekPayload} payload
|
|
268
314
|
* @returns {string}
|
|
269
315
|
*/
|
|
270
316
|
function appendReceiptSummary(baseLine, payload) {
|
|
@@ -284,8 +330,12 @@ function appendReceiptSummary(baseLine, payload) {
|
|
|
284
330
|
const maxWriterLen = Math.max(5, ...entries.map(([writerId]) => writerId.length));
|
|
285
331
|
const receiptLines = [` Tick ${payload.tick}:`];
|
|
286
332
|
for (const [writerId, entry] of entries) {
|
|
287
|
-
|
|
288
|
-
const
|
|
333
|
+
/** @type {Record<string, unknown>} */
|
|
334
|
+
const rec = /** @type {Record<string, unknown>} */ (entry);
|
|
335
|
+
const sha = typeof rec.sha === 'string' ? rec.sha.slice(0, 7) : '';
|
|
336
|
+
const opSummary = rec.opSummary && typeof rec.opSummary === 'object'
|
|
337
|
+
? /** @type {Record<string, number>} */ (rec.opSummary)
|
|
338
|
+
: /** @type {Record<string, number>} */ (rec);
|
|
289
339
|
receiptLines.push(` ${writerId.padEnd(maxWriterLen)} ${sha.padEnd(7)} ${formatOpSummaryPlain(opSummary)}`);
|
|
290
340
|
}
|
|
291
341
|
|
|
@@ -294,7 +344,7 @@ function appendReceiptSummary(baseLine, payload) {
|
|
|
294
344
|
|
|
295
345
|
/**
|
|
296
346
|
* Builds human-readable state count strings from a seek payload.
|
|
297
|
-
* @param {
|
|
347
|
+
* @param {SeekPayload} payload
|
|
298
348
|
* @returns {{nodesStr: string, edgesStr: string, patchesStr: string}}
|
|
299
349
|
*/
|
|
300
350
|
function buildStateStrings(payload) {
|
|
@@ -310,20 +360,20 @@ function buildStateStrings(payload) {
|
|
|
310
360
|
|
|
311
361
|
/**
|
|
312
362
|
* Renders the "tick" / "latest" / "load" seek action with receipt + structural diff.
|
|
313
|
-
* @param {
|
|
363
|
+
* @param {SeekPayload} payload
|
|
314
364
|
* @param {string} headerLine
|
|
315
365
|
* @returns {string}
|
|
316
366
|
*/
|
|
317
367
|
function renderSeekWithDiff(payload, headerLine) {
|
|
318
368
|
const base = appendReceiptSummary(headerLine, payload);
|
|
319
|
-
return base + formatStructuralDiff(payload);
|
|
369
|
+
return base + formatStructuralDiff(/** @type {import('../../src/visualization/renderers/ascii/seek.js').SeekPayload} */ (payload));
|
|
320
370
|
}
|
|
321
371
|
|
|
322
372
|
// ── Seek simple-action renderers ─────────────────────────────────────────────
|
|
323
373
|
|
|
324
374
|
/**
|
|
325
375
|
* Renders seek actions that don't involve state counts: clear-cache, list, drop, save.
|
|
326
|
-
* @param {
|
|
376
|
+
* @param {SeekPayload} payload
|
|
327
377
|
* @returns {string|null} Rendered string, or null if action is not simple
|
|
328
378
|
*/
|
|
329
379
|
function renderSeekSimple(payload) {
|
|
@@ -344,11 +394,11 @@ function renderSeekSimple(payload) {
|
|
|
344
394
|
|
|
345
395
|
/**
|
|
346
396
|
* Renders the cursor list action.
|
|
347
|
-
* @param {
|
|
397
|
+
* @param {SeekPayload} payload
|
|
348
398
|
* @returns {string}
|
|
349
399
|
*/
|
|
350
400
|
function renderSeekList(payload) {
|
|
351
|
-
if (payload.cursors.length === 0) {
|
|
401
|
+
if (!payload.cursors || payload.cursors.length === 0) {
|
|
352
402
|
return 'No saved cursors.\n';
|
|
353
403
|
}
|
|
354
404
|
const lines = [];
|
|
@@ -363,7 +413,7 @@ function renderSeekList(payload) {
|
|
|
363
413
|
|
|
364
414
|
/**
|
|
365
415
|
* Renders seek actions that show state: latest, load, tick, status.
|
|
366
|
-
* @param {
|
|
416
|
+
* @param {SeekPayload} payload
|
|
367
417
|
* @returns {string}
|
|
368
418
|
*/
|
|
369
419
|
function renderSeekState(payload) {
|
|
@@ -396,12 +446,220 @@ function renderSeekState(payload) {
|
|
|
396
446
|
payload,
|
|
397
447
|
);
|
|
398
448
|
}
|
|
399
|
-
return `${payload.graph}: no cursor active, ${payload.ticks.length} ticks available\n`;
|
|
449
|
+
return `${payload.graph}: no cursor active, ${(payload.ticks ?? []).length} ticks available\n`;
|
|
400
450
|
}
|
|
401
451
|
|
|
402
452
|
// ── Seek main renderer ──────────────────────────────────────────────────────
|
|
403
453
|
|
|
404
|
-
/** @param {
|
|
454
|
+
/** @param {SeekPayload} payload */
|
|
405
455
|
export function renderSeek(payload) {
|
|
406
456
|
return renderSeekSimple(payload) ?? renderSeekState(payload);
|
|
407
457
|
}
|
|
458
|
+
|
|
459
|
+
// ── Doctor renderer ──────────────────────────────────────────────────────────
|
|
460
|
+
|
|
461
|
+
/** @param {string} status */
|
|
462
|
+
function findingIcon(status) {
|
|
463
|
+
if (status === 'ok') {
|
|
464
|
+
return `${ANSI_GREEN}\u2713${ANSI_RESET}`;
|
|
465
|
+
}
|
|
466
|
+
if (status === 'warn') {
|
|
467
|
+
return `${ANSI_YELLOW}\u26A0${ANSI_RESET}`;
|
|
468
|
+
}
|
|
469
|
+
return `${ANSI_RED}\u2717${ANSI_RESET}`;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** @param {string} health */
|
|
473
|
+
function colorHealth(health) {
|
|
474
|
+
if (health === 'ok') {
|
|
475
|
+
return `${ANSI_GREEN}${health}${ANSI_RESET}`;
|
|
476
|
+
}
|
|
477
|
+
if (health === 'degraded') {
|
|
478
|
+
return `${ANSI_YELLOW}${health}${ANSI_RESET}`;
|
|
479
|
+
}
|
|
480
|
+
return `${ANSI_RED}${health}${ANSI_RESET}`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** @param {DoctorPayload} payload */
|
|
484
|
+
export function renderDoctor(payload) {
|
|
485
|
+
const lines = [
|
|
486
|
+
`Graph: ${payload.graph}`,
|
|
487
|
+
`Health: ${colorHealth(payload.health)}`,
|
|
488
|
+
`Checked: ${payload.checkedAt}`,
|
|
489
|
+
`Summary: ${payload.summary.checksRun} checks, ${payload.summary.findingsTotal} findings (${payload.summary.ok} ok, ${payload.summary.warn} warn, ${payload.summary.fail} fail)`,
|
|
490
|
+
'',
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
for (const f of payload.findings) {
|
|
494
|
+
lines.push(`${findingIcon(f.status)} ${f.id}: ${f.message}`);
|
|
495
|
+
if (f.fix) {
|
|
496
|
+
lines.push(` fix: ${f.fix}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (payload.summary.priorityActions.length > 0) {
|
|
501
|
+
lines.push('');
|
|
502
|
+
lines.push('Priority actions:');
|
|
503
|
+
for (const action of payload.summary.priorityActions) {
|
|
504
|
+
lines.push(` - ${action}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return `${lines.join('\n')}\n`;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ── Verify-audit renderer ────────────────────────────────────────────────────
|
|
512
|
+
|
|
513
|
+
/** @param {string} status */
|
|
514
|
+
function colorStatus(status) {
|
|
515
|
+
if (status === 'VALID' || status === 'PARTIAL') {
|
|
516
|
+
return `${ANSI_GREEN}${status}${ANSI_RESET}`;
|
|
517
|
+
}
|
|
518
|
+
return `${ANSI_RED}${status}${ANSI_RESET}`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** @param {VerifyAuditPayload} payload */
|
|
522
|
+
export function renderVerifyAudit(payload) {
|
|
523
|
+
const lines = [
|
|
524
|
+
`Graph: ${payload.graph}`,
|
|
525
|
+
`Verified: ${payload.verifiedAt}`,
|
|
526
|
+
`Chains: ${payload.summary.total} (${payload.summary.valid} valid, ${payload.summary.partial} partial, ${payload.summary.invalid} invalid)`,
|
|
527
|
+
];
|
|
528
|
+
|
|
529
|
+
for (const chain of payload.chains) {
|
|
530
|
+
lines.push('');
|
|
531
|
+
lines.push(` Writer: ${chain.writerId}`);
|
|
532
|
+
lines.push(` Status: ${colorStatus(chain.status)}`);
|
|
533
|
+
lines.push(` Receipts: ${chain.receiptsVerified} verified`);
|
|
534
|
+
if (chain.since) {
|
|
535
|
+
lines.push(` Since: ${chain.since}`);
|
|
536
|
+
}
|
|
537
|
+
for (const err of chain.errors) {
|
|
538
|
+
lines.push(` ${ANSI_RED}Error [${err.code}]: ${err.message}${ANSI_RESET}`);
|
|
539
|
+
}
|
|
540
|
+
for (const warn of chain.warnings) {
|
|
541
|
+
lines.push(` ${ANSI_YELLOW}Warning [${warn.code}]: ${warn.message}${ANSI_RESET}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (payload.trustWarning) {
|
|
546
|
+
lines.push('');
|
|
547
|
+
lines.push(`${ANSI_YELLOW}Trust: ${payload.trustWarning.message}${ANSI_RESET}`);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return `${lines.join('\n')}\n`;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// ── Trust renderer ────────────────────────────────────────────────────────
|
|
554
|
+
|
|
555
|
+
/** @param {string} verdict */
|
|
556
|
+
function colorVerdict(verdict) {
|
|
557
|
+
if (verdict === 'pass') {
|
|
558
|
+
return `${ANSI_GREEN}${verdict}${ANSI_RESET}`;
|
|
559
|
+
}
|
|
560
|
+
if (verdict === 'not_configured') {
|
|
561
|
+
return `${ANSI_YELLOW}${verdict}${ANSI_RESET}`;
|
|
562
|
+
}
|
|
563
|
+
return `${ANSI_RED}${verdict}${ANSI_RESET}`;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** @param {TrustPayload} payload */
|
|
567
|
+
export function renderTrust(payload) {
|
|
568
|
+
const lines = [
|
|
569
|
+
`Graph: ${payload.graph}`,
|
|
570
|
+
`Verdict: ${colorVerdict(payload.trustVerdict)}`,
|
|
571
|
+
`Mode: ${payload.mode}`,
|
|
572
|
+
`Source: ${payload.trust.source}`,
|
|
573
|
+
];
|
|
574
|
+
|
|
575
|
+
const { evidenceSummary } = payload.trust;
|
|
576
|
+
lines.push(`Evidence: ${evidenceSummary.activeKeys} active keys, ${evidenceSummary.revokedKeys} revoked keys, ${evidenceSummary.activeBindings} active bindings`);
|
|
577
|
+
|
|
578
|
+
if (payload.trust.explanations.length > 0) {
|
|
579
|
+
lines.push('');
|
|
580
|
+
for (const expl of payload.trust.explanations) {
|
|
581
|
+
const icon = expl.trusted ? `${ANSI_GREEN}\u2713${ANSI_RESET}` : `${ANSI_RED}\u2717${ANSI_RESET}`;
|
|
582
|
+
lines.push(` ${icon} ${expl.writerId}: ${expl.reasonCode}`);
|
|
583
|
+
lines.push(` ${ANSI_DIM}${expl.reason}${ANSI_RESET}`);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (payload.trust.untrustedWriters.length > 0) {
|
|
588
|
+
lines.push('');
|
|
589
|
+
lines.push(`${ANSI_RED}Untrusted: ${payload.trust.untrustedWriters.join(', ')}${ANSI_RESET}`);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return `${lines.join('\n')}\n`;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ── Patch renderers ──────────────────────────────────────────────────────────
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Formats a single operation line for patch show output.
|
|
599
|
+
* @param {PatchOp} op
|
|
600
|
+
* @returns {string|null}
|
|
601
|
+
*/
|
|
602
|
+
function formatPatchOp(op) {
|
|
603
|
+
if (op.type === 'NodeAdd') {
|
|
604
|
+
return ` + node ${op.node}`;
|
|
605
|
+
}
|
|
606
|
+
if (op.type === 'NodeTombstone') {
|
|
607
|
+
return ` - node ${op.node}`;
|
|
608
|
+
}
|
|
609
|
+
if (op.type === 'EdgeAdd') {
|
|
610
|
+
return ` + edge ${op.from} -[${op.label}]-> ${op.to}`;
|
|
611
|
+
}
|
|
612
|
+
if (op.type === 'EdgeTombstone') {
|
|
613
|
+
return ` - edge ${op.from} -[${op.label}]-> ${op.to}`;
|
|
614
|
+
}
|
|
615
|
+
if (op.type === 'PropSet') {
|
|
616
|
+
return ` ~ ${op.node}.${op.key} = ${JSON.stringify(op.value)}`;
|
|
617
|
+
}
|
|
618
|
+
if (op.type === 'BlobValue') {
|
|
619
|
+
return ` + blob ${op.node}`;
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** @param {PatchShowPayload} payload */
|
|
625
|
+
export function renderPatchShow(payload) {
|
|
626
|
+
const lines = [
|
|
627
|
+
`Graph: ${payload.graph}`,
|
|
628
|
+
`SHA: ${payload.sha}`,
|
|
629
|
+
`Writer: ${payload.writer}`,
|
|
630
|
+
`Lamport: ${payload.lamport}`,
|
|
631
|
+
`Schema: ${payload.schema}`,
|
|
632
|
+
`Operations: ${payload.ops.length}`,
|
|
633
|
+
'',
|
|
634
|
+
];
|
|
635
|
+
|
|
636
|
+
for (const op of payload.ops) {
|
|
637
|
+
const line = formatPatchOp(op);
|
|
638
|
+
if (line) {
|
|
639
|
+
lines.push(line);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return `${lines.join('\n')}\n`;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/** @param {PatchListPayload} payload */
|
|
647
|
+
export function renderPatchList(payload) {
|
|
648
|
+
const lines = [
|
|
649
|
+
`Graph: ${payload.graph}`,
|
|
650
|
+
`Patches: ${payload.showing}/${payload.total}`,
|
|
651
|
+
];
|
|
652
|
+
|
|
653
|
+
if (payload.writerFilter) {
|
|
654
|
+
lines.push(`Writer: ${payload.writerFilter}`);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
lines.push('');
|
|
658
|
+
|
|
659
|
+
for (const entry of payload.entries) {
|
|
660
|
+
const nodes = entry.nodeIds.length > 0 ? ` [${entry.nodeIds.join(', ')}]` : '';
|
|
661
|
+
lines.push(` ${entry.sha} L${String(entry.lamport).padStart(3)} ${entry.writer.padEnd(20)} ${entry.opCount} ops${nodes}`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
return `${lines.join('\n')}\n`;
|
|
665
|
+
}
|