mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-08-28 02:44:59 +00:00
Fix audit follow-up gates
Fix audit follow-up gates - remove gray-matter and use local YAML frontmatter handling - upgrade DOMPurify and clear production advisories - make CLI/MCP smoke skip cleanly without VK_API_KEY - reduce initial JS below the Mantine QA budget Closes #753 Closes #754 Closes #755
This commit is contained in:
parent
5bd4377f32
commit
013c2b5383
20 changed files with 165 additions and 101 deletions
|
|
@ -7,6 +7,7 @@ This document outlines the manual integration steps required to complete feature
|
|||
**Action:** Export the new prompt registry types
|
||||
|
||||
Add these lines to the exports:
|
||||
|
||||
```typescript
|
||||
// Prompt Registry Types
|
||||
export type {
|
||||
|
|
@ -41,9 +42,10 @@ app.use('/api/prompt-registry', promptRegistryRouter);
|
|||
**Location:** Find where other routes like `templates`, `tasks`, `chat` are registered. Add the prompt registry route in the same pattern.
|
||||
|
||||
**Expected pattern:**
|
||||
|
||||
```typescript
|
||||
app.use('/api/templates', templateRouter);
|
||||
app.use('/api/prompt-registry', promptRegistryRouter); // <-- Add this line
|
||||
app.use('/api/prompt-registry', promptRegistryRouter); // <-- Add this line
|
||||
app.use('/api/tasks', taskRouter);
|
||||
```
|
||||
|
||||
|
|
@ -151,7 +153,7 @@ These files are complete and require no further changes:
|
|||
|
||||
## Notes
|
||||
|
||||
- All files follow existing code patterns (zod validation, gray-matter for storage, React Query hooks)
|
||||
- All files follow existing code patterns (zod validation, YAML frontmatter storage, React Query hooks)
|
||||
- No external dependencies added beyond what's already in use
|
||||
- Storage uses file-based approach (consistent with template-service)
|
||||
- Endpoints follow REST conventions
|
||||
|
|
@ -160,6 +162,7 @@ These files are complete and require no further changes:
|
|||
## Questions?
|
||||
|
||||
Refer to reference implementations:
|
||||
|
||||
- **Template pattern:** `server/src/services/template-service.ts`
|
||||
- **Route pattern:** `server/src/routes/templates.ts`
|
||||
- **API client pattern:** `web/src/lib/api/entities.ts`
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ Tasks are markdown files. Settings are JSON. Workflows are YAML. No database, no
|
|||
| **Frontend** | React, Vite, Tailwind CSS, Shadcn UI | React 19, Vite 7.3, Tailwind 4.2 |
|
||||
| **Backend** | Express, WebSocket | Express 5.2 |
|
||||
| **Language** | TypeScript (strict mode) | 6.0 |
|
||||
| **Storage** | Markdown files with YAML frontmatter | gray-matter |
|
||||
| **Storage** | Markdown files with YAML frontmatter | yaml + local frontmatter helper |
|
||||
| **Git** | simple-git, worktree management | — |
|
||||
| **Testing** | Playwright (E2E), Vitest (unit) | Playwright 1.58, Vitest 4 |
|
||||
| **Runtime** | Node.js | 22+ |
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ The Kanban board is the central interface — a drag-and-drop workspace that ref
|
|||
|
||||
- **Task types** — Configurable type system with icons and color-coded card borders (code, research, content, automation, and custom types)
|
||||
- **Priority levels** — Low, medium, and high with visual indicators on cards
|
||||
- **Markdown storage** — Tasks stored as human-readable `.md` files with YAML frontmatter (via [gray-matter](https://github.com/jonschlinkert/gray-matter))
|
||||
- **Markdown storage** — Tasks stored as human-readable `.md` files with YAML frontmatter
|
||||
- **Dark/light mode** — Ships dark by default with a toggle in Settings → General → Appearance; persists to localStorage; inline script in `index.html` prevents flash of wrong theme on load
|
||||
- **Filter bar** — Search tasks by text, filter by project and task type; filters persist in URL query params
|
||||
- **Desktop shell controls** — Native-app-style toolbar with workspace selection, health state, view toggles, and left/right/bottom panel controls shared by the web and macOS app shells
|
||||
|
|
|
|||
77
pnpm-lock.yaml
generated
77
pnpm-lock.yaml
generated
|
|
@ -183,9 +183,6 @@ importers:
|
|||
file-type:
|
||||
specifier: ^22.0.1
|
||||
version: 22.0.1
|
||||
gray-matter:
|
||||
specifier: ^4.0.3
|
||||
version: 4.0.3
|
||||
helmet:
|
||||
specifier: ^8.2.0
|
||||
version: 8.2.0
|
||||
|
|
@ -344,8 +341,8 @@ importers:
|
|||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
dompurify:
|
||||
specifier: ^3.4.10
|
||||
version: 3.4.10
|
||||
specifier: ^3.4.11
|
||||
version: 3.4.11
|
||||
lucide-react:
|
||||
specifier: ^1.18.0
|
||||
version: 1.18.0(react@19.2.7)
|
||||
|
|
@ -2573,8 +2570,8 @@ packages:
|
|||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
dompurify@3.4.10:
|
||||
resolution: {integrity: sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==}
|
||||
dompurify@3.4.11:
|
||||
resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==}
|
||||
|
||||
domutils@3.2.2:
|
||||
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
|
||||
|
|
@ -2797,11 +2794,6 @@ packages:
|
|||
resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
esprima@4.0.1:
|
||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
esquery@1.7.0:
|
||||
resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
|
@ -2860,10 +2852,6 @@ packages:
|
|||
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
extend-shallow@2.0.1:
|
||||
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
extend@3.0.2:
|
||||
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
||||
|
||||
|
|
@ -3078,10 +3066,6 @@ packages:
|
|||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
gray-matter@4.0.3:
|
||||
resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
||||
has-bigints@1.1.0:
|
||||
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -3286,10 +3270,6 @@ packages:
|
|||
is-decimal@2.0.1:
|
||||
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
|
||||
|
||||
is-extendable@0.1.1:
|
||||
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -3446,10 +3426,6 @@ packages:
|
|||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
js-yaml@3.14.2:
|
||||
resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
|
||||
hasBin: true
|
||||
|
||||
js-yaml@4.2.0:
|
||||
resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==}
|
||||
hasBin: true
|
||||
|
|
@ -3517,10 +3493,6 @@ packages:
|
|||
keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
|
||||
kind-of@6.0.3:
|
||||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
klona@2.0.6:
|
||||
resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
|
@ -4602,10 +4574,6 @@ packages:
|
|||
scheduler@0.27.0:
|
||||
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
|
||||
|
||||
section-matter@1.0.0:
|
||||
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
secure-json-parse@4.1.0:
|
||||
resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==}
|
||||
|
||||
|
|
@ -4814,10 +4782,6 @@ packages:
|
|||
resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
strip-bom-string@1.0.0:
|
||||
resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
strip-indent@3.0.0:
|
||||
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -6506,7 +6470,7 @@ snapshots:
|
|||
|
||||
'@types/dompurify@3.2.0':
|
||||
dependencies:
|
||||
dompurify: 3.4.10
|
||||
dompurify: 3.4.11
|
||||
|
||||
'@types/esrecurse@4.3.1': {}
|
||||
|
||||
|
|
@ -7566,7 +7530,7 @@ snapshots:
|
|||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
dompurify@3.4.10:
|
||||
dompurify@3.4.11:
|
||||
optionalDependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
|
||||
|
|
@ -7984,8 +7948,6 @@ snapshots:
|
|||
acorn-jsx: 5.3.2(acorn@8.17.0)
|
||||
eslint-visitor-keys: 5.0.1
|
||||
|
||||
esprima@4.0.1: {}
|
||||
|
||||
esquery@1.7.0:
|
||||
dependencies:
|
||||
estraverse: 5.3.0
|
||||
|
|
@ -8068,10 +8030,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
extend-shallow@2.0.1:
|
||||
dependencies:
|
||||
is-extendable: 0.1.1
|
||||
|
||||
extend@3.0.2: {}
|
||||
|
||||
fast-copy@4.0.3: {}
|
||||
|
|
@ -8328,13 +8286,6 @@ snapshots:
|
|||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
gray-matter@4.0.3:
|
||||
dependencies:
|
||||
js-yaml: 3.14.2
|
||||
kind-of: 6.0.3
|
||||
section-matter: 1.0.0
|
||||
strip-bom-string: 1.0.0
|
||||
|
||||
has-bigints@1.1.0: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
|
@ -8552,8 +8503,6 @@ snapshots:
|
|||
|
||||
is-decimal@2.0.1: {}
|
||||
|
||||
is-extendable@0.1.1: {}
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-finalizationregistry@1.1.1:
|
||||
|
|
@ -8692,11 +8641,6 @@ snapshots:
|
|||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@3.14.2:
|
||||
dependencies:
|
||||
argparse: 1.0.10
|
||||
esprima: 4.0.1
|
||||
|
||||
js-yaml@4.2.0:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
|
@ -8796,8 +8740,6 @@ snapshots:
|
|||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
|
||||
kind-of@6.0.3: {}
|
||||
|
||||
klona@2.0.6: {}
|
||||
|
||||
launder@1.7.1:
|
||||
|
|
@ -10146,11 +10088,6 @@ snapshots:
|
|||
|
||||
scheduler@0.27.0: {}
|
||||
|
||||
section-matter@1.0.0:
|
||||
dependencies:
|
||||
extend-shallow: 2.0.1
|
||||
kind-of: 6.0.3
|
||||
|
||||
secure-json-parse@4.1.0: {}
|
||||
|
||||
semver-compare@1.0.0:
|
||||
|
|
@ -10407,8 +10344,6 @@ snapshots:
|
|||
dependencies:
|
||||
ansi-regex: 6.2.2
|
||||
|
||||
strip-bom-string@1.0.0: {}
|
||||
|
||||
strip-indent@3.0.0:
|
||||
dependencies:
|
||||
min-indent: 1.0.1
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@ let mcpTaskId;
|
|||
function usage() {
|
||||
console.log(`Usage: pnpm smoke:cli-mcp -- [options]
|
||||
|
||||
Runs the v5 release-candidate CLI and MCP compatibility smoke against a running
|
||||
Veritas Kanban server. Requires a write-capable scoped API key.
|
||||
Runs v5 release-candidate CLI and MCP compatibility checks. Local version/build
|
||||
checks always run. Live read/write smoke runs when a write-capable scoped API
|
||||
key is available.
|
||||
|
||||
Options:
|
||||
--api-url <url> API origin. Defaults to VK_API_URL or http://localhost:3001.
|
||||
|
|
@ -100,6 +101,10 @@ function warn(name, detail = '') {
|
|||
record('warn', name, detail);
|
||||
}
|
||||
|
||||
function skip(name, detail = '') {
|
||||
record('skip', name, detail);
|
||||
}
|
||||
|
||||
function check(name, condition, detail = '') {
|
||||
if (condition) pass(name, detail);
|
||||
else fail(name, detail);
|
||||
|
|
@ -468,11 +473,14 @@ async function main() {
|
|||
const expectedVersion = options.expectedVersion ?? rootPackage.version;
|
||||
|
||||
check('VK_API_URL is configured', Boolean(options.apiUrl), options.apiUrl);
|
||||
check(
|
||||
'VK_API_KEY is configured for write-capable CLI/MCP smoke',
|
||||
Boolean(options.apiKey),
|
||||
options.apiKey ? 'set' : 'missing'
|
||||
);
|
||||
if (options.apiKey) {
|
||||
pass('VK_API_KEY is configured for write-capable CLI/MCP smoke', 'set');
|
||||
} else {
|
||||
skip(
|
||||
'VK_API_KEY is configured for write-capable CLI/MCP smoke',
|
||||
'missing; set VK_API_KEY or pass --api-key to run the live read/write smoke'
|
||||
);
|
||||
}
|
||||
check(
|
||||
'Root package version matches expected',
|
||||
rootPackage.version === expectedVersion,
|
||||
|
|
@ -491,7 +499,12 @@ async function main() {
|
|||
await assertFile(cliBin, 'CLI build output exists');
|
||||
await assertFile(mcpBin, 'MCP build output exists');
|
||||
|
||||
if (!options.apiUrl || !options.apiKey) {
|
||||
if (!options.apiUrl) {
|
||||
printAndExit();
|
||||
}
|
||||
|
||||
if (!options.apiKey) {
|
||||
skip('CLI/MCP live read/write smoke', 'skipped because VK_API_KEY is missing');
|
||||
printAndExit();
|
||||
}
|
||||
|
||||
|
|
@ -532,13 +545,24 @@ async function main() {
|
|||
function printAndExit() {
|
||||
let failures = 0;
|
||||
let warnings = 0;
|
||||
let skips = 0;
|
||||
for (const entry of checks) {
|
||||
if (entry.status === 'fail') failures += 1;
|
||||
if (entry.status === 'warn') warnings += 1;
|
||||
const icon = entry.status === 'pass' ? 'PASS' : entry.status === 'warn' ? 'WARN' : 'FAIL';
|
||||
if (entry.status === 'skip') skips += 1;
|
||||
const icon =
|
||||
entry.status === 'pass'
|
||||
? 'PASS'
|
||||
: entry.status === 'warn'
|
||||
? 'WARN'
|
||||
: entry.status === 'skip'
|
||||
? 'SKIP'
|
||||
: 'FAIL';
|
||||
console.log(`${icon} ${entry.name}${printableDetail(entry.detail)}`);
|
||||
}
|
||||
console.log(`\nCLI/MCP compatibility smoke: ${failures} failure(s), ${warnings} warning(s)`);
|
||||
console.log(
|
||||
`\nCLI/MCP compatibility smoke: ${failures} failure(s), ${warnings} warning(s), ${skips} skip(s)`
|
||||
);
|
||||
process.exit(failures > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@
|
|||
"express": "^5.2.1",
|
||||
"express-rate-limit": "^8.5.2",
|
||||
"file-type": "^22.0.1",
|
||||
"gray-matter": "^4.0.3",
|
||||
"helmet": "^8.2.0",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"mammoth": "^1.12.0",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
import fs from 'fs/promises';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import matter from '../utils/frontmatter.js';
|
||||
import yaml from 'yaml';
|
||||
import { SqlitePortabilityService } from '../services/sqlite-portability-service.js';
|
||||
import { SqliteDatabase } from '../storage/sqlite/database.js';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import matter from 'gray-matter';
|
||||
import matter from '../utils/frontmatter.js';
|
||||
|
||||
// We need to mock the constructor's process.cwd() dependency
|
||||
// Instead, let's test the service by creating an instance and overriding the dir
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import matter from '../utils/frontmatter.js';
|
||||
import { nanoid } from 'nanoid';
|
||||
import type {
|
||||
ChatSession,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import matter, { type MarkdownFrontmatterFile } from '../utils/frontmatter.js';
|
||||
import { createLogger } from '../lib/logger.js';
|
||||
|
||||
const log = createLogger('integrity');
|
||||
|
|
@ -203,7 +203,7 @@ async function checkTaskFiles(
|
|||
report.filesChecked++;
|
||||
try {
|
||||
const raw = await fs.readFile(filePath, 'utf-8');
|
||||
let parsed: matter.GrayMatterFile<string>;
|
||||
let parsed: MarkdownFrontmatterFile<string>;
|
||||
try {
|
||||
parsed = matter(raw);
|
||||
} catch (fmErr) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { readdir, readFile, writeFile, unlink, mkdir } from 'fs/promises';
|
||||
import { fileExists } from '../storage/fs-helpers.js';
|
||||
import { join } from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import matter from '../utils/frontmatter.js';
|
||||
import type {
|
||||
PromptTemplate,
|
||||
PromptVersion,
|
||||
|
|
@ -379,6 +379,7 @@ export class PromptRegistryService {
|
|||
versions.push(data as PromptVersion);
|
||||
} catch (err) {
|
||||
log.error({ err: err }, `Error reading version ${file}`);
|
||||
versions.push({} as PromptVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import path from 'path';
|
|||
import readline from 'readline';
|
||||
import { createGunzip } from 'zlib';
|
||||
import type { SQLInputValue } from 'node:sqlite';
|
||||
import matter from 'gray-matter';
|
||||
import matter from '../utils/frontmatter.js';
|
||||
import yaml from 'yaml';
|
||||
import type {
|
||||
AnyTelemetryEvent,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import matter from '../utils/frontmatter.js';
|
||||
|
||||
export type TaskIdentityLocation = 'active' | 'backlog' | 'archive';
|
||||
export type TaskIdentityConflictKind = 'task-id' | 'business-id';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import fs from 'fs/promises';
|
||||
import { watch, batchedMap, type FSWatcher } from '../storage/fs-helpers.js';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import matter from '../utils/frontmatter.js';
|
||||
import { nanoid } from 'nanoid';
|
||||
import type {
|
||||
Task,
|
||||
|
|
@ -543,7 +543,7 @@ export class TaskService {
|
|||
private taskToMarkdown(task: Task): string {
|
||||
const { description, reviewComments, ...rest } = task;
|
||||
|
||||
// Filter out undefined values (gray-matter can't serialize them)
|
||||
// Filter out undefined values (YAML can't serialize them)
|
||||
const frontmatter = this.deepCleanUndefined(rest);
|
||||
|
||||
const content = matter.stringify(description || '', frontmatter);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { readdir, readFile, writeFile, unlink, mkdir } from 'fs/promises';
|
||||
import { fileExists } from '../storage/fs-helpers.js';
|
||||
import { join } from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import matter from '../utils/frontmatter.js';
|
||||
import type {
|
||||
TaskTemplate,
|
||||
CreateTemplateInput,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import matter from '../utils/frontmatter.js';
|
||||
import type { Task } from '@veritas-kanban/shared';
|
||||
import { createLogger } from '../lib/logger.js';
|
||||
import { getTasksBacklogDir } from '../utils/paths.js';
|
||||
|
|
|
|||
85
server/src/utils/frontmatter.ts
Normal file
85
server/src/utils/frontmatter.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
||||
|
||||
export interface MarkdownFrontmatterFile<T = string> {
|
||||
data: Record<string, any>;
|
||||
content: T;
|
||||
orig: T;
|
||||
matter: string;
|
||||
language: 'yaml';
|
||||
}
|
||||
|
||||
interface Frontmatter {
|
||||
(input: string): MarkdownFrontmatterFile<string>;
|
||||
stringify(content: string, data?: Record<string, unknown>): string;
|
||||
}
|
||||
|
||||
const DELIMITER_PATTERN = /^(---|\.\.\.)[ \t]*$/;
|
||||
|
||||
function asRecord(value: unknown): Record<string, any> {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return {};
|
||||
}
|
||||
return value as Record<string, any>;
|
||||
}
|
||||
|
||||
export function parseMarkdownFrontmatter(input: string): MarkdownFrontmatterFile<string> {
|
||||
const source = input.startsWith('\uFEFF') ? input.slice(1) : input;
|
||||
const firstLineEnd = source.indexOf('\n');
|
||||
const firstLine = (firstLineEnd === -1 ? source : source.slice(0, firstLineEnd)).replace(
|
||||
/\r$/,
|
||||
''
|
||||
);
|
||||
|
||||
if (firstLine !== '---' || firstLineEnd === -1) {
|
||||
return {
|
||||
data: {},
|
||||
content: source,
|
||||
orig: input,
|
||||
matter: '',
|
||||
language: 'yaml',
|
||||
};
|
||||
}
|
||||
|
||||
const matterStart = firstLineEnd + 1;
|
||||
let lineStart = matterStart;
|
||||
|
||||
while (lineStart <= source.length) {
|
||||
const nextLineEnd = source.indexOf('\n', lineStart);
|
||||
const lineEnd = nextLineEnd === -1 ? source.length : nextLineEnd;
|
||||
const line = source.slice(lineStart, lineEnd).replace(/\r$/, '');
|
||||
|
||||
if (DELIMITER_PATTERN.test(line)) {
|
||||
const matter = source.slice(matterStart, lineStart);
|
||||
const contentStart = nextLineEnd === -1 ? lineEnd : nextLineEnd + 1;
|
||||
const parsed = matter.trim() ? parseYaml(matter) : {};
|
||||
|
||||
return {
|
||||
data: asRecord(parsed),
|
||||
content: source.slice(contentStart),
|
||||
orig: input,
|
||||
matter,
|
||||
language: 'yaml',
|
||||
};
|
||||
}
|
||||
|
||||
if (nextLineEnd === -1) break;
|
||||
lineStart = nextLineEnd + 1;
|
||||
}
|
||||
|
||||
throw new Error('Unclosed YAML frontmatter block');
|
||||
}
|
||||
|
||||
export function stringifyMarkdownFrontmatter(
|
||||
content: string,
|
||||
data: Record<string, any> = {}
|
||||
): string {
|
||||
const yaml = stringifyYaml(data, { lineWidth: 0 }).trimEnd() || '{}';
|
||||
const body = content || '';
|
||||
return body ? `---\n${yaml}\n---\n${body}` : `---\n${yaml}\n---\n`;
|
||||
}
|
||||
|
||||
const frontmatter = parseMarkdownFrontmatter as Frontmatter;
|
||||
frontmatter.stringify = stringifyMarkdownFrontmatter;
|
||||
|
||||
export default frontmatter;
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
"@veritas-kanban/shared": "workspace:*",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dompurify": "^3.4.10",
|
||||
"dompurify": "^3.4.11",
|
||||
"lucide-react": "^1.18.0",
|
||||
"nanoid": "^5.1.11",
|
||||
"react": "^19.2.7",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { lazy, Suspense, useEffect, useState } from 'react';
|
||||
import { Box } from '@mantine/core';
|
||||
import { KanbanBoard } from './components/board/KanbanBoard';
|
||||
import { Header } from './components/layout/Header';
|
||||
import { Toaster } from './components/ui/toaster';
|
||||
import { KeyboardProvider } from './hooks/useKeyboard';
|
||||
|
|
@ -32,6 +31,12 @@ const LAZY_VIEW_COMPONENTS = Object.fromEntries(
|
|||
})
|
||||
) as Record<NavigationView, ReturnType<typeof lazy>>;
|
||||
|
||||
const KanbanBoard = lazy(() =>
|
||||
import('./components/board/KanbanBoard').then((mod) => ({
|
||||
default: mod.KanbanBoard,
|
||||
}))
|
||||
);
|
||||
|
||||
const FloatingChat = lazy(() =>
|
||||
import('./components/chat/FloatingChat').then((mod) => ({
|
||||
default: mod.FloatingChat,
|
||||
|
|
@ -84,7 +89,13 @@ function MainContent() {
|
|||
|
||||
if (runSessionShareId) return <RunSessionShareView shareId={runSessionShareId} />;
|
||||
|
||||
if (view === 'board') return <KanbanBoard />;
|
||||
if (view === 'board') {
|
||||
return (
|
||||
<Suspense fallback={<ViewLoading view="board" />}>
|
||||
<KanbanBoard />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
const ViewComponent = LAZY_VIEW_COMPONENTS[view];
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ export default defineConfig({
|
|||
},
|
||||
build: {
|
||||
chunkSizeWarningLimit: 400,
|
||||
modulePreload: {
|
||||
resolveDependencies(_filename, deps, context) {
|
||||
if (context.hostType !== 'html') return deps;
|
||||
return deps.filter((dep) => !dep.includes('vendor-dnd'));
|
||||
},
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue