@haus-tech/haus-workflow 4.8.0 → 4.9.0
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 +20 -0
- package/dist/cli.js +1412 -779
- package/docs/before-you-start.md +57 -0
- package/docs/glossary.md +56 -0
- package/library/global/agents/request-classifier.md +1 -1
- package/library/global/agents/request-scorer.md +1 -1
- package/library/global/skills/haus-workflow/references/new.md +18 -11
- package/library/global/skills/request/SKILL.md +315 -23
- package/library/global/skills/request/scripts/compare-scores.mjs +57 -7
- package/library/global/skills/request/scripts/contract-check.mjs +6 -17
- package/library/global/skills/request/scripts/lib.mjs +15 -0
- package/library/global/skills/request/scripts/quote-repair.mjs +166 -0
- package/package.json +5 -2
|
@@ -6,9 +6,17 @@
|
|
|
6
6
|
* prosa) och inringningens golv- och takankare. Prosa (why, drivers,
|
|
7
7
|
* rationale) jämförs ALDRIG.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Toleransregel: en poängskillnad ≤ 0,5 med samma outcome och confidence är
|
|
10
|
+
* prisbrus, inte oenighet. En ankarskillnad tolereras dessutom bara när banden
|
|
11
|
+
* överlappar eller angränsar; icke-angränsande band är hård diff även vid samma
|
|
12
|
+
* poäng. LIKA_TOLERANS hör till LIKA-familjen (exit 0) med
|
|
13
|
+
* en deterministisk vinnare (högsta poängen, skyddar marginalen). Den
|
|
14
|
+
* skriver aldrig om en fil och räknar aldrig fram ett medelvärde.
|
|
15
|
+
*
|
|
16
|
+
* Skriver LIKA, LIKA_TOLERANS (exit 0) eller OLIKA + diffrader (exit 1) på
|
|
17
|
+
* stdout; varningar går på stderr. Vid OLIKA väljer dispatchern ALDRIG en
|
|
18
|
+
* körning och räknar ALDRIG medelvärde — posten eskaleras till granskaren med
|
|
19
|
+
* båda inringningarna.
|
|
12
20
|
*
|
|
13
21
|
* Exit 2 = jämförelsen kan inte göras alls (dispatcherns fel, aldrig LIKA) —
|
|
14
22
|
* rälsen bor i lib.mjs och delas med compare-classifications.
|
|
@@ -37,14 +45,56 @@ if (a.outcome !== b.outcome) diffs.push(`outcome: ${show(a.outcome)} ↔ ${show(
|
|
|
37
45
|
// säkerhet är information granskaren ska se, precis som klassningens konfidenssida.
|
|
38
46
|
if (a.confidence !== b.confidence)
|
|
39
47
|
diffs.push(`confidence: ${show(a.confidence)} ↔ ${show(b.confidence)}`)
|
|
48
|
+
// Toleransregel (beslut 2026-08-28, ADR-0078): en poängskillnad inom 0,5 med
|
|
49
|
+
// samma outcome och confidence är prisbrus, inte oenighet. Båda pilotfallens
|
|
50
|
+
// eskaleringar gällde Δ = 0,25 och kostade ett helt nytt varv styck. Högsta
|
|
51
|
+
// poängen gäller (skyddar marginalen); ett medelvärde räknas fortfarande aldrig.
|
|
52
|
+
const TOLERANS = 0.5
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Ankarnummer på skalan A1..A17. Golv null (under-A1-fallet) läses som 0, alltså
|
|
56
|
+
* steget under A1. Ett ankare som inte går att tolka ger null och behandlas som
|
|
57
|
+
* icke-angränsande, så en oläslig inringning aldrig kan tystas som brus.
|
|
58
|
+
*/
|
|
59
|
+
const ankarNr = (v) => {
|
|
60
|
+
if (v === null || v === undefined) return 0
|
|
61
|
+
const m = /^A(\d+)$/.exec(String(v))
|
|
62
|
+
return m ? Number(m[1]) : null
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Inringningarna får toleransbehandling bara när de faktiskt pekar på samma del
|
|
66
|
+
* av skalan: överlappande eller angränsande band. Två körningar som landar på
|
|
67
|
+
* samma poäng men ringar in A5/A6 respektive A11/A12 är inte prisbrus, det är
|
|
68
|
+
* precis den drift dubbelkörningen finns för att visa, även när summan råkar bli
|
|
69
|
+
* densamma.
|
|
70
|
+
*/
|
|
71
|
+
const nastanSammaBand = (fa, ta, fb, tb) => {
|
|
72
|
+
const [l1, h1, l2, h2] = [ankarNr(fa), ankarNr(ta), ankarNr(fb), ankarNr(tb)]
|
|
73
|
+
if ([l1, h1, l2, h2].some((n) => n === null)) return false
|
|
74
|
+
return l1 <= h2 + 1 && l2 <= h1 + 1
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const tolerans = []
|
|
40
78
|
if (a.outcome === 'POANG' && b.outcome === 'POANG') {
|
|
41
|
-
|
|
79
|
+
const delta = Math.abs(a.score - b.score)
|
|
80
|
+
const inomTolerans = Number.isFinite(delta) && delta <= TOLERANS
|
|
81
|
+
if (a.score !== b.score)
|
|
82
|
+
(inomTolerans ? tolerans : diffs).push(`score: ${show(a.score)} ↔ ${show(b.score)}`)
|
|
42
83
|
const fa = a.bracket?.floor?.anchor ?? null
|
|
43
84
|
const fb = b.bracket?.floor?.anchor ?? null
|
|
44
85
|
const ta = a.bracket?.ceiling?.anchor ?? null
|
|
45
86
|
const tb = b.bracket?.ceiling?.anchor ?? null
|
|
46
|
-
|
|
47
|
-
if (
|
|
87
|
+
const ankarDiffs = []
|
|
88
|
+
if (fa !== fb) ankarDiffs.push(`bracket.floor.anchor: ${show(fa)} ↔ ${show(fb)}`)
|
|
89
|
+
if (ta !== tb) ankarDiffs.push(`bracket.ceiling.anchor: ${show(ta)} ↔ ${show(tb)}`)
|
|
90
|
+
;(inomTolerans && nastanSammaBand(fa, ta, fb, tb) ? tolerans : diffs).push(...ankarDiffs)
|
|
48
91
|
}
|
|
49
92
|
|
|
50
|
-
|
|
93
|
+
if (diffs.length === 0 && tolerans.length > 0) {
|
|
94
|
+
const winner = b.score > a.score ? 2 : 1
|
|
95
|
+
console.log(`LIKA_TOLERANS: högsta gäller ${Math.max(a.score, b.score)} (körning ${winner})`)
|
|
96
|
+
for (const t of tolerans) console.log(`- ${t}`)
|
|
97
|
+
process.exit(0)
|
|
98
|
+
}
|
|
99
|
+
// Hårda diffar visas tillsammans med toleransraderna: granskaren ska se helheten.
|
|
100
|
+
reportDiffs([...diffs, ...tolerans])
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { readFileSync } from 'node:fs'
|
|
15
15
|
|
|
16
|
-
import { refDriftWarning } from './lib.mjs'
|
|
16
|
+
import { refDriftWarning, norm, quoteOk } from './lib.mjs'
|
|
17
17
|
|
|
18
18
|
const argv = process.argv.slice(2)
|
|
19
19
|
let expectId = null
|
|
@@ -47,9 +47,6 @@ for (const [rel, expected] of Object.entries(EXPECTED_REFS)) {
|
|
|
47
47
|
const w = refDriftWarning(rel, expected)
|
|
48
48
|
if (w) warnings.push(w)
|
|
49
49
|
}
|
|
50
|
-
// Markdown-betoning (**fet**, *kursiv*, `kod`) strippas före jämförelse — ett
|
|
51
|
-
// citat som återges utan källans markeringar är fortfarande ordagrant.
|
|
52
|
-
const norm = (s) => s.replace(/[*`]/g, '').replace(/\s+/g, ' ').trim()
|
|
53
50
|
let doc
|
|
54
51
|
try {
|
|
55
52
|
doc = JSON.parse(readFileSync(jsonPath, 'utf8'))
|
|
@@ -69,17 +66,9 @@ try {
|
|
|
69
66
|
process.exit(2)
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
* "[...]" ett giltigt belägg för vad som helst). */
|
|
76
|
-
const quoteOk = (q) => {
|
|
77
|
-
const segments = q
|
|
78
|
-
.split(/\s*\[(?:\.\.\.|…)\]\s*/)
|
|
79
|
-
.map((seg) => norm(seg.replace(/^(\.\.\.|…)|(\.\.\.|…)$/g, '')))
|
|
80
|
-
.filter((seg) => seg.length > 0)
|
|
81
|
-
return segments.length > 0 && segments.every((seg) => source.includes(seg))
|
|
82
|
-
}
|
|
69
|
+
// quoteOk kommer från lib.mjs (delad med quote-repair.mjs); bind källan lokalt
|
|
70
|
+
// så befintliga anropsställen (`ok(f.belagg)` osv.) förblir oförändrade.
|
|
71
|
+
const ok = (q) => quoteOk(q, source)
|
|
83
72
|
|
|
84
73
|
/** Alla strängvärden ur ett godtyckligt värde (drivers kan vara objekt). */
|
|
85
74
|
const collectStrings = (v) => {
|
|
@@ -254,7 +243,7 @@ if (doc.classification !== undefined) {
|
|
|
254
243
|
// inte låg kvalitet.
|
|
255
244
|
if (!f.belagg || norm(f.belagg).length === 0)
|
|
256
245
|
problems.push(`fråga ${f.id}: belägg saknas (obligatoriskt, ordagrant citat)`)
|
|
257
|
-
else if (!
|
|
246
|
+
else if (!ok(f.belagg))
|
|
258
247
|
problems.push(
|
|
259
248
|
`fråga ${f.id}: belägg är inte ordagrant citat ur underlaget: "${f.belagg.slice(0, 70)}..."`,
|
|
260
249
|
)
|
|
@@ -296,7 +285,7 @@ if (doc.classification !== undefined) {
|
|
|
296
285
|
for (const [i, d] of (Array.isArray(doc.drivers) ? doc.drivers : []).entries())
|
|
297
286
|
for (const str of collectStrings(d))
|
|
298
287
|
for (const m of str.matchAll(/["“”„«»]([^"“”„«»]{15,})["“”„«»]/g))
|
|
299
|
-
if (!
|
|
288
|
+
if (!ok(m[1]))
|
|
300
289
|
problems.push(
|
|
301
290
|
`drivers[${i}]: citat är inte ordagrant ur underlaget: "${m[1].slice(0, 70)}..."`,
|
|
302
291
|
)
|
|
@@ -111,3 +111,18 @@ export function reportDiffs(diffs) {
|
|
|
111
111
|
for (const d of diffs) console.log(`- ${d}`)
|
|
112
112
|
process.exit(1)
|
|
113
113
|
}
|
|
114
|
+
|
|
115
|
+
/** Markdown-betoning strippas före jämförelse: ett citat som återges utan
|
|
116
|
+
* källans fetstil- eller `kod`-markeringar är fortfarande ordagrant. */
|
|
117
|
+
export const norm = (s) => s.replace(/[*`]/g, '').replace(/\s+/g, ' ').trim()
|
|
118
|
+
|
|
119
|
+
/** Ett "ordagrant" citat får hoppa med [...] och kortas med inledande/avslutande …
|
|
120
|
+
* Varje kvarvarande segment måste vara exakt substräng av det normaliserade
|
|
121
|
+
* underlaget. Tomt efter filtrering = INTE ordagrant. */
|
|
122
|
+
export const quoteOk = (q, normalizedSource) => {
|
|
123
|
+
const segments = q
|
|
124
|
+
.split(/\s*\[(?:\.\.\.|…)\]\s*/)
|
|
125
|
+
.map((seg) => norm(seg.replace(/^(\.\.\.|…)|(\.\.\.|…)$/g, '')))
|
|
126
|
+
.filter((seg) => seg.length > 0)
|
|
127
|
+
return segments.length > 0 && segments.every((seg) => normalizedSource.includes(seg))
|
|
128
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Deterministisk citaträttning FÖRE omdispatch. Pilotsessionerna 2026-08-28
|
|
4
|
+
* visade att classifiern komponerar belägg i stället för att citera (4 av 4
|
|
5
|
+
* förstapass fälldes av contract-check), och varje fällning kostade en full
|
|
6
|
+
* omkörning på 6-9 min. Det här scriptet räddar det räddningsbara: ett citat
|
|
7
|
+
* som inte är exakt substräng ersätts med sin ENTYDIGT närmaste källmening.
|
|
8
|
+
* Ingen entydig träff = ingen rättning (hellre omdispatch än fel belägg), och
|
|
9
|
+
* skrivningen är allt eller inget så en halvrättad fil aldrig arkiveras.
|
|
10
|
+
* En kandidat med annan polaritet än citatet (se NEGATIONER) är aldrig giltig:
|
|
11
|
+
* rättningen får byta form på pekaren, aldrig vända påståendet.
|
|
12
|
+
*
|
|
13
|
+
* Rättar samma fält som contract-check kontrollerar: classification.fragor[].belagg
|
|
14
|
+
* samt citattecken-omgärdade segment (>= 15 tecken) i score-JSON:ens drivers.
|
|
15
|
+
* Bedömer aldrig innehåll; byter aldrig mening, bara formen på pekaren.
|
|
16
|
+
*
|
|
17
|
+
* Användning: node quote-repair.mjs <run.json> <spec.md> [impact.md]
|
|
18
|
+
* Stdout: "RÄTTAT (N):" + rader, "ORÄTTBART (N):" + rader, eller "INGET ATT RÄTTA".
|
|
19
|
+
* Exit 0 = inga orättbara citat kvar (rättade eller inga), 1 = orättbara kvar
|
|
20
|
+
* (filen orörd), 2 = användningsfel/oläsbar input (dispatcherfel).
|
|
21
|
+
*/
|
|
22
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
23
|
+
|
|
24
|
+
import { norm, quoteOk } from './lib.mjs'
|
|
25
|
+
|
|
26
|
+
const [jsonPath, specPath, impactPath] = process.argv.slice(2)
|
|
27
|
+
if (!jsonPath || !specPath) {
|
|
28
|
+
console.error('användning: node quote-repair.mjs <run.json> <spec.md> [impact.md]')
|
|
29
|
+
process.exit(2)
|
|
30
|
+
}
|
|
31
|
+
let doc, sourceRaw
|
|
32
|
+
try {
|
|
33
|
+
doc = JSON.parse(readFileSync(jsonPath, 'utf8'))
|
|
34
|
+
sourceRaw =
|
|
35
|
+
readFileSync(specPath, 'utf8') + '\n' + (impactPath ? readFileSync(impactPath, 'utf8') : '')
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error(`kunde inte läsa input: ${e.message}`)
|
|
38
|
+
process.exit(2)
|
|
39
|
+
}
|
|
40
|
+
const source = norm(sourceRaw)
|
|
41
|
+
|
|
42
|
+
// Kandidater: källans meningar (rad- och meningsgränser) plus par av
|
|
43
|
+
// angränsande meningar, så ett tvåmeningscitat kan få en exakt motsvarighet.
|
|
44
|
+
const sentences = sourceRaw
|
|
45
|
+
.split(/\n+|(?<=[.!?])\s+(?=[A-ZÅÄÖ0-9])/u)
|
|
46
|
+
.map((s) => s.trim())
|
|
47
|
+
.filter((s) => norm(s).length >= 15)
|
|
48
|
+
const candidates = [...sentences]
|
|
49
|
+
for (let i = 0; i + 1 < sentences.length; i++)
|
|
50
|
+
candidates.push(`${sentences[i]} ${sentences[i + 1]}`)
|
|
51
|
+
|
|
52
|
+
const tokens = (s) =>
|
|
53
|
+
new Set(
|
|
54
|
+
norm(s)
|
|
55
|
+
.toLowerCase()
|
|
56
|
+
.split(/[^\p{L}\p{N}]+/u)
|
|
57
|
+
.filter(Boolean),
|
|
58
|
+
)
|
|
59
|
+
const dice = (A, B) => {
|
|
60
|
+
let inter = 0
|
|
61
|
+
for (const t of A) if (B.has(t)) inter++
|
|
62
|
+
return (2 * inter) / (A.size + B.size || 1)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Polaritetsvakt. Dice över tokenmängder är blind för negation: "Ingen" är ett
|
|
67
|
+
* ord av nio, men vänder påståendet. Utan den här listan "rättas" ett belägg som
|
|
68
|
+
* säger att behörighetslogik ÄNDRAS till källmeningen som säger att den INTE
|
|
69
|
+
* ändras, och contract-check släpper igenom det, alltså tvärtemot vad
|
|
70
|
+
* citatgrinden finns för. Ett citat får bara ersättas av en kandidat med exakt
|
|
71
|
+
* samma uppsättning negationsord.
|
|
72
|
+
*/
|
|
73
|
+
const NEGATIONER = new Set([
|
|
74
|
+
'ingen',
|
|
75
|
+
'inga',
|
|
76
|
+
'inget',
|
|
77
|
+
'inte',
|
|
78
|
+
'icke',
|
|
79
|
+
'aldrig',
|
|
80
|
+
'utan',
|
|
81
|
+
'ej',
|
|
82
|
+
'saknas',
|
|
83
|
+
'varken',
|
|
84
|
+
])
|
|
85
|
+
const negationer = (s) => {
|
|
86
|
+
const found = []
|
|
87
|
+
for (const t of tokens(s)) if (NEGATIONER.has(t)) found.push(t)
|
|
88
|
+
return found.sort().join(' ')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Entydighetskravet är själva säkerhetsregeln: bästa träffen måste vara både
|
|
92
|
+
// nära nog (>= 0.6) och klart ensam (marginal >= 0.15), annars rättas inget.
|
|
93
|
+
// Polaritetsvakten går FÖRE poängsättningen: en kandidat med annan uppsättning
|
|
94
|
+
// negationsord är aldrig en giltig ersättare, hur nära den än ligger i Dice.
|
|
95
|
+
// En par-kandidat som bara är bästa träffen med en angränsande mening bilagd
|
|
96
|
+
// (delsträngsöverlapp åt endera hållet) är samma källplats, inte en
|
|
97
|
+
// konkurrerande: annars slår varannan stark enmeningsträff ut sig själv på
|
|
98
|
+
// marginalkravet så snart en granne delar ett enda vanligt ord.
|
|
99
|
+
const findReplacement = (quote) => {
|
|
100
|
+
const q = tokens(quote)
|
|
101
|
+
const pol = negationer(quote)
|
|
102
|
+
const scored = candidates
|
|
103
|
+
.filter((c) => negationer(c) === pol)
|
|
104
|
+
.map((c) => ({ c, s: dice(q, tokens(c)) }))
|
|
105
|
+
.sort((a, b) => b.s - a.s)
|
|
106
|
+
const best = scored[0]
|
|
107
|
+
if (!best || best.s < 0.6) return null
|
|
108
|
+
const second =
|
|
109
|
+
scored.find((x) => x.c !== best.c && !x.c.includes(best.c) && !best.c.includes(x.c))?.s ?? 0
|
|
110
|
+
if (best.s - second >= 0.15 && quoteOk(best.c, source)) return best.c
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const trunc = (s) => (s.length > 60 ? `${s.slice(0, 60)}…` : s)
|
|
115
|
+
// Citattecken i en ersättning som ska splitsas IN mellan citattecken skulle
|
|
116
|
+
// stänga citatet för tidigt: strängen blir obalanserad och nästa läsning ser ett
|
|
117
|
+
// annat citat än det som godkändes. Sådana kandidater refuseras.
|
|
118
|
+
const CITATTECKEN = /["“”„«»]/
|
|
119
|
+
const repaired = []
|
|
120
|
+
const unfixable = []
|
|
121
|
+
const fixQuote = (val, label, forbidQuoteChars = false) => {
|
|
122
|
+
if (quoteOk(val, source)) return val
|
|
123
|
+
const r = findReplacement(val)
|
|
124
|
+
if (r && !(forbidQuoteChars && CITATTECKEN.test(r))) {
|
|
125
|
+
repaired.push(`- ${label}: "${trunc(val)}" → "${trunc(r)}"`)
|
|
126
|
+
return r
|
|
127
|
+
}
|
|
128
|
+
unfixable.push(`- ${label}: "${trunc(val)}"`)
|
|
129
|
+
return val
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (doc.classification && typeof doc.classification === 'object') {
|
|
133
|
+
for (const f of Array.isArray(doc.classification.fragor) ? doc.classification.fragor : [])
|
|
134
|
+
if (f && typeof f.belagg === 'string' && norm(f.belagg).length > 0)
|
|
135
|
+
f.belagg = fixQuote(f.belagg, `fråga ${f.id}`)
|
|
136
|
+
} else if (doc.outcome !== undefined) {
|
|
137
|
+
// Samma citat-regex som contract-check: raka och typografiska citattecken.
|
|
138
|
+
const fixInString = (str, label) =>
|
|
139
|
+
str.replace(
|
|
140
|
+
/(["“”„«»])([^"“”„«»]{15,})(["“”„«»])/g,
|
|
141
|
+
(_m, o, inner, c) => o + fixQuote(inner, label, true) + c,
|
|
142
|
+
)
|
|
143
|
+
const walk = (v, label) =>
|
|
144
|
+
typeof v === 'string'
|
|
145
|
+
? fixInString(v, label)
|
|
146
|
+
: Array.isArray(v)
|
|
147
|
+
? v.map((x) => walk(x, label))
|
|
148
|
+
: v && typeof v === 'object'
|
|
149
|
+
? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x, label)]))
|
|
150
|
+
: v
|
|
151
|
+
if (Array.isArray(doc.drivers)) doc.drivers = doc.drivers.map((d, i) => walk(d, `drivers[${i}]`))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (unfixable.length > 0) {
|
|
155
|
+
console.log(`ORÄTTBART (${unfixable.length}):`)
|
|
156
|
+
for (const u of unfixable) console.log(u)
|
|
157
|
+
process.exit(1)
|
|
158
|
+
}
|
|
159
|
+
if (repaired.length > 0) {
|
|
160
|
+
writeFileSync(jsonPath, JSON.stringify(doc, null, 2) + '\n')
|
|
161
|
+
console.log(`RÄTTAT (${repaired.length}):`)
|
|
162
|
+
for (const r of repaired) console.log(r)
|
|
163
|
+
} else {
|
|
164
|
+
console.log('INGET ATT RÄTTA')
|
|
165
|
+
}
|
|
166
|
+
process.exit(0)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haus-tech/haus-workflow",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "Haus AI workflow CLI for Claude Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"library/global",
|
|
18
18
|
"library/catalog",
|
|
19
19
|
"README.md",
|
|
20
|
-
"CHANGELOG.md"
|
|
20
|
+
"CHANGELOG.md",
|
|
21
|
+
"docs/before-you-start.md",
|
|
22
|
+
"docs/glossary.md"
|
|
21
23
|
],
|
|
22
24
|
"scripts": {
|
|
23
25
|
"build": "rm -rf dist && esbuild src/cli.ts --bundle --packages=external --platform=node --format=esm --outdir=dist --external:@inquirer/checkbox",
|
|
@@ -39,6 +41,7 @@
|
|
|
39
41
|
"verify:full": "node scripts/verify.mjs --coverage",
|
|
40
42
|
"check:contract-behavior": "node --import tsx scripts/contract-behavior-check.mjs",
|
|
41
43
|
"check:mutation-table": "node scripts/check-mutation-table.mjs",
|
|
44
|
+
"check:docs-site": "node scripts/check-docs-site.mjs",
|
|
42
45
|
"postinstall": "node ./scripts/postinstall.mjs || true",
|
|
43
46
|
"prepare": "lefthook install || true",
|
|
44
47
|
"security:audit": "yarn npm audit -A -R --severity high --environment production"
|