@haus-tech/haus-workflow 5.12.1 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/README.md +7 -4
- package/dist/cli.js +7944 -4000
- package/library/catalog/manifest.json +126 -9
- package/library/global/agents/request-design-reviewer.md +54 -0
- package/library/global/agents/request-designer.md +113 -0
- package/library/global/skills/haus-workflow/SKILL.md +10 -3
- package/library/global/skills/haus-workflow/references/init.md +7 -4
- package/library/global/skills/haus-workflow/references/new.md +1 -1
- package/library/global/skills/request/SKILL.md +493 -107
- package/library/global/skills/request/scripts/archive.mjs +736 -56
- package/library/global/skills/request/scripts/bedom.mjs +651 -0
- package/library/global/skills/request/scripts/korning.mjs +911 -70
- package/library/global/skills/request/scripts/lib.mjs +328 -3
- package/library/global/skills/request/scripts/precheck.mjs +134 -34
- package/library/global/skills/request/scripts/svep.mjs +44 -14
- package/library/global/skills/request-assess-impact/SKILL.md +1 -4
- package/library/global/skills/request-sync/SKILL.md +8 -2
- package/package.json +4 -3
|
@@ -20,13 +20,15 @@
|
|
|
20
20
|
* steg som ålägger startaren en skrivning måste peka ut vem som utför den.
|
|
21
21
|
*
|
|
22
22
|
* Användning:
|
|
23
|
-
* node svep.mjs --repo-rot <abs> --konto <namn> --modell <m> [--modell <m>]...
|
|
23
|
+
* node svep.mjs --repo-rot <abs> [--arkiv <abs>] --konto <namn> --modell <m> [--modell <m>]...
|
|
24
24
|
* --exp <experiments/<slug>> [--exp ...]...
|
|
25
25
|
* [--tickets-parallellt 2] [--modeller-parallellt 4]
|
|
26
26
|
* [--om | --hoppa-över] [--claude <kommando>]
|
|
27
27
|
*
|
|
28
|
-
* Ett relativt --exp löses mot
|
|
29
|
-
*
|
|
28
|
+
* Ett relativt --exp löses mot arkivroten: --arkiv när den ges (ARKIVROT: från
|
|
29
|
+
* `archive.mjs rot`), annars härledd ur --repo-rot:s git common dir, alltså
|
|
30
|
+
* <huvudutcheckning>/.haus-workflow/requests/ (ADR-0154, ADR-0155).
|
|
31
|
+
* Varje experimentkatalog måste bära onskemal.md, kallmanifest.md och experiment.json
|
|
30
32
|
* med `ticket` (huvudkonversationen förbereder dem, utvärdera steg 1 till 3);
|
|
31
33
|
* saknas något märks ticketen och hoppas över, svepet fortsätter.
|
|
32
34
|
*
|
|
@@ -44,7 +46,7 @@ import { existsSync, readFileSync, writeFileSync, readdirSync, realpathSync } fr
|
|
|
44
46
|
import { spawn, spawnSync } from 'node:child_process'
|
|
45
47
|
import path from 'node:path'
|
|
46
48
|
import { fileURLToPath } from 'node:url'
|
|
47
|
-
import { canonicalModel, matcharModell, avgorDeps, schemalagg } from './lib.mjs'
|
|
49
|
+
import { arkivRot, canonicalModel, matcharModell, avgorDeps, schemalagg } from './lib.mjs'
|
|
48
50
|
|
|
49
51
|
// matcharModell, avgorDeps och schemalagg flyttade till lib.mjs (delade
|
|
50
52
|
// byggstenar för requestmotorns kontrollscript); re-exporteras här så
|
|
@@ -68,6 +70,7 @@ const STANDARD_MODELLTAK = 4
|
|
|
68
70
|
const SIGINT_EXIT = 130
|
|
69
71
|
const VARDEFLAGGOR = [
|
|
70
72
|
'repo-rot',
|
|
73
|
+
'arkiv',
|
|
71
74
|
'konto',
|
|
72
75
|
'modell',
|
|
73
76
|
'exp',
|
|
@@ -78,7 +81,7 @@ const VARDEFLAGGOR = [
|
|
|
78
81
|
const BOOLFLAGGOR = ['om', 'hoppa-över']
|
|
79
82
|
|
|
80
83
|
export const ANVANDNING = [
|
|
81
|
-
'användning: svep.mjs --repo-rot <abs> --konto <namn> --modell <m> [--modell <m>]...',
|
|
84
|
+
'användning: svep.mjs --repo-rot <abs> [--arkiv <abs>] --konto <namn> --modell <m> [--modell <m>]...',
|
|
82
85
|
' --exp <experiments/<slug>> [--exp ...]...',
|
|
83
86
|
` [--tickets-parallellt ${STANDARD_TAK}] [--modeller-parallellt ${STANDARD_MODELLTAK}]`,
|
|
84
87
|
' [--om | --hoppa-över] [--claude <kommando>]',
|
|
@@ -119,6 +122,8 @@ export function tolkaArgv(argv) {
|
|
|
119
122
|
if (bool.om && bool['hoppa-över']) return { fel: '--om och --hoppa-över utesluter varandra' }
|
|
120
123
|
if (!path.isAbsolute(enkla['repo-rot']))
|
|
121
124
|
return { fel: `--repo-rot måste vara en absolut sökväg (fick "${enkla['repo-rot']}")` }
|
|
125
|
+
if (enkla.arkiv !== undefined && !path.isAbsolute(enkla.arkiv))
|
|
126
|
+
return { fel: `--arkiv måste vara en absolut sökväg (fick "${enkla.arkiv}")` }
|
|
122
127
|
const dubbletter = listor.modell.filter((m, i) => listor.modell.indexOf(m) !== i)
|
|
123
128
|
if (dubbletter.length > 0)
|
|
124
129
|
return {
|
|
@@ -142,13 +147,22 @@ export function tolkaArgv(argv) {
|
|
|
142
147
|
om: bool.om,
|
|
143
148
|
hoppaOver: bool['hoppa-över'] || !bool.om,
|
|
144
149
|
claude: enkla.claude ?? 'claude',
|
|
150
|
+
arkiv: enkla.arkiv ?? null,
|
|
145
151
|
}
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
/** Ett relativt --exp är arkivrelativt (`experiments/<slug>`), som SKILL.md
|
|
149
|
-
* skriver det; ett absolut används som det är. */
|
|
150
|
-
export function losExp(exp,
|
|
151
|
-
return path.isAbsolute(exp) ? exp : path.join(
|
|
155
|
+
* skriver det, och löses mot arkivroten; ett absolut används som det är. */
|
|
156
|
+
export function losExp(exp, arkiv) {
|
|
157
|
+
return path.isAbsolute(exp) ? exp : path.join(arkiv, exp)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Arkivroten när --arkiv saknas: huvudutcheckningens arkiv via git common
|
|
161
|
+
* dir (ADR-0154), så en worktree som --repo-rot aldrig blir arkivets hem.
|
|
162
|
+
* Utan git, eller med en common dir som inte heter .git, faller den
|
|
163
|
+
* tillbaka på <repoRot>/.haus-workflow/requests. */
|
|
164
|
+
export function losArkiv(repoRot) {
|
|
165
|
+
return arkivRot(repoRot)?.arkivRot ?? path.join(repoRot, '.haus-workflow', 'requests')
|
|
152
166
|
}
|
|
153
167
|
|
|
154
168
|
/** `default` som modellvärde ger katalognamnet `baseline/` (The experiment area: `experiments/`). */
|
|
@@ -305,10 +319,13 @@ export function sparrKataloger(dir) {
|
|
|
305
319
|
return delar.length === 0 ? [dir] : delar.flatMap(sparrKataloger)
|
|
306
320
|
}
|
|
307
321
|
|
|
322
|
+
/** En USD-cell: `decimaler` decimaler, `-` när beloppet saknas. Delas med
|
|
323
|
+
* bedom.mjs, som visar två decimaler där svepet visar fyra. */
|
|
324
|
+
export const usd = (u, decimaler = 4) => (Number.isFinite(u) ? u.toFixed(decimaler) : '-')
|
|
325
|
+
|
|
308
326
|
/** Sluttabellen: en rad per (ticket, modell). */
|
|
309
327
|
export function renderaTabell(rader) {
|
|
310
328
|
const wall = (ms) => (Number.isFinite(ms) ? `${ms}ms` : '-')
|
|
311
|
-
const usd = (u) => (Number.isFinite(u) ? u.toFixed(4) : '-')
|
|
312
329
|
return [
|
|
313
330
|
'| ticket | modell | körning | status | wall | USD |',
|
|
314
331
|
'| --- | --- | --- | --- | --- | --- |',
|
|
@@ -330,7 +347,7 @@ const arkiv = (args, cwd) =>
|
|
|
330
347
|
|
|
331
348
|
const felText = (r) => (r.stderr || r.stdout || '').trim() || `exit ${r.status}`
|
|
332
349
|
|
|
333
|
-
const skrivMatning = (katalog, matning) =>
|
|
350
|
+
export const skrivMatning = (katalog, matning) =>
|
|
334
351
|
writeFileSync(path.join(katalog, 'matning.json'), JSON.stringify(matning, null, 2) + '\n')
|
|
335
352
|
|
|
336
353
|
const lasKorningsrad = (expDir, nyckel) => {
|
|
@@ -346,8 +363,20 @@ const lasKorningsrad = (expDir, nyckel) => {
|
|
|
346
363
|
* runt den. korning.json skrivs av korning.mjs självt (filens ägare); här
|
|
347
364
|
* sparas bara stderr som stderr.log. matning.json skrivs ÄVEN när processen
|
|
348
365
|
* faller eller inte går att starta: en kraschad körning har en klocktid.
|
|
349
|
-
* `pagaende` är svepets register för Ctrl-C.
|
|
350
|
-
|
|
366
|
+
* `pagaende` är svepets register för Ctrl-C. `args` ersätter
|
|
367
|
+
* experimentlägets argument (bedom.mjs skickar produktionslägets `--post`,
|
|
368
|
+
* ADR-0155); utan `args` är anropet svepets som förut. */
|
|
369
|
+
export function korInre({
|
|
370
|
+
claude,
|
|
371
|
+
gid,
|
|
372
|
+
modell,
|
|
373
|
+
katalog,
|
|
374
|
+
expDir,
|
|
375
|
+
repoRot,
|
|
376
|
+
pagaende,
|
|
377
|
+
nyckel,
|
|
378
|
+
args = null,
|
|
379
|
+
}) {
|
|
351
380
|
return new Promise((resolve) => {
|
|
352
381
|
const startad = new Date()
|
|
353
382
|
let stderr = ''
|
|
@@ -373,7 +402,7 @@ function korInre({ claude, gid, modell, katalog, expDir, repoRot, pagaende, nyck
|
|
|
373
402
|
try {
|
|
374
403
|
barn = spawn(
|
|
375
404
|
process.execPath,
|
|
376
|
-
korningArgs({ katalog, expDir, repoRot, gid, modell, claude }),
|
|
405
|
+
args ?? korningArgs({ katalog, expDir, repoRot, gid, modell, claude }),
|
|
377
406
|
{
|
|
378
407
|
cwd: repoRot,
|
|
379
408
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -695,7 +724,8 @@ async function main(argv) {
|
|
|
695
724
|
process.exit(SIGINT_EXIT)
|
|
696
725
|
})
|
|
697
726
|
|
|
698
|
-
const
|
|
727
|
+
const arkivrot = val.arkiv ?? losArkiv(val.repoRot)
|
|
728
|
+
const exps = val.exps.map((e) => losExp(e, arkivrot))
|
|
699
729
|
const resultat = await schemalagg(exps, val.taket, (exp) => korTicket(exp, val, pagaende, ut))
|
|
700
730
|
|
|
701
731
|
const rader = []
|
|
@@ -80,10 +80,7 @@ If the item comes back with the outcome `DELA_UPP`, it could not be bracketed ag
|
|
|
80
80
|
|
|
81
81
|
The last point decides whether the split is worth anything. If all parts require the same manual walkthrough of the same flows in the same environments, the heaviest work is multiplied by the split instead of divided. In that case, write that out.
|
|
82
82
|
|
|
83
|
-
**
|
|
84
|
-
|
|
85
|
-
- If the part can be delivered on its own, it becomes a **separate request**, with its own spec and its own delivery. Write that, but do not write the spec: `request-write-spec` does that.
|
|
86
|
-
- If it cannot be delivered on its own, it becomes a **part of the same item**, delivered together with the rest.
|
|
83
|
+
**Every part becomes a subtask under the same item**, never a separate request, whether or not the part could be delivered on its own. Whether a part could be delivered on its own still matters for the order of the work: write it per part, since it says which part can ship first. It never changes where the part ends up.
|
|
87
84
|
|
|
88
85
|
**The parts inherit the item's spec and this analysis.** Write no separate spec and no separate analysis per part. Each part gets a scope line that says which part of the spec it covers, and is scored against the shared source material. The customer ordered one thing; the split is internal structure.
|
|
89
86
|
|
|
@@ -25,12 +25,16 @@ Read `.haus-workflow/request.json` in the repo root:
|
|
|
25
25
|
"customer_board": "1202738613029434",
|
|
26
26
|
"customer_section": "1202738613029441",
|
|
27
27
|
"customer_section_name": "Mina sidor",
|
|
28
|
-
"collection_board": "1217668109816859"
|
|
28
|
+
"collection_board": "1217668109816859",
|
|
29
|
+
"design_mode": "interview",
|
|
30
|
+
"auto_sync_spec": true
|
|
29
31
|
}
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
`customer_section` is missing when the board is not divided into sections, and when the setup was run before the section step existed. `customer_section_name` exists only so it can be shown in confirmations: **always match on gid, never on name.**
|
|
33
35
|
|
|
36
|
+
`design_mode` is `interview` (default, also when the key is absent) or `draft`. `draft` (ADR-0156) lets `/request bedöm` draft the design of an approved item that has none and have a second machine read it before the item is priced; the setup never asks for it, a human edits the file to opt in. `auto_sync_spec` (`true` by default, also when the key is absent): the request skill places a spec at 🔍 without a "go" unless it is `false` (ADR-0157); the setup writes the default and never asks for it, a human edits the file to opt out.
|
|
37
|
+
|
|
34
38
|
**If the file is missing: run the setup below.** Never guess from the repo name or the folder name.
|
|
35
39
|
|
|
36
40
|
If the file exists: **verify the board once, before the first write.** Look up `customer_board` by gid and print the name: _"Writing to Vafab Mina sidor, is that right?"_ Putting a customer's request on the wrong customer board is the only error in the entire flow that another customer can see, and it cannot be cleaned up quietly.
|
|
@@ -77,7 +81,9 @@ Fetch the fields on both boards and go through three things:
|
|
|
77
81
|
"customer_board": "1217634399775198",
|
|
78
82
|
"customer_section": "1217634399775205",
|
|
79
83
|
"customer_section_name": "Mina sidor",
|
|
80
|
-
"collection_board": "1217668109816859"
|
|
84
|
+
"collection_board": "1217668109816859",
|
|
85
|
+
"design_mode": "interview",
|
|
86
|
+
"auto_sync_spec": true
|
|
81
87
|
}
|
|
82
88
|
```
|
|
83
89
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haus-tech/haus-workflow",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.1.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",
|