@haus-tech/haus-workflow 6.0.0 → 6.2.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.
@@ -117,8 +117,11 @@ After the command completes, follow the relevant post-run steps below.
117
117
  first; user content preserved), then re-runs setup from scratch. Say what it keeps:
118
118
  an item the user edited and its lock row, and a `.claude/rules/haus.md` haus did not
119
119
  write. `haus undo --force` removes those too, after a backup, except a
120
- `.claude/rules/haus.md` that is a link or a folder, which stays either way; mention
121
- it, but never add `--force` yourself. On confirm run
120
+ `.claude/rules/haus.md` that is a link or a folder, which stays either way. In workspace
121
+ scope it also keeps a linked copy edited at the workspace root and a
122
+ `.haus-workflow/WORKSPACE.md` haus does not own; `haus workspace undo --force` removes
123
+ those too, with what each member keeps, after a backup, a link or a folder again
124
+ excepted. Mention it, but never add `--force` yourself. On confirm run
122
125
  `haus undo --yes` (project) or `haus workspace undo --yes` (workspace), then
123
126
  continue by the scope already detected in Step 1.5 — workspace: step 1 below
124
127
  (deep-docs ask, `haus workspace setup --write`, per-member documentation pass,
@@ -222,7 +222,7 @@ stay there.
222
222
  finds no tests at all. The skeleton below fills in the real body once the runner
223
223
  config is decided; what matters here is that the key exists first.
224
224
  4. Write: `haus apply --write`. It reads the profile: `.haus-workflow/WORKFLOW.md` now
225
- starts with `# PoC Workflow Standard`, and there is no `.github/workflows/haus-gate.yml`.
225
+ starts with `# PoC Workflow Standard`, and there is no haus gate (`lefthook.yml`).
226
226
 
227
227
  Fast path: the chained init already ran `haus apply --write` inside Phase 3, before this
228
228
  phase's `"manus"` script exists, so `.haus-workflow/workflow-config.md`'s "Run the manus"
@@ -236,11 +236,14 @@ its stop (`designa` step 5), with no runs row, since the design is no dispatch.
236
236
  later): an item never goes a third spec round on the same choice. Older archives
237
237
  lack the field and are read as `0`.
238
238
  - `coverage_post` mirrors the score envelope's optional `coverage_post: { yta, omfattning }`
239
- (ADR-0119). `archive.mjs add-run` copies the field from the
240
- latest main pricing file (no suffix) that carries it; a `-run2` or `-avvisad`
241
- file never sets it, whatever the stamping order. The skeleton (`ny-korning`/`ny-del`) sets
242
- it to `null`. `bluf` shows `täckningspost: <yta>` in the `anm` column when
243
- the field is set.
239
+ (ADR-0119) from the score pricing file that counts, the same file `bluf` reads the score
240
+ from: the round's main file, or the file a ruling's `galler_fil` names (ADR-0159).
241
+ `archive.mjs` recomputes it on `add-run` of a score pricing file, on `utslag` on the
242
+ score axis and on `set-status priced`, and sets it to `null` when that file has no
243
+ post or is not written yet; `respec` clears it. A `-run2` file sets it exactly when a
244
+ ruling makes it the counting file. Never write the field by hand. The skeleton
245
+ (`ny-korning`/`ny-del`) sets it to `null`. `bluf` shows `täckningspost: <yta>` in the
246
+ `anm` column when the field is set.
244
247
  - `design` is written ONLY by `archive.mjs add-design` and reset ONLY by
245
248
  `archive.mjs respec`: `{ "path": "<absolute path to the canonical file in the repo>",
246
249
  "file": "design.md", "sha256": "<marker-stripped hash of design.md, identical to the canonical file>",
@@ -741,15 +744,14 @@ dispatches no agent and runs no check in this leg. `--repo-rot` is the checkout
741
744
  code the assessor reads (the session's cwd; a worktree is fine) and `--arkiv` is
742
745
  `ARKIVROT:` from `rot`. The starter writes the running receipt (`archive.mjs korning-start`,
743
746
  `state.korning`) before the process starts and clears it after
744
- (`korning-slut`), skips an item whose receipt carries a living pid (`pågår`), and stops
745
- starting items when the batch's cost reaches `max_usd_per_batch` in
746
- `.haus-workflow/request.json` (default 100 USD; `--max-usd` overrides one run), counting an
747
- item in flight at the average of the finished ones. Read its
747
+ (`korning-slut`), and skips an item whose receipt carries a living pid (`pågår`). There is
748
+ no spend cap: every item that passes the gate starts, and the cost shows instead, as the
749
+ USD per item in the table and the total spent on the line under it (ADR-0162). Read its
748
750
  final table (`| post | status | poäng | utfall | wall | USD |`), paste it under the BLUF
749
751
  in step 8, and route every row by its status: `priced` goes on to step 8, `stopp` and
750
752
  `väntar på dom` take the stop and OLIKA routing below, `ofärdig` shows the reason from
751
- the table and `<post>/korningar/<körning>/stderr.log`, `grind` and `tak nått` show the
752
- reason and stop, and a `pågår` row is left alone.
753
+ the table and `<post>/korningar/<körning>/stderr.log`, `grind` shows the
754
+ reason and stops, and a `pågår` row is left alone.
753
755
  `bedom.mjs` passes the board's design mode on to `korning.mjs` as `--design-mode`
754
756
  (`design_mode` in `.haus-workflow/request.json`; `interview` and an absent key read as
755
757
  `intervju`; the flag overrides one run). In draft mode the process drafts a missing design
@@ -2012,22 +2014,20 @@ reference files whose rules it mirrors and WARNS when a catalog bump has changed
2012
2014
  and skipped. `--arkiv` is the archive root; omitted, it is derived from
2013
2015
  `--repo-rot`'s git common dir (ADR-0154). Exit 0 when no run became
2014
2016
  `ofärdig`, 1 otherwise, 2 on a usage error.
2015
- - `scripts/bedom.mjs --repo-rot <abs> [--arkiv <abs>] --post <gid|gid/del-N>... [--parallellt 2] [--max-usd <n>] [--claude <kommando>] [--om] [--sprak <svenska|engelska>]`:
2017
+ - `scripts/bedom.mjs --repo-rot <abs> [--arkiv <abs>] --post <gid|gid/del-N>... [--parallellt 2] [--claude <kommando>] [--om] [--sprak <svenska|engelska>]`:
2016
2018
  production's starter (see `bedöm`), one `korning.mjs --post` process per item, two at a
2017
2019
  time. `--post` is relative to the archive root (`--arkiv`, or derived from `--repo-rot`'s
2018
2020
  git common dir as in `svep.mjs`); an absolute `--post` is a usage error. Per item: the
2019
2021
  receipt via `archive.mjs korning-start`/`korning-slut` (the latter always, also when the
2020
2022
  run fails), the run directory `<post>/korningar/<stämpel>/` with `stderr.log`,
2021
- `matning.json` and the raw replies under `steg/`, the cap from `request.json`
2022
- (`max_usd_per_batch`, default 100 USD, `--max-usd` overrides; once the finished items'
2023
- USD, plus each item in flight at the average of the finished ones, reaches it no further
2024
- item starts, `tak nått`, and a running item is never killed), `--startare <its own pid>` on
2023
+ `matning.json` and the raw replies under `steg/`, no spend cap (every item that passes
2024
+ the gate starts, ADR-0162), `--startare <its own pid>` on
2025
2025
  every `korning.mjs` it starts, SIGINT and SIGTERM both killing the running items and
2026
2026
  clearing their receipts (exit 130),
2027
2027
  and the final table `| post | status | poäng | utfall | wall | USD |` in argument order
2028
- with the line `tak: <n> USD, använt: <m> USD`. Exit 0 when every item ended in a result
2029
- (`priced`, `stopp`, `väntar på dom`), 1 on `ofärdig`, `pågår`, `grind` or `tak nått`, 2
2030
- on a usage error (ADR-0155).
2028
+ with the line `använt: <m> USD`. Exit 0 when every item ended in a result
2029
+ (`priced`, `stopp`, `väntar på dom`), 1 on `ofärdig`, `pågår` or `grind`, 2
2030
+ on a usage error (ADR-0155, ADR-0162).
2031
2031
  `bedom.mjs` takes `--design-mode draft|intervju` and passes it to every `korning.mjs` it
2032
2032
  starts; without the flag the value is `request.json`'s `design_mode` (`interview` is read
2033
2033
  as `intervju`), and without that key `intervju` (ADR-0156).
@@ -998,23 +998,21 @@ if (cmd === 'add-run') {
998
998
  const rubrik = forstaRubrik(readFileSync(path.join(katalog, 'spec.md'), 'utf8'))
999
999
  if (rubrik) titelFalt = { title: rubrik, slug: slugAv(rubrik) }
1000
1000
  }
1001
- // Täckningspost (ADR-0119, #508): senaste HUVUDFILEN som bär fältet
1002
- // vinner, precis som titeln följer spec.md på disk. -run2 och -avvisad
1003
- // sätter den aldrig, oavsett stämplingsordning: under LIKA_TOLERANS blir
1004
- // den ersatta körningen -run2 och stämplas ofta efter huvudfilen, och
1005
- // dess yta nådde då state, bluf och request-syncs täckningsdeluppgift
1006
- // (Convendum 1219234151224225, 2026-10-14). Bara poängaxelns PRISfil
1007
- // (GALLANDE_RE, samma mönster som maxN läser): en guardfil har en egen
1008
- // N-sekvens och dess utfall synkas aldrig (SKILL.md, arkivträdet), så
1009
- // den får inte ersätta den prissatta postens täckning. En huvudfil som
1010
- // (ännu) inte finns är inte add-runs jobb att fälla, den stämplas ofta
1011
- // innan agenten skrivit filen. Finns den redan är trasig JSON eller ett
1012
- // icke-objekt ett arkivfel som andra anropare i den här filen, fångas
1013
- // inte här: readPricingFileOrThrow kastar oskyddad.
1014
- if (GALLANDE_RE('score').test(file) && existsSync(path.join(katalog, file))) {
1015
- const data = readPricingFileOrThrow(file, katalog)
1016
- if (data.coverage_post !== null && typeof data.coverage_post === 'object')
1017
- titelFalt = { ...titelFalt, coverage_post: data.coverage_post }
1001
+ // Täckningspost (ADR-0159, ändrar ADR-0119 beslut 4; #763, #687): fältet
1002
+ // härleds ur den poängfil som GÄLLER efter den här raden, aldrig ur filen
1003
+ // som råkar stämplas. Bara en poängprisfil (huvudfil, -run2 eller
1004
+ // -avvisad) räknar om: en guardfil har en egen N-sekvens och synkas
1005
+ // aldrig (SKILL.md, arkivträdet), och spec.md/impact.md får inte väcka en
1006
+ // post som respec redan rensat. En -run2 eller -avvisad från en NYARE
1007
+ // runda än den gällande (huvudfilen är inte stämplad än) räknar inte om
1008
+ // heller: den rundan pågår och dess gällande fil finns inte än, så fältet
1009
+ // behåller sitt värde (null efter en respec) tills rundans huvudfil
1010
+ // stämplas. Se gallandeTackning för regeln.
1011
+ const poangfil = /^score-pricing-v(\d+)(-[a-z0-9-]+)?\.json$/.exec(file)
1012
+ if (poangfil) {
1013
+ const efter = { ...state, runs: [...runs, row] }
1014
+ if (Number(poangfil[1]) <= maxN(efter.runs, 'score'))
1015
+ titelFalt = { ...titelFalt, coverage_post: gallandeTackning(efter) }
1018
1016
  }
1019
1017
  // ADR-0153 beslut 3: brädkvittot betyder att den gällande prisrundan
1020
1018
  // ligger på brädan. En ny rundas huvudfil gör ett tidigare kvitto
@@ -1239,6 +1237,13 @@ if (cmd === 'add-run') {
1239
1237
  }
1240
1238
  }
1241
1239
  state.granskarutslag = [...(state.granskarutslag ?? []), row]
1240
+ // ADR-0159: en dom på poängaxeln kan byta den gällande filen, så
1241
+ // täckningsposten räknas om ur den. En trasig gällande fil fäller domen
1242
+ // (exit 2) innan något skrivs.
1243
+ // En dom om ett äldre varv byter aldrig den gällande filen (liftingUtslag
1244
+ // kräver n === maxN) och räknar därför inte om.
1245
+ if (row.typ === 'viktning' && row.axel === 'score' && row.n === maxN(state.runs ?? [], 'score'))
1246
+ state.coverage_post = gallandeTackning(state)
1242
1247
  writeStateOrThrow(state)
1243
1248
  rowCount = state.granskarutslag.length
1244
1249
  } catch (e) {
@@ -1273,6 +1278,10 @@ if (cmd === 'add-run') {
1273
1278
  }
1274
1279
  if (stopp.length === 0) {
1275
1280
  state.status = status
1281
+ // ADR-0159: priced är steget före synken, så täckningsposten räknas om
1282
+ // här också. En huvudfil som stämplades innan den skrevs får då sin
1283
+ // post även när ingen senare stämpling kom.
1284
+ if (status === 'priced') state.coverage_post = gallandeTackning(state)
1276
1285
  writeStateOrThrow(state)
1277
1286
  }
1278
1287
  } catch (e) {
@@ -1989,6 +1998,12 @@ if (cmd === 'add-run') {
1989
1998
  nytt.board = null
1990
1999
  antal++
1991
2000
  }
2001
+ // ADR-0159: täckningsposten hörde till en prisrunda som inte längre
2002
+ // gäller; nästa poängrunda räknar fram den på nytt.
2003
+ if (nytt.coverage_post !== null && nytt.coverage_post !== undefined) {
2004
+ nytt.coverage_post = null
2005
+ antal++
2006
+ }
1992
2007
  // Ett dött kvitto (levande vägrades ovan) hör till en körning som inte
1993
2008
  // finns; en ny spec nollar det.
1994
2009
  if (nytt.korning !== null && nytt.korning !== undefined) {
@@ -3238,6 +3253,22 @@ function gallandeDokumentFil(state, typ, n) {
3238
3253
  return liftingUtslag(state, typ, n)?.galler_fil ?? `${typ}-pricing-v${n}.json`
3239
3254
  }
3240
3255
 
3256
+ /** Täckningsposten (ADR-0159, ändrar ADR-0119 beslut 4): `coverage_post` ur
3257
+ * den poängfil som gäller, `gallandeDokumentFil(state, 'score', maxN)`, så
3258
+ * att granskarens `galler_fil` styr på samma sätt som för poäng och utfall.
3259
+ * null när ingen poängrunda finns, när den gällande filen ännu inte skrivits
3260
+ * (den stämplas ofta före agentens fil; nästa omräkning fyller i den) och
3261
+ * när filen saknar ett objekt i fältet. Finns filen men är trasig kastar
3262
+ * readPricingFileOrThrow ArkivFel, samma arkivfel som andra läsare i filen. */
3263
+ function gallandeTackning(state, d = katalog) {
3264
+ const n = maxN(state.runs ?? [], 'score')
3265
+ if (n === 0) return null
3266
+ const fil = gallandeDokumentFil(state, 'score', n)
3267
+ if (!existsSync(path.join(d, fil))) return null
3268
+ const cp = readPricingFileOrThrow(fil, d).coverage_post
3269
+ return cp !== null && typeof cp === 'object' && !Array.isArray(cp) ? cp : null
3270
+ }
3271
+
3241
3272
  /** Läser en gällande pricing-fil (classification eller score) ur katalogen
3242
3273
  * och returnerar den PARSADE, validerade datan. Arkivet PÅSTÅR via sin
3243
3274
  * runs-rad (eller ett viktningsutslags `galler_fil`) att filen finns och är
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Startaren för produktionens prisning (ADR-0155 beslut 1, 3, 4 och 5):
3
+ * Startaren för produktionens prisning (ADR-0155 beslut 1, 3 och 5; beslut 4
4
+ * ersatt av ADR-0162):
4
5
  * `bedöm`:s steg 3 till 7 körs som EN `korning.mjs`-process per post i
5
6
  * produktionsläget (`--post`), startad härifrån, så huvudkonversationen
6
7
  * dispatchar ingen agent för prisbenet. Huvudkonversationen behåller grinden,
@@ -9,7 +10,7 @@
9
10
  *
10
11
  * Användning:
11
12
  * node bedom.mjs --repo-rot <abs> [--arkiv <abs>] --post <gid|gid/del-N> [--post ...]...
12
- * [--parallellt 2] [--max-usd <n>] [--claude <kommando>] [--om]
13
+ * [--parallellt 2] [--claude <kommando>] [--om]
13
14
  * [--sprak <svenska|engelska>] [--design-mode <draft|intervju>]
14
15
  *
15
16
  * `--post` är relativ arkivroten: `--arkiv` när den ges (ARKIVROT: från
@@ -20,7 +21,7 @@
20
21
  * Per post, i ordning: förkontroll (state.json läsbar, ingen experimentkatalog,
21
22
  * ingen splitrot med `del-N/` under sig, `approval` satt, inget stående
22
23
  * `stopp`, inte redan `priced`/`guard_run` utan `--om`; annars raden `grind`
23
- * och inget startas), taket, körningskatalogen
24
+ * och inget startas), körningskatalogen
24
25
  * `<post>/korningar/<YYYYMMDDTHHMMSS>/`, kvittot `archive.mjs korning-start`
25
26
  * (en levande pid ger raden `pågår` och den tomma katalogen tas bort),
26
27
  * `korning.mjs` med `stderr.log` och `matning.json` i körningskatalogen och
@@ -28,22 +29,17 @@
28
29
  * `korning.json`:s `total_cost_usd`, `archive.mjs sparr` och statusen. Kvittot
29
30
  * rensas alltid med `archive.mjs korning-slut` i en finally.
30
31
  *
31
- * Taket (beslut 4): `max_usd_per_batch` i `.haus-workflow/request.json`
32
- * (`<arkiv>/../request.json`), 100 USD när nyckeln saknas, `--max-usd` vinner
33
- * för en körning. Före varje poststart jämförs summan mot taket: de klara
34
- * posterna med sin USD plus varje post i flykt till de klaras snittkostnad
35
- * (0 så länge ingen är klar). När summan nått taket startas inga fler poster
36
- * (raden `tak nått`), och ingen pågående körning dödas. Två poster som startar
37
- * samtidigt innan någon är klar passerar båda kontrollen: det är accepterat,
38
- * taket stoppar nästa start och aldrig en körning som pågår.
32
+ * Inget tak (ADR-0162 ersätter ADR-0155 beslut 4): varje post som klarar
33
+ * förkontrollen startas, oavsett vad batchen kostat. Kostnaden syns i stället:
34
+ * USD per post i tabellen och summan på sista raden.
39
35
  *
40
36
  * stdout under körningen: `START <post>`, `KLAR <post> <status>`,
41
- * `GRIND <post>: <orsak>`, `PÅGÅR <post> (pid N)`, `TAK <post>`; till sist
37
+ * `GRIND <post>: <orsak>`, `PÅGÅR <post> (pid N)`; till sist
42
38
  * tabellen `| post | status | poäng | utfall | wall | USD |` och raden
43
- * `tak: <n> USD, använt: <m.mm> USD`.
39
+ * `använt: <m.mm> USD`.
44
40
  *
45
41
  * Exit 0 = varje rad är `priced`, `stopp` eller `väntar på dom`; 1 = minst en
46
- * rad är `ofärdig`, `pågår`, `grind` eller `tak nått`; 2 = användningsfel.
42
+ * rad är `ofärdig`, `pågår` eller `grind`; 2 = användningsfel.
47
43
  * SIGINT och SIGTERM skickar SIGTERM till varje pågående körning, skriver
48
44
  * matning.json med signalen, rensar varje kvitto med korning-slut och avslutar
49
45
  * med 130. Ett oväntat kast i huvudloopen skrivs på stderr och ger exit 2.
@@ -58,30 +54,20 @@ const HAR = path.dirname(fileURLToPath(import.meta.url))
58
54
  const ARCHIVE = path.join(HAR, 'archive.mjs')
59
55
  const KORNING = path.join(HAR, 'korning.mjs')
60
56
 
61
- export const STANDARD_TAK_USD = 100
62
57
  const STANDARD_PARALLELLT = 2
63
58
  const SIGNAL_EXIT = 130
64
59
  /** Signalerna som avbryter batchen; samma hanterare för båda. */
65
60
  export const AVBROTTSSIGNALER = ['SIGINT', 'SIGTERM']
66
61
  const SPRAKEN = ['svenska', 'engelska']
67
62
  const DESIGNLAGEN = ['draft', 'intervju']
68
- const VARDEFLAGGOR = [
69
- 'repo-rot',
70
- 'arkiv',
71
- 'post',
72
- 'parallellt',
73
- 'max-usd',
74
- 'claude',
75
- 'sprak',
76
- 'design-mode',
77
- ]
63
+ const VARDEFLAGGOR = ['repo-rot', 'arkiv', 'post', 'parallellt', 'claude', 'sprak', 'design-mode']
78
64
  const BOOLFLAGGOR = ['om']
79
65
  /** Statusar som är ett resultat: exit 0 när varje rad bär en av dem. */
80
66
  const RESULTAT = ['priced', 'stopp', 'väntar på dom']
81
67
 
82
68
  export const ANVANDNING = [
83
69
  'användning: bedom.mjs --repo-rot <abs> [--arkiv <abs>] --post <gid|gid/del-N> [--post ...]...',
84
- ` [--parallellt ${STANDARD_PARALLELLT}] [--max-usd <n>] [--claude <kommando>] [--om]`,
70
+ ` [--parallellt ${STANDARD_PARALLELLT}] [--claude <kommando>] [--om]`,
85
71
  ` [--sprak <${SPRAKEN.join('|')}>] [--design-mode <${DESIGNLAGEN.join('|')}>]`,
86
72
  ].join('\n')
87
73
 
@@ -127,12 +113,6 @@ export function tolkaArgv(argv) {
127
113
  const parText = enkla.parallellt ?? String(STANDARD_PARALLELLT)
128
114
  if (!/^\d+$/.test(parText) || Number(parText) < 1)
129
115
  return { fel: `--parallellt måste vara ett heltal >= 1 (fick "${parText}")` }
130
- let maxUsd = null
131
- if (enkla['max-usd'] !== undefined) {
132
- maxUsd = Number(enkla['max-usd'])
133
- if (enkla['max-usd'].trim() === '' || !Number.isFinite(maxUsd) || maxUsd < 0)
134
- return { fel: `--max-usd måste vara ett tal >= 0 (fick "${enkla['max-usd']}")` }
135
- }
136
116
  if (enkla.sprak !== undefined && !SPRAKEN.includes(enkla.sprak))
137
117
  return { fel: `--sprak måste vara ${SPRAKEN.join(' eller ')} (fick "${enkla.sprak}")` }
138
118
  if (enkla['design-mode'] !== undefined && !DESIGNLAGEN.includes(enkla['design-mode']))
@@ -144,7 +124,6 @@ export function tolkaArgv(argv) {
144
124
  arkiv: enkla.arkiv ?? null,
145
125
  poster: normerade,
146
126
  parallellt: Number(parText),
147
- maxUsd,
148
127
  claude: enkla.claude ?? 'claude',
149
128
  om,
150
129
  sprak: enkla.sprak ?? null,
@@ -164,16 +143,6 @@ export function losPost(post, arkiv) {
164
143
  return { dir }
165
144
  }
166
145
 
167
- /** Taket för körningen: flaggan vinner, sedan `max_usd_per_batch` i
168
- * `<arkiv>/../request.json` (`.haus-workflow/request.json`), sedan 100. En
169
- * saknad eller trasig fil är defaulten, aldrig ett fel. */
170
- export function lasTak({ arkiv, maxUsd = null }) {
171
- if (maxUsd !== null && maxUsd !== undefined) return maxUsd
172
- const v = lasRequestJson(arkiv)?.max_usd_per_batch
173
- if (typeof v === 'number' && Number.isFinite(v) && v >= 0) return v
174
- return STANDARD_TAK_USD
175
- }
176
-
177
146
  /** `<arkiv>/../request.json` som objekt, eller null när den saknas eller är
178
147
  * trasig (defaulten gäller då, aldrig ett fel). */
179
148
  function lasRequestJson(arkiv) {
@@ -294,9 +263,6 @@ export function avgorStatus({ exitCode, signal = null, state, sparrExit }) {
294
263
  return { status: 'ofärdig', orsak: `status ${state?.status ?? 'okänd'} utan stopp eller spärr` }
295
264
  }
296
265
 
297
- /** Har summan (de klara posterna plus de i flykt till snittet) nått taket? */
298
- export const kapNatt = (summaUsd, tak) => summaUsd >= tak
299
-
300
266
  /** Standardlöparen för archive.mjs: synkron, cwd i posten. */
301
267
  export const arkivStandard = (args, cwd) =>
302
268
  spawnSync(process.execPath, [ARCHIVE, ...args], { cwd, encoding: 'utf8' })
@@ -421,7 +387,7 @@ const lasUsd = (runDir) => {
421
387
  /** En post, hela ordningen ur ADR-0155. Kastar aldrig medvetet; ett kast
422
388
  * blir en `ofärdig`-rad i korBatch. */
423
389
  async function korPost(post, ctx) {
424
- const { ut, arkivKor, korKorning, kvitton, summa, tak, konto } = ctx
390
+ const { ut, arkivKor, korKorning, kvitton, konto } = ctx
425
391
  const rad = (status, extra = {}) => ({ post, status, ...extra })
426
392
  const los = losPost(post, ctx.arkiv)
427
393
  if (los.fel) {
@@ -439,10 +405,6 @@ async function korPost(post, ctx) {
439
405
  ut(`GRIND ${post}: ${grind}`)
440
406
  return rad('grind', { orsak: grind, ...lasPoang(dir) })
441
407
  }
442
- if (kapNatt(summa(), tak)) {
443
- ut(`TAK ${post}`)
444
- return rad('tak nått')
445
- }
446
408
  const runDir = korningsKatalog(dir, ctx.now ? ctx.now() : new Date())
447
409
  const start = startaKorning(dir, ctx.pid ?? process.pid, runDir, { arkivKor })
448
410
  if (start.pagar) {
@@ -457,7 +419,6 @@ async function korPost(post, ctx) {
457
419
  }
458
420
  if (start.varning) console.error(`${post}: ${start.varning}`)
459
421
  kvitton.add(dir)
460
- konto.iFlykt++
461
422
  let belopp = null
462
423
  try {
463
424
  ut(`START ${post}`)
@@ -488,10 +449,7 @@ async function korPost(post, ctx) {
488
449
  usd: belopp,
489
450
  })
490
451
  } finally {
491
- // Posten är klar för taket: ur flykten, in bland de klara med sin USD
492
- // (en post utan korning.json räknas som 0).
493
- konto.iFlykt--
494
- konto.klara++
452
+ // Postens USD in i summan (en post utan korning.json räknas som 0).
495
453
  if (Number.isFinite(belopp)) konto.anvant += belopp
496
454
  const slut = avslutaKorning(dir, { arkivKor })
497
455
  if (!slut.ok) console.error(`VARNING ${post}: ${slut.fel}`)
@@ -499,20 +457,14 @@ async function korPost(post, ctx) {
499
457
  }
500
458
  }
501
459
 
502
- /** Summan taket prövas mot: de klara posternas USD plus varje post i flykt
503
- * till de klaras snittkostnad (0 så länge ingen är klar). */
504
- export const taksumma = ({ anvant, klara, iFlykt }) =>
505
- anvant + (klara > 0 ? iFlykt * (anvant / klara) : 0)
506
-
507
- /** Kör posterna med högst `parallellt` i flykt. Taket prövas vid varje
508
- * poststart mot `taksumma` (en post utan korning.json räknas som 0).
509
- * Returnerar raderna i argv-ordning och de klara posternas summa. */
460
+ /** Kör posterna med högst `parallellt` i flykt. Returnerar raderna i
461
+ * argv-ordning och de klara posternas summa (en post utan korning.json
462
+ * räknas som 0). */
510
463
  export async function korBatch({
511
464
  poster,
512
465
  arkiv,
513
466
  repoRot,
514
467
  parallellt,
515
- tak,
516
468
  om,
517
469
  claude,
518
470
  sprak,
@@ -525,7 +477,7 @@ export async function korBatch({
525
477
  pid = process.pid,
526
478
  now,
527
479
  }) {
528
- const konto = { anvant: 0, klara: 0, iFlykt: 0 }
480
+ const konto = { anvant: 0 }
529
481
  const ctx = {
530
482
  arkiv,
531
483
  repoRot,
@@ -533,7 +485,6 @@ export async function korBatch({
533
485
  claude,
534
486
  sprak,
535
487
  designMode,
536
- tak,
537
488
  ut,
538
489
  arkivKor,
539
490
  korKorning,
@@ -542,7 +493,6 @@ export async function korBatch({
542
493
  pid,
543
494
  now,
544
495
  konto,
545
- summa: () => taksumma(konto),
546
496
  }
547
497
  const resultat = await schemalagg(poster, parallellt, (post) => korPost(post, ctx))
548
498
  const rader = resultat.map((r, i) =>
@@ -604,7 +554,6 @@ async function main(argv) {
604
554
  console.error(ANVANDNING)
605
555
  return 2
606
556
  }
607
- const tak = lasTak({ arkiv, maxUsd: val.maxUsd })
608
557
  const designMode = lasDesignlage({ arkiv, designMode: val.designMode })
609
558
  const ut = (rad) => process.stdout.write(rad + '\n')
610
559
  const pagaende = new Map()
@@ -617,7 +566,6 @@ async function main(argv) {
617
566
  arkiv,
618
567
  repoRot: val.repoRot,
619
568
  parallellt: val.parallellt,
620
- tak,
621
569
  om: val.om,
622
570
  claude: val.claude,
623
571
  sprak: val.sprak,
@@ -628,7 +576,7 @@ async function main(argv) {
628
576
  })
629
577
  ut('')
630
578
  ut(renderaTabell(rader))
631
- ut(`tak: ${tak} USD, använt: ${anvant.toFixed(2)} USD`)
579
+ ut(`använt: ${anvant.toFixed(2)} USD`)
632
580
  return rader.every((r) => RESULTAT.includes(r.status)) ? 0 : 1
633
581
  }
634
582
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haus-tech/haus-workflow",
3
- "version": "6.0.0",
3
+ "version": "6.2.0",
4
4
  "description": "Haus AI workflow CLI for Claude Code.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,11 +34,12 @@
34
34
  "format": "prettier --write --cache --cache-location .prettiercache .",
35
35
  "typecheck": "tsc --noEmit",
36
36
  "pack:local": "yarn pack",
37
- "release": "GITHUB_TOKEN=$(gh auth token) LEFTHOOK_EXCLUDE=branch-guard release-it",
38
- "release:dry": "GITHUB_TOKEN=$(gh auth token) LEFTHOOK_EXCLUDE=branch-guard release-it --dry-run",
37
+ "release": "GITHUB_TOKEN=$(gh auth token) LEFTHOOK_EXCLUDE=branch-guard,ci-local release-it",
38
+ "release:dry": "GITHUB_TOKEN=$(gh auth token) LEFTHOOK_EXCLUDE=branch-guard,ci-local release-it --dry-run",
39
39
  "prepack": "yarn build",
40
40
  "verify": "node scripts/verify.mjs",
41
41
  "verify:full": "node scripts/verify.mjs --coverage",
42
+ "ci:local": "node scripts/ci-local.mjs --force",
42
43
  "check:contract-behavior": "node --import tsx scripts/contract-behavior-check.mjs",
43
44
  "check:mutation-table": "node scripts/check-mutation-table.mjs",
44
45
  "check:docs-site": "node scripts/check-docs-site.mjs",