@haus-tech/haus-workflow 4.9.0 → 5.0.1
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 +33 -0
- package/dist/cli.js +1381 -871
- package/library/catalog/manifest.json +97 -34
- package/library/global/agents/request-assessor.md +2 -2
- package/library/global/agents/request-scorer.md +13 -8
- package/library/global/agents/request-specifier-eskalerad.md +1 -1
- package/library/global/agents/request-specifier.md +1 -1
- package/library/global/skills/request/SKILL.md +691 -374
- package/library/global/skills/request/scripts/archive.mjs +1376 -0
- package/library/global/skills/request/scripts/check-lasta.mjs +64 -0
- package/library/global/skills/request/scripts/compare-scores.mjs +23 -15
- package/library/global/skills/request/scripts/contract-check.mjs +108 -36
- package/library/global/skills/request/scripts/lib.mjs +66 -0
- package/library/global/skills/request/scripts/precheck.mjs +30 -27
- package/library/global/skills/request/scripts/report.mjs +571 -0
- package/library/global/skills/request/scripts/resolve-anchors.mjs +243 -0
- package/library/global/skills/request-assess-impact/SKILL.md +10 -10
- package/library/global/skills/request-score/SKILL.md +19 -10
- package/library/global/skills/request-sync/SKILL.md +7 -7
- package/library/global/skills/request-write-spec/SKILL.md +6 -6
- package/library/global/skills/request-write-spec/references/specformat.md +2 -2
- package/package.json +1 -1
- package/library/global/agents/request-classifier.md +0 -55
- package/library/global/skills/request/scripts/compare-classifications.mjs +0 -94
- package/library/global/skills/request/scripts/quote-repair.mjs +0 -166
- package/library/global/skills/request-classify/SKILL.md +0 -79
- package/library/global/skills/request-classify/references/klassningsinstruktion.md +0 -152
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministisk dubbelkörningsdiff för klassningar (driftvakten på MEDIUM+).
|
|
3
|
-
* Jämför utfall, basklass, varje frågas svar och vilken sida om KONFIDENSGRÄNS
|
|
4
|
-
* (0.8) konfidensen ligger, mellan två oberoende körningar på samma underlag.
|
|
5
|
-
* Prosafält (belägg, motiveringar) jämförs INTE — två korrekta körningar
|
|
6
|
-
* formulerar sig olika, det är svaren som ska vara stabila. Konfidensens SIDA
|
|
7
|
-
* om gränsen är däremot inte prosa: den avgör ESKALERA enligt protokollet.
|
|
8
|
-
*
|
|
9
|
-
* Skriver LIKA (exit 0) eller OLIKA + diffrader (exit 1) på stdout; varningar
|
|
10
|
-
* går på stderr så en verdiktparser aldrig sväljer dem. Vid OLIKA väljer
|
|
11
|
-
* dispatchern ALDRIG en körning och räknar ALDRIG medelvärde — posten
|
|
12
|
-
* eskaleras till granskaren med båda körningarna.
|
|
13
|
-
*
|
|
14
|
-
* Exit 2 = jämförelsen kan inte göras alls (dispatcherns fel, aldrig LIKA) —
|
|
15
|
-
* rälsen bor i lib.mjs och delas med compare-scores.
|
|
16
|
-
*
|
|
17
|
-
* Användning: node compare-classifications.mjs <körning1.json> <körning2.json>
|
|
18
|
-
*/
|
|
19
|
-
import { loadRunPair, refDriftWarning, reportDiffs } from './lib.mjs'
|
|
20
|
-
|
|
21
|
-
// KONFIDENSGRANS speglar klassningsinstruktionen — varna (stderr) vid katalogdrift.
|
|
22
|
-
const REF_SHA = '811f0bff62c749255e86da7ea8db0ea868ebc9df14262f9889101c110d9d14ff'
|
|
23
|
-
{
|
|
24
|
-
const w = refDriftWarning('request-classify/references/klassningsinstruktion.md', REF_SHA)
|
|
25
|
-
if (w) console.error(`VARNING: ${w} — kontrollera KONFIDENSGRANS`)
|
|
26
|
-
}
|
|
27
|
-
const KONFIDENSGRANS = 0.8
|
|
28
|
-
|
|
29
|
-
const { a: docA, b: docB } = loadRunPair(
|
|
30
|
-
'användning: node compare-classifications.mjs <körning1.json> <körning2.json>',
|
|
31
|
-
)
|
|
32
|
-
const a = docA.classification
|
|
33
|
-
const b = docB.classification
|
|
34
|
-
if (!a || !b) {
|
|
35
|
-
console.error('minst en fil saknar classification-nyckeln — är båda classify-körningar?')
|
|
36
|
-
process.exit(2)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Frågelagret är jämförelsens kärna — utan kompletta, unikt id-satta rader kan
|
|
40
|
-
// en äkta oenighet tyst försvinna. Då vägrar vi hellre än gissar.
|
|
41
|
-
const validateFragor = (rows, label) => {
|
|
42
|
-
if (!Array.isArray(rows) || rows.length === 0) {
|
|
43
|
-
console.error(`${label}: fragor saknas eller är tom — kan inte jämföra`)
|
|
44
|
-
process.exit(2)
|
|
45
|
-
}
|
|
46
|
-
const seen = new Set()
|
|
47
|
-
for (const f of rows) {
|
|
48
|
-
if (typeof f?.id !== 'string' || f.id.trim() === '') {
|
|
49
|
-
console.error(`${label}: en frågerad saknar id — kan inte jämföra`)
|
|
50
|
-
process.exit(2)
|
|
51
|
-
}
|
|
52
|
-
if (seen.has(f.id)) {
|
|
53
|
-
console.error(`${label}: dubblett-id "${f.id}" — kan inte jämföra`)
|
|
54
|
-
process.exit(2)
|
|
55
|
-
}
|
|
56
|
-
seen.add(f.id)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
validateFragor(a.fragor, 'körning 1')
|
|
60
|
-
validateFragor(b.fragor, 'körning 2')
|
|
61
|
-
|
|
62
|
-
const show = (v) => JSON.stringify(v ?? null)
|
|
63
|
-
const normSvar = (v) =>
|
|
64
|
-
String(v ?? '')
|
|
65
|
-
.trim()
|
|
66
|
-
.toUpperCase()
|
|
67
|
-
|
|
68
|
-
const diffs = []
|
|
69
|
-
if (a.utfall !== b.utfall) diffs.push(`utfall: ${show(a.utfall)} ↔ ${show(b.utfall)}`)
|
|
70
|
-
if ((a.basklass ?? null) !== (b.basklass ?? null))
|
|
71
|
-
diffs.push(`basklass: ${show(a.basklass)} ↔ ${show(b.basklass)}`)
|
|
72
|
-
|
|
73
|
-
const byId = (rows) => new Map(rows.map((f) => [f.id, f]))
|
|
74
|
-
const mapA = byId(a.fragor)
|
|
75
|
-
const mapB = byId(b.fragor)
|
|
76
|
-
for (const id of new Set([...mapA.keys(), ...mapB.keys()])) {
|
|
77
|
-
const fa = mapA.get(id)
|
|
78
|
-
const fb = mapB.get(id)
|
|
79
|
-
if (!fa || !fb) {
|
|
80
|
-
// Körningarna stannade på olika steg — samma fakta gav olika väg.
|
|
81
|
-
diffs.push(`fråga ${id}: besvarad i ${fa ? 'körning 1' : 'körning 2'} men inte i den andra`)
|
|
82
|
-
continue
|
|
83
|
-
}
|
|
84
|
-
if (normSvar(fa.svar) !== normSvar(fb.svar))
|
|
85
|
-
diffs.push(`fråga ${id}: ${show(fa.svar)} ↔ ${show(fb.svar)}`)
|
|
86
|
-
const sideA = (fa.konfidens ?? 0) >= KONFIDENSGRANS
|
|
87
|
-
const sideB = (fb.konfidens ?? 0) >= KONFIDENSGRANS
|
|
88
|
-
if (sideA !== sideB)
|
|
89
|
-
diffs.push(
|
|
90
|
-
`fråga ${id}: konfidens på olika sida om ${KONFIDENSGRANS} (${show(fa.konfidens)} ↔ ${show(fb.konfidens)})`,
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
reportDiffs(diffs)
|
|
@@ -1,166 +0,0 @@
|
|
|
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)
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: request-classify
|
|
3
|
-
description: Kör Haus klassningsprotokoll på en spec plus underlag och levererar klass, belägg per fråga och en kundvänlig motivering. Använd alltid när användaren vill "klassa", "kör klassningen", "sätt klass", "vilken klass blir det här", eller när en spec har en påverkansanalys och ska prissättas. Används även som grind i byggläget på en fullständig implementationsplan före första kodraden — "kör guarden", "omklassa", "stämmer klassen fortfarande". Använd den även när klassning bara antyds, t.ex. när någon klistrar in en spec med underlag och frågar vad det landar på. Output är JSON, aldrig ett pris.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# request-classify
|
|
7
|
-
|
|
8
|
-
Du klassar en post enligt Haus klassningsprotokoll. Reglerna finns i `references/klassningsinstruktion.md` — läs den innan du klassar första posten i en session.
|
|
9
|
-
|
|
10
|
-
**Går filen inte att läsa: klassa inte.** Säg att `references/klassningsinstruktion.md` saknas och att skillen inte kan köras utan den. Fortsätt aldrig på minnet av hur protokollet brukar se ut — det är precis det den här skillen finns för att förhindra, och en klassning som ser rimlig ut men inte följer kriterierna går inte att upptäcka i efterhand. Den är auktoritativ. Det här dokumentet restatar aldrig ett kriterium; två versioner av samma regel glider isär, och då mäter ni protokollet mot sig självt.
|
|
11
|
-
|
|
12
|
-
**Du sätter aldrig pris.** Klass → pris görs av prislistan, av `request-offer`. Ligger prislistan i sammanhanget: använd den inte och säg till. Vet du vad klasserna kostar drar du mot den klass som känns rimlig att ta betalt för.
|
|
13
|
-
|
|
14
|
-
**Du skriver aldrig underlaget.** Saknas det, avbryt med `INPUT_SAKNAS`. Skriv inte en påverkansanalys själv — då bedömer du din egen text, och belägglogiken blir teater. `request-assess-impact` gör det, och helst i en egen körning.
|
|
15
|
-
|
|
16
|
-
## Två lägen
|
|
17
|
-
|
|
18
|
-
**Prisklassning.** Spec + påverkansanalys. Utfallet blir prisunderlaget till kund och hamnar i `Class (offered)`.
|
|
19
|
-
|
|
20
|
-
**Grind.** Spec + fullständig implementationsplan, efter kundens godkännande och före första kodraden. Utfallet hamnar i `Class (revised)` och diffas fråga för fråga mot prisklassningen.
|
|
21
|
-
|
|
22
|
-
Samma instruktion, samma frågor, samma skill. Bygg aldrig en separat variant för grinden: divergerar lägena mäter grinden skillnaden mellan två promptar i stället för mellan tunn och tjock information, och då är `Class (offered)` mot `Class (revised)` värdelöst som kalibreringsdata.
|
|
23
|
-
|
|
24
|
-
I grindläget: endast ny strukturell information ur fullplanen får ändra ett svar, aldrig en omvärdering av samma fakta. Rapportera vilken fråga som bytte svar och citatet som orsakade bytet. Grinden är symmetrisk — en lägre klass rapporteras lika tydligt som en högre.
|
|
25
|
-
|
|
26
|
-
Bygger fullplanen ett annat vägval än det som såldes är det en klasshändelse även när klassen råkar bli densamma. Skriv ut det.
|
|
27
|
-
|
|
28
|
-
## Läs underlaget som ett dokument
|
|
29
|
-
|
|
30
|
-
Du hämtar ordagranna citat ur det. Det är bevismaterial, inte parametrar — och det är därför du inte får ha skrivit det.
|
|
31
|
-
|
|
32
|
-
Ge inte dig själv repot i den här körningen. Har du det börjar du verifiera saker vid sidan av underlaget, och då klassar du inte längre det du fått. Saknas något i underlaget är svaret låg konfidens, inte en egen efterforskning.
|
|
33
|
-
|
|
34
|
-
## Output
|
|
35
|
-
|
|
36
|
-
```json
|
|
37
|
-
{
|
|
38
|
-
"request_id": "",
|
|
39
|
-
"mode": "pricing | guard",
|
|
40
|
-
"classification": {},
|
|
41
|
-
"customer": {
|
|
42
|
-
"class": "",
|
|
43
|
-
"rationale": "",
|
|
44
|
-
"conditions": [],
|
|
45
|
-
"dependencies": []
|
|
46
|
-
},
|
|
47
|
-
"guard_diff": []
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
`classification` är instruktionens JSON från Steg 4, **ordagrant och orört**, med sina svenska nycklar. Ändra ingenting där — den ska gå att diffa mot maskinversionen för att se om skillen driftat. Att ytterlagret är engelskt och innerlagret protokollets är avsiktligt, inte slarv.
|
|
52
|
-
|
|
53
|
-
`customer` är ditt eget lager. Domänord, aldrig fältnamn ur något ärendesystem — mappningen till Asana eller annat sker i `request-sync` och ingen annanstans.
|
|
54
|
-
|
|
55
|
-
`guard_diff` fylls bara i grindläget: en post per fråga som bytte svar, med frågans id, båda svaren och citatet ur fullplanen.
|
|
56
|
-
|
|
57
|
-
### Motiveringen
|
|
58
|
-
|
|
59
|
-
Kundens språk. Beskriver arbetet som ska göras, inte hur protokollet räknade. Inga protokolltermer — bump, basklass, blast radius, touchpoint, B1, S2. Kunden ska kunna invända mot omfattningen, inte mot modellen; en motivering som exponerar mekaniken bjuder in till att argumentera med protokollet i stället för med scopet.
|
|
60
|
-
|
|
61
|
-
Den ska vara förenlig med specens _Ingår inte_. Motiverar du klassen med arbete som specen uttryckligen undantar har du klassat fel post.
|
|
62
|
-
|
|
63
|
-
### Utfall utan klass
|
|
64
|
-
|
|
65
|
-
Blir utfallet `KONCEPT`, `DIAGNOS` eller `TILLBAKA_TILL_SPEC` sätts `customer.class` till utfallet och `rationale` beskriver i kundvänligt språk vad som behöver hända och varför — de två första går till kund som ett eget förslag.
|
|
66
|
-
|
|
67
|
-
`INPUT_SAKNAS` och `ESKALERA` är interna. `customer` lämnas tomt; de ska aldrig nå kund.
|
|
68
|
-
|
|
69
|
-
## Efter körning
|
|
70
|
-
|
|
71
|
-
Granskaren läser svaren fråga för fråga, inte slutklassen. Ett rätt slutresultat med fel belägg är en felkalibrering som råkade landa rätt, och den kommer tillbaka i en post där den inte gör det.
|
|
72
|
-
|
|
73
|
-
Korrigeringar går till kriterietexten och kalibreringsloggen — aldrig till priset på den enskilda posten.
|
|
74
|
-
|
|
75
|
-
## Acceptanstestet — granskarens sak, inte din
|
|
76
|
-
|
|
77
|
-
Skillen verifieras genom att köras mot fall med känt facit. Det underlaget ligger med flit **inte** i den här mappen — en körning som kan läsa facit kan inte längre testas. Du ska varken leta efter det eller rapportera det som saknat; du får underlaget som vanlig input när testet körs, utan att veta att det är ett test.
|
|
78
|
-
|
|
79
|
-
För granskaren: testet gäller **svaren**, inte bara slutklassen. Ett rätt slutresultat på fel belägg är en felkalibrering som råkade landa rätt.
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
# Klassningsinstruktion (maskinversion)
|
|
2
|
-
|
|
3
|
-
**v0.6 · Internt.** Körbar instruktion för AI-klassning av ett inkommande önskemål. Principerna finns i klassningsprotokollet, som är överordnat vid konflikt. Det dokumentet är för människor och ingår inte i körningen — hittar du en regelkonflikt eskalerar du enligt regel 6, och granskaren avgör mot protokollet. v0.6: B2 omdefinierad. Frågan flyttar från vad planen säger om testning till vad ändringen kräver, och gränsen går mellan verifiering (en människa tittar, kostar minuter) och testning (en människa arbetar sig igenom, kostar timmar). Den gamla lydelsen tillät belägg endast ur byggets verifieringssteg, vilket gjorde att en plan som glömde nämna manuell testning slapp bumpen — kriteriet mätte planens utförlighet i stället för arbetets innehåll. v0.5: kriterier måste vara oberoende — samma förhållande får inte räknas två gånger, och touchpoint-kriteriet räknar bara lager som inget annat kriterium redan gjort anspråk på. Hålet uppstod i v0.4 när kriterier gick från antingen-eller till att adderas. v0.4: LARGE tillagd och stegen gjorda enhetliga — ett Medium-kriterium är basen, varje ytterligare kriterium och varje bump är ett steg upp, taket är LARGE. Tidigare saknades steget mellan MEDIUM och offert helt, vilket gjorde varje Medium med en bump till offert. v0.3 efter divergenstest (två körningar, samma underlag, olika klass utan eskalering): flerstegsflöde och datamodelländring omdefinierade. v0.2 efter två oberoende testkörningar: konsekvens-B1, bygg-avgränsad B2, undantag för ärvda antaganden och robusthetsinstruktioner, genomförbarhetsvillkor.
|
|
4
|
-
|
|
5
|
-
## Roll och uppgift
|
|
6
|
-
|
|
7
|
-
Du klassar ett utvecklingsönskemål till prisklass enligt reglerna nedan. Du fattar inga andra beslut: du bedömer inte om posten bör byggas, du föreslår inga lösningar, du estimerar inte pris. Ditt utfall är antingen en klass, en omdirigering (Koncept/Diagnos/spec) eller en eskalering till människa. Du gissar aldrig — ett svar utan belägg i underlaget är fel svar även om det råkar bli rätt.
|
|
8
|
-
|
|
9
|
-
## Obligatoriskt underlag — två körlägen
|
|
10
|
-
|
|
11
|
-
Instruktionen körs i två lägen med samma frågor:
|
|
12
|
-
|
|
13
|
-
**Läge 1 — prisklassning:** spec (godkänd av Haus) + klassningsplan (en sida, strukturlagret: berörda repon/filer, serverdatamodell ja/nej, integrationspunkter, sekvensflöden som ändras, designbehov, testbehov i bygget, risker/antaganden, beroenden). Utfallet är prisunderlaget till kund.
|
|
14
|
-
|
|
15
|
-
**Läge 2 — guard:** spec + fullständig implementationsplan (efter kundens godkännande, före första kodraden). Utfallet diffas fråga för fråga mot läge 1. Avvikelse rapporteras med exakt vilken fråga som bytte svar och citatet ur fullplanen som orsakade bytet — endast ny strukturell information får ändra klass.
|
|
16
|
-
|
|
17
|
-
Saknas underlaget för läget, eller är det ofullständigt på någon punkt du behöver för en fråga: avbryt med utfall `INPUT_SAKNAS` och lista exakt vad som saknas. Klassa aldrig på enbart önskemålets formulering.
|
|
18
|
-
|
|
19
|
-
## Konfiguration [JUSTERA MOT KODBAS — defaults gäller tills dess]
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
DELAD_KOD: paths/paket som används av >1 kund eller av plattformen
|
|
23
|
-
[DEFAULT: allt under packages/shared, gemensamma Vendure-plugins,
|
|
24
|
-
Elementor-widgets i centralt bibliotek, npm-paket i privat registry]
|
|
25
|
-
DRIFTMILJÖ: [DEFAULT: infrastruktur-repon, CI/CD-konfig, Dockerfiles,
|
|
26
|
-
miljövariabler, Northflank/Vercel-konfiguration]
|
|
27
|
-
KONSEKVENSRISK: fel i posten kan orsaka: felaktig betalning/pris · felaktig
|
|
28
|
-
åtkomst (någon ser/gör det den inte får) · förlust av data som
|
|
29
|
-
inte kan återskapas · att befintlig funktion slutar fungera
|
|
30
|
-
för användare. Typiska hemvister: auth/behörighet, checkout,
|
|
31
|
-
radering/migrering, personuppgiftsflöden — men hemvisten är
|
|
32
|
-
exempel, KONSEKVENSEN är frågan. Ny lagring av användarkopplad
|
|
33
|
-
data räknas alltid (drar med sig gallring/registerutdrag/
|
|
34
|
-
raderingskedjor) — dock som designkrav, inte automatisk bump:
|
|
35
|
-
bump endast om planen INTE redan hanterar det (nyckelval,
|
|
36
|
-
radering, gallring adresserade = hanterat). Återanvändning av
|
|
37
|
-
befintlig säkerhetsmekanik enligt etablerat mönster räknas
|
|
38
|
-
ALDRIG som träff.
|
|
39
|
-
INTEGRATIONSPUNKT: koppling till system utanför applikationens egen kodbas
|
|
40
|
-
[DEFAULT: ERP, betalväxel, e-post/SMS-tjänst, extern API,
|
|
41
|
-
filimport/-export mot tredje part]
|
|
42
|
-
OFFERTGRÄNS: strukturell, aldrig tid — träffas om något gäller:
|
|
43
|
-
paket av ≥3 relaterade delposter · flera fristående flöden/system
|
|
44
|
-
ändras i kombination · ny fristående komponent/plugin byggs
|
|
45
|
-
(inte ändring i befintlig) · planen listar ≥2 av varandra
|
|
46
|
-
oberoende arbetsblock som vart och ett ensamt vore Medium
|
|
47
|
-
KONFIDENSGRÄNS: 0.8 — under denna på någon enskild fråga → eskalera
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Ordning
|
|
51
|
-
|
|
52
|
-
Kör stegen i exakt denna ordning. Första stopp/omdirigering avslutar körningen.
|
|
53
|
-
|
|
54
|
-
### Steg 0 — Inputkontroll
|
|
55
|
-
|
|
56
|
-
Finns spec + plan, och täcker planen: berörda filer, datamodell, integrationer, testbehov? Nej → `INPUT_SAKNAS`.
|
|
57
|
-
|
|
58
|
-
### Steg 1 — Stoppfrågor (JA på någon → omdirigera, klassa inte)
|
|
59
|
-
|
|
60
|
-
**S1 — Oavgjort vägval:** Innehåller planen fler än en föreslagen lösningsväg utan att en är vald, en formulering i stil med "beroende på", "alternativt", "behöver beslutas", eller en designfråga (utseende, flöde, UX) som inte är avgjord i specen? → JA = utfall `KONCEPT`
|
|
61
|
-
|
|
62
|
-
**S2 — Okänd orsak/omfattning:** Bygger planen på en hypotes om vad som är fel eller hur systemet beter sig ("troligen", "bör undersökas", "om det visar sig att")? Är felbild eller nuläge obekräftat — och går det att undersöka? → JA = utfall `DIAGNOS`
|
|
63
|
-
|
|
64
|
-
_Undantag (räknas INTE som S2-träff):_ ett antagande som redan gäller i det befintliga systemet, är dokumenterat i planen och riskmarkerat ("om X visar sig falskt, leta här") är ärvd, hanterad risk — inte en hypotes. Detsamma gäller antaganden som inte GÅR att undersöka (t.ex. kräver åtkomst som bara kunden har): de blir ett genomförbarhetsvillkor (se Steg 3b), aldrig ett stopp — en Diagnos som inte kan utföras löser ingenting.
|
|
65
|
-
|
|
66
|
-
**S3 — Låg konfidens i planen:** Flaggar planen själv osäkerhet om VAD som ska byggas, eller finns obesvarade frågor i specen som påverkar bygget? → JA = utfall `TILLBAKA_TILL_SPEC`
|
|
67
|
-
|
|
68
|
-
_Undantag (räknas INTE som S3-träff):_ robusthetsinstruktioner — "om verktyg X klagar, gör Y", "träffar inte selektorn, byt till det som renderas" — är anvisningar för oväntat verktygsbeteende under bygget. En plan som anger vad man gör när något beter sig oväntat är säkrare än en som inte gör det, inte osäkrare.
|
|
69
|
-
|
|
70
|
-
Osäkerhet får aldrig lösas genom att välja en högre klass "för säkerhets skull".
|
|
71
|
-
|
|
72
|
-
### Steg 2 — Basklass (första raden uppifrån som stämmer)
|
|
73
|
-
|
|
74
|
-
**ÖVER KLASSERNA** om något av villkoren i OFFERTGRÄNS träffas. → utfall `OFFERT`
|
|
75
|
-
|
|
76
|
-
**LARGE** om exakt 2 av MEDIUM-kriterierna nedan träffas av samma sammanhängande ändring — delar som inte kan levereras var för sig. Går de att leverera separat är det inte en post: den ska delas i flera requests, en per beslut. Träffas ≥3 kriterier → utfall `OFFERT`.
|
|
77
|
-
|
|
78
|
-
**MEDIUM** om exakt ett gäller:
|
|
79
|
-
|
|
80
|
-
- **Flerstegsflöde:** en sekvens användaren passerar i ordning (wizard, checkout, ansökan) där ≥2 steg ändras. Att en funktion syns eller triggas på flera ställen i UI:t är INTE ett flerstegsflöde — flera ingångar till samma markering/åtgärd räknas som ett flöde. Antal ändrade filer, komponenter eller vyer är aldrig i sig ett klasskriterium.
|
|
81
|
-
- **Datamodelländring:** ny/ändrad serverlagrad struktur — tabell, kolumn, entitet, relation eller constraint i databas — även "bara ett unikt-villkor" räknas. Det som gör den till Medium är följdkostnaderna: migrering, driftsättningsordning, backup, gallringsansvar. Klientlagring räknas inte (localStorage/sessionStorage/minne), oavsett hur strukturerad den är — den har inga av följdkostnaderna.
|
|
82
|
-
- **Designinsats:** nytt utseende eller nytt UI-mönster som inte finns i systemet idag (återanvändning av befintlig komponent räknas inte)
|
|
83
|
-
- **≥3 touchpoints** av: frontend · backend · databas · integration. Räkna endast lager som inget annat träffat kriterium redan gör anspråk på — träffar datamodellkriteriet är databaslagret förbrukat, träffar designinsatsen är frontend förbrukat. Kriteriet utvärderas sist och finns för att fånga bredd hos poster som inte träffar något specifikt; det läggs aldrig ovanpå ett kriterium som redan slagit.
|
|
84
|
-
|
|
85
|
-
**SMALL** om: logik ändras (villkor, beräkning, validering, beteende) inom ett avgränsat flöde · högst 1 integrationspunkt berörs · ingen datamodelländring.
|
|
86
|
-
|
|
87
|
-
**TINY** om: ingen logik ändras — text, översättning, konfigurationsvärde, stil/CSS, byte av länk/bild — och planen anger att inga nya tester behöver skrivas.
|
|
88
|
-
|
|
89
|
-
_Rimlighetskontroll:_ om filantal/repon i planen skarpt motsäger klassen (t.ex. Tiny men 15 filer i 3 repon) → sänk konfidensen på basklassfrågan under KONFIDENSGRÄNS.
|
|
90
|
-
|
|
91
|
-
### Steg 3 — Bumpar (varje JA = ett steg upp; passeras LARGE → `OFFERT`)
|
|
92
|
-
|
|
93
|
-
Klasstegen är TINY → SMALL → MEDIUM → LARGE. Omfattning och risk räknas likadant: ett Medium-kriterium är basen, varje ytterligare kriterium är ett steg upp, och varje bump är ett steg upp. Passeras LARGE åt något håll → `OFFERT`.
|
|
94
|
-
|
|
95
|
-
Alltså: 1 kriterium + 0 bumpar = MEDIUM · 1 + 1 = LARGE · 2 + 0 = LARGE · 2 + 1 = OFFERT · 3 + 0 = OFFERT.
|
|
96
|
-
|
|
97
|
-
Eftersom kriterier nu adderas måste de vara oberoende. Samma förhållande får aldrig räknas två gånger — varken två basklasskriterier som beskriver samma lager, eller en bump som upprepar det basklassen redan fångat. Är du osäker på om två träffar beskriver samma sak: räkna en gång och notera tvekan i frågeraden.
|
|
98
|
-
|
|
99
|
-
Bumpar mäter konsekvens av fel, aldrig att posten tekniskt vidrör en kategori. Taket är undantaget (historiskt ~3 % av poster) — slår protokollet ofta i taket är protokollet felkalibrerat, inte posterna.
|
|
100
|
-
|
|
101
|
-
**B1 — Konsekvensrisk:** Kan ett fel i just denna post orsaka något av det som listas i KONSEKVENSRISK? Ställ frågan konkret: "vad är det värsta som händer om det här blir fel?" — visas en notis felaktigt är svaret nej; debiteras någon fel eller ser någon annans data är svaret ja. (Att koden anropar eller ligger nära känslig mekanik räcker inte; befintliga mönster som återanvänds räknas aldrig.)
|
|
102
|
-
|
|
103
|
-
**B2 — Testbehov som inte går att automatisera bort:** Förutsätt att ändringen täcks av automatiska tester, även om de inte finns i dag. Manuell testning ska aldrig göra om det maskinen klarar. Kvarstår något som ändå kräver en människa?
|
|
104
|
-
|
|
105
|
-
_Skilj på verifiering och testning._ **Verifiering** är att en människa tittar och bekräftar att något blev som tänkt: ett nytt menyval, ett fält i ett befintligt formulär, en knapp enligt befintligt mönster, en kolumn i en befintlig tabell. Det kostar minuter och räknas INTE. **Testning** är att en människa måste arbeta sig igenom något: gå ett flöde steg för steg, pröva olika utfall och feltillstånd, använda en fysisk enhet, eller bedöma om upplevelsen håller. Det kostar timmar och räknas.
|
|
106
|
-
|
|
107
|
-
Testning krävs typiskt när ändringen rör: ett nytt eller ändrat flöde som någon måste gå igenom från början till slut · identifiering som inte går att köra automatiskt (BankID, extern inloggningstjänst, betalväxelns egen dialog) · affärskritiska flöden där automatiken finns men inte räcker som enda kvitto före driftsättning (betalning, order, behörighet) · upplevd kvalitet, när bedömningen av hur något känns eller ser ut är poängen med uppdraget — en ny vy, ett omgjort flöde; en justerad färg eller marginal är verifiering · fysiska enheter (AR, kamera, riktiga telefoner, utskrift, skanner) · externa system utan testmiljö, där enda sättet att veta är att köra mot skarpt.
|
|
108
|
-
|
|
109
|
-
Belägg hämtas ur ändringens innehåll: vad som byggs enligt spec och plan. Kundens acceptanstest räknas aldrig — det är ett leveransvillkor, inte en byggkostnad.
|
|
110
|
-
|
|
111
|
-
**B3 — Blast radius:** Ändras något inom DELAD_KOD eller DRIFTMILJÖ så att fler kunder än beställaren, eller SLA-ytan, kan påverkas?
|
|
112
|
-
|
|
113
|
-
### Steg 3b — Genomförbarhetsvillkor (påverkar inte klassen)
|
|
114
|
-
|
|
115
|
-
Om planen vilar på ett dokumenterat antagande som inte går att verifiera före bygget (S2-undantaget): notera det som villkor i output. Posten klassas som vanligt; villkoret betyder "bygger på att X — faller X tidigt avbryts posten utan debitering". Kommersiellt är det Haus som tar risken, inte kunden; enstaka avbrutna poster är portföljkostnad, som överdrag. Villkoret ska alltid med i förslaget kunden ser.
|
|
116
|
-
|
|
117
|
-
Notera även **beroenden**: leveranser som måste vara klara först (andra poster, ramverk, tredje part). Beroenden påverkar aldrig klassen — de påverkar leveranslöftet och ska synas i kundens förslag.
|
|
118
|
-
|
|
119
|
-
### Steg 4 — Output
|
|
120
|
-
|
|
121
|
-
Svara med exakt denna struktur och inget annat:
|
|
122
|
-
|
|
123
|
-
```json
|
|
124
|
-
{
|
|
125
|
-
"utfall": "TINY | SMALL | MEDIUM | LARGE | OFFERT | KONCEPT | DIAGNOS | TILLBAKA_TILL_SPEC | INPUT_SAKNAS | ESKALERA",
|
|
126
|
-
"basklass": "TINY | SMALL | MEDIUM | LARGE | OVER | null",
|
|
127
|
-
"fragor": [
|
|
128
|
-
{"id": "S1", "svar": "JA/NEJ", "belagg": "ordagrant citat ur spec/plan", "konfidens": 0.0}
|
|
129
|
-
// ... samtliga S1–S3, basklassrad, B1–B3
|
|
130
|
-
],
|
|
131
|
-
"bumpar_traffade": ["B1", ...],
|
|
132
|
-
"genomforbarhetsvillkor": ["'bygger på att X' — tom lista om inga"],
|
|
133
|
-
"beroenden": ["leveranser som måste vara klara först — tom lista om inga"],
|
|
134
|
-
"slutklass_motivering": "en mening: basklass X pga <kriterium>, +N bump pga <B#>",
|
|
135
|
-
"eskaleringsskal": "endast vid ESKALERA: vilken fråga och varför",
|
|
136
|
-
"saknas": ["endast vid INPUT_SAKNAS"]
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
## Hårda regler
|
|
141
|
-
|
|
142
|
-
1. **Belägg är obligatoriskt.** Varje JA/NEJ ska ha ett ordagrant citat ur spec eller plan. Hittar du inget citat är svaret inte NEJ — det är låg konfidens. Beläggets källa spelar roll: för B2 ska citatet visa VAD som byggs (flödet, enheten, den externa tjänsten), inte vad planen råkar säga om testning — en plan som glömmer nämna manuell testning ska inte slippa bumpen. Citat ur acceptans-/kundmoment är ogiltigt belägg.
|
|
143
|
-
2. **Konfidens < 0.8 på någon fråga → utfall `ESKALERA`.** Klassa aldrig på gissning. En eskalerad post klassas av människa; det är ett korrekt utfall, inte ett misslyckande. Undantag: om frågan inte är utfallsavgörande (svaret ändrar inte slutklassen åt något håll) räcker det att notera den låga konfidensen i frågeraden.
|
|
144
|
-
3. **Du klassar planen, inte önskemålet.** Önskemålets ton, storleksord ("en liten grej") eller kundens egen bedömning påverkar ingenting.
|
|
145
|
-
4. **Tid existerar inte i klassningen.** Timmar, estimat och tidsord förekommer varken i din bedömning eller i något outputfält — alla gränser i denna instruktion är strukturella (vad som ändras), aldrig temporala (hur länge det tar).
|
|
146
|
-
5. **Ingen prisberäkning.** Klass → pris görs av prislistan, aldrig av dig.
|
|
147
|
-
6. **Vid regelkonflikt:** eskalera med skälet — ändra aldrig en regel själv.
|
|
148
|
-
7. **Konsekvens före kategori.** När du tvekar på en bump, ställ alltid frågan "vad är det värsta som händer om posten blir fel?" — och svara på den, inte på om posten nuddar en kategori.
|
|
149
|
-
|
|
150
|
-
## Efter körning (människans del — inte din)
|
|
151
|
-
|
|
152
|
-
Människan granskar svaren fråga för fråga (inte bara slutklassen), godkänner eller korrigerar, och loggar. Korrigeringar matas tillbaka som kalibrering av konfig och kriterier — aldrig som prisändring på den enskilda posten.
|