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>
139 lines
2.8 KiB
JavaScript
139 lines
2.8 KiB
JavaScript
'use strict'
|
|
|
|
// The ABNF grammar in the spec is totally ambiguous.
|
|
//
|
|
// This parser follows the operator precedence defined in the
|
|
// `Order of Precedence and Parentheses` section.
|
|
|
|
module.exports = function (tokens) {
|
|
var index = 0
|
|
|
|
function hasMore () {
|
|
return index < tokens.length
|
|
}
|
|
|
|
function token () {
|
|
return hasMore() ? tokens[index] : null
|
|
}
|
|
|
|
function next () {
|
|
if (!hasMore()) {
|
|
throw new Error()
|
|
}
|
|
index++
|
|
}
|
|
|
|
function parseOperator (operator) {
|
|
var t = token()
|
|
if (t && t.type === 'OPERATOR' && operator === t.string) {
|
|
next()
|
|
return t.string
|
|
}
|
|
}
|
|
|
|
function parseWith () {
|
|
if (parseOperator('WITH')) {
|
|
var t = token()
|
|
if (t && t.type === 'EXCEPTION') {
|
|
next()
|
|
return t.string
|
|
}
|
|
throw new Error('Expected exception after `WITH`')
|
|
}
|
|
}
|
|
|
|
function parseLicenseRef () {
|
|
// TODO: Actually, everything is concatenated into one string
|
|
// for backward-compatibility but it could be better to return
|
|
// a nice structure.
|
|
var begin = index
|
|
var string = ''
|
|
var t = token()
|
|
if (t.type === 'DOCUMENTREF') {
|
|
next()
|
|
string += 'DocumentRef-' + t.string + ':'
|
|
if (!parseOperator(':')) {
|
|
throw new Error('Expected `:` after `DocumentRef-...`')
|
|
}
|
|
}
|
|
t = token()
|
|
if (t.type === 'LICENSEREF') {
|
|
next()
|
|
string += 'LicenseRef-' + t.string
|
|
return { license: string }
|
|
}
|
|
index = begin
|
|
}
|
|
|
|
function parseLicense () {
|
|
var t = token()
|
|
if (t && t.type === 'LICENSE') {
|
|
next()
|
|
var node = { license: t.string }
|
|
if (parseOperator('+')) {
|
|
node.plus = true
|
|
}
|
|
var exception = parseWith()
|
|
if (exception) {
|
|
node.exception = exception
|
|
}
|
|
return node
|
|
}
|
|
}
|
|
|
|
function parseParenthesizedExpression () {
|
|
var left = parseOperator('(')
|
|
if (!left) {
|
|
return
|
|
}
|
|
|
|
var expr = parseExpression()
|
|
|
|
if (!parseOperator(')')) {
|
|
throw new Error('Expected `)`')
|
|
}
|
|
|
|
return expr
|
|
}
|
|
|
|
function parseAtom () {
|
|
return (
|
|
parseParenthesizedExpression() ||
|
|
parseLicenseRef() ||
|
|
parseLicense()
|
|
)
|
|
}
|
|
|
|
function makeBinaryOpParser (operator, nextParser) {
|
|
return function parseBinaryOp () {
|
|
var left = nextParser()
|
|
if (!left) {
|
|
return
|
|
}
|
|
|
|
if (!parseOperator(operator)) {
|
|
return left
|
|
}
|
|
|
|
var right = parseBinaryOp()
|
|
if (!right) {
|
|
throw new Error('Expected expression')
|
|
}
|
|
return {
|
|
left: left,
|
|
conjunction: operator.toLowerCase(),
|
|
right: right
|
|
}
|
|
}
|
|
}
|
|
|
|
var parseAnd = makeBinaryOpParser('AND', parseAtom)
|
|
var parseExpression = makeBinaryOpParser('OR', parseAnd)
|
|
|
|
var node = parseExpression()
|
|
if (!node || hasMore()) {
|
|
throw new Error('Syntax error')
|
|
}
|
|
return node
|
|
}
|