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>
136 lines
4.5 KiB
JavaScript
136 lines
4.5 KiB
JavaScript
'use strict'
|
|
|
|
// Extracted from node/lib/internal/fixed_queue.js
|
|
|
|
// Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two.
|
|
const kSize = 2048
|
|
const kMask = kSize - 1
|
|
|
|
// The FixedQueue is implemented as a singly-linked list of fixed-size
|
|
// circular buffers. It looks something like this:
|
|
//
|
|
// head tail
|
|
// | |
|
|
// v v
|
|
// +-----------+ <-----\ +-----------+ <------\ +-----------+
|
|
// | [null] | \----- | next | \------- | next |
|
|
// +-----------+ +-----------+ +-----------+
|
|
// | item | <-- bottom | item | <-- bottom | undefined |
|
|
// | item | | item | | undefined |
|
|
// | item | | item | | undefined |
|
|
// | item | | item | | undefined |
|
|
// | item | | item | bottom --> | item |
|
|
// | item | | item | | item |
|
|
// | ... | | ... | | ... |
|
|
// | item | | item | | item |
|
|
// | item | | item | | item |
|
|
// | undefined | <-- top | item | | item |
|
|
// | undefined | | item | | item |
|
|
// | undefined | | undefined | <-- top top --> | undefined |
|
|
// +-----------+ +-----------+ +-----------+
|
|
//
|
|
// Or, if there is only one circular buffer, it looks something
|
|
// like either of these:
|
|
//
|
|
// head tail head tail
|
|
// | | | |
|
|
// v v v v
|
|
// +-----------+ +-----------+
|
|
// | [null] | | [null] |
|
|
// +-----------+ +-----------+
|
|
// | undefined | | item |
|
|
// | undefined | | item |
|
|
// | item | <-- bottom top --> | undefined |
|
|
// | item | | undefined |
|
|
// | undefined | <-- top bottom --> | item |
|
|
// | undefined | | item |
|
|
// +-----------+ +-----------+
|
|
//
|
|
// Adding a value means moving `top` forward by one, removing means
|
|
// moving `bottom` forward by one. After reaching the end, the queue
|
|
// wraps around.
|
|
//
|
|
// When `top === bottom` the current queue is empty and when
|
|
// `top + 1 === bottom` it's full. This wastes a single space of storage
|
|
// but allows much quicker checks.
|
|
|
|
/**
|
|
* @type {FixedCircularBuffer}
|
|
* @template T
|
|
*/
|
|
class FixedCircularBuffer {
|
|
/** @type {number} */
|
|
bottom = 0
|
|
/** @type {number} */
|
|
top = 0
|
|
/** @type {Array<T|undefined>} */
|
|
list = new Array(kSize).fill(undefined)
|
|
/** @type {T|null} */
|
|
next = null
|
|
|
|
/** @returns {boolean} */
|
|
isEmpty () {
|
|
return this.top === this.bottom
|
|
}
|
|
|
|
/** @returns {boolean} */
|
|
isFull () {
|
|
return ((this.top + 1) & kMask) === this.bottom
|
|
}
|
|
|
|
/**
|
|
* @param {T} data
|
|
* @returns {void}
|
|
*/
|
|
push (data) {
|
|
this.list[this.top] = data
|
|
this.top = (this.top + 1) & kMask
|
|
}
|
|
|
|
/** @returns {T|null} */
|
|
shift () {
|
|
const nextItem = this.list[this.bottom]
|
|
if (nextItem === undefined) { return null }
|
|
this.list[this.bottom] = undefined
|
|
this.bottom = (this.bottom + 1) & kMask
|
|
return nextItem
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @template T
|
|
*/
|
|
module.exports = class FixedQueue {
|
|
constructor () {
|
|
/** @type {FixedCircularBuffer<T>} */
|
|
this.head = this.tail = new FixedCircularBuffer()
|
|
}
|
|
|
|
/** @returns {boolean} */
|
|
isEmpty () {
|
|
return this.head.isEmpty()
|
|
}
|
|
|
|
/** @param {T} data */
|
|
push (data) {
|
|
if (this.head.isFull()) {
|
|
// Head is full: Creates a new queue, sets the old queue's `.next` to it,
|
|
// and sets it as the new main queue.
|
|
this.head = this.head.next = new FixedCircularBuffer()
|
|
}
|
|
this.head.push(data)
|
|
}
|
|
|
|
/** @returns {T|null} */
|
|
shift () {
|
|
const tail = this.tail
|
|
const next = tail.shift()
|
|
if (tail.isEmpty() && tail.next !== null) {
|
|
// If there is another queue, it forms the new tail.
|
|
this.tail = tail.next
|
|
tail.next = null
|
|
}
|
|
return next
|
|
}
|
|
}
|