@haus-tech/haus-workflow 6.2.0 → 6.4.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 +36 -0
- package/README.md +1 -0
- package/dist/cli.js +28250 -16572
- package/library/global/settings-fragments/hooks.json +14 -0
- package/library/global/skills/haus-workflow/references/clone.md +136 -0
- package/library/global/skills/request/SKILL.md +50 -18
- package/library/global/skills/request/scripts/archive.mjs +81 -8
- package/library/global/skills/request/scripts/bedom.mjs +3 -3
- package/library/global/skills/request/scripts/korning.mjs +45 -12
- package/library/global/skills/request/scripts/lib.mjs +20 -6
- package/library/global/skills/request/scripts/svep.mjs +4 -4
- package/library/schemas/localdev.v2.schema.json +369 -0
- package/package.json +3 -1
|
@@ -957,7 +957,7 @@ export function korAgent({ agent, modell, prompt, fil, maxTurer, ctx }) {
|
|
|
957
957
|
let tidsgransMs = null
|
|
958
958
|
let timer = null
|
|
959
959
|
let samtidiga = null
|
|
960
|
-
const
|
|
960
|
+
const avslutaAgent = ({ exitCode, spawnfel = null }) => {
|
|
961
961
|
if (klar) return
|
|
962
962
|
klar = true
|
|
963
963
|
if (timer !== null) clearTimeout(timer)
|
|
@@ -991,9 +991,9 @@ export function korAgent({ agent, modell, prompt, fil, maxTurer, ctx }) {
|
|
|
991
991
|
.then(
|
|
992
992
|
(r) => {
|
|
993
993
|
stdout = String(r?.stdout ?? '')
|
|
994
|
-
|
|
994
|
+
avslutaAgent({ exitCode: r?.exitCode ?? 0 })
|
|
995
995
|
},
|
|
996
|
-
(e) =>
|
|
996
|
+
(e) => avslutaAgent({ exitCode: null, spawnfel: e?.message ?? String(e) }),
|
|
997
997
|
)
|
|
998
998
|
return
|
|
999
999
|
}
|
|
@@ -1004,7 +1004,7 @@ export function korAgent({ agent, modell, prompt, fil, maxTurer, ctx }) {
|
|
|
1004
1004
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1005
1005
|
})
|
|
1006
1006
|
} catch (e) {
|
|
1007
|
-
|
|
1007
|
+
avslutaAgent({ exitCode: null, spawnfel: e.message })
|
|
1008
1008
|
return
|
|
1009
1009
|
}
|
|
1010
1010
|
ctx.barn.add(barn)
|
|
@@ -1024,11 +1024,11 @@ export function korAgent({ agent, modell, prompt, fil, maxTurer, ctx }) {
|
|
|
1024
1024
|
})
|
|
1025
1025
|
barn.on('error', (e) => {
|
|
1026
1026
|
ctx.barn.delete(barn)
|
|
1027
|
-
|
|
1027
|
+
avslutaAgent({ exitCode: null, spawnfel: e.message })
|
|
1028
1028
|
})
|
|
1029
1029
|
barn.on('close', (code) => {
|
|
1030
1030
|
ctx.barn.delete(barn)
|
|
1031
|
-
|
|
1031
|
+
avslutaAgent({ exitCode: code })
|
|
1032
1032
|
})
|
|
1033
1033
|
barn.stdin.on('error', () => {})
|
|
1034
1034
|
barn.stdin.end(prompt)
|
|
@@ -1415,6 +1415,9 @@ const forstaRubrikI = (text) => {
|
|
|
1415
1415
|
|
|
1416
1416
|
const INTERVJU_RE = /^INTERVJU KRÄVS:\s*(.*)$/u
|
|
1417
1417
|
|
|
1418
|
+
/** Svarets första rad läst som `INTERVJU KRÄVS: <skäl>`, matchen eller null. */
|
|
1419
|
+
const lasIntervju = (svar) => INTERVJU_RE.exec(svar.trim().split('\n')[0].trim())
|
|
1420
|
+
|
|
1418
1421
|
/** Designern (ADR-0156 beslut 1 och 6): ett agentsteg som skriver ett utkast
|
|
1419
1422
|
* eller svarar `INTERVJU KRÄVS:`. Returnerar `{ status: null }` när ett
|
|
1420
1423
|
* utkast bokfördes (eller steget inte gäller), `stopp` på INTERVJU KRÄVS
|
|
@@ -1449,8 +1452,28 @@ export async function korDesigner(del, ctx) {
|
|
|
1449
1452
|
ctx,
|
|
1450
1453
|
})
|
|
1451
1454
|
if (!res.ok) return { status: 'ofärdig', orsak: `design: ${res.orsak}` }
|
|
1452
|
-
|
|
1453
|
-
|
|
1455
|
+
let svar = utanYttreStaket(res.text)
|
|
1456
|
+
let intervju = lasIntervju(svar)
|
|
1457
|
+
// #746: ett INTERVJU KRÄVS utan skäl är ett scriptbevisat brott (stopp
|
|
1458
|
+
// vägrar ett tomt --orsak). Det prövas före varje skrivning: EN
|
|
1459
|
+
// omdispatch (ADR-0092), sedan ofärdig, och inget kvitto i postens katalog.
|
|
1460
|
+
if (ctx.post && intervju && intervju[1].trim() === '') {
|
|
1461
|
+
const brist = '- INTERVJU KRÄVS: saknar skäl; skriv skälet efter kolonet på samma rad'
|
|
1462
|
+
logg(`BROTT ${delNamn(ctx, del.dir)} designer: INTERVJU KRÄVS utan skäl, omdispatch`)
|
|
1463
|
+
const om = await korAgent({
|
|
1464
|
+
agent: 'request-designer',
|
|
1465
|
+
modell: ctx.modell,
|
|
1466
|
+
prompt: omdispatchPrompt(prompt, [brist]),
|
|
1467
|
+
fil: `steg/1${del.nr}-designer-omdispatch.json`,
|
|
1468
|
+
maxTurer: MAX_TURER.designer,
|
|
1469
|
+
ctx,
|
|
1470
|
+
})
|
|
1471
|
+
if (!om.ok) return { status: 'ofärdig', orsak: `design: ${om.orsak}` }
|
|
1472
|
+
svar = utanYttreStaket(om.text)
|
|
1473
|
+
intervju = lasIntervju(svar)
|
|
1474
|
+
if (intervju && intervju[1].trim() === '')
|
|
1475
|
+
return { status: 'ofärdig', orsak: 'design: INTERVJU KRÄVS utan skäl efter omdispatchen' }
|
|
1476
|
+
}
|
|
1454
1477
|
if (intervju) {
|
|
1455
1478
|
const orsak = `intervju krävs: ${intervju[1].trim()}`
|
|
1456
1479
|
logg(`STOPP ${delNamn(ctx, del.dir)} ${orsak}`)
|
|
@@ -2106,12 +2129,20 @@ function arkiveraDubbel(del, n, k1, k2, ctx) {
|
|
|
2106
2129
|
const diff = jam.rader.map((r) => r.replace(/^- /, ''))
|
|
2107
2130
|
skrivPris(del, huvud, vinnaren)
|
|
2108
2131
|
skrivPris(del, run2, andra)
|
|
2109
|
-
|
|
2132
|
+
// #754: på en OLIKA står diffraderna på körning 1:s runs-rad, som i det
|
|
2133
|
+
// manuella flödet (SKILL.md bedöm steg 6). Arkiv skrivna före #754 bär dem
|
|
2134
|
+
// på körning 2:s rad; detaljer <post> state visar båda.
|
|
2135
|
+
const olikaDiff = (k) => (jam.verdikt === 'OLIKA' && k === k1 ? diff : [])
|
|
2136
|
+
addRun(
|
|
2137
|
+
del.dir,
|
|
2138
|
+
scorerRad(vinnaren, huvud, { doubleRun, note: scorerNote(vinnaren, olikaDiff(vinnaren)) }),
|
|
2139
|
+
ctx,
|
|
2140
|
+
)
|
|
2110
2141
|
addRun(
|
|
2111
2142
|
del.dir,
|
|
2112
2143
|
scorerRad(andra, run2, {
|
|
2113
2144
|
doubleRun,
|
|
2114
|
-
note: scorerNote(andra,
|
|
2145
|
+
note: scorerNote(andra, olikaDiff(andra)),
|
|
2115
2146
|
// En OLIKA har ingen gällande fil att peka på förrän domen är fälld.
|
|
2116
2147
|
...(jam.verdikt === 'OLIKA' ? {} : { supersededBy: huvud }),
|
|
2117
2148
|
}),
|
|
@@ -2230,7 +2261,7 @@ function postStatus(ctx, korStatus) {
|
|
|
2230
2261
|
* en dödad korning.mjs, och korning.json skrivs aldrig, så både pengarna och
|
|
2231
2262
|
* mätningen försvinner tyst. Kör aldrig återstoden av flödet: en avbruten
|
|
2232
2263
|
* körning ÄR ofärdig. */
|
|
2233
|
-
function avbryt(ctx, allaSteg, nyckel, signal) {
|
|
2264
|
+
export function avbryt(ctx, allaSteg, nyckel, signal, { exit = process.exit } = {}) {
|
|
2234
2265
|
const antal = ctx.barn.size
|
|
2235
2266
|
for (const barn of ctx.barn) barn.kill('SIGTERM')
|
|
2236
2267
|
skrivKorningsjson(ctx, allaSteg)
|
|
@@ -2253,7 +2284,9 @@ function avbryt(ctx, allaSteg, nyckel, signal) {
|
|
|
2253
2284
|
if (r.status !== 0) logg(`VARNING experiment --status ofärdig: ${felText(r)}`)
|
|
2254
2285
|
}
|
|
2255
2286
|
logg(`AVBRUTEN ${signal}: ${antal} barnprocess(er) dödade, korning.json skriven`)
|
|
2256
|
-
|
|
2287
|
+
// #748: samma STATUS-rad som avslutet, så startaren läser mer än exitkoden.
|
|
2288
|
+
if (ctx.post) process.stdout.write('STATUS: ofärdig\n')
|
|
2289
|
+
exit(SIGNAL_EXIT)
|
|
2257
2290
|
}
|
|
2258
2291
|
|
|
2259
2292
|
/** Vaktens intervall: startaren prövas var 30:e sekund. */
|
|
@@ -623,17 +623,31 @@ export const avvikelseEtikett = (text) => {
|
|
|
623
623
|
}
|
|
624
624
|
|
|
625
625
|
/**
|
|
626
|
-
* Fritexten efter etiketten
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
626
|
+
* Fritexten efter etiketten, utan inledande skiljetecken eller streck
|
|
627
|
+
* (bindestreck, U+2012, U+2013, U+2014; #753), eller null när raden saknas
|
|
628
|
+
* eller ingen text följer. Bär etikettraden ingen text läses första
|
|
629
|
+
* icke-tomma raden efter den, utom när den raden är nästa fält (`**…:**`)
|
|
630
|
+
* eller en rubrik (#753). Etiketten själv läses lika strikt som förut.
|
|
631
|
+
* precheck skriver texten som `AVVIKELSE-TEXT:` så orkestratorn kan se en
|
|
632
|
+
* designkonflikt (texten börjar med `design conflict:`, #596) utan att läsa
|
|
633
|
+
* filen själv.
|
|
630
634
|
*/
|
|
631
635
|
export const avvikelseText = (text) => {
|
|
632
636
|
const rad = granskareRadRe('Avvikelse mot specen').source
|
|
633
|
-
const
|
|
637
|
+
const skilje = '[ \\t.,;:!?\\-\\u2012\\u2013\\u2014]*'
|
|
638
|
+
const str = String(text)
|
|
639
|
+
const m = new RegExp(`${rad}[ \\t]*\\p{L}+${skilje}(.*)$`, 'imu').exec(str)
|
|
634
640
|
if (!m) return null
|
|
635
641
|
const rest = m[1].trim()
|
|
636
|
-
|
|
642
|
+
if (rest !== '') return rest
|
|
643
|
+
const nasta = str
|
|
644
|
+
.slice(m.index + m[0].length)
|
|
645
|
+
.split(/\r?\n/)
|
|
646
|
+
.map((l) => l.trim())
|
|
647
|
+
.find((l) => l !== '')
|
|
648
|
+
if (nasta === undefined || /^\*\*[^*]+:\*\*|^#/u.test(nasta)) return null
|
|
649
|
+
const foljande = nasta.replace(new RegExp(`^${skilje}`, 'u'), '').trim()
|
|
650
|
+
return foljande === '' ? null : foljande
|
|
637
651
|
}
|
|
638
652
|
|
|
639
653
|
/**
|
|
@@ -504,11 +504,11 @@ export async function korVarv({ gid, modell, expDir, repoRot, konto, claude, pag
|
|
|
504
504
|
)
|
|
505
505
|
if (prov.status !== 0) varningar.push(`proveniens: ${felText(prov)}`)
|
|
506
506
|
|
|
507
|
-
let
|
|
507
|
+
let belopp = null
|
|
508
508
|
let modellfel = null
|
|
509
509
|
try {
|
|
510
510
|
const korning = JSON.parse(readFileSync(path.join(katalog, 'korning.json'), 'utf8'))
|
|
511
|
-
if (typeof korning.total_cost_usd === 'number')
|
|
511
|
+
if (typeof korning.total_cost_usd === 'number') belopp = korning.total_cost_usd
|
|
512
512
|
if (exitCode === 0) {
|
|
513
513
|
// korning.mjs har redan gjort verifieringen per steg (modellverifiering);
|
|
514
514
|
// svepets egen verifieraModeller är reserven när det fältet saknas
|
|
@@ -543,9 +543,9 @@ export async function korVarv({ gid, modell, expDir, repoRot, konto, claude, pag
|
|
|
543
543
|
}
|
|
544
544
|
const orsak = avg.orsak ?? inre?.orsak ?? null
|
|
545
545
|
ut(
|
|
546
|
-
`KLAR ${gid} ${modell} ${avg.status} ${wall_ms}ms${
|
|
546
|
+
`KLAR ${gid} ${modell} ${avg.status} ${wall_ms}ms${belopp !== null ? ` ${belopp.toFixed(4)} USD` : ''}`,
|
|
547
547
|
)
|
|
548
|
-
return { modell, status: avg.status, orsak, korning: nyckel, wall_ms, usd, varningar }
|
|
548
|
+
return { modell, status: avg.status, orsak, korning: nyckel, wall_ms, usd: belopp, varningar }
|
|
549
549
|
}
|
|
550
550
|
|
|
551
551
|
/** `väntar på dom` omvärderas: sparr exit 0 på alla löv betyder att domen är
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/WeAreHausTech/haus-workflow/library/schemas/localdev.v2.schema.json",
|
|
4
|
+
"title": "haus localdev.yml (version 2)",
|
|
5
|
+
"description": "The CLI-parsed part of .haus-workflow/localdev.yml (ADR-0163). Only `version`, `instance` and the templates in `env` are validated by the CLI (src/workspace/env/manifest.ts, which is the enforcement); every other top-level key is agent-interpreted by the clone skill and left open here. Templates: {{slug}}, {{port.<name>}}, {{db.<name>}}, {{redis.<name>.db}}, {{search.<name>.prefix}}, {{site.<name>.url|host|root}}.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"version": {
|
|
9
|
+
"enum": [1, 2]
|
|
10
|
+
},
|
|
11
|
+
"instance": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"description": "Per-worktree environment. A repo file may declare only ports and processes.",
|
|
15
|
+
"properties": {
|
|
16
|
+
"ports": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"propertyNames": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"pattern": "^[a-z0-9][a-z0-9_-]*$"
|
|
21
|
+
},
|
|
22
|
+
"additionalProperties": {
|
|
23
|
+
"type": "integer",
|
|
24
|
+
"minimum": 1,
|
|
25
|
+
"maximum": 65535
|
|
26
|
+
},
|
|
27
|
+
"maxProperties": 20,
|
|
28
|
+
"description": "name -> main-checkout port; a worktree gets lease base + declaration index"
|
|
29
|
+
},
|
|
30
|
+
"datastores": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"propertyNames": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"pattern": "^[a-z0-9][a-z0-9_-]*$"
|
|
35
|
+
},
|
|
36
|
+
"additionalProperties": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"required": ["engine"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"engine": {
|
|
42
|
+
"enum": [
|
|
43
|
+
"mariadb",
|
|
44
|
+
"mysql",
|
|
45
|
+
"postgres",
|
|
46
|
+
"redis",
|
|
47
|
+
"elasticsearch",
|
|
48
|
+
"opensearch",
|
|
49
|
+
"mssql"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"compose": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"additionalProperties": false,
|
|
55
|
+
"required": ["project", "service"],
|
|
56
|
+
"properties": {
|
|
57
|
+
"project": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"minLength": 1
|
|
60
|
+
},
|
|
61
|
+
"service": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"minLength": 1
|
|
64
|
+
},
|
|
65
|
+
"file": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"minLength": 1,
|
|
68
|
+
"description": "Compose file, relative to the workspace root, passed as -f when env up has to create the engine (for a non-default name such as docker-compose.local.yml)."
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"native": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"additionalProperties": false,
|
|
75
|
+
"required": ["host", "port"],
|
|
76
|
+
"description": "An engine on the host, outside Docker (mariadb, mysql, elasticsearch, opensearch). haus never starts it; SQL credentials come only from MYSQL_USER / MYSQL_PWD or ~/.my.cnf.",
|
|
77
|
+
"properties": {
|
|
78
|
+
"host": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"pattern": "^[A-Za-z0-9._:-]+$"
|
|
81
|
+
},
|
|
82
|
+
"port": {
|
|
83
|
+
"type": "integer",
|
|
84
|
+
"minimum": 1,
|
|
85
|
+
"maximum": 65535
|
|
86
|
+
},
|
|
87
|
+
"client": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"pattern": "^[A-Za-z0-9._/+-]+$",
|
|
90
|
+
"description": "mysql/mariadb client: a command on PATH or an absolute path (default: mysql or mariadb on PATH)"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"port": {
|
|
95
|
+
"type": "integer",
|
|
96
|
+
"minimum": 1,
|
|
97
|
+
"maximum": 65535,
|
|
98
|
+
"description": "The engine port. Optional with native: (defaults to native.port)."
|
|
99
|
+
},
|
|
100
|
+
"isolation": {
|
|
101
|
+
"enum": ["database", "db-index", "key-prefix", "prefix", "container"],
|
|
102
|
+
"description": "Default per engine: database (SQL, mssql included), db-index (redis), prefix (search). container has no driver yet."
|
|
103
|
+
},
|
|
104
|
+
"prefix": {
|
|
105
|
+
"type": "string",
|
|
106
|
+
"description": "The main checkout's index or key prefix (default empty)."
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"oneOf": [
|
|
110
|
+
{
|
|
111
|
+
"required": ["compose", "port"]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"required": ["native"]
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"databases": {
|
|
120
|
+
"type": "object",
|
|
121
|
+
"propertyNames": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"pattern": "^[a-z0-9][a-z0-9_-]*$"
|
|
124
|
+
},
|
|
125
|
+
"additionalProperties": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"additionalProperties": false,
|
|
128
|
+
"required": ["on", "name"],
|
|
129
|
+
"properties": {
|
|
130
|
+
"on": {
|
|
131
|
+
"type": "string",
|
|
132
|
+
"description": "A datastore name"
|
|
133
|
+
},
|
|
134
|
+
"name": {
|
|
135
|
+
"type": "string",
|
|
136
|
+
"minLength": 1
|
|
137
|
+
},
|
|
138
|
+
"perWorktree": {
|
|
139
|
+
"enum": ["clone", "empty"],
|
|
140
|
+
"default": "clone"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"sites": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"propertyNames": {
|
|
148
|
+
"type": "string",
|
|
149
|
+
"pattern": "^[a-z0-9][a-z0-9_-]*$"
|
|
150
|
+
},
|
|
151
|
+
"additionalProperties": {
|
|
152
|
+
"type": "object",
|
|
153
|
+
"additionalProperties": false,
|
|
154
|
+
"required": ["kind", "root", "host"],
|
|
155
|
+
"properties": {
|
|
156
|
+
"kind": {
|
|
157
|
+
"const": "herd"
|
|
158
|
+
},
|
|
159
|
+
"mode": {
|
|
160
|
+
"enum": ["per-worktree", "shared-lock"],
|
|
161
|
+
"default": "per-worktree"
|
|
162
|
+
},
|
|
163
|
+
"root": {
|
|
164
|
+
"type": "string",
|
|
165
|
+
"minLength": 1,
|
|
166
|
+
"description": "Relative path inside the repo; no leading / and no .."
|
|
167
|
+
},
|
|
168
|
+
"host": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"minLength": 1,
|
|
171
|
+
"description": "May use templates, e.g. {{slug}}.shop.se"
|
|
172
|
+
},
|
|
173
|
+
"mainHost": {
|
|
174
|
+
"type": "string",
|
|
175
|
+
"description": "The main checkout's host (default: basename of root)"
|
|
176
|
+
},
|
|
177
|
+
"secure": {
|
|
178
|
+
"type": "boolean",
|
|
179
|
+
"default": false
|
|
180
|
+
},
|
|
181
|
+
"php": {
|
|
182
|
+
"type": ["string", "number"]
|
|
183
|
+
},
|
|
184
|
+
"copy": {
|
|
185
|
+
"enum": ["cow", "none"],
|
|
186
|
+
"description": "cow clones the main docroot; none uses the worktree folder as it is (the default, and required, for bedrock-env and laravel-env, whose root is a member repo)."
|
|
187
|
+
},
|
|
188
|
+
"share": {
|
|
189
|
+
"type": "array",
|
|
190
|
+
"items": {
|
|
191
|
+
"type": "string",
|
|
192
|
+
"minLength": 1
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
"plugins": {
|
|
196
|
+
"type": "object",
|
|
197
|
+
"additionalProperties": {
|
|
198
|
+
"type": "string",
|
|
199
|
+
"minLength": 1
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
"config": {
|
|
203
|
+
"type": "object",
|
|
204
|
+
"additionalProperties": false,
|
|
205
|
+
"required": ["kind"],
|
|
206
|
+
"properties": {
|
|
207
|
+
"kind": {
|
|
208
|
+
"enum": ["wp-config-override", "bedrock-env", "laravel-env"],
|
|
209
|
+
"description": "wp-config-override writes wp-config.haus-instance.php; bedrock-env and laravel-env write the defines into the site repo's .env (instance-owned keys)."
|
|
210
|
+
},
|
|
211
|
+
"defines": {
|
|
212
|
+
"type": "object",
|
|
213
|
+
"propertyNames": {
|
|
214
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
215
|
+
},
|
|
216
|
+
"additionalProperties": {
|
|
217
|
+
"type": "string"
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
"after": {
|
|
223
|
+
"type": "array",
|
|
224
|
+
"items": {
|
|
225
|
+
"type": "string",
|
|
226
|
+
"minLength": 1
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
"processes": {
|
|
233
|
+
"type": "object",
|
|
234
|
+
"propertyNames": {
|
|
235
|
+
"type": "string",
|
|
236
|
+
"pattern": "^[a-z0-9][a-z0-9_-]*$"
|
|
237
|
+
},
|
|
238
|
+
"additionalProperties": {
|
|
239
|
+
"type": "object",
|
|
240
|
+
"additionalProperties": false,
|
|
241
|
+
"required": ["start"],
|
|
242
|
+
"properties": {
|
|
243
|
+
"repo": {
|
|
244
|
+
"type": "string",
|
|
245
|
+
"description": "Required in the workspace file; defaults to the owning repo in a repo file. In a single-repo project (ADR-0167), the folder inside the repo the process runs in (`.` for the root)"
|
|
246
|
+
},
|
|
247
|
+
"start": {
|
|
248
|
+
"type": "string",
|
|
249
|
+
"minLength": 1
|
|
250
|
+
},
|
|
251
|
+
"port": {
|
|
252
|
+
"type": "string",
|
|
253
|
+
"description": "A name from instance.ports"
|
|
254
|
+
},
|
|
255
|
+
"health": {
|
|
256
|
+
"type": "string"
|
|
257
|
+
},
|
|
258
|
+
"healthTimeoutSec": {
|
|
259
|
+
"type": "number",
|
|
260
|
+
"exclusiveMinimum": 0,
|
|
261
|
+
"default": 120
|
|
262
|
+
},
|
|
263
|
+
"optional": {
|
|
264
|
+
"type": "boolean",
|
|
265
|
+
"default": false
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
"copyLocal": {
|
|
271
|
+
"type": "array",
|
|
272
|
+
"items": {
|
|
273
|
+
"type": "object",
|
|
274
|
+
"additionalProperties": false,
|
|
275
|
+
"required": ["repo", "path"],
|
|
276
|
+
"properties": {
|
|
277
|
+
"repo": {
|
|
278
|
+
"type": "string",
|
|
279
|
+
"minLength": 1
|
|
280
|
+
},
|
|
281
|
+
"path": {
|
|
282
|
+
"type": "string",
|
|
283
|
+
"minLength": 1,
|
|
284
|
+
"description": "Relative path inside the repo; no leading / and no .."
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
"teardown": {
|
|
290
|
+
"type": "array",
|
|
291
|
+
"items": {
|
|
292
|
+
"type": "string",
|
|
293
|
+
"minLength": 1
|
|
294
|
+
},
|
|
295
|
+
"description": "Shell commands (templated) env down --destroy and env gc --write run after stopping processes and before dropping databases; workspace file only, must be idempotent"
|
|
296
|
+
},
|
|
297
|
+
"after": {
|
|
298
|
+
"type": "array",
|
|
299
|
+
"items": {
|
|
300
|
+
"type": "string",
|
|
301
|
+
"minLength": 1
|
|
302
|
+
},
|
|
303
|
+
"description": "Shell commands (templated) env up runs once per environment in the worktree root, after the .env files and .haus-env.json are written (for example seeding a per-worktree bucket from the main one, ADR-0167); a failed one is retried by the next env up; workspace file only, must be idempotent"
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"env": {
|
|
308
|
+
"type": "array",
|
|
309
|
+
"items": {
|
|
310
|
+
"type": "object",
|
|
311
|
+
"required": ["sinks"],
|
|
312
|
+
"properties": {
|
|
313
|
+
"value": {
|
|
314
|
+
"type": ["string", "number"]
|
|
315
|
+
},
|
|
316
|
+
"source": {
|
|
317
|
+
"type": "object",
|
|
318
|
+
"required": ["provides"],
|
|
319
|
+
"properties": {
|
|
320
|
+
"repo": {
|
|
321
|
+
"type": "string"
|
|
322
|
+
},
|
|
323
|
+
"provides": {
|
|
324
|
+
"type": "string"
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
"sinks": {
|
|
329
|
+
"type": "array",
|
|
330
|
+
"items": {
|
|
331
|
+
"type": "object",
|
|
332
|
+
"required": ["repo", "key"],
|
|
333
|
+
"properties": {
|
|
334
|
+
"repo": {
|
|
335
|
+
"type": "string",
|
|
336
|
+
"minLength": 1,
|
|
337
|
+
"description": "A member repo (id or folder) in a workspace; in a single-repo project (ADR-0167) a folder inside the repo in canonical form, `.` for the root or `apps/server` for a monorepo app, whose .env receives the key"
|
|
338
|
+
},
|
|
339
|
+
"key": {
|
|
340
|
+
"type": "string",
|
|
341
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
"anyOf": [
|
|
348
|
+
{
|
|
349
|
+
"required": ["value"]
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
"required": ["source"]
|
|
353
|
+
}
|
|
354
|
+
]
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
"if": {
|
|
359
|
+
"required": ["instance"]
|
|
360
|
+
},
|
|
361
|
+
"then": {
|
|
362
|
+
"properties": {
|
|
363
|
+
"version": {
|
|
364
|
+
"const": 2
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
"required": ["version"]
|
|
368
|
+
}
|
|
369
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haus-tech/haus-workflow",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
4
4
|
"description": "Haus AI workflow CLI for Claude Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"scripts/postinstall.mjs",
|
|
17
17
|
"library/global",
|
|
18
18
|
"library/catalog",
|
|
19
|
+
"library/schemas",
|
|
19
20
|
"README.md",
|
|
20
21
|
"CHANGELOG.md",
|
|
21
22
|
"docs/before-you-start.md",
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
"test:quick": "node scripts/test-fast.mjs --quick",
|
|
29
30
|
"test:full": "node scripts/run-tests.mjs tests/**/*.test.js",
|
|
30
31
|
"test:coverage": "c8 yarn test:full",
|
|
32
|
+
"test:integration": "HAUS_INTEGRATION=1 node scripts/run-tests.mjs tests/integration/*.int.test.js",
|
|
31
33
|
"coverage:check": "c8 --check-coverage yarn test:full",
|
|
32
34
|
"lint": "eslint src --cache --cache-location .eslintcache",
|
|
33
35
|
"format:check": "prettier --check --cache --cache-location .prettiercache .",
|