@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.
- package/CHANGELOG.md +20 -0
- package/README.md +13 -15
- package/dist/cli.js +14 -1
- package/library/global/skills/haus-workflow/SKILL.md +162 -185
- package/library/global/skills/haus-workflow/references/clone.md +191 -0
- package/library/global/skills/haus-workflow/references/new.md +15 -0
- package/library/global/skills/request-assess-impact/SKILL.md +45 -1
- package/library/global/skills/request-score/SKILL.md +285 -0
- package/library/global/skills/request-score/references/poangskala.md +1288 -0
- package/library/global/skills/request-sync/SKILL.md +169 -57
- package/package.json +1 -1
- package/library/global/skills/haus-workflow/references/cloneandsetup.md +0 -172
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: request-sync
|
|
3
|
-
description: Läser och skriver requests mot ärendesystemet (idag Asana), och sätter upp kopplingen mellan ett repo och rätt kundbräda. Använd alltid när användaren vill "sätt upp", "koppla repot mot Asana", "kör uppsättningen", "hämta requests", "vad ligger för granskning", "lägg in klassningen i Asana", "skriv tillbaka", "flytta ärendet", "skapa en request", eller när output från `request-write-spec`, `request-assess-impact` eller `request-
|
|
3
|
+
description: Läser och skriver requests mot ärendesystemet (idag Asana), och sätter upp kopplingen mellan ett repo och rätt kundbräda. Använd alltid när användaren vill "sätt upp", "koppla repot mot Asana", "kör uppsättningen", "hämta requests", "vad ligger för granskning", "lägg in klassningen i Asana", "skriv tillbaka", "flytta ärendet", "skapa en request", eller när output från `request-write-spec`, `request-assess-impact` eller `request-score` ska landa på brädan. Detta är den enda skillen som känner till fältnamn, statusvärden och brädor — alla andra skills i request-familjen arbetar mot domän-JSON.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# request-sync
|
|
@@ -65,7 +65,7 @@ Hämta brädans sektioner. **En sektion:** ingen fråga, använd den. **Flera se
|
|
|
65
65
|
|
|
66
66
|
Hämta fälten på båda brädorna och gå igenom tre saker:
|
|
67
67
|
|
|
68
|
-
- **Finns fälten?** Kundbrädan behöver `Original request`, `Customer`, `Requested by` och kundstatusfältet. Samlingsbrädan behöver dessa plus `Offer status`, `Source`, `Request type`, `Funding`, `
|
|
68
|
+
- **Finns fälten?** Kundbrädan behöver `Original request`, `Customer`, `Requested by` och kundstatusfältet. Samlingsbrädan behöver dessa plus `Offer status`, `Source`, `Request type`, `Funding`, `Score`, `Conditional classification`, `Decision`, `Decision reason`, `Spec iterations`, `Class iterations`.
|
|
69
69
|
- **Är de delade fälten samma fält?** Jämför gid, inte namn. Två fält med samma namn och olika gid är en dubblett — de driftar isär omedelbart, och felet syns först hos kunden.
|
|
70
70
|
- **Går alternativen att slå upp?** Normaliserat, enligt regeln ovan. Rapportera vilka som inte gick att matcha och vilka alternativ som faktiskt finns.
|
|
71
71
|
|
|
@@ -112,24 +112,41 @@ Hittar du inte ett fält du ska skriva till: avbryt och säg vilket. Skapa aldri
|
|
|
112
112
|
|
|
113
113
|
Domän-JSON till vänster, fältnamn till höger. Ingen annan skill får se högerkolumnen.
|
|
114
114
|
|
|
115
|
-
| Domän
|
|
116
|
-
|
|
|
117
|
-
| `request_id`
|
|
118
|
-
| `original_request`
|
|
119
|
-
| `source`
|
|
120
|
-
| `customer`
|
|
121
|
-
| `requested_by`
|
|
122
|
-
| `type`
|
|
123
|
-
| `funding`
|
|
124
|
-
| `spec`
|
|
125
|
-
| `impact`
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
|
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
115
|
+
| Domän | Fält | Not |
|
|
116
|
+
| --------------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| `request_id` | task gid | — |
|
|
118
|
+
| `original_request` | Original request + beskrivningens `## Ursprunglig förfrågan` | Skrivs en gång. Ändras aldrig, inte ens för stavfel |
|
|
119
|
+
| `source` | Source | Kunden skrev själv · Mejl · Möte · Haus initierade |
|
|
120
|
+
| `customer` | Customer | |
|
|
121
|
+
| `requested_by` | Requested by | people-fält, kräver user gid |
|
|
122
|
+
| `type` | Request type | Bug · Improvement · Wear |
|
|
123
|
+
| `funding` | Funding | |
|
|
124
|
+
| `spec` | beskrivningens `## Specifikation` | Bara den sektionen. Låst efter `Decision`, se nedan |
|
|
125
|
+
| `impact` | subtask `Påverkansanalys vN` | Ny subtask varje varv, aldrig en redigering |
|
|
126
|
+
| `bracket` + `drivers` | subtask `Poängsättning vN` | Ny subtask varje varv, aldrig en redigering |
|
|
127
|
+
| `score` | Score | Skrivs aldrig över efter kundbeslut. Aldrig på en förälder som har poängsatta delar |
|
|
128
|
+
| `outcome: DELA_UPP` | ingen `Score`, posten stannar på ✅ Spec ok | Se _Uppdelade poster_. Räknarna rörs inte — de räknar granskningsvarv, och ingen människa har skickat tillbaka posten |
|
|
129
|
+
| delposter | subtask per del, med `Score` | Se _Uppdelade poster_ |
|
|
130
|
+
| `customer_rationale` | beskrivningens `## Lösning och omfattning` | Låst efter `Decision`, se nedan |
|
|
131
|
+
| `assumptions_stated` ej tom | beskrivningens `## Lösning och omfattning` | Kunden godkänner dem med priset |
|
|
132
|
+
| `alternatives` ej tom | beskrivningens `## Lösning och omfattning` | Egna vägar med var sitt pris |
|
|
133
|
+
| `conditions` ej tom | Conditional classification | se nedan |
|
|
134
|
+
| `decision` | Decision | Beställd · Ej beställd · Uppskjuten |
|
|
135
|
+
| `decision_reason` | Decision reason | |
|
|
136
|
+
|
|
137
|
+
**`Class rationale` skrivs inte längre.** Motiveringen bor i beskrivningens `## Lösning och omfattning`,
|
|
138
|
+
där kunden faktiskt läser den. Att skriva samma text på två ställen gav två versioner
|
|
139
|
+
som kunde glida isär, och fältet fanns bara för att kunna filtrera på en text ingen
|
|
140
|
+
filtrerar på. Fältet får ligga kvar i Asana med sina gamla värden som historik.
|
|
141
|
+
|
|
142
|
+
**Två fältnamn är historiska.** `Class iterations` och `Conditional classification`
|
|
143
|
+
döptes när modellen satte klasser i stället för poäng. Innehållet är oförändrat i sak —
|
|
144
|
+
en varvräknare och en ja/nej-flagga för villkor — så de används vidare under sina gamla
|
|
145
|
+
namn hellre än att data flyttas. Döps de om i Asana måste den här mappningen uppdateras
|
|
146
|
+
samtidigt.
|
|
147
|
+
|
|
148
|
+
`Score` är ett taltfält. Skriv poängen som tal, inte som text, så att den går att
|
|
149
|
+
sortera och summera i listvyer.
|
|
133
150
|
|
|
134
151
|
`Conditional classification` är härledd: villkorslistan är tom eller inte. Sätt alltid ett värde när en klassning skrivs — aldrig tomt, eftersom tomt är reserverat för "ingen har klassat än". Bär fältet flera värden samtidigt är det felkonfigurerat som flerval; skriv ett värde och säg till.
|
|
135
152
|
|
|
@@ -156,10 +173,10 @@ _Vad vi föreslår att vi bygger — uppdaterad 2026-08-21_
|
|
|
156
173
|
|
|
157
174
|
---
|
|
158
175
|
|
|
159
|
-
##
|
|
160
|
-
|
|
176
|
+
## Lösning och omfattning
|
|
177
|
+
_Vad vi gör, vad det bygger på och vad det kostar — uppdaterad 2026-08-21_
|
|
161
178
|
|
|
162
|
-
[
|
|
179
|
+
[kundvänlig motivering, därunder eventuella förutsättningar]
|
|
163
180
|
```
|
|
164
181
|
|
|
165
182
|
**Linjen mellan sektionerna** skrivs som `<hr/>`. Asana renderar den i tasks — till skillnad från `<h3>`, som inte finns i beskrivningsfältet. Håll dig därför till `<h2>` för rubrikerna (`##` i markdown), aldrig en nivå djupare.
|
|
@@ -172,49 +189,144 @@ Raden är i kundens språk, precis som resten av beskrivningen. Aldrig skillnamn
|
|
|
172
189
|
|
|
173
190
|
Varje sektion har exakt en källa:
|
|
174
191
|
|
|
175
|
-
| Sektion
|
|
176
|
-
|
|
|
177
|
-
| `## Ursprunglig förfrågan`
|
|
178
|
-
| `## Specifikation`
|
|
179
|
-
| `##
|
|
192
|
+
| Sektion | Källa | Skrivs |
|
|
193
|
+
| --------------------------- | --------------------------------------------------------------------------- | ---------------------------- |
|
|
194
|
+
| `## Ursprunglig förfrågan` | kundens ord, eller utdraget ur mejl/brief | vid skapandet, sedan aldrig |
|
|
195
|
+
| `## Specifikation` | `request-write-spec` | vid varje specvarv |
|
|
196
|
+
| `## Lösning och omfattning` | `request-score`: `customer_rationale`, `assumptions_stated`, `alternatives` | vid varje poängsättningsvarv |
|
|
180
197
|
|
|
181
198
|
- **Varje steg uppdaterar bara sin egen sektion och lämnar resten orörd.** Tidigare skrev bara en skill till beskrivningen; nu skriver flera, och ett steg som ersätter hela fältet raderar de andras arbete. Det gäller även datumraden: du uppdaterar din egen sektions rad, aldrig någon annans.
|
|
182
199
|
- **Ursprunglig förfrågan** är kundens ord ordagrant, eller det relevanta utdraget ur ett längre mejl eller en brief. Skrivs en gång, redigeras aldrig. Fältet `Original request` behålls parallellt som skrivskyddad kopia.
|
|
183
|
-
- **
|
|
200
|
+
- **Lösning och omfattning** är den kundvänliga motiveringen, de förutsättningar posten vilar på, och eventuella alternativa lösningar med var sitt pris. Inga protokolltermer, inga frågerader, **och aldrig poängen** — den är ett internt mått och står bara i `Score`-fältet. Ordningen är motivering, förutsättningar, alternativ. Förutsättningarna godkänner kunden tillsammans med priset; alternativen är andra vägar kunden kan välja i stället, var och en med sitt eget pris.
|
|
184
201
|
|
|
185
202
|
Beskrivningen har ingen sektion för vägval, och ska inte få en. Val som påverkar vad kunden får besvaras i specen — som acceptanskriterium eller under _Ingår inte_ — och är därmed redan en del av `## Specifikation`. Val som bara rör hur vi bygger är interna och ligger i påverkansanalysens subtask.
|
|
186
203
|
|
|
187
204
|
### Beskrivningen låses vid kundbeslut
|
|
188
205
|
|
|
189
|
-
Så snart `Decision` har ett värde är beskrivningen det kunden sagt ja till. **Skriv inte om någon sektion efter den punkten.** Det gäller alla tre sektionerna, oavsett
|
|
206
|
+
Så snart `Decision` har ett värde är beskrivningen det kunden sagt ja till. **Skriv inte om någon sektion efter den punkten.** Det gäller alla tre sektionerna, oavsett vad som ändrats.
|
|
190
207
|
|
|
191
208
|
Ägartabellen ovan beskriver vad som gäller fram till kundbeslutet.
|
|
192
209
|
|
|
193
210
|
**Läs `Decision` innan varje skrivning till beskrivningen.** Det är en kontroll som ska utföras, inte något du antar utifrån ärendets status — ett ärende kan ha fått ett beslut mellan två av dina körningar.
|
|
194
211
|
|
|
195
|
-
|
|
212
|
+
Körs poängsättningen om på ett ärende som redan har ett `Decision`: skriv inte över
|
|
213
|
+
`Score`. Lägg poängsättningen som ny subtask, flytta posten till ⚖️ och stanna där. En
|
|
214
|
+
människa avgör vad kunden får se, och det blir i så fall ett nytt beslut — inte en tyst
|
|
215
|
+
redigering av ett gammalt. Kontrollera `Decision` innan du skriver, oavsett varför
|
|
216
|
+
körningen gjordes.
|
|
196
217
|
|
|
197
|
-
|
|
218
|
+
Skälet: en poäng som stiger är en prishöjning. Sker den genom att beskrivningen skrivs om finns inget spår av vad kunden faktiskt godkände, och den fria exiten — modellens centrala kundlöfte — går inte att lösa ut, eftersom det inte längre går att visa vad som ändrats.
|
|
198
219
|
|
|
199
|
-
|
|
220
|
+
Låset gäller beskrivningen och `Score`, som bär den poäng priset räknades ur. Det som är internt får alltid skrivas — subtaskarna (det är hela poängen med dem: de är löpnumrerade och ärver inte kundbrädan, så en poängsättning efter kundbeslut landar där som vanligt), `Conditional classification` och räknarna.
|
|
200
221
|
|
|
201
|
-
|
|
222
|
+
### Grindläget stöds inte
|
|
202
223
|
|
|
203
|
-
|
|
224
|
+
`request-score` kan köras som grind på en fullständig implementationsplan, med
|
|
225
|
+
`mode: "guard"`. **Den utdatan skrivs inte till Asana.** Det finns inget fält för en
|
|
226
|
+
reviderad poäng och inget ska skapas förrän någon bestämt hur en avvikelse ska hanteras
|
|
227
|
+
mot kund.
|
|
228
|
+
|
|
229
|
+
Får du en körning med `mode: "guard"`: avbryt, säg att grindläget inte synkas, och
|
|
230
|
+
lämna ärendet orört. Skriv aldrig guard-poängen till `Score` — det fältet bär den
|
|
231
|
+
poäng kunden fick sitt pris på.
|
|
232
|
+
|
|
233
|
+
**Påverkansanalys och poängsättning läggs som subtasks, inte som kommentarer.**
|
|
204
234
|
|
|
205
235
|
```
|
|
206
236
|
Ticket
|
|
207
237
|
├── beskrivning: de tre sektionerna ovan
|
|
208
|
-
|
|
209
|
-
|
|
238
|
+
└── Analys (subtask, ingen Score)
|
|
239
|
+
├── Påverkansanalys vN (under Analys, ingen Score)
|
|
240
|
+
└── Poängsättning vN (under Analys, ingen Score)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Är posten uppdelad, eller saknar den berörda ytan tester, tillkommer arbetsrader ovanför
|
|
244
|
+
Analys:
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
Ticket (ingen egen Score — bär summan av delarna)
|
|
248
|
+
├── beskrivning: de tre sektionerna ovan
|
|
249
|
+
├── Testtäckning – <yta> (subtask, egen Score)
|
|
250
|
+
├── <delmängd A> (subtask, egen Score)
|
|
251
|
+
├── <delmängd B> (subtask, egen Score)
|
|
252
|
+
└── Analys (subtask, ingen Score)
|
|
253
|
+
├── Påverkansanalys vN (under Analys, ingen Score)
|
|
254
|
+
└── Poängsättning vN (under Analys, ingen Score)
|
|
210
255
|
```
|
|
211
256
|
|
|
257
|
+
Se _Analysraderna_ längre ned för vad Analys är och när den skapas.
|
|
258
|
+
|
|
212
259
|
- Subtasks ärver inte kundbrädan och är därmed interna. Kontrollera att de inte multi-homas dit.
|
|
213
|
-
- **Ett nytt varv skapar en ny subtask, den ersätter inte den förra.** Namnge med löpnummer: "
|
|
214
|
-
-
|
|
215
|
-
- Finns flera parallella underlag namnges subtaskarna så parningen syns: "
|
|
260
|
+
- **Ett nytt varv skapar en ny subtask, den ersätter inte den förra.** Namnge med löpnummer: "Poängsättning v1", "Poängsättning v2". Beskrivningen kan inte bära historik, och det är subtaskarna som ger den tillbaka — i läsbar form, till skillnad från en kommentarsström.
|
|
261
|
+
- Poängsättningens subtask innehåller inringningen i läsbar form — vilka referensuppdrag posten vägdes mot, om den är större eller mindre än vart och ett, och på vilken grund — och därunder den råa JSON:en. Inringningen är det granskaren läser: en rimlig poäng med fel jämförelser är en felkalibrering som råkade landa rätt.
|
|
262
|
+
- Finns flera parallella underlag namnges subtaskarna så parningen syns: "Poängsättning — delad komponent", "Poängsättning — specialfall".
|
|
263
|
+
|
|
264
|
+
**Kommentarer används inte längre för påverkansanalys eller poängsättning.** De är kvar för det som faktiskt kommenteras — feedback vid bakåtflytt — och där gäller fortsatt att de är append-only och aldrig redigeras.
|
|
265
|
+
|
|
266
|
+
## Uppdelade poster
|
|
267
|
+
|
|
268
|
+
Har poängsättningen svarat `DELA_UPP` och analysen pekat ut delarna, skriv dem så här.
|
|
269
|
+
|
|
270
|
+
**Delar som går att leverera för sig blir egna requests** på kundbrädan, alltså vanliga
|
|
271
|
+
tasks och inte subtasks. De har egen spec, eget beslut och egen leverans. Länka dem till
|
|
272
|
+
ursprungsposten.
|
|
273
|
+
|
|
274
|
+
**Delar som inte går att leverera för sig blir subtasks** under posten, en per del, med
|
|
275
|
+
sin poäng i `Score` och en rad överst som säger vilken del av specen delen täcker.
|
|
276
|
+
|
|
277
|
+
**Föräldern får ingen egen `Score`.** Har fältet ett värde sedan tidigare nollas det när
|
|
278
|
+
den första poängsatta subtasken skapas. Asana rullar upp barnens värden till den
|
|
279
|
+
hopfällda föräldern, så ett eget värde där dubbelräknas eller döljer summan.
|
|
280
|
+
|
|
281
|
+
**`Score` skrivs bara på arbetsrader.** `Analys` och raderna under den lämnas tomma.
|
|
282
|
+
Analysraderna blir fler för varje granskningsvarv, och ett värde där skulle räkna
|
|
283
|
+
omarbetning som omfattning.
|
|
284
|
+
|
|
285
|
+
**En del som blev `OVER_SKALAN` får ingen siffra.** Föräldern visar då en summa plus en
|
|
286
|
+
rad utan värde. Det är ett riktigt utfall, inte ett fel — skriv i beskrivningens
|
|
287
|
+
`## Lösning och omfattning` att en del offereras separat, i kundens språk.
|
|
288
|
+
|
|
289
|
+
### Saknad testtäckning
|
|
290
|
+
|
|
291
|
+
Rapporterar analysen att den berörda ytan saknar automatiska tester: skapa en subtask
|
|
292
|
+
`Testtäckning – <yta>` med egen poäng, och gör implementationens subtask beroende av den
|
|
293
|
+
med `add_dependencies`.
|
|
294
|
+
|
|
295
|
+
Ordningen bär något. Testerna ska beskriva hur ytan fungerar innan ändringen görs;
|
|
296
|
+
skrivna efteråt beskriver de det nya beteendet och bevisar ingenting om vad som gick
|
|
297
|
+
sönder. Testraden hör därför till bygget, inte till förarbetet, trots att den kommer
|
|
298
|
+
först.
|
|
299
|
+
|
|
300
|
+
### Analysraderna
|
|
301
|
+
|
|
302
|
+
**Alla rader som hör till undersökningen ligger under en subtask som heter `Analys`** –
|
|
303
|
+
påverkansanalys, klassning och poängsättning, ett varv i taget. De ligger alltså ett steg
|
|
304
|
+
längre in än arbetsraderna, inte bredvid dem.
|
|
305
|
+
|
|
306
|
+
Skälet är att analysraderna blir många. Varje granskningsvarv lägger till en, och de
|
|
307
|
+
läses sällan efter att posten är prissatt. Ligger de på samma nivå som arbetet dränker de
|
|
308
|
+
det som faktiskt ska byggas.
|
|
309
|
+
|
|
310
|
+
**En `Analys` per post.** Den skapas första gången en analysrad ska skrivas och
|
|
311
|
+
återanvänds sedan. Ett nytt varv lägger en ny rad under den, aldrig en ny `Analys`.
|
|
312
|
+
Hittar du redan en: använd den, skapa ingen andra.
|
|
313
|
+
|
|
314
|
+
**`Analys` ligger sist i listan**, under arbetsraderna. Arbetet är det man öppnar posten
|
|
315
|
+
för att se.
|
|
316
|
+
|
|
317
|
+
**Varken `Analys` eller dess rader får `Score`.** Asana rullar upp barnens värden till
|
|
318
|
+
föräldern, så ett värde där skulle räknas in i postens summa. Analysraderna blir dessutom
|
|
319
|
+
fler för varje granskningsvarv, och ett värde på dem skulle räkna omarbetning som
|
|
320
|
+
omfattning.
|
|
321
|
+
|
|
322
|
+
**Arbetsraderna ligger kvar direkt under posten.** Testtäckning enligt _Saknad
|
|
323
|
+
testtäckning_ och delarna enligt _Uppdelade poster_ är arbete som ska byggas och bär egen
|
|
324
|
+
`Score`, så de ska synas utan att någon fäller ut något.
|
|
216
325
|
|
|
217
|
-
|
|
326
|
+
Bygg ingen gruppering ovanpå den här. Namnprefix på raderna och Asanas avdelare
|
|
327
|
+
(`is_rendered_as_separator`) användes tidigare för att skilja bygget från förarbetet, och
|
|
328
|
+
båda är borttagna: nästlingen gör samma sak, och avdelarna gick bara att sätta med ett
|
|
329
|
+
direkt anrop mot Asanas API som de flesta sessioner inte kan göra.
|
|
218
330
|
|
|
219
331
|
## Var en request kommer ifrån
|
|
220
332
|
|
|
@@ -246,12 +358,12 @@ Blir det flera requests ur samma underlag får var och en **sitt eget relevanta
|
|
|
246
358
|
|
|
247
359
|
Kundbrädan bär bara det kunden ska se. Interna fält finns enbart på samlingsbrädan, och en task kan inte bära dem förrän den ligger i båda projekten.
|
|
248
360
|
|
|
249
|
-
| Bara samlingsbrädan | Delade, syns på båda
|
|
250
|
-
| ---------------------------------- |
|
|
251
|
-
| Offer status · Source | Kundstatus
|
|
252
|
-
| Class
|
|
253
|
-
|
|
|
254
|
-
|
|
|
361
|
+
| Bara samlingsbrädan | Delade, syns på båda |
|
|
362
|
+
| ---------------------------------- | ------------------------------------------ |
|
|
363
|
+
| Offer status · Source | Kundstatus |
|
|
364
|
+
| Spec iterations · Class iterations | Original request · Customer · Requested by |
|
|
365
|
+
| Conditional classification | Request type · Funding |
|
|
366
|
+
| | Score · Decision · Decision reason |
|
|
255
367
|
|
|
256
368
|
Ett delat fält är **samma** fält på båda brädorna. Finns två fält med samma namn och olika gid är det en dubblett, inte en delning — de driftar isär omedelbart och felet syns först när en kund tittar på något som inte uppdaterats. Upptäcker du en sådan: skriv inte till någon av dem, säg till.
|
|
257
369
|
|
|
@@ -298,13 +410,13 @@ Matcha alltid på gid. `customer_section_name` finns bara för att kunna visas i
|
|
|
298
410
|
|
|
299
411
|
**Offer status** driver arbetet. Varje läge är en inkorg:
|
|
300
412
|
|
|
301
|
-
| Status
|
|
302
|
-
|
|
|
303
|
-
| 📥 Inkommet
|
|
304
|
-
| 🔍 Spec redo för granskning
|
|
305
|
-
| ✅ Spec ok
|
|
306
|
-
| ⚖️
|
|
307
|
-
| 📤 Klar – till kund
|
|
413
|
+
| Status | Väntar på |
|
|
414
|
+
| ------------------------------------ | ----------------------------------------- |
|
|
415
|
+
| 📥 Inkommet | `request-write-spec` |
|
|
416
|
+
| 🔍 Spec redo för granskning | människa |
|
|
417
|
+
| ✅ Spec ok | `request-assess-impact` + `request-score` |
|
|
418
|
+
| ⚖️ Poängsättning redo för granskning | människa |
|
|
419
|
+
| 📤 Klar – till kund | kundens svar, se `Decision` |
|
|
308
420
|
|
|
309
421
|
**Request status** är kundens vy och härleds av en Asana-regel ur `Offer status`. **Sätt den en enda gång — vid skapandet, innan `Offer status` finns att härleda ur.** Därefter rör du den aldrig; regeln äger den. Startvärdet ska vara exakt det regeln hade härlett, så att fälten aldrig kan säga emot varandra.
|
|
310
422
|
|
|
@@ -316,9 +428,9 @@ Skälet: `Request status` är det enda i hela flödet en kund ser. Två fält so
|
|
|
316
428
|
|
|
317
429
|
Feedback är en kommentar plus en flytt bakåt till närmaste AI-läge. Två regler:
|
|
318
430
|
|
|
319
|
-
**Bumpa räknaren.** Bakåt till 📥 höjer `Spec iterations`. Bakåt till ✅ höjer `Class iterations
|
|
431
|
+
**Bumpa räknaren.** Bakåt till 📥 höjer `Spec iterations`. Bakåt till ✅ höjer `Class iterations` (fältets namn är historiskt; det räknar poängsättningsvarv). Läs nuvarande värde och skriv värdet plus ett — sätt aldrig ett absolut tal från minnet.
|
|
320
432
|
|
|
321
|
-
**Ändrad spec ogiltigförklarar
|
|
433
|
+
**Ändrad spec ogiltigförklarar poängsättningen.** Går en post bakåt till 📥 ska den passera ⚖️ igen på vägen tillbaka. Skriv inte över `Score` med ett nytt värde utan att posten gjort den resan.
|
|
322
434
|
|
|
323
435
|
En bakåtflytt producerar en ny spec, och den ska in i beskrivningen — läs därför `Decision` först. Har posten redan ett kundbeslut gäller låsregeln och beskrivningen rörs inte.
|
|
324
436
|
|
|
@@ -330,7 +442,7 @@ En bakåtflytt producerar en ny spec, och den ska in i beskrivningen — läs d
|
|
|
330
442
|
|
|
331
443
|
**Skriver till beskrivningen utan att ha läst `Decision`.** Har ärendet ett kundbeslut är beskrivningen låst, oavsett vad som ändrats.
|
|
332
444
|
|
|
333
|
-
**Rör `
|
|
445
|
+
**Rör `Score` efter att `Decision` är satt.** De bär samma innehåll som beskrivningen och är låsta med den. Blir poängen högre vid en omkörning är det ett nytt beslut, inte en överskrivning — poängsättningen läggs som subtask, posten flyttas tillbaka till ⚖️, och människan avgör vad kunden får se.
|
|
334
446
|
|
|
335
447
|
**Sätter `Decision` själv.** Det är kundens svar, det kommer utifrån.
|
|
336
448
|
|
package/package.json
CHANGED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
# project:cloneandsetup procedure
|
|
2
|
-
|
|
3
|
-
Clone a project's repos **and** set each one up for local development — node version, dependencies, databases, cross-repo links, and env. This is `project:clone` followed by a per-repo setup pass and a localdev orchestration pass.
|
|
4
|
-
|
|
5
|
-
**Always ask before doing work — never assume.** Cloning and setup hit the network, need auth, and can touch databases; confirm before each phase, and respect repos the user already has.
|
|
6
|
-
|
|
7
|
-
## Step 1 — Clone
|
|
8
|
-
|
|
9
|
-
Run the full `project:clone` flow by following `references/clone.md` end to end (name → one repo; no name → workspace repos from `repos.manifest.json`). Carry the resulting repo list into Step 2.
|
|
10
|
-
|
|
11
|
-
## Step 2 — Prerequisite gate (one consolidated check)
|
|
12
|
-
|
|
13
|
-
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:
|
|
14
|
-
|
|
15
|
-
- **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.
|
|
16
|
-
- **Docker daemon** — running (`docker info`); required for any repo's `needs:` services.
|
|
17
|
-
- **PHP environment** — `valet`, `herd`, `ddev`, or `php` on PATH, if any repo is a PHP/WordPress site.
|
|
18
|
-
- **WP-CLI** — `wp --version`, if any repo's `seed:` pulls a WordPress DB.
|
|
19
|
-
- **Node versions** — the versions named in each repo's `.nvmrc` / `engines.node` are installable via `nvm`.
|
|
20
|
-
- **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.
|
|
21
|
-
|
|
22
|
-
### Data-access preflight
|
|
23
|
-
|
|
24
|
-
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.
|
|
25
|
-
|
|
26
|
-
1. **Collect every seed:** each repo's `seed` plus the workspace `seeds`. For each, determine its required access:
|
|
27
|
-
- If `seed` is an **object with `access`**, run each entry's `check` (a read-only probe; exit 0 = reachable).
|
|
28
|
-
- 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.
|
|
29
|
-
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).
|
|
30
|
-
3. **Resolve every ✗ with one consolidated choice**, then remember it for the run:
|
|
31
|
-
- **Set up access now** → print the `login` text, let the user do it, **re-check**, then seed normally.
|
|
32
|
-
- **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.
|
|
33
|
-
- **Abort.**
|
|
34
|
-
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.
|
|
35
|
-
|
|
36
|
-
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."
|
|
37
|
-
|
|
38
|
-
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.
|
|
39
|
-
|
|
40
|
-
## Step 3 — Per-repo dependency pass
|
|
41
|
-
|
|
42
|
-
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.
|
|
43
|
-
|
|
44
|
-
**Leave `.env` to Step 4 → "Env".** The dependency pass installs only; the env phase writes each repo's `.env` deterministically once services, links, and values are in place.
|
|
45
|
-
|
|
46
|
-
## Step 4 — Local-dev orchestration
|
|
47
|
-
|
|
48
|
-
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 3 only.
|
|
49
|
-
|
|
50
|
-
### House conventions (the "how", so repos specify only the "what")
|
|
51
|
-
|
|
52
|
-
Specify the least. Infer the rest from the repo's stack + these conventions, and work out the concrete commands at run time:
|
|
53
|
-
|
|
54
|
-
- **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.
|
|
55
|
-
- **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`.
|
|
56
|
-
- **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.
|
|
57
|
-
- **Dependencies** install from the lockfile (Step 3).
|
|
58
|
-
- 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.
|
|
59
|
-
|
|
60
|
-
### The `localdev.yml` format
|
|
61
|
-
|
|
62
|
-
**Per-repo — `<repo>/.haus-workflow/localdev.yml`** (how to set up THAT repo alone; sibling-agnostic):
|
|
63
|
-
|
|
64
|
-
All fields optional. **Prefer intent (`needs`/`build`/`seed`/`serve`) over explicit `steps`** —
|
|
65
|
-
the conventions above turn intent into commands.
|
|
66
|
-
|
|
67
|
-
```yaml
|
|
68
|
-
needs: [mysql] # services this repo requires; provisioned in Docker, env wired to them
|
|
69
|
-
needsEnv: [WP_HOME, DB_NAME] # env keys that must be present to run/serve
|
|
70
|
-
build: 'yarn build' # optional: the build command (run after deps)
|
|
71
|
-
seed: 'dep db:pull staging-oderland' # optional: how to load data; remote/destructive → confirm first
|
|
72
|
-
# `seed` may also be an OBJECT when the load needs external access (AWS, SSH, …) so the
|
|
73
|
-
# Step-2 data-access preflight can check it up front and offer an empty-DB fallback:
|
|
74
|
-
seed:
|
|
75
|
-
run: './scripts/pull-aws-db.sh --yes' # the seed command
|
|
76
|
-
empty-ok: true # default true: skipping yields a coherent EMPTY DB (schema via migrations).
|
|
77
|
-
# # false = the repo is broken without data → a missing-access gap HARD-STOPS.
|
|
78
|
-
access: # what the user must have to run `run`; each entry probed in the Step-2 preflight
|
|
79
|
-
- label: 'AWS CLI credentials — eu-north-1 / 842675986884'
|
|
80
|
-
check: 'aws sts get-caller-identity --region eu-north-1' # read-only probe; exit 0 = OK
|
|
81
|
-
login: 'Set up the AWS CLI (brew install awscli; aws configure …) — or pick empty DB.'
|
|
82
|
-
serve: # optional: how it runs (printed as a next-step, never auto-started)
|
|
83
|
-
via: herd # herd | valet | docker | command — for PHP envs, bring-your-own (don't install)
|
|
84
|
-
url: 'https://example.test'
|
|
85
|
-
start: 'yarn dev'
|
|
86
|
-
steps: # optional escape hatch: explicit ordered shell steps when intent isn't enough
|
|
87
|
-
- run: 'composer install'
|
|
88
|
-
node: 10 # nvm version for THIS step
|
|
89
|
-
remote: true # SSH → confirm first
|
|
90
|
-
destructive: true # overwrites data → confirm first
|
|
91
|
-
optional: true # failure is non-fatal, continue
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
**Workspace — `<workspace>/.haus-workflow/localdev.yml`** (the glue BETWEEN repos):
|
|
95
|
-
|
|
96
|
-
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.
|
|
97
|
-
|
|
98
|
-
```yaml
|
|
99
|
-
order: [repo-a, repo-b] # setup/startup order, by manifest id
|
|
100
|
-
links:
|
|
101
|
-
- { type: symlink, from: <repo-folder>, to: <repo-folder>/path/to/link }
|
|
102
|
-
- { type: composer-path, in: <repo>, dep: <sibling-repo> }
|
|
103
|
-
- { type: yarn-link, in: [<repo>, ...], dep: <sibling-package-repo> }
|
|
104
|
-
env:
|
|
105
|
-
- value: 'app_local' # a chosen literal — the recorded source of truth, OR …
|
|
106
|
-
# source: { repo: <repo>, provides: '<value>' } # … a value produced by another repo
|
|
107
|
-
sinks:
|
|
108
|
-
- { repo: <repo>, key: DB_NAME } # written under `key` into each sink's .env
|
|
109
|
-
seeds: # optional: data sources NOT owned by a manifest repo (e.g. a shared WordPress host
|
|
110
|
-
# pulled by a workspace script). Same shape as a per-repo `seed` object; included in
|
|
111
|
-
# the Step-2 data-access preflight.
|
|
112
|
-
- label: 'WordPress host (example.se)'
|
|
113
|
-
run: './scripts/pull-wp-example.sh'
|
|
114
|
-
empty-ok: true
|
|
115
|
-
access:
|
|
116
|
-
- label: 'SSH user@host'
|
|
117
|
-
check: 'ssh -o BatchMode=yes -o ConnectTimeout=5 user@host true'
|
|
118
|
-
login: 'Add your SSH key to the host (ssh-add …) / request access — or skip the pull.'
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Run order
|
|
122
|
-
|
|
123
|
-
1. **Discover** `.haus-workflow/localdev.yml` in the workspace root and each repo.
|
|
124
|
-
2. **Resolve order** from the workspace `order` (repos not listed run last, in manifest order). No workspace file → manifest order.
|
|
125
|
-
3. **Per repo, in order**, apply intent via the conventions:
|
|
126
|
-
- **`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:`.
|
|
127
|
-
- **`build`** → run it (honor any node version the repo's docs note).
|
|
128
|
-
- **`serve`** → for `via: herd` / PHP envs, install nothing — verify the dev's environment serves `web/`, and report the URL.
|
|
129
|
-
- **`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-2 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.
|
|
130
|
-
- **`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.
|
|
131
|
-
4. **Links** (workspace-owned, performed generically — do NOT call a repo's own `setup-dev-mode.sh`, which is deprecated):
|
|
132
|
-
- `symlink` → `ln -s <from> <to>`; replace an existing symlink, but never clobber a real directory without confirmation.
|
|
133
|
-
- `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>`.
|
|
134
|
-
- `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`).
|
|
135
|
-
5. **Env (deterministic):** write each repo's `.env` from known values — no improvising.
|
|
136
|
-
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.
|
|
137
|
-
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.
|
|
138
|
-
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.
|
|
139
|
-
|
|
140
|
-
**Dev-defaults table** — used only when neither `localdev.yml` nor a generated secret supplies the value:
|
|
141
|
-
|
|
142
|
-
| Key | Default |
|
|
143
|
-
| -------------------------------- | ------------------------------------------------------------------- |
|
|
144
|
-
| `DB_HOST` | `127.0.0.1` |
|
|
145
|
-
| `DB_PORT` | `3306` (mysql) / `5432` (postgres) — or the port chosen on conflict |
|
|
146
|
-
| `DB_USER` / `DB_PASSWORD` | `root` / a generated secret |
|
|
147
|
-
| `WP_HOME`, `WP_SITEURL`, `*_URL` | the repo's `serve.url` |
|
|
148
|
-
| `WP_ENV` / `APP_ENV` | `development` |
|
|
149
|
-
|
|
150
|
-
6. **Report, then offer to start.** Give the per-repo summary, then **ask the user whether to start everything now.**
|
|
151
|
-
- **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`).
|
|
152
|
-
- **No** → just print the ordered start commands + follow-ups as next steps; start nothing.
|
|
153
|
-
|
|
154
|
-
**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.
|
|
155
|
-
|
|
156
|
-
## Step 5 — Report and define "done"
|
|
157
|
-
|
|
158
|
-
**"Done" is an explicit terminal state**, not "looks set up". The preparation reaches:
|
|
159
|
-
|
|
160
|
-
- datastores up (workspace `docker compose` project),
|
|
161
|
-
- dependencies installed and builds green,
|
|
162
|
-
- links wired,
|
|
163
|
-
- each repo's `.env` written from known values.
|
|
164
|
-
|
|
165
|
-
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.
|
|
166
|
-
|
|
167
|
-
Summarise per repo (node, deps, localdev steps, links, env). Then **ask whether to start everything now**:
|
|
168
|
-
|
|
169
|
-
- **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.
|
|
170
|
-
- **No** → print the ordered start commands + follow-ups instead, and start nothing.
|
|
171
|
-
|
|
172
|
-
Never start the app without that explicit yes.
|