@haus-tech/haus-workflow 4.3.0 → 4.5.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.
@@ -14,6 +14,13 @@ whenever this procedure is reached with **no name** — whether from the no-arg
14
14
  no argument — do **not** silently fall through to Mode B (workspace); ask first, via
15
15
  `AskUserQuestion`:
16
16
 
17
+ **Skip this question when `SKILL.md`'s Step 1.5 already determined the cwd is Workspace
18
+ scope** (a `haus.workspace.yaml`/`repos.manifest.json` already exists at the git root) —
19
+ in that case go straight to Mode B (unless a `name` was already given, in which case Mode
20
+ A takes over as usual). Asking "single repo or whole workspace?" again here would
21
+ contradict a scope Step 1.5 already resolved. Only ask below when Step 1.5 found Project
22
+ or Not-set-up scope (or no scope check ran, e.g. reached from an older caller).
23
+
17
24
  ```
18
25
  "Clone a single repo, or a whole workspace?"
19
26
  Options:
@@ -63,3 +70,187 @@ Clone a whole **workspace** from its manifest. Workspace-only (a `repos.manifest
63
70
  **Transport fallback.** Manifest URLs may be SSH (`git@github.com:org/repo.git` or `ssh://…`). Probe SSH connectivity once up front (`ssh -T git@github.com`); if it fails, **auto-fall back to the HTTPS URL** (`https://github.com/org/repo.git`) using your `gh auth` credentials, clone over HTTPS, and **report that you switched transport** so the user knows their SSH is down. Don't halt the run for an SSH outage when HTTPS works.
64
71
 
65
72
  7. After the loop, report which repos were cloned, reused (local), skipped (already present), and failed. Remind the user that installing dependencies and configuring each repo (`.env`, services) is still a manual step for now.
73
+
74
+ ## Local-dev setup (optional)
75
+
76
+ **Always ask before doing this work — never assume.** Everything from here through Step 4
77
+ hits the network, needs auth, and can touch databases; confirm before each phase, and
78
+ respect repos the user already has.
79
+
80
+ Use `AskUserQuestion`:
81
+
82
+ ```
83
+ "Set the cloned repo(s) up for local dev too?"
84
+ Options:
85
+ 1. Yes
86
+ 2. No
87
+ ```
88
+
89
+ The `cloneandsetup` and `project:cloneandsetup` aliases pre-answer **Yes** — when reached
90
+ via one of those aliases, **skip this `AskUserQuestion` call entirely** and carry the
91
+ resulting repo list straight into Step 1 below. On a bare **No** answer (not via one of
92
+ those aliases), stop after the clone report. On a manual **Yes**, carry the resulting repo
93
+ list into the setup phases below.
94
+
95
+ ### Step 1 — Prerequisite gate (one consolidated check)
96
+
97
+ Before any setup work, probe **everything** the workspace will need and surface **all** gaps in a single prompt — never discover them piecemeal mid-flow. For the repos being set up, check:
98
+
99
+ - **Auth tokens** — `NODE_AUTH_TOKEN` exported (private `@`-scoped npm packages fail without it); `~/.composer/auth.json` (or a repo-local `auth.json`) present if any repo has private composer deps.
100
+ - **Docker daemon** — running (`docker info`); required for any repo's `needs:` services.
101
+ - **PHP environment** — `valet`, `herd`, `ddev`, or `php` on PATH, if any repo is a PHP/WordPress site.
102
+ - **WP-CLI** — `wp --version`, if any repo's `seed:` pulls a WordPress DB.
103
+ - **Node versions** — the versions named in each repo's `.nvmrc` / `engines.node` are installable via `nvm`.
104
+ - **Data-access preflight** — for every data load the workspace will run (each repo's `seed` **and** the workspace `seeds`), check up front that the user can actually reach the source, so nobody ends up with a half-set-up environment that only fails at the seed step. See below.
105
+
106
+ #### Data-access preflight
107
+
108
+ The single most common way cloneAndSetup leaves a **half-completed environment** is discovering at the `seed:` step — after cloning, installing, and bringing up services — that the user lacks the AWS/SSH/etc. access to pull the data. Catch it **here**, before any heavy work, and make the data outcome a deliberate choice.
109
+
110
+ 1. **Collect every seed:** each repo's `seed` plus the workspace `seeds`. For each, determine its required access:
111
+ - If `seed` is an **object with `access`**, run each entry's `check` (a read-only probe; exit 0 = reachable).
112
+ - If `seed` is a **bare string**, infer the channel and probe generically: `aws …`/SSM → `aws sts get-caller-identity`; `dep db:pull …` / `ssh`/`scp`/`rsync <user@host>` → `ssh -o BatchMode=yes -o ConnectTimeout=5 <host> true`; `northflank …` → `northflank whoami`. A purely local seed (no remote) needs nothing.
113
+ 2. **Report one table** alongside the rest of the gate — per data source: its `label`, the access it needs, and ✓ reachable / ✗ + the `login` remedy (verbatim).
114
+ 3. **Resolve every ✗ with one consolidated choice**, then remember it for the run:
115
+ - **Set up access now** → print the `login` text, let the user do it, **re-check**, then seed normally.
116
+ - **Create that DB EMPTY for now** (only when `empty-ok` is true — the default) → the datastore comes up empty (schema via migrations/`needs:`), the app still boots, and the user can run that one seed later. Name the exact command to fill it.
117
+ - **Abort.**
118
+ 4. **Only a gap on a seed with `empty-ok: false` is a HARD STOP** (the repo is genuinely broken without data — log in or abort). Every other gap offers the empty path; one source's missing access never blocks another's.
119
+
120
+ This is the one prompt the "no half-completed environment" goal justifies. The two valid end-states are **seeded** or **deliberately empty** — never a third "did the expensive work then died at seed."
121
+
122
+ Then list the repos and what each will run (node, deps, localdev steps, **data sources + the chosen seed/empty plan**), **report every gap at once**, and get a single go-ahead. For **reused** local clones, ask whether to re-run setup. For each gap, name what it blocks; the user decides whether to fix it now (**ask before any global/system install**) or proceed and skip the affected steps. Don't begin per-repo work until this gate is acknowledged.
123
+
124
+ ### Step 2 — Per-repo dependency pass
125
+
126
+ For each repo, in its own directory, detect and install from the repo's own files (read its `docs/setup.md` / `CLAUDE.md` / `README.md` first — they win). Select node from `.nvmrc`/`engines.node` (`nvm install`), enable the pinned package manager (`corepack enable`), install JS deps (`yarn`/`pnpm`/`npm` by lockfile), composer deps if `composer.json` + `composer` present. Run each repo's steps in one login shell so the node version stays active. Per-repo failure is reported and skipped, not fatal.
127
+
128
+ **Leave `.env` to Step 3 → "Env".** The dependency pass installs only; the env phase writes each repo's `.env` deterministically once services, links, and values are in place.
129
+
130
+ ### Step 3 — Local-dev orchestration
131
+
132
+ This is the phase that takes the workspace from "installed" to "ready to run." It is driven by `localdev.yml` files; a repo with none is set up by Step 2 only.
133
+
134
+ #### House conventions (the "how", so repos specify only the "what")
135
+
136
+ Specify the least. Infer the rest from the repo's stack + these conventions, and work out the concrete commands at run time:
137
+
138
+ - **IMPORTANT — never install anything without asking first; global/system tools especially** (Homebrew, Docker, Laravel Herd, global `npm`/`composer` packages). Detect what's already present; if something required is missing, name it, say why it's needed, and get an explicit **yes** before installing. Prefer the least-invasive option, and don't switch or override tools the dev already has.
139
+ - **PHP / WordPress sites are served by the developer's own PHP environment.** If they already have one — detect `valet`, `herd`, `ddev`, or `php` on PATH — **use it**: just satisfy the env contract (docroot → the repo's `web/`, HTTPS) and report the URL; never override what they already run. **Only when no local PHP environment exists** (a completely fresh machine) suggest installing **[Laravel Herd](https://herd.laravel.com)** as the default (asking first) and point its docroot at `web/` + `herd secure`.
140
+ - **Databases and other services (a repo's `needs:`) run in Docker as a shared, workspace-owned `docker-compose` project** — grouped and self-contained (`up`/`down` together, named volumes), namespaced per workspace instance via `COMPOSE_PROJECT_NAME` + its own host ports so the workspace can run multiple times side by side. Image/port/env per the house convention (`mysql:8`, `postgres:16`, …). Don't reuse a repo's own compose that bind-mounts repo-relative init files (`seed.sql`) — author the service in the workspace compose. Services come up **empty** (seeding is the separate `seed:` step); record chosen host ports in the workspace `localdev.yml` `env` map.
141
+ - **Dependencies** install from the lockfile (Step 2).
142
+ - A repo's `localdev.yml` carries **only what can't be inferred** — its `needs`, repo-specific `build`/`seed` commands, env keys, and URL. Detect the stack (e.g. Bedrock = `composer.json` + `web/` docroot + `wp-cli.yml`; Vendure/Node = `docker-compose.yml` + `@vendure/*`) and apply the matching convention.
143
+
144
+ #### The `localdev.yml` format
145
+
146
+ **Per-repo — `<repo>/.haus-workflow/localdev.yml`** (how to set up THAT repo alone; sibling-agnostic):
147
+
148
+ All fields optional. **Prefer intent (`needs`/`build`/`seed`/`serve`) over explicit `steps`** —
149
+ the conventions above turn intent into commands.
150
+
151
+ ```yaml
152
+ needs: [mysql] # services this repo requires; provisioned in Docker, env wired to them
153
+ needsEnv: [WP_HOME, DB_NAME] # env keys that must be present to run/serve
154
+ build: 'yarn build' # optional: the build command (run after deps)
155
+ seed: 'dep db:pull staging-oderland' # optional: how to load data; remote/destructive → confirm first
156
+ # `seed` may also be an OBJECT when the load needs external access (AWS, SSH, …) so the
157
+ # Step-1 data-access preflight can check it up front and offer an empty-DB fallback:
158
+ seed:
159
+ run: './scripts/pull-aws-db.sh --yes' # the seed command
160
+ empty-ok: true # default true: skipping yields a coherent EMPTY DB (schema via migrations).
161
+ # # false = the repo is broken without data → a missing-access gap HARD-STOPS.
162
+ access: # what the user must have to run `run`; each entry probed in the Step-1 preflight
163
+ - label: 'AWS CLI credentials — eu-north-1 / 842675986884'
164
+ check: 'aws sts get-caller-identity --region eu-north-1' # read-only probe; exit 0 = OK
165
+ login: 'Set up the AWS CLI (brew install awscli; aws configure …) — or pick empty DB.'
166
+ serve: # optional: how it runs (printed as a next-step, never auto-started)
167
+ via: herd # herd | valet | docker | command — for PHP envs, bring-your-own (don't install)
168
+ url: 'https://example.test'
169
+ start: 'yarn dev'
170
+ steps: # optional escape hatch: explicit ordered shell steps when intent isn't enough
171
+ - run: 'composer install'
172
+ node: 10 # nvm version for THIS step
173
+ remote: true # SSH → confirm first
174
+ destructive: true # overwrites data → confirm first
175
+ optional: true # failure is non-fatal, continue
176
+ ```
177
+
178
+ **Workspace — `<workspace>/.haus-workflow/localdev.yml`** (the glue BETWEEN repos):
179
+
180
+ The workspace `env` map is the **single source of truth for cross-repo values** — DB names, ports, host URLs are chosen once and recorded here as literals. The env phase reads values **from here** and writes them into each repo's `.env`. Recording them here (not only in a repo's `.env`) means a later `seed:` / `db:pull` step can find them without depending on env-file load order.
181
+
182
+ ```yaml
183
+ order: [repo-a, repo-b] # setup/startup order, by manifest id
184
+ links:
185
+ - { type: symlink, from: <repo-folder>, to: <repo-folder>/path/to/link }
186
+ - { type: composer-path, in: <repo>, dep: <sibling-repo> }
187
+ - { type: yarn-link, in: [<repo>, ...], dep: <sibling-package-repo> }
188
+ env:
189
+ - value: 'app_local' # a chosen literal — the recorded source of truth, OR …
190
+ # source: { repo: <repo>, provides: '<value>' } # … a value produced by another repo
191
+ sinks:
192
+ - { repo: <repo>, key: DB_NAME } # written under `key` into each sink's .env
193
+ seeds: # optional: data sources NOT owned by a manifest repo (e.g. a shared WordPress host
194
+ # pulled by a workspace script). Same shape as a per-repo `seed` object; included in
195
+ # the Step-1 data-access preflight.
196
+ - label: 'WordPress host (example.se)'
197
+ run: './scripts/pull-wp-example.sh'
198
+ empty-ok: true
199
+ access:
200
+ - label: 'SSH user@host'
201
+ check: 'ssh -o BatchMode=yes -o ConnectTimeout=5 user@host true'
202
+ login: 'Add your SSH key to the host (ssh-add …) / request access — or skip the pull.'
203
+ ```
204
+
205
+ #### Run order
206
+
207
+ 1. **Discover** `.haus-workflow/localdev.yml` in the workspace root and each repo.
208
+ 2. **Resolve order** from the workspace `order` (repos not listed run last, in manifest order). No workspace file → manifest order.
209
+ 3. **Per repo, in order**, apply intent via the conventions:
210
+ - **`needs`** → add the service to the **workspace compose project** and `docker compose up -d` if not already running (namespaced by `COMPOSE_PROJECT_NAME`, free host ports per instance; not the repo's own bind-mounting compose). Comes up **empty**; record host ports in the workspace `localdev.yml` `env` map — data lands in `seed:`.
211
+ - **`build`** → run it (honor any node version the repo's docs note).
212
+ - **`serve`** → for `via: herd` / PHP envs, install nothing — verify the dev's environment serves `web/`, and report the URL.
213
+ - **`seed`** → populate the empty datastore (`seed.run`, or the bare-string form). **Always a distinct, confirm-gated step, separate from `needs:` bring-up** (don't conflate "DB up" with "DB has data"). **Honor the data plan chosen in the Step-1 data-access preflight** — don't re-discover access here: if the user chose _empty_ for this source, skip it (the DB stays empty, schema via migrations) and print the command to fill it later; if they chose _seed_, run it. Still verify the run-time prerequisites the preflight couldn't (the target repo's **`.env` written** — done in the Env step above; WP-CLI present for `wp`/`db:pull` seeds) and **confirm first** for every remote (SSH/AWS) or destructive (overwrites data) seed — every run, even on re-run. Missing prerequisite → skip with a clear message, don't guess.
214
+ - **`steps`** (escape hatch) → run in order, selecting `node:` per step, `optional:` failures continue, **confirming before any `remote:`/`destructive:` step** — every run, even on re-run.
215
+ 4. **Links** (workspace-owned, performed generically — do NOT call a repo's own `setup-dev-mode.sh`, which is deprecated):
216
+ - `symlink` → `ln -s <from> <to>`; replace an existing symlink, but never clobber a real directory without confirmation.
217
+ - `composer-path` → in `in`'s `composer.json`, set the `dep`'s require to `{ "type": "path", "url": "../<dep-folder>", "options": { "symlink": true } }`, then `composer update <vendor/dep>`.
218
+ - `yarn-link` → `yarn link` in the `dep` repo, then `yarn link <pkg-name>` in each `in` repo (read `<pkg-name>` from the dep's `package.json`).
219
+ 5. **Env (deterministic):** write each repo's `.env` from known values — no improvising.
220
+ 1. **Compute** each repo's values, in this precedence: the workspace `localdev.yml` `env` map (chosen literals + cross-repo `source` values — the single source of truth) → generated secrets (DB passwords/tokens minted this run) → **a value already present locally** in a sibling repo's `.env.example` / `.env` (matched by key, or by the same value under a framework-prefix swap `MIX_` ↔ `VITE_` ↔ `NEXT_PUBLIC_` ↔ `REACT_APP_`) — reuse it: it is already on the machine, so copying it into this repo's local (gitignored) `.env` adds nothing new to git → the **dev-defaults table** below for anything still unset.
221
+ 2. **Write** them into each repo's `.env` (create it if absent; **upsert** keys, never clobbering a value the user already set). These are local dev files — write them directly.
222
+ 3. **Secrets that exist nowhere locally** (third-party API keys, prod credentials, license keys the machine doesn't already have — §1 reuse only applies when the value is present locally) can't be invented, so the flow **explicitly tells the user it needs them** — never silently leaving a blank and proceeding as if set up. Put each in as a clearly-marked `KEY=` blank and report it, naming **which key**, **what it unlocks** (the step or feature that fails without it), and **how to get it manually**: from the provider's own account/dashboard (e.g. the Laravel Nova licence page, the Mediaflow API account), by copying it from the deployed environment's env vars (the server / hosting config), or by asking a colleague who already has it. **NEVER write a secret value into a git-tracked file:** `.env` writes are local and gitignored (fine — reuse freely), but the workspace `localdev.yml` `env` map is committed, so it carries non-secret cross-repo literals only (DB names, ports, host URLs), never credentials. Report all blanks together as one actionable list, not piecemeal.
223
+
224
+ **Dev-defaults table** — used only when neither `localdev.yml` nor a generated secret supplies the value:
225
+
226
+ | Key | Default |
227
+ | -------------------------------- | ------------------------------------------------------------------- |
228
+ | `DB_HOST` | `127.0.0.1` |
229
+ | `DB_PORT` | `3306` (mysql) / `5432` (postgres) — or the port chosen on conflict |
230
+ | `DB_USER` / `DB_PASSWORD` | `root` / a generated secret |
231
+ | `WP_HOME`, `WP_SITEURL`, `*_URL` | the repo's `serve.url` |
232
+ | `WP_ENV` / `APP_ENV` | `development` |
233
+
234
+ 6. **Report, then offer to start.** Give the per-repo summary, then **ask the user whether to start everything now.**
235
+ - **Yes** → start each repo in the workspace `order` (its `serve.start`, e.g. `yarn dev`; bring up any remaining foreground services), run any follow-ups (e.g. `wp sync-products sync`), then **print the live URLs** (each repo's `serve.url`).
236
+ - **No** → just print the ordered start commands + follow-ups as next steps; start nothing.
237
+
238
+ **Default is "ready to run" (D2):** the preparation — datastores up (workspace `docker compose` project), seeds applied (confirm-gated), links, builds, each repo's `.env` written — always happens. Starting the **foreground** dev servers and the initial product sync happen **only if the user says yes** to the start prompt above; otherwise they're printed, not run.
239
+
240
+ ### Step 4 — Report and define "done"
241
+
242
+ **"Done" is an explicit terminal state**, not "looks set up". The preparation reaches:
243
+
244
+ - datastores up (workspace `docker compose` project),
245
+ - dependencies installed and builds green,
246
+ - links wired,
247
+ - each repo's `.env` written from known values.
248
+
249
+ From there, **live URLs are reachable only if** every required secret is satisfiable **and** the user starts the servers. If a secret can't be minted (a third-party key, a prod credential), say exactly which keys are still blank and what the user must fill — don't imply links a missing secret will quietly break.
250
+
251
+ Summarise per repo (node, deps, localdev steps, links, env). Then **ask whether to start everything now**:
252
+
253
+ - **Yes** → start the dev servers in workspace `order`, run any follow-ups, and **print the live URLs** so the user can open the running app.
254
+ - **No** → print the ordered start commands + follow-ups instead, and start nothing.
255
+
256
+ Never start the app without that explicit yes.
@@ -10,6 +10,21 @@ the agent; collect answers with AskUserQuestion and pass everything as flags.
10
10
 
11
11
  ## Phase 0 — Entry
12
12
 
13
+ **Scope check first.** If `SKILL.md`'s Step 1.5 found the cwd is **Workspace** scope (or
14
+ **Member repo of a workspace** resolved to "Whole workspace"), ask via `AskUserQuestion`
15
+ before anything else: "Add the new project as a member of this workspace, or scaffold it
16
+ standalone elsewhere?"
17
+
18
+ - **Add as workspace member** — scaffold under the workspace root (default folder: the
19
+ project name, sibling to the other member repos) instead of the cwd. After Phase 3
20
+ creates the repo, tell the user to add an entry for it to `haus.workspace.yaml` /
21
+ `repos.manifest.json` themselves — this flow only scaffolds and does not write either
22
+ file (no CLI command mutates them yet).
23
+ - **Standalone** — proceed exactly as below, unaffected by the workspace.
24
+
25
+ Skip this ask entirely when Step 1.5 found Project or Not-set-up scope — proceed straight
26
+ to the rest of Phase 0.
27
+
13
28
  A description argument ("a frontend app for shopping") goes straight to Phase 1.
14
29
  No-argument invocation: ask "What do you want to build?" as free text. Do not present
15
30
  a stack menu.
@@ -41,6 +41,11 @@ Befintligt mönster som återanvänds, eller något som inte finns idag. Namnge
41
41
  **Verifiering**
42
42
  Vad som kan bevisas automatiskt, och vad som kräver att en människa kör igenom det — i hur många scenarier, på hur många enheter, i hur många miljöer. Endast bygget. Kundens acceptanstest hör inte hit; skriv det inte ens som notering.
43
43
 
44
+ Skriv dessutom ut **om den berörda ytan har automatiska tester i dag**, och exakt vilken del som saknar dem. Det avgör om posten ska få en egen testpost, byggd före ändringen. Blanda inte ihop det med sådant som kräver en människa av sin natur — kontroll mot en extern tjänsts testläge, visuell granskning, flera enheter eller miljöer — det står kvar oavsett testsvit.
45
+
46
+ **Delbarhet**
47
+ Faller arbetet i delar som var för sig går att uppskatta? Ett kort svar räcker normalt: ja med delarna uppräknade i en mening, eller nej med skälet. Begär poängsättningen en uppdelning skrivs delarna ut fullständigt, se nedan.
48
+
44
49
  **Konsekvens av fel**
45
50
  Vad som händer i värsta fall om posten blir fel. Konkret, inte kategoriskt: "en notis visas i fel ordning" respektive "en användare ser en annan kunds order" är olika saker. Återanvänds befintlig säkerhets- eller behörighetsmekanik enligt etablerat mönster — skriv det, och skriv att det är etablerat.
46
51
 
@@ -55,6 +60,25 @@ Vad som måste vara klart först — andra poster, ramverk, tredje part.
55
60
 
56
61
  Varje påstående ska vara belagt i koden eller specen. Kan du inte belägga något: skriv ordagrant att du inte kunnat verifiera det, och varför. Det är ett bättre utfall än en rad som ser säker ut — klassaren läser osäkerheten och eskalerar, och det är rätt.
57
62
 
63
+ ## När poängsättningen begär en uppdelning
64
+
65
+ Kommer posten tillbaka med utfallet `DELA_UPP` gick den inte att ringa in mot skalan som helhet. Din uppgift är då att peka ut delarna. Poängsättningen får varken läsa repot eller skriva underlag, så den kan inte göra det själv.
66
+
67
+ **Skriv ut varje del med tre saker:** vad den omfattar, uttryckt som vilken del av specen den täcker; om den går att driftsätta och lämna i produktion på egen hand; och vad den delar med de övriga i fråga om manuell genomgång.
68
+
69
+ Den sista punkten avgör om uppdelningen är värd något. Kräver alla delar samma manuella genomgång av samma flöden i samma miljöer, så multipliceras det tyngsta arbetet av uppdelningen i stället för att delas. Skriv i så fall ut det.
70
+
71
+ **Två sorters delar, och de hamnar olika:**
72
+
73
+ - Går delen att leverera på egen hand blir den en **egen request**, med egen spec och egen leverans. Skriv det, men skriv inte specen — det är `request-write-spec` som gör det.
74
+ - Går den inte att leverera på egen hand blir den en **del av samma post**, som levereras tillsammans med resten.
75
+
76
+ **Delarna ärver postens spec och den här analysen.** Skriv ingen egen spec och ingen egen analys per del. Varje del får en omfattningsrad som säger vilken del av specen den täcker, och poängsätts mot det gemensamma underlaget. Kunden har beställt en sak; uppdelningen är intern struktur.
77
+
78
+ **Dela ett steg.** Kommer en del tillbaka och inte går att ringa in heller, ska den offereras — inte delas vidare. Delar man tills varje bit får plats under skalans tak blir summan för hög av skäl som inte har med arbetet att göra.
79
+
80
+ **Minst två delar.** Blir det bara en har posten inte delats, och då är svaret att den inte är delbar.
81
+
58
82
  ## Vägval
59
83
 
60
84
  **Du beslutar, du frågar inte.** Ett vägval är en gaffel där båda vägarna fungerar. Du väljer och redovisar valet — det är därför steget klarar sig utan en människa, och det är också vad kunden betalar oss för.
@@ -90,9 +114,29 @@ Analysen som ett dokument, en sida. Sedan ett kort block till granskaren:
90
114
  ## Till granskaren
91
115
 
92
116
  **Ej verifierat:** [vad du inte kunnat belägga i koden, och varför]
93
- **Avvikelse mot specen:** [om koden säger något annat än specen förutsätter · utfallsval specen lämnat obesvarade]
117
+ **Avvikelse mot specen:** [om koden säger något annat än specen förutsätter]
118
+ **Obesvarade utfallsval:** [ett stycke per val — se nedan]
94
119
  ```
95
120
 
121
+ ### Obesvarade utfallsval
122
+
123
+ Ett utfallsval som specen lämnat öppet skrivs ut med tre saker, och alla tre behövs:
124
+
125
+ 1. **Vilka alternativen är.** Namnge dem, och var i koden vart och ett finns om de
126
+ bygger på något befintligt.
127
+ 2. **Om alternativen skiljer sig i vad som måste byggas.** Skriv rakt ut "båda mönstren
128
+ finns redan, samma arbete oavsett" eller "det ena kräver att X också ändras". Det är
129
+ en observation om koden, inte en bedömning av omfattning — du skriver aldrig att
130
+ något är stort, litet, enkelt eller dyrt.
131
+ 3. **Vilket alternativ analysen är skriven utifrån.** Du måste utgå från något för att
132
+ kunna beskriva systemet. Skriv "beskriven utifrån X", aldrig "vi väljer X" — valet
133
+ är inte ditt.
134
+
135
+ Skälet till punkt 2: den som poängsätter posten har inte repot och kan inte se om
136
+ alternativen kostar olika. Utan din rad måste den anta det värsta och stoppa posten,
137
+ även när båda vägarna är samma arbete. Det är den vanligaste orsaken till att en post
138
+ studsar tillbaka till specen i onödan.
139
+
96
140
  Sista raden är den viktigaste. Förutsätter specen något som inte stämmer i systemet är det ett fel som ska tillbaka till specen, inte något som ska klassas.
97
141
 
98
142
  ## Vad du aldrig gör
@@ -0,0 +1,285 @@
1
+ ---
2
+ name: request-score
3
+ description: Sätter poäng på en spec plus påverkansanalys genom att jämföra uppdraget mot en skala av sjutton referensuppdrag. Använd alltid när användaren vill "sätt poäng", "poängsätt", "kör poängen", "vad landar det här på", "var på skalan ligger det", eller när en spec har en påverkansanalys och ska prissättas. Används även som grind på en fullständig implementationsplan före första kodraden — "kör guarden", "stämmer poängen fortfarande". Använd den även när poängsättning bara antyds, t.ex. när någon klistrar in en spec med underlag och frågar hur stort det är. Output är JSON med poäng och belägg, aldrig ett pris.
4
+ ---
5
+
6
+ # request-score
7
+
8
+ Du sätter poäng på en post genom att jämföra den mot referensuppdragen i
9
+ `references/poangskala.md`. Läs skalan innan du poängsätter första posten i en session.
10
+
11
+ **Går filen inte att läsa: poängsätt inte.** Säg att `references/poangskala.md` saknas
12
+ och att skillen inte kan köras utan den. Sätt aldrig en poäng på minnet av hur skalan
13
+ brukar se ut. Skalan ändras när nya referensuppdrag läggs till, och en poäng satt på
14
+ en gammal skala ser likadan ut som en satt på rätt skala.
15
+
16
+ **Du sätter aldrig pris.** Poäng blir pris genom en fast processavgift plus poängen
17
+ gånger en konverteringsfaktor, och det räknas någon annanstans. Ligger prislistan i
18
+ sammanhanget: använd den inte och säg till. Vet du vad poängen kostar drar du mot den
19
+ poäng som känns rimlig att ta betalt för, och då mäter du inte längre uppdraget.
20
+
21
+ **Du skriver aldrig underlaget.** Saknas spec eller påverkansanalys, avbryt med
22
+ `INPUT_SAKNAS`. Skriv inte en påverkansanalys själv — då jämför du din egen text mot
23
+ skalan, och belägget blir teater. `request-assess-impact` gör det, helst i en egen
24
+ körning.
25
+
26
+ **Ge inte dig själv repot i den här körningen.** Har du det börjar du verifiera saker
27
+ vid sidan av underlaget, och då poängsätter du inte längre det du fått. Saknas något i
28
+ underlaget är svaret låg konfidens, inte en egen efterforskning.
29
+
30
+ ## Stoppfrågorna först
31
+
32
+ Poäng sätts bara på en post som går att bygga. Fyra utfall stoppar poängsättningen:
33
+
34
+ | Utfall | När | Vad som ska hända |
35
+ | -------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
36
+ | `KONCEPT` | Formen är okänd — designfråga, oavgjort vägval, flera lösningsvägar utan beslut | Egen post som levererar ett förslag och en poäng på bygget |
37
+ | `DIAGNOS` | Orsaken är okänd och går att undersöka. Gäller framför allt fel där ingen vet varför något är trasigt | Tidsboxad undersökning som levererar besked och en poängsatt åtgärd |
38
+ | `TILLBAKA_TILL_SPEC` | Specen lämnar ett utfallsval obesvarat **och valet ändrar arbetsmängden** | Specen kompletteras, sedan ny körning |
39
+ | `DELA_UPP` | Posten går inte att ringa in på egen hand — se nedan | Ny analysrunda som pekar ut delarna, sedan en körning per del |
40
+
41
+ ### När posten inte går att ringa in
42
+
43
+ Två lägen ger `DELA_UPP`:
44
+
45
+ - Du hittar inget tak, alltså inget referensuppdrag som posten säkert är mindre än.
46
+ - Golv och tak ligger långt isär och inget referensuppdrag finns emellan, alltså det
47
+ läge som annars bara hade gett `confidence: lag`.
48
+
49
+ Skriv i `notes` vilket av lägena det var, och vilka delar av påverkansanalysen som
50
+ pekar mot att posten rymmer flera skilda arbeten. **Peka inte ut delarna själv.** Du
51
+ får varken läsa repot eller skriva underlaget, och att avgöra vad som går att leverera
52
+ för sig kräver båda. `request-assess-impact` gör uppdelningen; du säger bara att den
53
+ behövs.
54
+
55
+ Delningen görs ett steg. Kommer en del tillbaka och inte går att ringa in heller, blir
56
+ den `OVER_SKALAN` och offereras — den delas inte vidare.
57
+
58
+ **En post som redan har poängsatta delar poängsätts aldrig som helhet.** Föräldern bär
59
+ summan av sina delar, inte en egen siffra.
60
+
61
+ ### Obesvarade utfallsval
62
+
63
+ Påverkansanalysens granskarblock listar under _Obesvarade utfallsval_ de val specen
64
+ lämnat öppna, var och en med alternativen, om de skiljer sig i vad som måste byggas,
65
+ och vilket alternativ analysen är skriven utifrån.
66
+
67
+ En post stoppas nästan aldrig för ett sådant val. Gå igenom dem i fyra steg.
68
+
69
+ **Steg 1 — stryk det som inte löser problemet.** Läs specens rubrik _Problem_. Ett
70
+ alternativ som inte löser det problemet är inget alternativ, det är en otillräcklig
71
+ lösning, och det ska varken väljas eller presenteras. Att prissätta det vore att sälja
72
+ något som inte gör det kunden bett om.
73
+
74
+ Testet är specens egna ord, inte din uppfattning om vad kunden borde vilja. Löser
75
+ alternativet problemet som det är beskrivet? Delvis räcker inte.
76
+
77
+ **Steg 2 — återstår ingenting, stoppa.** Då motsäger specen sig själv: problemet går
78
+ inte att lösa inom det den beskriver. Sätt `outcome` till `TILLBAKA_TILL_SPEC` och
79
+ skriv i `customer_rationale` vad som behöver avgöras, i kundens språk.
80
+
81
+ **Steg 3 — ändrar valet arbetsmängden?** Analysens rad om huruvida alternativen skiljer
82
+ sig i vad som måste byggas är svaret. Gör de inte det är valet en **förutsättning**:
83
+ välj det rimligaste, skriv in det i `assumptions_stated` med vad som valts och varför,
84
+ och poängsätt vidare. Kunden godkänner förutsättningen tillsammans med spec och pris.
85
+
86
+ Typexempel på förutsättning: vilket av två befintliga sätt att visa att något laddar som
87
+ används, vilken av två likvärdiga formuleringar en text får, var i en befintlig vy ett
88
+ fält placeras.
89
+
90
+ **Steg 4 — flera alternativ som alla löser problemet.** Poängsätt **det enklaste** som
91
+ postens poäng. Poängsätt varje övrigt alternativ för sig, med egen inringning, och lägg
92
+ dem i `alternatives`. De går till kund som alternativa lösningar med egna priser.
93
+
94
+ Saknas analysens rad om arbetsmängd — den kan vara skriven före den regeln — avgör
95
+ själv utifrån vad analysen beskriver, och skriv i `notes` att raden saknades.
96
+
97
+ Varje alternativ ringas in precis som huvudalternativet. Ett alternativ med en poäng
98
+ men utan inringning är lika ogiltigt som en post utan.
99
+
100
+ Ett antagande som inte går att undersöka är inget stopp. Det blir ett villkor på posten:
101
+ "bygger på att X; faller X tidigt avbryts posten utan debitering". Osäkerhet löses med
102
+ kunskap eller villkor, aldrig med högre poäng för säkerhets skull.
103
+
104
+ ## Metoden: ringa in, aldrig skatta fritt
105
+
106
+ En siffra som kommer direkt ur en helhetsbedömning går inte att granska och blir olika
107
+ mellan två körningar. Ringa in i stället, i fyra steg. Alla fyra redovisas i utfallet.
108
+
109
+ **Steg 1 — hitta ett golv.** Läs skalan och hitta ett referensuppdrag som posten
110
+ **säkert är större än**. Skriv ut vilket och varför.
111
+
112
+ **Steg 2 — hitta ett tak.** Hitta ett referensuppdrag som posten **säkert är mindre
113
+ än**. Skriv ut vilket och varför.
114
+
115
+ Får du inget golv: posten är mindre än A1 och får A1:s poäng. Skalans nedre ände är
116
+ ändå dominerad av den fasta processavgiften.
117
+
118
+ Får du inget tak: posten är större än skalans högsta referensuppdrag. Sätt då `DELA_UPP`
119
+ och ingen poäng, så att posten först prövas i delar. Är detta redan en del av en delad
120
+ post sätter du i stället `OVER_SKALAN` — underlaget för den nivån är för tunt för att
121
+ jämföra mot, och posten ska offereras separat.
122
+
123
+ **Steg 3 — snäva in.** Gå igenom referensuppdragen mellan golv och tak. För varje: är
124
+ posten större eller mindre, och på vilken grund? Fortsätt tills du har två
125
+ angränsande referensuppdrag som posten ligger mellan.
126
+
127
+ **Steg 4 — placera.** Sätt poängen mellan de två. Ligger posten närmare det ena, säg
128
+ vilket och varför. Landar den på samma nivå som ett referensuppdrag, ge den det
129
+ ankarets poäng rakt av.
130
+
131
+ **Avrunda till skalans upplösning.** Under 3 poäng till närmaste 0,25, alltså 0,25,
132
+ 0,5, 0,75, 1, 1,25 och så vidare. Från 3 poäng och uppåt till närmaste hela poäng. En
133
+ finare siffra än så låtsas om en noggrannhet som inte finns: två oberoende bedömare
134
+ hamnar i olika klass i en femtedel av fallen. Avrundningen är mekanisk och sker efter
135
+ placeringen, aldrig genom att du väljer ett rundare tal medan du jämför.
136
+
137
+ ## Vad du jämför på
138
+
139
+ Jämför på vad ändringen **drar med sig**, inte på hur mycket kod som skrivs. Det som
140
+ skiljer referensuppdragen åt, i fallande ordning efter hur mycket de väger:
141
+
142
+ **Om en människa måste verifiera för hand, och i hur många fall.** Det är den tydligaste
143
+ skillnaden genom hela skalan. Ett uppdrag som täcks av automatiska tester ligger lågt.
144
+ Ett som kräver genomgång i flera scenarier, på flera enheter eller i flera miljöer
145
+ ligger högt.
146
+
147
+ **Förutsätt alltid att automatiska tester finns**, även när påverkansanalysen säger att
148
+ repot saknar testsvit. Räkna bara det manuella arbete som återstår när en fungerande
149
+ testsvit gjort sitt: det som kräver en människa av sin natur, alltså kontroll mot en
150
+ extern tjänsts testläge, visuell granskning, eller genomgång på flera enheter eller i
151
+ flera miljöer. Saknas testsviten är det inte en rabatt: täckningen byggs som en egen
152
+ post före ändringen och poängsätts för sig. Skriv en rad under `notes` om vilken yta
153
+ som saknar täckning, så att den posten går att skapa.
154
+
155
+ **Vad som händer om det blir fel.** Felaktig betalning, felaktig åtkomst till någon
156
+ annans uppgifter, förlorad data eller bruten befintlig funktion väger tungt. Fel text
157
+ eller fel sorteringsordning väger lätt.
158
+
159
+ **Om ändringen rör kod som flera kunder använder.** Då tillkommer migrering per
160
+ installation och en risk som når utanför beställaren. Se A9 och A10.
161
+
162
+ **Om ett externt system berörs**, och om kopplingen är ny eller befintlig.
163
+
164
+ **Om en sekvens som användaren passerar i ordning ändras**, och hur många steg.
165
+
166
+ **Om något ska formges som inte finns i systemet i dag.**
167
+
168
+ Antal filer och antal rader är **inte** jämförelsegrunder. Se noten sist i skalan: två
169
+ referensuppdrag med samma poäng skiljer sig med en tiopotens i filantal.
170
+
171
+ ## Typen
172
+
173
+ Använd typen för att hitta jämförbara referensuppdrag snabbare, ingenting annat. Ett
174
+ uppdrag av en typ som saknas i skalan poängsätts mot de närmaste i storlek, inte mot
175
+ någon påhittad justering. Skriv ut vilken typ du bedömt posten som — den loggas för att
176
+ kunna mäta i efterhand om någon typ hamnar systematiskt fel.
177
+
178
+ ## Grindläget
179
+
180
+ Körs skillen på en fullständig implementationsplan efter kundens godkännande, sätt
181
+ `mode` till `guard`. Samma skala, samma metod.
182
+
183
+ Endast ny strukturell information ur fullplanen får flytta poängen, aldrig en
184
+ omvärdering av samma fakta. Rapportera vilket jämförelsesteg som ändrades och citatet ur
185
+ fullplanen som orsakade det. Grinden är symmetrisk — en lägre poäng rapporteras lika
186
+ tydligt som en högre.
187
+
188
+ Bygger fullplanen ett annat vägval än det som såldes är det en händelse att rapportera
189
+ även när poängen blir densamma.
190
+
191
+ ## Output
192
+
193
+ ```json
194
+ {
195
+ "request_id": "",
196
+ "mode": "pricing | guard",
197
+ "outcome": "POANG | KONCEPT | DIAGNOS | TILLBAKA_TILL_SPEC | DELA_UPP | OVER_SKALAN | INPUT_SAKNAS",
198
+ "score": null,
199
+ "type": "",
200
+ "bracket": {
201
+ "floor": { "anchor": "", "score": null, "why": "" },
202
+ "ceiling": { "anchor": "", "score": null, "why": "" },
203
+ "between": ["", ""],
204
+ "steps": []
205
+ },
206
+ "drivers": [],
207
+ "confidence": "hog | medel | lag",
208
+ "conditions": [],
209
+ "assumptions_stated": [],
210
+ "alternatives": [],
211
+ "dependencies": [],
212
+ "notes": [],
213
+ "customer_rationale": "",
214
+ "guard_diff": []
215
+ }
216
+ ```
217
+
218
+ `score` sätts bara när `outcome` är `POANG`, och alltid på skalans upplösning: steg om
219
+ 0,25 under 3 poäng, hela poäng från 3 och uppåt.
220
+
221
+ `bracket.steps` är jämförelserna från steg 3, en per referensuppdrag du vägde mot, var
222
+ och en med ankarets id, om posten är större eller mindre, och på vilken grund. Det är
223
+ det som gör poängen granskbar. En poäng utan inringning är inte ett giltigt utfall.
224
+
225
+ `drivers` är de två eller tre faktorer som mest avgjorde placeringen, med citat ur
226
+ påverkansanalysen. Citat, inte parafras.
227
+
228
+ `assumptions_stated` är de utfallsval specen lämnat öppna som **inte** ändrar
229
+ arbetsmängden: vad du valt, och varför. De går med förslaget till kund som
230
+ förutsättningar och godkänns tillsammans med specen och priset.
231
+
232
+ `alternatives` är de övriga vägar som också löser problemet, en post var med `label`
233
+ (vad alternativet innebär, i kundens språk), `score`, `bracket` och `adds` (vad som
234
+ tillkommer jämfört med det valda). Alternativ som inte löser specens problem hör inte
235
+ hit och ska utelämnas helt. Är listan tom fanns bara en väg som löser problemet.
236
+
237
+ `notes` är iakttagelser som inte hör till poängen men som någon bör känna till, till
238
+ exempel att ett repo saknar testsvit.
239
+
240
+ **Ett antagande som analysen flaggat som overifierat ska alltid ut.** Går det att
241
+ undersöka hör det till `notes` med hur det undersöks. Går det inte att undersöka blir
242
+ det ett villkor i `conditions`, formulerat som "bygger på att X; faller X tidigt
243
+ avbryts posten utan debitering". Ett overifierat antagande som varken syns i det ena
244
+ eller det andra försvinner tyst, och då bär Haus en risk ingen beslutat om.
245
+
246
+ `confidence` sätts till `lag` när posten inte liknar något referensuppdrag, när golv och
247
+ tak ligger långt isär och inget referensuppdrag finns emellan, eller när
248
+ påverkansanalysen anger att något inte kunnat verifieras och det påverkar placeringen.
249
+ Låg konfidens är ett giltigt utfall och ska inte döljas genom att välja en poäng som
250
+ ser rimlig ut.
251
+
252
+ ### Motiveringen till kund
253
+
254
+ `customer_rationale` skrivs i kundens språk och beskriver arbetet som ska göras, inte
255
+ hur skalan räknade. Nämn aldrig poäng, referensuppdrag, ankare eller skalan. Kunden ska
256
+ kunna invända mot omfattningen, inte mot modellen.
257
+
258
+ Finns alternativ: beskriv dem sist, som egna vägar med var sitt pris. En mening var
259
+ räcker — vad kunden får i stället, och vad som tillkommer. Priset räknas ur poängen
260
+ någon annanstans; skriv aldrig ut poängen.
261
+
262
+ Den ska vara förenlig med specens _Ingår inte_. Motiverar du poängen med arbete som
263
+ specen uttryckligen undantar har du poängsatt fel post.
264
+
265
+ ## Efter körning
266
+
267
+ Granskaren läser inringningen, inte slutpoängen. En rimlig poäng med fel jämförelser är
268
+ en felkalibrering som råkade landa rätt, och den kommer tillbaka i en post där den inte
269
+ gör det.
270
+
271
+ Korrigeringar går till skalan, alltså till referensuppdragen eller till den här
272
+ instruktionen, och till kalibreringsloggen. Aldrig till priset på den enskilda posten.
273
+
274
+ Visar det sig återkommande att poster av ett visst slag är svåra att placera, är det ett
275
+ tecken på att skalan har ett hål och behöver ett referensuppdrag till. Rapportera det.
276
+
277
+ ## Acceptanstestet — granskarens sak, inte din
278
+
279
+ Skillen verifieras genom att köras mot poster med känt utfall. Det underlaget ligger med
280
+ flit **inte** i den här mappen — en körning som kan läsa facit kan inte längre testas. Du
281
+ ska varken leta efter det eller rapportera det som saknat.
282
+
283
+ För granskaren gäller två mått: att två oberoende körningar av samma post ger samma
284
+ poäng, och att inringningen håller vid granskning. Det första är det som avgör om
285
+ kunderna får samma pris för samma arbete.