litclaude-ai 0.3.3 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -0
- package/README.md +5 -5
- package/README_ko-KR.md +5 -5
- package/RELEASE_CHECKLIST.md +8 -6
- package/cover.png +0 -0
- package/generate_cover.py +1 -1
- package/package.json +1 -1
- package/plugins/litclaude/.claude-plugin/plugin.json +1 -1
- package/plugins/litclaude/bin/litclaude-hook.js +42 -6
- package/plugins/litclaude/commands/dynamic-workflow.md +6 -6
- package/plugins/litclaude/commands/litresearch.md +37 -0
- package/plugins/litclaude/commands/review-work.md +5 -5
- package/plugins/litclaude/hooks/hooks.json +12 -0
- package/plugins/litclaude/lib/litgoal/autoloop.mjs +78 -0
- package/plugins/litclaude/lib/litgoal/cli.mjs +8 -0
- package/plugins/litclaude/skills/lit-loop/SKILL.md +12 -10
- package/plugins/litclaude/skills/lit-plan/SKILL.md +9 -10
- package/plugins/litclaude/skills/litgoal/SKILL.md +41 -5
- package/plugins/litclaude/skills/litresearch/SKILL.md +148 -0
- package/plugins/litclaude/skills/lsp-setup/SKILL.md +151 -0
- package/plugins/litclaude/skills/lsp-setup/references/bash/README.md +63 -0
- package/plugins/litclaude/skills/lsp-setup/references/c-cpp/README.md +72 -0
- package/plugins/litclaude/skills/lsp-setup/references/csharp/README.md +81 -0
- package/plugins/litclaude/skills/lsp-setup/references/dart/README.md +53 -0
- package/plugins/litclaude/skills/lsp-setup/references/elixir/README.md +57 -0
- package/plugins/litclaude/skills/lsp-setup/references/go/README.md +59 -0
- package/plugins/litclaude/skills/lsp-setup/references/haskell/README.md +64 -0
- package/plugins/litclaude/skills/lsp-setup/references/java/README.md +66 -0
- package/plugins/litclaude/skills/lsp-setup/references/julia/README.md +62 -0
- package/plugins/litclaude/skills/lsp-setup/references/kotlin/README.md +68 -0
- package/plugins/litclaude/skills/lsp-setup/references/lua/README.md +64 -0
- package/plugins/litclaude/skills/lsp-setup/references/php/README.md +56 -0
- package/plugins/litclaude/skills/lsp-setup/references/python/README.md +68 -0
- package/plugins/litclaude/skills/lsp-setup/references/ruby/README.md +62 -0
- package/plugins/litclaude/skills/lsp-setup/references/rust/README.md +61 -0
- package/plugins/litclaude/skills/lsp-setup/references/swift/README.md +60 -0
- package/plugins/litclaude/skills/lsp-setup/references/terraform/README.md +61 -0
- package/plugins/litclaude/skills/lsp-setup/references/typescript/README.md +77 -0
- package/plugins/litclaude/skills/lsp-setup/references/yaml/README.md +60 -0
- package/plugins/litclaude/skills/lsp-setup/references/zig/README.md +55 -0
- package/plugins/litclaude/skills/lsp-setup/scripts/detect-lsp.ts +220 -0
- package/plugins/litclaude/skills/lsp-setup/scripts/lsp-server-table.ts +146 -0
- package/plugins/litclaude/skills/lsp-setup/scripts/tsconfig.json +18 -0
- package/plugins/litclaude/skills/lsp-setup/scripts/verify-lsp.ts +242 -0
- package/plugins/litclaude/skills/start-work/SKILL.md +12 -13
- package/plugins/litclaude/skills/visual-qa/SKILL.md +259 -0
- package/plugins/litclaude/skills/visual-qa/scripts/ansi.ts +17 -0
- package/plugins/litclaude/skills/visual-qa/scripts/cli.ts +96 -0
- package/plugins/litclaude/skills/visual-qa/scripts/east-asian-width.ts +72 -0
- package/plugins/litclaude/skills/visual-qa/scripts/image-diff.ts +109 -0
- package/plugins/litclaude/skills/visual-qa/scripts/png-crc.ts +27 -0
- package/plugins/litclaude/skills/visual-qa/scripts/png-decode.ts +206 -0
- package/plugins/litclaude/skills/visual-qa/scripts/png-synth.ts +57 -0
- package/plugins/litclaude/skills/visual-qa/scripts/tui-grid.ts +88 -0
- package/plugins/litclaude/skills/visual-qa/scripts/types.ts +54 -0
- package/scripts/qa-portable-install.sh +1 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer"
|
|
2
|
+
import { inflateSync } from "node:zlib"
|
|
3
|
+
|
|
4
|
+
import { PNG_SIGNATURE } from "./png-crc.ts"
|
|
5
|
+
import type { DecodedImage } from "./types.ts"
|
|
6
|
+
|
|
7
|
+
export class PngDecodeError extends Error {
|
|
8
|
+
readonly name = "PngDecodeError"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface PngHeader {
|
|
12
|
+
readonly width: number
|
|
13
|
+
readonly height: number
|
|
14
|
+
readonly bitDepth: number
|
|
15
|
+
readonly colorType: number
|
|
16
|
+
readonly channels: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface PngChunk {
|
|
20
|
+
readonly type: string
|
|
21
|
+
readonly data: Buffer
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readChunks(buffer: Buffer): readonly PngChunk[] {
|
|
25
|
+
const chunks: PngChunk[] = []
|
|
26
|
+
let offset = 8
|
|
27
|
+
while (offset + 8 <= buffer.length) {
|
|
28
|
+
const length = buffer.readUInt32BE(offset)
|
|
29
|
+
const type = buffer.toString("ascii", offset + 4, offset + 8)
|
|
30
|
+
const dataStart = offset + 8
|
|
31
|
+
const dataEnd = dataStart + length
|
|
32
|
+
if (dataEnd + 4 > buffer.length) break
|
|
33
|
+
chunks.push({ type, data: buffer.subarray(dataStart, dataEnd) })
|
|
34
|
+
offset = dataEnd + 4
|
|
35
|
+
}
|
|
36
|
+
return chunks
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function channelsForColorType(colorType: number): number {
|
|
40
|
+
switch (colorType) {
|
|
41
|
+
case 0:
|
|
42
|
+
return 1
|
|
43
|
+
case 2:
|
|
44
|
+
return 3
|
|
45
|
+
case 4:
|
|
46
|
+
return 2
|
|
47
|
+
case 6:
|
|
48
|
+
return 4
|
|
49
|
+
default:
|
|
50
|
+
throw new PngDecodeError(`unsupported color type ${colorType}`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseHeader(data: Buffer): PngHeader {
|
|
55
|
+
if (data.length < 13) {
|
|
56
|
+
throw new PngDecodeError("invalid IHDR chunk length")
|
|
57
|
+
}
|
|
58
|
+
const colorType = data[9] ?? 0
|
|
59
|
+
return {
|
|
60
|
+
width: data.readUInt32BE(0),
|
|
61
|
+
height: data.readUInt32BE(4),
|
|
62
|
+
bitDepth: data[8] ?? 0,
|
|
63
|
+
colorType,
|
|
64
|
+
channels: channelsForColorType(colorType),
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function paeth(a: number, b: number, c: number): number {
|
|
69
|
+
const p = a + b - c
|
|
70
|
+
const pa = Math.abs(p - a)
|
|
71
|
+
const pb = Math.abs(p - b)
|
|
72
|
+
const pc = Math.abs(p - c)
|
|
73
|
+
if (pa <= pb && pa <= pc) return a
|
|
74
|
+
if (pb <= pc) return b
|
|
75
|
+
return c
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function unfilterRow(filterType: number, row: Buffer, prev: Buffer | null, bpp: number): Buffer {
|
|
79
|
+
const out = Buffer.alloc(row.length)
|
|
80
|
+
for (let i = 0; i < row.length; i++) {
|
|
81
|
+
const raw = row[i] ?? 0
|
|
82
|
+
const a = i >= bpp ? (out[i - bpp] ?? 0) : 0
|
|
83
|
+
const b = prev ? (prev[i] ?? 0) : 0
|
|
84
|
+
const c = i >= bpp && prev ? (prev[i - bpp] ?? 0) : 0
|
|
85
|
+
switch (filterType) {
|
|
86
|
+
case 0:
|
|
87
|
+
out[i] = raw
|
|
88
|
+
break
|
|
89
|
+
case 1:
|
|
90
|
+
out[i] = (raw + a) & 0xff
|
|
91
|
+
break
|
|
92
|
+
case 2:
|
|
93
|
+
out[i] = (raw + b) & 0xff
|
|
94
|
+
break
|
|
95
|
+
case 3:
|
|
96
|
+
out[i] = (raw + ((a + b) >> 1)) & 0xff
|
|
97
|
+
break
|
|
98
|
+
case 4:
|
|
99
|
+
out[i] = (raw + paeth(a, b, c)) & 0xff
|
|
100
|
+
break
|
|
101
|
+
default:
|
|
102
|
+
throw new PngDecodeError(`unsupported filter type ${filterType}`)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return out
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function decodePixels(idat: Buffer, width: number, height: number, bpp: number): Buffer {
|
|
109
|
+
const inflated = inflateSync(idat)
|
|
110
|
+
const rowBytes = width * bpp
|
|
111
|
+
if (inflated.length < height * (rowBytes + 1)) {
|
|
112
|
+
throw new PngDecodeError("truncated image data")
|
|
113
|
+
}
|
|
114
|
+
const pixels = Buffer.alloc(width * height * bpp)
|
|
115
|
+
let prev: Buffer | null = null
|
|
116
|
+
for (let y = 0; y < height; y++) {
|
|
117
|
+
const rowStart = y * (rowBytes + 1)
|
|
118
|
+
const filterType = inflated[rowStart] ?? 0
|
|
119
|
+
const filtered = inflated.subarray(rowStart + 1, rowStart + 1 + rowBytes)
|
|
120
|
+
const row = unfilterRow(filterType, filtered, prev, bpp)
|
|
121
|
+
row.copy(pixels, y * rowBytes)
|
|
122
|
+
prev = row
|
|
123
|
+
}
|
|
124
|
+
return pixels
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function normalizeToRgba(
|
|
128
|
+
pixels: Buffer,
|
|
129
|
+
pixelCount: number,
|
|
130
|
+
channels: number,
|
|
131
|
+
): { readonly rgba: Uint8Array; readonly hasTransparent: boolean } {
|
|
132
|
+
const rgba = new Uint8Array(pixelCount * 4)
|
|
133
|
+
let hasTransparent = false
|
|
134
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
135
|
+
const src = i * channels
|
|
136
|
+
let r = 0
|
|
137
|
+
let g = 0
|
|
138
|
+
let b = 0
|
|
139
|
+
let a = 255
|
|
140
|
+
switch (channels) {
|
|
141
|
+
case 1: {
|
|
142
|
+
const v = pixels[src] ?? 0
|
|
143
|
+
r = v
|
|
144
|
+
g = v
|
|
145
|
+
b = v
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
case 2: {
|
|
149
|
+
const v = pixels[src] ?? 0
|
|
150
|
+
r = v
|
|
151
|
+
g = v
|
|
152
|
+
b = v
|
|
153
|
+
a = pixels[src + 1] ?? 255
|
|
154
|
+
break
|
|
155
|
+
}
|
|
156
|
+
case 3: {
|
|
157
|
+
r = pixels[src] ?? 0
|
|
158
|
+
g = pixels[src + 1] ?? 0
|
|
159
|
+
b = pixels[src + 2] ?? 0
|
|
160
|
+
break
|
|
161
|
+
}
|
|
162
|
+
default: {
|
|
163
|
+
r = pixels[src] ?? 0
|
|
164
|
+
g = pixels[src + 1] ?? 0
|
|
165
|
+
b = pixels[src + 2] ?? 0
|
|
166
|
+
a = pixels[src + 3] ?? 255
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const dst = i * 4
|
|
170
|
+
rgba[dst] = r
|
|
171
|
+
rgba[dst + 1] = g
|
|
172
|
+
rgba[dst + 2] = b
|
|
173
|
+
rgba[dst + 3] = a
|
|
174
|
+
if (a < 255) hasTransparent = true
|
|
175
|
+
}
|
|
176
|
+
return { rgba, hasTransparent }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function decodePng(buffer: Buffer): DecodedImage {
|
|
180
|
+
if (buffer.length < 8 || !buffer.subarray(0, 8).equals(PNG_SIGNATURE)) {
|
|
181
|
+
throw new PngDecodeError("not a PNG file (bad signature)")
|
|
182
|
+
}
|
|
183
|
+
const chunks = readChunks(buffer)
|
|
184
|
+
const ihdr = chunks.find((chunk) => chunk.type === "IHDR")
|
|
185
|
+
if (ihdr === undefined) {
|
|
186
|
+
throw new PngDecodeError("missing IHDR chunk")
|
|
187
|
+
}
|
|
188
|
+
const header = parseHeader(ihdr.data)
|
|
189
|
+
if (header.bitDepth !== 8) {
|
|
190
|
+
throw new PngDecodeError(`unsupported bit depth ${header.bitDepth}`)
|
|
191
|
+
}
|
|
192
|
+
const idatChunks = chunks.filter((chunk) => chunk.type === "IDAT")
|
|
193
|
+
if (idatChunks.length === 0) {
|
|
194
|
+
throw new PngDecodeError("missing IDAT chunk")
|
|
195
|
+
}
|
|
196
|
+
const idat = Buffer.concat(idatChunks.map((chunk) => chunk.data))
|
|
197
|
+
const pixels = decodePixels(idat, header.width, header.height, header.channels)
|
|
198
|
+
const normalized = normalizeToRgba(pixels, header.width * header.height, header.channels)
|
|
199
|
+
return {
|
|
200
|
+
width: header.width,
|
|
201
|
+
height: header.height,
|
|
202
|
+
rgba: normalized.rgba,
|
|
203
|
+
hasAlphaChannel: header.colorType === 4 || header.colorType === 6,
|
|
204
|
+
hasTransparentPixels: normalized.hasTransparent,
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer"
|
|
2
|
+
import { deflateSync } from "node:zlib"
|
|
3
|
+
|
|
4
|
+
import { crc32, PNG_SIGNATURE } from "./png-crc.ts"
|
|
5
|
+
|
|
6
|
+
const BIT_DEPTH_8 = 8
|
|
7
|
+
const COLOR_TYPE_RGBA = 6
|
|
8
|
+
const FILTER_NONE = 0
|
|
9
|
+
const RGBA_CHANNELS = 4
|
|
10
|
+
|
|
11
|
+
function pngChunk(type: string, data: Buffer): Buffer {
|
|
12
|
+
const typeBuffer = Buffer.from(type, "ascii")
|
|
13
|
+
const length = Buffer.alloc(4)
|
|
14
|
+
length.writeUInt32BE(data.length, 0)
|
|
15
|
+
const crcBuffer = Buffer.alloc(4)
|
|
16
|
+
crcBuffer.writeUInt32BE(crc32(Buffer.concat([typeBuffer, data])), 0)
|
|
17
|
+
return Buffer.concat([length, typeBuffer, data, crcBuffer])
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function encodeRgbaPng(width: number, height: number, rgba: Uint8Array): Buffer {
|
|
21
|
+
const rowBytes = width * RGBA_CHANNELS
|
|
22
|
+
const raw = Buffer.alloc(height * (rowBytes + 1))
|
|
23
|
+
for (let y = 0; y < height; y++) {
|
|
24
|
+
const rowStart = y * (rowBytes + 1)
|
|
25
|
+
raw[rowStart] = FILTER_NONE
|
|
26
|
+
for (let x = 0; x < rowBytes; x++) {
|
|
27
|
+
raw[rowStart + 1 + x] = rgba[y * rowBytes + x] ?? 0
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const header = Buffer.alloc(13)
|
|
31
|
+
header.writeUInt32BE(width, 0)
|
|
32
|
+
header.writeUInt32BE(height, 4)
|
|
33
|
+
header[8] = BIT_DEPTH_8
|
|
34
|
+
header[9] = COLOR_TYPE_RGBA
|
|
35
|
+
return Buffer.concat([
|
|
36
|
+
PNG_SIGNATURE,
|
|
37
|
+
pngChunk("IHDR", header),
|
|
38
|
+
pngChunk("IDAT", deflateSync(raw)),
|
|
39
|
+
pngChunk("IEND", Buffer.alloc(0)),
|
|
40
|
+
])
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function solidRgba(
|
|
44
|
+
width: number,
|
|
45
|
+
height: number,
|
|
46
|
+
color: readonly [number, number, number, number],
|
|
47
|
+
): Uint8Array {
|
|
48
|
+
const rgba = new Uint8Array(width * height * RGBA_CHANNELS)
|
|
49
|
+
for (let pixel = 0; pixel < width * height; pixel++) {
|
|
50
|
+
const offset = pixel * RGBA_CHANNELS
|
|
51
|
+
rgba[offset] = color[0]
|
|
52
|
+
rgba[offset + 1] = color[1]
|
|
53
|
+
rgba[offset + 2] = color[2]
|
|
54
|
+
rgba[offset + 3] = color[3]
|
|
55
|
+
}
|
|
56
|
+
return rgba
|
|
57
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { hasAnsi, stripAnsi } from "./ansi.ts"
|
|
2
|
+
import { charWidth, stringWidth } from "./east-asian-width.ts"
|
|
3
|
+
import type { OverflowLine, TuiCheckResult } from "./types.ts"
|
|
4
|
+
|
|
5
|
+
const BOX_DRAWING_START = 0x2500
|
|
6
|
+
const BOX_DRAWING_END = 0x257f
|
|
7
|
+
const MAX_WIDE_COLUMNS = 64
|
|
8
|
+
|
|
9
|
+
function splitLines(text: string): string[] {
|
|
10
|
+
const lines = text.split(/\r?\n/)
|
|
11
|
+
if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop()
|
|
12
|
+
return lines
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isFrameLine(plain: string): boolean {
|
|
16
|
+
for (const char of plain) {
|
|
17
|
+
const codePoint = char.codePointAt(0)
|
|
18
|
+
if (codePoint !== undefined && codePoint >= BOX_DRAWING_START && codePoint <= BOX_DRAWING_END) {
|
|
19
|
+
return true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function wideStartColumns(plain: string): number[] {
|
|
26
|
+
const columns: number[] = []
|
|
27
|
+
let column = 0
|
|
28
|
+
for (const char of plain) {
|
|
29
|
+
const codePoint = char.codePointAt(0)
|
|
30
|
+
if (codePoint === undefined) continue
|
|
31
|
+
const width = charWidth(codePoint)
|
|
32
|
+
if (width === 2) columns.push(column)
|
|
33
|
+
column += width
|
|
34
|
+
}
|
|
35
|
+
return columns
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function summarize(
|
|
39
|
+
lineCount: number,
|
|
40
|
+
maxWidth: number,
|
|
41
|
+
expectedColumns: number,
|
|
42
|
+
overflowCount: number,
|
|
43
|
+
borderMisaligned: boolean,
|
|
44
|
+
containsAnsi: boolean,
|
|
45
|
+
): string {
|
|
46
|
+
const parts = [`${lineCount} line(s)`, `max width ${maxWidth}/${expectedColumns}`]
|
|
47
|
+
if (overflowCount > 0) parts.push(`${overflowCount} overflow line(s)`)
|
|
48
|
+
if (borderMisaligned) parts.push("borders misaligned")
|
|
49
|
+
if (containsAnsi) parts.push("contains ANSI")
|
|
50
|
+
return `${parts.join("; ")}.`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function checkTui(text: string, expectedColumns: number): TuiCheckResult {
|
|
54
|
+
const lines = splitLines(text)
|
|
55
|
+
const lineWidths: number[] = []
|
|
56
|
+
const overflowLines: OverflowLine[] = []
|
|
57
|
+
const wideColumns = new Set<number>()
|
|
58
|
+
const frameWidths = new Set<number>()
|
|
59
|
+
|
|
60
|
+
for (let index = 0; index < lines.length; index++) {
|
|
61
|
+
const plain = stripAnsi(lines[index] ?? "")
|
|
62
|
+
const width = stringWidth(plain)
|
|
63
|
+
lineWidths.push(width)
|
|
64
|
+
if (expectedColumns > 0 && width > expectedColumns) {
|
|
65
|
+
overflowLines.push({ line: index + 1, width })
|
|
66
|
+
}
|
|
67
|
+
if (isFrameLine(plain)) frameWidths.add(width)
|
|
68
|
+
for (const column of wideStartColumns(plain)) {
|
|
69
|
+
if (wideColumns.size < MAX_WIDE_COLUMNS) wideColumns.add(column)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const maxWidth = lineWidths.reduce((max, width) => (width > max ? width : max), 0)
|
|
74
|
+
const borderMisaligned = frameWidths.size > 1
|
|
75
|
+
const containsAnsi = hasAnsi(text)
|
|
76
|
+
return {
|
|
77
|
+
command: "tui-check",
|
|
78
|
+
expectedColumns,
|
|
79
|
+
lineCount: lines.length,
|
|
80
|
+
lineWidths,
|
|
81
|
+
maxWidth,
|
|
82
|
+
overflowLines,
|
|
83
|
+
borderMisaligned,
|
|
84
|
+
wideCharColumns: [...wideColumns].sort((a, b) => a - b),
|
|
85
|
+
hasAnsi: containsAnsi,
|
|
86
|
+
summary: summarize(lines.length, maxWidth, expectedColumns, overflowLines.length, borderMisaligned, containsAnsi),
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface DecodedImage {
|
|
2
|
+
readonly width: number
|
|
3
|
+
readonly height: number
|
|
4
|
+
readonly rgba: Uint8Array
|
|
5
|
+
readonly hasAlphaChannel: boolean
|
|
6
|
+
readonly hasTransparentPixels: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface Hotspot {
|
|
10
|
+
readonly gridX: number
|
|
11
|
+
readonly gridY: number
|
|
12
|
+
readonly x: number
|
|
13
|
+
readonly y: number
|
|
14
|
+
readonly width: number
|
|
15
|
+
readonly height: number
|
|
16
|
+
readonly diffRatio: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ImageDimensions {
|
|
20
|
+
readonly width: number
|
|
21
|
+
readonly height: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ImageDiffResult {
|
|
25
|
+
readonly command: "image-diff"
|
|
26
|
+
readonly dimensionsMatch: boolean
|
|
27
|
+
readonly reference: ImageDimensions
|
|
28
|
+
readonly actual: ImageDimensions
|
|
29
|
+
readonly totalPixels: number
|
|
30
|
+
readonly diffPixels: number
|
|
31
|
+
readonly diffRatio: number
|
|
32
|
+
readonly similarityScore: number
|
|
33
|
+
readonly alphaChannelIntact: boolean
|
|
34
|
+
readonly hotspots: readonly Hotspot[]
|
|
35
|
+
readonly summary: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface OverflowLine {
|
|
39
|
+
readonly line: number
|
|
40
|
+
readonly width: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface TuiCheckResult {
|
|
44
|
+
readonly command: "tui-check"
|
|
45
|
+
readonly expectedColumns: number
|
|
46
|
+
readonly lineCount: number
|
|
47
|
+
readonly lineWidths: readonly number[]
|
|
48
|
+
readonly maxWidth: number
|
|
49
|
+
readonly overflowLines: readonly OverflowLine[]
|
|
50
|
+
readonly borderMisaligned: boolean
|
|
51
|
+
readonly wideCharColumns: readonly number[]
|
|
52
|
+
readonly hasAnsi: boolean
|
|
53
|
+
readonly summary: string
|
|
54
|
+
}
|
|
@@ -104,7 +104,7 @@ echo "DOCTOR_PASS" >> "$EVIDENCE"
|
|
|
104
104
|
|
|
105
105
|
if command -v claude >/dev/null 2>&1; then
|
|
106
106
|
run_step CLAUDE_PLUGIN_DETAILS claude plugin details litclaude@litclaude-ai
|
|
107
|
-
if ! grep -Eq "Skills \\([0-9]+\\)" "$EVIDENCE" || ! grep -q "lit-loop" "$EVIDENCE" || ! grep -q "lit-plan" "$EVIDENCE" || ! grep -q "Hooks (
|
|
107
|
+
if ! grep -Eq "Skills \\([0-9]+\\)" "$EVIDENCE" || ! grep -q "lit-loop" "$EVIDENCE" || ! grep -q "lit-plan" "$EVIDENCE" || ! grep -q "Hooks (5)" "$EVIDENCE"; then
|
|
108
108
|
echo "CLAUDE_PLUGIN_DETAILS_FAIL: expected LitClaude skills/hooks inventory" >> "$EVIDENCE"
|
|
109
109
|
echo "CLAUDE_PLUGIN_DETAILS_FAIL"
|
|
110
110
|
exit 1
|