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>
102 lines
3.0 KiB
JavaScript
102 lines
3.0 KiB
JavaScript
'use strict';
|
|
|
|
|
|
var common = require('./common');
|
|
|
|
|
|
// get snippet for a single line, respecting maxLength
|
|
function getLine(buffer, lineStart, lineEnd, position, maxLineLength) {
|
|
var head = '';
|
|
var tail = '';
|
|
var maxHalfLength = Math.floor(maxLineLength / 2) - 1;
|
|
|
|
if (position - lineStart > maxHalfLength) {
|
|
head = ' ... ';
|
|
lineStart = position - maxHalfLength + head.length;
|
|
}
|
|
|
|
if (lineEnd - position > maxHalfLength) {
|
|
tail = ' ...';
|
|
lineEnd = position + maxHalfLength - tail.length;
|
|
}
|
|
|
|
return {
|
|
str: head + buffer.slice(lineStart, lineEnd).replace(/\t/g, '→') + tail,
|
|
pos: position - lineStart + head.length // relative position
|
|
};
|
|
}
|
|
|
|
|
|
function padStart(string, max) {
|
|
return common.repeat(' ', max - string.length) + string;
|
|
}
|
|
|
|
|
|
function makeSnippet(mark, options) {
|
|
options = Object.create(options || null);
|
|
|
|
if (!mark.buffer) return null;
|
|
|
|
if (!options.maxLength) options.maxLength = 79;
|
|
if (typeof options.indent !== 'number') options.indent = 1;
|
|
if (typeof options.linesBefore !== 'number') options.linesBefore = 3;
|
|
if (typeof options.linesAfter !== 'number') options.linesAfter = 2;
|
|
|
|
var re = /\r?\n|\r|\0/g;
|
|
var lineStarts = [ 0 ];
|
|
var lineEnds = [];
|
|
var match;
|
|
var foundLineNo = -1;
|
|
|
|
while ((match = re.exec(mark.buffer))) {
|
|
lineEnds.push(match.index);
|
|
lineStarts.push(match.index + match[0].length);
|
|
|
|
if (mark.position <= match.index && foundLineNo < 0) {
|
|
foundLineNo = lineStarts.length - 2;
|
|
}
|
|
}
|
|
|
|
if (foundLineNo < 0) foundLineNo = lineStarts.length - 1;
|
|
|
|
var result = '', i, line;
|
|
var lineNoLength = Math.min(mark.line + options.linesAfter, lineEnds.length).toString().length;
|
|
var maxLineLength = options.maxLength - (options.indent + lineNoLength + 3);
|
|
|
|
for (i = 1; i <= options.linesBefore; i++) {
|
|
if (foundLineNo - i < 0) break;
|
|
line = getLine(
|
|
mark.buffer,
|
|
lineStarts[foundLineNo - i],
|
|
lineEnds[foundLineNo - i],
|
|
mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo - i]),
|
|
maxLineLength
|
|
);
|
|
result = common.repeat(' ', options.indent) + padStart((mark.line - i + 1).toString(), lineNoLength) +
|
|
' | ' + line.str + '\n' + result;
|
|
}
|
|
|
|
line = getLine(mark.buffer, lineStarts[foundLineNo], lineEnds[foundLineNo], mark.position, maxLineLength);
|
|
result += common.repeat(' ', options.indent) + padStart((mark.line + 1).toString(), lineNoLength) +
|
|
' | ' + line.str + '\n';
|
|
result += common.repeat('-', options.indent + lineNoLength + 3 + line.pos) + '^' + '\n';
|
|
|
|
for (i = 1; i <= options.linesAfter; i++) {
|
|
if (foundLineNo + i >= lineEnds.length) break;
|
|
line = getLine(
|
|
mark.buffer,
|
|
lineStarts[foundLineNo + i],
|
|
lineEnds[foundLineNo + i],
|
|
mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo + i]),
|
|
maxLineLength
|
|
);
|
|
result += common.repeat(' ', options.indent) + padStart((mark.line + i + 1).toString(), lineNoLength) +
|
|
' | ' + line.str + '\n';
|
|
}
|
|
|
|
return result.replace(/\n$/, '');
|
|
}
|
|
|
|
|
|
module.exports = makeSnippet;
|