tftsr-devops_investigation/node_modules/mdast-util-gfm-table/lib/index.js

301 lines
8.0 KiB
JavaScript
Raw Normal View History

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-15 03:36:25 +00:00
/**
* @typedef {import('mdast').InlineCode} InlineCode
* @typedef {import('mdast').Table} Table
* @typedef {import('mdast').TableCell} TableCell
* @typedef {import('mdast').TableRow} TableRow
*
* @typedef {import('markdown-table').Options} MarkdownTableOptions
*
* @typedef {import('mdast-util-from-markdown').CompileContext} CompileContext
* @typedef {import('mdast-util-from-markdown').Extension} FromMarkdownExtension
* @typedef {import('mdast-util-from-markdown').Handle} FromMarkdownHandle
*
* @typedef {import('mdast-util-to-markdown').Options} ToMarkdownExtension
* @typedef {import('mdast-util-to-markdown').Handle} ToMarkdownHandle
* @typedef {import('mdast-util-to-markdown').State} State
* @typedef {import('mdast-util-to-markdown').Info} Info
*/
/**
* @typedef Options
* Configuration.
* @property {boolean | null | undefined} [tableCellPadding=true]
* Whether to add a space of padding between delimiters and cells (default:
* `true`).
* @property {boolean | null | undefined} [tablePipeAlign=true]
* Whether to align the delimiters (default: `true`).
* @property {MarkdownTableOptions['stringLength'] | null | undefined} [stringLength]
* Function to detect the length of table cell content, used when aligning
* the delimiters between cells (optional).
*/
import {ok as assert} from 'devlop'
import {markdownTable} from 'markdown-table'
import {defaultHandlers} from 'mdast-util-to-markdown'
/**
* Create an extension for `mdast-util-from-markdown` to enable GFM tables in
* markdown.
*
* @returns {FromMarkdownExtension}
* Extension for `mdast-util-from-markdown` to enable GFM tables.
*/
export function gfmTableFromMarkdown() {
return {
enter: {
table: enterTable,
tableData: enterCell,
tableHeader: enterCell,
tableRow: enterRow
},
exit: {
codeText: exitCodeText,
table: exitTable,
tableData: exit,
tableHeader: exit,
tableRow: exit
}
}
}
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function enterTable(token) {
const align = token._align
assert(align, 'expected `_align` on table')
this.enter(
{
type: 'table',
align: align.map(function (d) {
return d === 'none' ? null : d
}),
children: []
},
token
)
this.data.inTable = true
}
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function exitTable(token) {
this.exit(token)
this.data.inTable = undefined
}
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function enterRow(token) {
this.enter({type: 'tableRow', children: []}, token)
}
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function exit(token) {
this.exit(token)
}
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function enterCell(token) {
this.enter({type: 'tableCell', children: []}, token)
}
// Overwrite the default code text data handler to unescape escaped pipes when
// they are in tables.
/**
* @this {CompileContext}
* @type {FromMarkdownHandle}
*/
function exitCodeText(token) {
let value = this.resume()
if (this.data.inTable) {
value = value.replace(/\\([\\|])/g, replace)
}
const node = this.stack[this.stack.length - 1]
assert(node.type === 'inlineCode')
node.value = value
this.exit(token)
}
/**
* @param {string} $0
* @param {string} $1
* @returns {string}
*/
function replace($0, $1) {
// Pipes work, backslashes dont (but cant escape pipes).
return $1 === '|' ? $1 : $0
}
/**
* Create an extension for `mdast-util-to-markdown` to enable GFM tables in
* markdown.
*
* @param {Options | null | undefined} [options]
* Configuration.
* @returns {ToMarkdownExtension}
* Extension for `mdast-util-to-markdown` to enable GFM tables.
*/
export function gfmTableToMarkdown(options) {
const settings = options || {}
const padding = settings.tableCellPadding
const alignDelimiters = settings.tablePipeAlign
const stringLength = settings.stringLength
const around = padding ? ' ' : '|'
return {
unsafe: [
{character: '\r', inConstruct: 'tableCell'},
{character: '\n', inConstruct: 'tableCell'},
// A pipe, when followed by a tab or space (padding), or a dash or colon
// (unpadded delimiter row), could result in a table.
{atBreak: true, character: '|', after: '[\t :-]'},
// A pipe in a cell must be encoded.
{character: '|', inConstruct: 'tableCell'},
// A colon must be followed by a dash, in which case it could start a
// delimiter row.
{atBreak: true, character: ':', after: '-'},
// A delimiter row can also start with a dash, when followed by more
// dashes, a colon, or a pipe.
// This is a stricter version than the built in check for lists, thematic
// breaks, and setex heading underlines though:
// <https://github.com/syntax-tree/mdast-util-to-markdown/blob/51a2038/lib/unsafe.js#L57>
{atBreak: true, character: '-', after: '[:|-]'}
],
handlers: {
inlineCode: inlineCodeWithTable,
table: handleTable,
tableCell: handleTableCell,
tableRow: handleTableRow
}
}
/**
* @type {ToMarkdownHandle}
* @param {Table} node
*/
function handleTable(node, _, state, info) {
return serializeData(handleTableAsData(node, state, info), node.align)
}
/**
* This function isnt really used normally, because we handle rows at the
* table level.
* But, if someone passes in a table row, this ensures we make somewhat sense.
*
* @type {ToMarkdownHandle}
* @param {TableRow} node
*/
function handleTableRow(node, _, state, info) {
const row = handleTableRowAsData(node, state, info)
const value = serializeData([row])
// `markdown-table` will always add an align row
return value.slice(0, value.indexOf('\n'))
}
/**
* @type {ToMarkdownHandle}
* @param {TableCell} node
*/
function handleTableCell(node, _, state, info) {
const exit = state.enter('tableCell')
const subexit = state.enter('phrasing')
const value = state.containerPhrasing(node, {
...info,
before: around,
after: around
})
subexit()
exit()
return value
}
/**
* @param {Array<Array<string>>} matrix
* @param {Array<string | null | undefined> | null | undefined} [align]
*/
function serializeData(matrix, align) {
return markdownTable(matrix, {
align,
// @ts-expect-error: `markdown-table` types should support `null`.
alignDelimiters,
// @ts-expect-error: `markdown-table` types should support `null`.
padding,
// @ts-expect-error: `markdown-table` types should support `null`.
stringLength
})
}
/**
* @param {Table} node
* @param {State} state
* @param {Info} info
*/
function handleTableAsData(node, state, info) {
const children = node.children
let index = -1
/** @type {Array<Array<string>>} */
const result = []
const subexit = state.enter('table')
while (++index < children.length) {
result[index] = handleTableRowAsData(children[index], state, info)
}
subexit()
return result
}
/**
* @param {TableRow} node
* @param {State} state
* @param {Info} info
*/
function handleTableRowAsData(node, state, info) {
const children = node.children
let index = -1
/** @type {Array<string>} */
const result = []
const subexit = state.enter('tableRow')
while (++index < children.length) {
// Note: the positional info as used here is incorrect.
// Making it correct would be impossible due to aligning cells?
// And it would need copy/pasting `markdown-table` into this project.
result[index] = handleTableCell(children[index], node, state, info)
}
subexit()
return result
}
/**
* @type {ToMarkdownHandle}
* @param {InlineCode} node
*/
function inlineCodeWithTable(node, parent, state) {
let value = defaultHandlers.inlineCode(node, parent, state)
if (state.stack.includes('tableCell')) {
value = value.replace(/\|/g, '\\$&')
}
return value
}
}