@haus-tech/haus-workflow 5.9.1 → 5.10.1
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 +13 -0
- package/dist/cli.js +13 -19
- package/library/catalog/manifest.json +4 -4
- package/library/global/agents/request-assessor.md +5 -1
- package/library/global/agents/request-specifier.md +3 -1
- package/library/global/skills/request/SKILL.md +394 -59
- package/library/global/skills/request/scripts/archive.mjs +546 -49
- package/library/global/skills/request/scripts/korning.mjs +41 -4
- package/library/global/skills/request/scripts/lib.mjs +189 -0
- package/library/global/skills/request/scripts/precheck.mjs +105 -130
- package/library/global/skills/request-assess-impact/SKILL.md +12 -0
- package/library/global/skills/request-sync/SKILL.md +61 -100
- package/library/global/skills/request-write-spec/SKILL.md +15 -1
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: request-sync
|
|
3
|
-
description: Reads and writes requests against the ticket system (Asana today), and sets up the binding between a repo and the right customer board. Always use when the user wants "sätt upp", "koppla repot mot Asana", "kör uppsättningen", "hämta requests", "vad ligger för granskning", "lägg in poängen i Asana", "skriv tillbaka", "flytta ärendet", "skapa en request", or when output from `request-write-spec
|
|
3
|
+
description: Reads and writes requests against the ticket system (Asana today), and sets up the binding between a repo and the right customer board. Always use when the user wants "sätt upp", "koppla repot mot Asana", "kör uppsättningen", "hämta requests", "vad ligger för granskning", "lägg in poängen i Asana", "skriv tillbaka", "flytta ärendet", "skapa en request", or when output from `request-write-spec` or `request-score` is to land on the board. This is the only skill that knows field names, status values and boards; every other skill in the request family works against domain JSON.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# request-sync
|
|
@@ -112,28 +112,28 @@ If you cannot find a field you are to write to: abort and say which one. Never c
|
|
|
112
112
|
|
|
113
113
|
Domain JSON on the left, field names on the right. No other skill may see the right-hand column.
|
|
114
114
|
|
|
115
|
-
| Domain | Field | Note
|
|
116
|
-
| ------------------------------ | --------------------------------------------------------------- |
|
|
117
|
-
| `request_id` | task gid | —
|
|
118
|
-
| `original_request` | Original request + the description's `## Ursprunglig förfrågan` | Written once. Never changed, not even for typos
|
|
119
|
-
| `original_request_title` | Original request title | Written once. Never changed, not even for typos. Empty when the customer never wrote a title
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
123
|
-
| `
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
128
|
-
| `score` | Score | Never overwritten after a customer decision. Never on a parent that has scored parts
|
|
129
|
-
| `outcome: DELA_UPP` | no `Score`, the item stays at ✅ Spec ok |
|
|
130
|
-
| split parts | a subtask per part, with `Score` |
|
|
131
|
-
| `customer_rationale` | the description's `## Lösning och omfattning` | Locked after `Decision`, see below
|
|
132
|
-
| `assumptions_stated` non-empty | the description's `## Lösning och omfattning` | The customer approves them with the price
|
|
133
|
-
| `alternatives` non-empty | the description's `## Lösning och omfattning` | Separate routes, each with its own price
|
|
134
|
-
| `conditions` non-empty | Has conditions | see below
|
|
135
|
-
| `decision` | Decision | Beställd · Ej beställd · Uppskjuten
|
|
136
|
-
| `decision_reason` | Decision reason |
|
|
115
|
+
| Domain | Field | Note |
|
|
116
|
+
| ------------------------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| `request_id` | task gid | — |
|
|
118
|
+
| `original_request` | Original request + the description's `## Ursprunglig förfrågan` | Written once. Never changed, not even for typos |
|
|
119
|
+
| `original_request_title` | Original request title | Written once. Never changed, not even for typos. Empty when the customer never wrote a title |
|
|
120
|
+
| `title` (spec) | task name, only in the three automatic cases or on `byt titel` | The customer's title stays otherwise, see _At creation_ |
|
|
121
|
+
| `source` | Source | Kunden skrev själv · Mejl · Möte · Haus initierade |
|
|
122
|
+
| `customer` | Customer | |
|
|
123
|
+
| `requested_by` | Requested by | people field, requires a user gid |
|
|
124
|
+
| `type` | Request type | Bug · Improvement · Wear |
|
|
125
|
+
| `funding` | Funding | |
|
|
126
|
+
| `spec` | the description's `## Specifikation` | Only that section. Locked after `Decision`, see below |
|
|
127
|
+
| `umbrella` (spec) | the description's `## Specifikation` | Only after a human's yes; never a rename. Locked after `Decision`, like `spec` |
|
|
128
|
+
| `score` | Score | Never overwritten after a customer decision. Never on a parent that has scored parts |
|
|
129
|
+
| `outcome: DELA_UPP` | no `Score`, the item stays at ✅ Spec ok | Moves to ⚖️ when the last part's subtask is written, see _Split items_. The counters are not touched: they count review rounds, and no human has sent the item back |
|
|
130
|
+
| split parts | a subtask per part, with `Score` | Always a subtask, never a separate request, see _Split items_ |
|
|
131
|
+
| `customer_rationale` | the description's `## Lösning och omfattning` | Locked after `Decision`, see below |
|
|
132
|
+
| `assumptions_stated` non-empty | the description's `## Lösning och omfattning` | The customer approves them with the price |
|
|
133
|
+
| `alternatives` non-empty | the description's `## Lösning och omfattning` | Separate routes, each with its own price |
|
|
134
|
+
| `conditions` non-empty | Has conditions | see below |
|
|
135
|
+
| `decision` | Decision | Beställd · Ej beställd · Uppskjuten |
|
|
136
|
+
| `decision_reason` | Decision reason | |
|
|
137
137
|
|
|
138
138
|
**`Class rationale` is no longer written.** The rationale lives in the description's `## Lösning och omfattning`,
|
|
139
139
|
where the customer actually reads it. Writing the same text in two places gave two versions
|
|
@@ -154,7 +154,7 @@ Asana converts markdown to `html_notes`. Use `html_notes` for the description, f
|
|
|
154
154
|
|
|
155
155
|
## The description and the subtasks
|
|
156
156
|
|
|
157
|
-
**The description is the only thing anyone actually reads, and it is also what the customer sees.** It must therefore carry the request in readable form and nothing else. The machinery (impact analysis and scoring) lives in subtasks, which do not inherit the customer board and are therefore internal without anyone needing to remember it.
|
|
157
|
+
**The description is the only thing anyone actually reads, and it is also what the customer sees.** It must therefore carry the request in readable form and nothing else. The machinery (impact analysis and scoring) never reaches the board: it lives in the archive (ADR-0144). The subtasks carry only work rows, which do not inherit the customer board and are therefore internal without anyone needing to remember it.
|
|
158
158
|
|
|
159
159
|
The description has these headings, in this order, separated by a horizontal line:
|
|
160
160
|
|
|
@@ -199,7 +199,7 @@ Each section has exactly one source:
|
|
|
199
199
|
- **`## Ursprunglig förfrågan`** is the customer's words verbatim, or the relevant excerpt from a longer email or brief. Written once, never edited. The `Original request` field is kept in parallel as a read-only copy.
|
|
200
200
|
- **`## Lösning och omfattning`** is the customer-friendly rationale, the assumptions the item rests on, and any alternative solutions, each with its own price. No protocol terms, no question rows, **and never the score**: it is an internal measure and appears only in the `Score` field. The order is rationale, assumptions, alternatives. The customer approves the assumptions together with the price; the alternatives are other routes the customer can choose instead, each with its own price.
|
|
201
201
|
|
|
202
|
-
The description has no section for route choices, and must not get one. Choices that affect what the customer gets are answered in the spec, as an acceptance criterion or under _Ingår inte_ / _Not included_, and are thereby already part of `## Specifikation`. Choices that only concern how we build are internal and live in the impact analysis
|
|
202
|
+
The description has no section for route choices, and must not get one. Choices that affect what the customer gets are answered in the spec, as an acceptance criterion or under _Ingår inte_ / _Not included_, and are thereby already part of `## Specifikation`. Choices that only concern how we build are internal and live in the impact analysis, in the archive.
|
|
203
203
|
|
|
204
204
|
### The description is locked at the customer decision
|
|
205
205
|
|
|
@@ -210,14 +210,19 @@ The ownership table above describes what applies up to the customer decision.
|
|
|
210
210
|
**Read `Decision` before every write to the description.** It is a check that must be performed, not something you assume from the ticket's status: a ticket can have received a decision between two of your runs.
|
|
211
211
|
|
|
212
212
|
If the scoring is rerun on a ticket that already has a `Decision`: do not overwrite
|
|
213
|
-
`Score`.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
`Score`. Write one comment on the ticket that says only that the scoring was rerun after the
|
|
214
|
+
customer's decision and that a human will follow up, in the customer's language, with neither
|
|
215
|
+
the new score nor its archive path, move the item to ⚖️ and stop there. The customer reads
|
|
216
|
+
the comments on their board, so the number stays in the archive alone
|
|
217
|
+
(`.haus-workflow/requests/<gid>/score-pricing-vN.json`, read via the orchestrator's
|
|
218
|
+
`detaljer <item>`); the ⚖️ move is invisible to them, `Offer status` lives on the collection
|
|
219
|
+
board only (ADR-0144). A human decides what the customer gets to see, and in that case it
|
|
220
|
+
becomes a new decision, not a silent edit of an old one. Check `Decision` before you write,
|
|
221
|
+
regardless of why the run was made.
|
|
217
222
|
|
|
218
223
|
The reason: a score that rises is a price increase. If it happens by rewriting the description, there is no trace of what the customer actually approved, and the free exit (the model's central customer promise) cannot be exercised, because it is no longer possible to show what has changed.
|
|
219
224
|
|
|
220
|
-
The lock covers the description and `Score`, which carries the score the price was calculated from. What is internal may always be written:
|
|
225
|
+
The lock covers the description and `Score`, which carries the score the price was calculated from. What is internal may always be written: `Has conditions`, the counters and the one comment above.
|
|
221
226
|
|
|
222
227
|
### Guard mode is not supported
|
|
223
228
|
|
|
@@ -230,63 +235,43 @@ If you get a run with `mode: "guard"`: abort, say that guard mode is not synced,
|
|
|
230
235
|
leave the ticket untouched. Never write the guard score to `Score`: that field carries the
|
|
231
236
|
score the customer got their price on.
|
|
232
237
|
|
|
233
|
-
**
|
|
238
|
+
**The machinery stays in the archive (ADR-0144).** The impact analysis and the scorer's comparison are never written to Asana: both live in the archive (`impact.md` and the pricing files), and the reviewer reads them with `/request detaljer <item>`. No `Analys`, `Påverkansanalys vN` or `Poängsättning vN` subtask is created. Existing `Analys` rows on older tickets are left untouched: the flow never deletes on the board.
|
|
234
239
|
|
|
235
|
-
|
|
236
|
-
Ticket
|
|
237
|
-
├── description: the three sections above
|
|
238
|
-
└── Analys (subtask, no Score)
|
|
239
|
-
├── Påverkansanalys vN (under Analys, no Score)
|
|
240
|
-
└── Poängsättning vN (under Analys, no Score)
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
If the item is split, or the affected area lacks tests, work rows are added above
|
|
244
|
-
`Analys`:
|
|
240
|
+
The subtasks are work rows only:
|
|
245
241
|
|
|
246
242
|
```
|
|
247
|
-
Ticket (no Score of its own; carries the sum
|
|
248
|
-
├── description: the three sections
|
|
249
|
-
├── Testtäckning – <yta> (subtask, own Score)
|
|
250
|
-
├── <
|
|
251
|
-
|
|
252
|
-
└── Analys (subtask, no Score)
|
|
253
|
-
├── Påverkansanalys vN (under Analys, no Score)
|
|
254
|
-
└── Poängsättning vN (under Analys, no Score)
|
|
243
|
+
Ticket (no Score of its own when it has parts; carries the roll-up sum)
|
|
244
|
+
├── description: the three sections
|
|
245
|
+
├── Testtäckning – <yta> (subtask, own Score, when the analysis reports missing tests)
|
|
246
|
+
├── <part 1: changed state> (subtask, own Score, one scope line at the top)
|
|
247
|
+
└── <part 2: changed state> (subtask, own Score or none for OVER_SKALAN)
|
|
255
248
|
```
|
|
256
249
|
|
|
257
|
-
See _The `Analys` rows_ further down for what `Analys` is and when it is created.
|
|
258
|
-
|
|
259
250
|
- Subtasks do not inherit the customer board and are therefore internal. Check that they are not multi-homed there.
|
|
260
|
-
-
|
|
261
|
-
- **A new round creates a new subtask; it does not replace the previous one.** Name them with a sequence number: "Poängsättning v1", "Poängsättning v2". The description cannot carry history, and it is the subtasks that give it back, in readable form, unlike a comment stream.
|
|
262
|
-
- The `Poängsättning vN` subtask holds the scorer's comparison in readable form (the `bracket` in the JSON): which items on the reference scale the item was weighed against, whether it is larger or smaller than each of them, and on what grounds; below that, the raw JSON. The comparison is what the reviewer reads: a reasonable score with the wrong comparisons is a miscalibration that happened to land right.
|
|
263
|
-
- If there are several parallel sets of source material, the subtasks are named so the pairing shows: "Poängsättning — delad komponent", "Poängsättning — specialfall".
|
|
251
|
+
- **The work rows stay directly under the item.** Test coverage per _Missing test coverage_ and the parts per _Split items_ are work to be built and carry their own `Score`, so they must be visible without anyone expanding anything.
|
|
264
252
|
|
|
265
|
-
**Comments are
|
|
253
|
+
**Comments are not used for impact analysis or scoring.** They remain for what is actually commented on, feedback on a backward move, plus the single trace of a rerun after a customer decision (_The description is locked at the customer decision_), and there it still applies that they are append-only and never edited.
|
|
266
254
|
|
|
267
255
|
## Split items
|
|
268
256
|
|
|
269
|
-
If the scoring has answered `DELA_UPP` and the analysis has pointed out the parts, write them like this.
|
|
257
|
+
If the scoring has answered `DELA_UPP` and the analysis has pointed out the parts, write them like this (ADR-0144).
|
|
270
258
|
|
|
271
|
-
**
|
|
272
|
-
tasks and not subtasks. They have their own spec, their own decision and their own delivery. Link them to
|
|
273
|
-
the original item.
|
|
259
|
+
**Every part is a subtask under the item**, never a separate request, whether or not the part could be delivered on its own. The customer ordered one thing and decides once, on the parent; the split is internal structure.
|
|
274
260
|
|
|
275
|
-
**
|
|
276
|
-
its score in `Score` and a line at the top that says which part of the spec the part covers.
|
|
261
|
+
**A part's subtask is created in the sync that follows that part's own `bedöm` round.** Name it as the part's changed state, in the spec's language (the same rule as the spec title), put one line at the top that says which part of the parent's spec it covers, in the same language, and write the part's score in `Score`. Report the subtask's gid back to the orchestrator, which records it in the archive. When the orchestrator passes a part together with a recorded subtask gid, update that subtask: never create a second one for the same part.
|
|
277
262
|
|
|
278
263
|
**The parent gets no `Score` of its own.** If the field has a value from before, it is cleared when
|
|
279
264
|
the first scored subtask is created. Asana rolls the children's values up to the
|
|
280
265
|
collapsed parent, so a value of its own there is double-counted or hides the sum.
|
|
281
266
|
|
|
282
|
-
|
|
283
|
-
The `Analys` rows grow in number with every review round, and a value there would count
|
|
284
|
-
rework as scope.
|
|
285
|
-
|
|
286
|
-
**A part that became `OVER_SKALAN` gets no number.** The parent then shows a sum plus a
|
|
267
|
+
**A part that became `OVER_SKALAN` gets a subtask without a number.** The parent then shows a sum plus a
|
|
287
268
|
row without a value. It is a real outcome, not an error: write in the description's
|
|
288
269
|
`## Lösning och omfattning` that a part is quoted separately, in the customer's language.
|
|
289
270
|
|
|
271
|
+
**The parent moves when every part has a score.** While parts remain, the parent stays at ✅ Spec ok. In the run that writes the last part's subtask, move the parent to ⚖️ Poängsättning redo för granskning when the orchestrator reports every part done (`priced`, or an `OVER_SKALAN` outcome, as many parts as the split has) and the board agrees: every part's subtask exists with the matching `Score`, and none for `OVER_SKALAN`. Asana is the truth and the archive says how many parts there are: if a subtask is missing or its `Score` differs, say which one and leave the parent where it is. The customer then decides once, on the parent, with the roll-up sum as the price basis. A later sync of the parent alone, the row `sync föräldern till brädan (request-sync ⚖️)` that `nasta-steg` shows once every part is done, applies the same check and moves the parent; it writes no part subtask.
|
|
272
|
+
|
|
273
|
+
**The umbrella description.** When the orchestrator has rewritten the parent's spec one level up (an umbrella spec, after a human's yes), write the description's `## Specifikation` from it like any spec round: `Decision` read first, the lock applies, and the task is never renamed on the way. The umbrella title reaches the task name only through `byt titel`.
|
|
274
|
+
|
|
290
275
|
### Missing test coverage
|
|
291
276
|
|
|
292
277
|
If the analysis reports that the affected area lacks automated tests: create a subtask
|
|
@@ -298,36 +283,12 @@ written afterwards, they describe the new behavior and prove nothing about what
|
|
|
298
283
|
broke. The test row therefore belongs to the build, not to the preparatory work, even though it comes
|
|
299
284
|
first.
|
|
300
285
|
|
|
301
|
-
###
|
|
302
|
-
|
|
303
|
-
**All rows that belong to the investigation sit under one subtask called `Analys`**:
|
|
304
|
-
impact analysis and scoring, one round at a time. They thus sit one step
|
|
305
|
-
further in than the work rows, not beside them.
|
|
306
|
-
|
|
307
|
-
The reason is that the analysis rows become many. Every review round adds one, and they are
|
|
308
|
-
rarely read after the item is priced. If they sit at the same level as the work, they drown
|
|
309
|
-
what is actually to be built.
|
|
310
|
-
|
|
311
|
-
**One `Analys` per item.** It is created the first time an analysis row is to be written and
|
|
312
|
-
is then reused. A new round adds a new row under it, never a new `Analys`.
|
|
313
|
-
If you already find one: use it, do not create a second.
|
|
314
|
-
|
|
315
|
-
**`Analys` sits last in the list**, below the work rows. The work is what one opens the item
|
|
316
|
-
to see.
|
|
317
|
-
|
|
318
|
-
**Neither `Analys` nor its rows may carry a `Score`.** Asana rolls the children's values up to
|
|
319
|
-
the parent, so a value there would be counted into the item's sum. The analysis rows also grow
|
|
320
|
-
in number with every review round, and a value on them would count rework as
|
|
321
|
-
scope.
|
|
322
|
-
|
|
323
|
-
**The work rows stay directly under the item.** Test coverage per _Missing
|
|
324
|
-
test coverage_ and the parts per _Split items_ are work to be built and carry their own
|
|
325
|
-
`Score`, so they must be visible without anyone expanding anything.
|
|
286
|
+
### No grouping on top of the work rows
|
|
326
287
|
|
|
327
|
-
Build no grouping on top of
|
|
288
|
+
Build no grouping on top of the work rows. Name prefixes on the rows and Asana's separators
|
|
328
289
|
(`is_rendered_as_separator`) were used earlier to separate the build from the preparatory work, and
|
|
329
|
-
both are removed: the
|
|
330
|
-
direct call against Asana's API that most sessions cannot make.
|
|
290
|
+
both are removed: the preparatory rows no longer reach the board, and the separators could only be
|
|
291
|
+
set with a direct call against Asana's API that most sessions cannot make.
|
|
331
292
|
|
|
332
293
|
## Where a request comes from
|
|
333
294
|
|
|
@@ -335,12 +296,12 @@ The origin decides what you do, and it also decides how the source material is t
|
|
|
335
296
|
|
|
336
297
|
### The customer wrote on their own board (`Source` = `Kunden skrev själv`)
|
|
337
298
|
|
|
338
|
-
The customer's words sit in the description and in the task name
|
|
299
|
+
The customer's words sit in the description and in the task name. The description is overwritten later, turned into section form; the task name is kept and is changed only in the three automatic cases or on `byt titel` (_The task name follows the customer_, under _At creation_). Those two, the rewrite and a rename, are the **only destructive operations in the entire flow**, and the order is not negotiable:
|
|
339
300
|
|
|
340
301
|
1. Copy the description verbatim to `Original request` and the task name verbatim to `Original request title`
|
|
341
302
|
2. **Read both fields back and verify that the text has landed**
|
|
342
303
|
3. Only then rewrite the description: the same text verbatim under `## Ursprunglig förfrågan`, the spec under `## Specifikation`
|
|
343
|
-
4. **Never change the task name before `Original request title` has been written and read back.**
|
|
304
|
+
4. **Never change the task name before `Original request title` has been written and read back.** A rename happens only in the three automatic cases or on `byt titel`, often in a later run, and by then the customer's title exists only in that field
|
|
344
305
|
|
|
345
306
|
If you skip step 2 and the write silently fails, the customer's own words are gone forever, which is exactly what `Original request` exists to prevent, and a rename without step 4 does the same thing to the title. `Source` = `Kunden skrev själv`.
|
|
346
307
|
|
|
@@ -350,7 +311,7 @@ Here the agent creates both the task and the spec in one sweep, and the customer
|
|
|
350
311
|
|
|
351
312
|
**Paste the relevant passage from the email or the note verbatim** into `Original request` and under `## Ursprunglig förfrågan` in the description: not a summary, not a paraphrase. The risk of drift is greatest exactly here, because the source material is rawer and the agent has interpreted more.
|
|
352
313
|
|
|
353
|
-
**The title** is the email's subject line or the note's own title, verbatim, in `Original request title`. If the customer never wrote a title, the field is left empty. A title we formulated ourselves looks like the customer's words in the review, and a field that mixes the two is worse than an empty one.
|
|
314
|
+
**The title** is the email's subject line or the note's own title, verbatim, in `Original request title`. If the customer never wrote a title, the field is left empty. A title we formulated ourselves looks like the customer's words in the review, and a field that mixes the two is worse than an empty one. When the source holds several requests, those tasks are named by their spec title (the third automatic case under _The task name follows the customer_); `Original request title` still carries the subject or the note's title on each of them.
|
|
354
315
|
|
|
355
316
|
If several requests come from the same source material, each one gets **its own relevant passage**, not the whole email pasted once per item. A field that contains everything holds nothing.
|
|
356
317
|
|
|
@@ -400,7 +361,7 @@ If you try to set `Offer status` or `Source` in step 1, the lookup aborts, and y
|
|
|
400
361
|
|
|
401
362
|
After creation you never touch `Request status` again. From then on it is derived by the Asana rule from `Offer status`.
|
|
402
363
|
|
|
403
|
-
The task name
|
|
364
|
+
**The task name follows the customer (ADR-0144).** At creation it is the customer's own title (the task name they wrote, the email's subject line or the note's own title), or the request's own words when the customer never wrote one. When the spec is written, the task is renamed to the spec's title (the changed state, not the job) automatically in exactly three cases: the task name is empty; `Original request title` is empty (the customer never wrote a title, so the current name is one the flow made up from the request's own words); or the task is one the flow created as one of several from one source, a bundle sibling minted at sync, or one request out of an email or note that held several (the customer's title then names the source, not that task, while `Original request title` still carries the source title on each of them). All three cases are checked after `Original request title` has been written and read back. In every other case the name stays: it is the title the customer recognizes the item by, and a task the customer created on their own board keeps its name (it is not one the flow created). The orchestrator's `byt titel <item>` is the only other rename, on request: it passes the spec title from the archive, and the same order applies. Either way, the rename requires that `Original request title` carries the original title and that it has been read back (it is empty only when the customer never wrote one), see _The customer wrote on their own board_.
|
|
404
365
|
|
|
405
366
|
### Section on the customer board
|
|
406
367
|
|
|
@@ -447,7 +408,7 @@ A backward move produces a new spec, and it is to go into the description: so re
|
|
|
447
408
|
|
|
448
409
|
**Write to the description without having read `Decision`.** If the ticket has a customer decision, the description is locked, regardless of what has changed.
|
|
449
410
|
|
|
450
|
-
**Touch `Score` after `Decision` is set.** They carry the same content as the description and are locked with it. If the score comes out higher on a rerun, it is a new decision, not an overwrite: the scoring
|
|
411
|
+
**Touch `Score` after `Decision` is set.** They carry the same content as the description and are locked with it. If the score comes out higher on a rerun, it is a new decision, not an overwrite: one comment says only that the scoring was rerun (never the number or the path, the customer reads comments), the item is moved back to ⚖️, and the human decides what the customer gets to see.
|
|
451
412
|
|
|
452
413
|
**Set `Decision` yourself.** It is the customer's answer; it comes from outside.
|
|
453
414
|
|
|
@@ -15,7 +15,7 @@ Read `references/specformat.md` before you write your first spec in a session. I
|
|
|
15
15
|
|
|
16
16
|
## The format
|
|
17
17
|
|
|
18
|
-
Every spec has exactly these headings, in this order. The last three only when needed. Each heading is shown as its Swedish / English literal (see The customer's language below).
|
|
18
|
+
Every spec has exactly these headings, in this order. The last three only when needed. Each heading is shown as its Swedish / English literal (see The customer's language below). An umbrella spec adds one section, see _The umbrella spec (PARAPLY)_.
|
|
19
19
|
|
|
20
20
|
**Title**: the changed state, not the task. "Verksamheter sätter upp sin egen teamstruktur", not "Bygg adminvy för team". The customer prioritises on the title; a task title makes them prioritise work instead of outcomes.
|
|
21
21
|
|
|
@@ -118,6 +118,20 @@ Price-decisive open choice is the only thing in the reviewer block that stops th
|
|
|
118
118
|
|
|
119
119
|
**Custom solution versus platform feature.** If the source manifest, or your own search of the repo, turns up a built-in feature of the platform or an already installed package that covers the request, and you choose to build a custom surface instead, or conversely rule out a working built-in surface in favour of a custom one, it is ALWAYS `Price-decisive open choice: yes` with both alternatives named (built-in feature and custom build respectively), never an entry under Decisions I made. The price difference between the two is at its largest exactly there, and only the customer can choose. A custom build that does not even mention the built-in surface looks like the only way, and the customer pays three times the price without having chosen it.
|
|
120
120
|
|
|
121
|
+
## The umbrella spec (PARAPLY)
|
|
122
|
+
|
|
123
|
+
When the input carries `PARAPLY:`, the item has been split into parts that are priced against the approved spec, and the parent is to be rewritten one level up so that its description covers the whole task. `PARAPLY:` carries the parent's approved spec verbatim and, under `DELAR:`, one line per part: its name (the part's changed state) and its scope line.
|
|
124
|
+
|
|
125
|
+
Write ONE spec in the same format, with the same rules and the same form check as any spec, followed by its own reviewer block:
|
|
126
|
+
|
|
127
|
+
- **The title** is the changed state for the whole task, not for any one part.
|
|
128
|
+
- **Ingår** / **Included** is the umbrella's one extra section, directly after När det är klart / Done when: every part by its name, one line each, taken from `DELAR:` and written in the spec's language. It is the only place the parts are named.
|
|
129
|
+
- **Ingår inte** / **Not included** and **När det är klart** / **Done when** describe the whole, never one part.
|
|
130
|
+
- **Nothing a part's assessment decided is decided again.** The parts were analysed and priced against the approved spec; the umbrella restates that scope one level up and adds no outcome, boundary or condition the approved spec did not carry.
|
|
131
|
+
- **The reviewer block** lists nothing new unless the umbrella itself raises a choice. Settled choices stay settled, and _Price-decisive open choice_ is `no` unless the umbrella raises one.
|
|
132
|
+
|
|
133
|
+
The umbrella replaces the parent's spec only. The parts keep the approved spec they were priced against, and the parent's price stays the sum of its parts.
|
|
134
|
+
|
|
121
135
|
## Example in brief
|
|
122
136
|
|
|
123
137
|
Raw request:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haus-tech/haus-workflow",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.10.1",
|
|
4
4
|
"description": "Haus AI workflow CLI for Claude Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,8 +34,8 @@
|
|
|
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) release-it",
|
|
38
|
-
"release:dry": "GITHUB_TOKEN=$(gh auth token) release-it --dry-run",
|
|
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",
|
|
39
39
|
"prepack": "yarn build",
|
|
40
40
|
"verify": "node scripts/verify.mjs",
|
|
41
41
|
"verify:full": "node scripts/verify.mjs --coverage",
|