tftsr-devops_investigation/node_modules/micromark-util-subtokenize/dev/lib/splice-buffer.js
Shaun Arman 8839075805 feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.

Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)

Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)

DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload

Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 22:36:25 -05:00

289 lines
7.8 KiB
JavaScript

import {constants} from 'micromark-util-symbol'
/**
* Some of the internal operations of micromark do lots of editing
* operations on very large arrays. This runs into problems with two
* properties of most circa-2020 JavaScript interpreters:
*
* - Array-length modifications at the high end of an array (push/pop) are
* expected to be common and are implemented in (amortized) time
* proportional to the number of elements added or removed, whereas
* other operations (shift/unshift and splice) are much less efficient.
* - Function arguments are passed on the stack, so adding tens of thousands
* of elements to an array with `arr.push(...newElements)` will frequently
* cause stack overflows. (see <https://stackoverflow.com/questions/22123769/rangeerror-maximum-call-stack-size-exceeded-why>)
*
* SpliceBuffers are an implementation of gap buffers, which are a
* generalization of the "queue made of two stacks" idea. The splice buffer
* maintains a cursor, and moving the cursor has cost proportional to the
* distance the cursor moves, but inserting, deleting, or splicing in
* new information at the cursor is as efficient as the push/pop operation.
* This allows for an efficient sequence of splices (or pushes, pops, shifts,
* or unshifts) as long such edits happen at the same part of the array or
* generally sweep through the array from the beginning to the end.
*
* The interface for splice buffers also supports large numbers of inputs by
* passing a single array argument rather passing multiple arguments on the
* function call stack.
*
* @template T
* Item type.
*/
export class SpliceBuffer {
/**
* @param {ReadonlyArray<T> | null | undefined} [initial]
* Initial items (optional).
* @returns
* Splice buffer.
*/
constructor(initial) {
/** @type {Array<T>} */
this.left = initial ? [...initial] : []
/** @type {Array<T>} */
this.right = []
}
/**
* Array access;
* does not move the cursor.
*
* @param {number} index
* Index.
* @return {T}
* Item.
*/
get(index) {
if (index < 0 || index >= this.left.length + this.right.length) {
throw new RangeError(
'Cannot access index `' +
index +
'` in a splice buffer of size `' +
(this.left.length + this.right.length) +
'`'
)
}
if (index < this.left.length) return this.left[index]
return this.right[this.right.length - index + this.left.length - 1]
}
/**
* The length of the splice buffer, one greater than the largest index in the
* array.
*/
get length() {
return this.left.length + this.right.length
}
/**
* Remove and return `list[0]`;
* moves the cursor to `0`.
*
* @returns {T | undefined}
* Item, optional.
*/
shift() {
this.setCursor(0)
return this.right.pop()
}
/**
* Slice the buffer to get an array;
* does not move the cursor.
*
* @param {number} start
* Start.
* @param {number | null | undefined} [end]
* End (optional).
* @returns {Array<T>}
* Array of items.
*/
slice(start, end) {
/** @type {number} */
const stop =
end === null || end === undefined ? Number.POSITIVE_INFINITY : end
if (stop < this.left.length) {
return this.left.slice(start, stop)
}
if (start > this.left.length) {
return this.right
.slice(
this.right.length - stop + this.left.length,
this.right.length - start + this.left.length
)
.reverse()
}
return this.left
.slice(start)
.concat(
this.right.slice(this.right.length - stop + this.left.length).reverse()
)
}
/**
* Mimics the behavior of Array.prototype.splice() except for the change of
* interface necessary to avoid segfaults when patching in very large arrays.
*
* This operation moves cursor is moved to `start` and results in the cursor
* placed after any inserted items.
*
* @param {number} start
* Start;
* zero-based index at which to start changing the array;
* negative numbers count backwards from the end of the array and values
* that are out-of bounds are clamped to the appropriate end of the array.
* @param {number | null | undefined} [deleteCount=0]
* Delete count (default: `0`);
* maximum number of elements to delete, starting from start.
* @param {Array<T> | null | undefined} [items=[]]
* Items to include in place of the deleted items (default: `[]`).
* @return {Array<T>}
* Any removed items.
*/
splice(start, deleteCount, items) {
/** @type {number} */
const count = deleteCount || 0
this.setCursor(Math.trunc(start))
const removed = this.right.splice(
this.right.length - count,
Number.POSITIVE_INFINITY
)
if (items) chunkedPush(this.left, items)
return removed.reverse()
}
/**
* Remove and return the highest-numbered item in the array, so
* `list[list.length - 1]`;
* Moves the cursor to `length`.
*
* @returns {T | undefined}
* Item, optional.
*/
pop() {
this.setCursor(Number.POSITIVE_INFINITY)
return this.left.pop()
}
/**
* Inserts a single item to the high-numbered side of the array;
* moves the cursor to `length`.
*
* @param {T} item
* Item.
* @returns {undefined}
* Nothing.
*/
push(item) {
this.setCursor(Number.POSITIVE_INFINITY)
this.left.push(item)
}
/**
* Inserts many items to the high-numbered side of the array.
* Moves the cursor to `length`.
*
* @param {Array<T>} items
* Items.
* @returns {undefined}
* Nothing.
*/
pushMany(items) {
this.setCursor(Number.POSITIVE_INFINITY)
chunkedPush(this.left, items)
}
/**
* Inserts a single item to the low-numbered side of the array;
* Moves the cursor to `0`.
*
* @param {T} item
* Item.
* @returns {undefined}
* Nothing.
*/
unshift(item) {
this.setCursor(0)
this.right.push(item)
}
/**
* Inserts many items to the low-numbered side of the array;
* moves the cursor to `0`.
*
* @param {Array<T>} items
* Items.
* @returns {undefined}
* Nothing.
*/
unshiftMany(items) {
this.setCursor(0)
chunkedPush(this.right, items.reverse())
}
/**
* Move the cursor to a specific position in the array. Requires
* time proportional to the distance moved.
*
* If `n < 0`, the cursor will end up at the beginning.
* If `n > length`, the cursor will end up at the end.
*
* @param {number} n
* Position.
* @return {undefined}
* Nothing.
*/
setCursor(n) {
if (
n === this.left.length ||
(n > this.left.length && this.right.length === 0) ||
(n < 0 && this.left.length === 0)
)
return
if (n < this.left.length) {
// Move cursor to the this.left
const removed = this.left.splice(n, Number.POSITIVE_INFINITY)
chunkedPush(this.right, removed.reverse())
} else {
// Move cursor to the this.right
const removed = this.right.splice(
this.left.length + this.right.length - n,
Number.POSITIVE_INFINITY
)
chunkedPush(this.left, removed.reverse())
}
}
}
/**
* Avoid stack overflow by pushing items onto the stack in segments
*
* @template T
* Item type.
* @param {Array<T>} list
* List to inject into.
* @param {ReadonlyArray<T>} right
* Items to inject.
* @return {undefined}
* Nothing.
*/
function chunkedPush(list, right) {
/** @type {number} */
let chunkStart = 0
if (right.length < constants.v8MaxSafeChunkSize) {
list.push(...right)
} else {
while (chunkStart < right.length) {
list.push(
...right.slice(chunkStart, chunkStart + constants.v8MaxSafeChunkSize)
)
chunkStart += constants.v8MaxSafeChunkSize
}
}
}