@haus-tech/haus-workflow 5.6.1 → 5.7.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 +2 -0
- package/library/global/agents/request-assessor.md +52 -51
- package/library/global/agents/request-scorer.md +52 -52
- package/library/global/agents/request-specifier-eskalerad.md +46 -44
- package/library/global/agents/request-specifier.md +43 -40
- package/library/global/skills/request/SKILL.md +1415 -1360
- package/library/global/skills/request/scripts/contract-check.mjs +17 -1
- package/library/global/skills/request/scripts/kontroll.mjs +7 -5
- package/library/global/skills/request/scripts/korning.mjs +16 -3
- package/library/global/skills/request/scripts/lib.mjs +289 -17
- package/library/global/skills/request/scripts/precheck.mjs +105 -40
- package/library/global/skills/request-assess-impact/SKILL.md +144 -140
- package/library/global/skills/request-score/SKILL.md +290 -266
- package/library/global/skills/request-sync/SKILL.md +246 -245
- package/library/global/skills/request-write-spec/SKILL.md +63 -61
- package/library/global/skills/request-write-spec/references/specformat.md +64 -60
- package/package.json +1 -1
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: request-sync
|
|
3
|
-
description:
|
|
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`, `request-assess-impact` 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
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
You are the interface to the ticket system. Everything that knows what a field is called lives here, and nowhere else in the request family. If Asana is replaced, this skill is rewritten and the others are not touched.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Two boards, one direction
|
|
11
11
|
|
|
12
|
-
**
|
|
12
|
+
**The customer board** is the origin. Every customer object has its own, and it is the one the customer sees. All requests are created here.
|
|
13
13
|
|
|
14
|
-
**
|
|
14
|
+
**The collection board**, TECH · Offer pipeline, `1217668109816859`, is the projection. Requests end up there automatically, and it is where the agents pick up work.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Never create a request directly on the collection board. A request born there is never visible to the customer, and the error is discovered only when someone wonders why it is missing.
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## Session start
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Read `.haus-workflow/request.json` in the repo root:
|
|
21
21
|
|
|
22
22
|
```json
|
|
23
23
|
{
|
|
@@ -29,47 +29,47 @@ Läs `.haus-workflow/request.json` i repo-roten:
|
|
|
29
29
|
}
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
`customer_section`
|
|
32
|
+
`customer_section` is missing when the board is not divided into sections, and when the setup was run before the section step existed. `customer_section_name` exists only so it can be shown in confirmations: **always match on gid, never on name.**
|
|
33
33
|
|
|
34
|
-
**
|
|
34
|
+
**If the file is missing: run the setup below.** Never guess from the repo name or the folder name.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
If the file exists: **verify the board once, before the first write.** Look up `customer_board` by gid and print the name: _"Writing to Vafab Mina sidor, is that right?"_ Putting a customer's request on the wrong customer board is the only error in the entire flow that another customer can see, and it cannot be cleaned up quietly.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Board names are not unique, and several boards can belong to the same customer. Always use gid, never a name search.
|
|
39
39
|
|
|
40
|
-
##
|
|
40
|
+
## Setup
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Run this when `.haus-workflow/request.json` is missing, and when the user asks for it: "sätt upp", "koppla repot", "kör uppsättningen igen". One step at a time, with the user's answer in between. Never ask everything at once.
|
|
43
43
|
|
|
44
|
-
**1
|
|
44
|
+
**1. Where is the repo root?**
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Normally the working directory. If you are unsure, or you are in a subfolder: ask. The file must be in the root, not wherever the session happens to be.
|
|
47
47
|
|
|
48
|
-
**2
|
|
48
|
+
**2. Which customer board?**
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Ask for an Asana URL: `.../project/<gid>/...` gives you the gid directly and is the least error-prone. If you get a customer name instead: search for the boards and show the hits with name and gid, one per line. Never choose for the user when there is more than one.
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
Look up the gid and read the name back for confirmation. **If the name looks like a test or trial board ("test", "sandbox", "torrsim", a year that has passed), say so plainly and ask once more.** A miswired customer board is discovered only when a customer sees someone else's request.
|
|
53
53
|
|
|
54
|
-
**3
|
|
54
|
+
**3. Which section on the customer board?**
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
Some customer boards are divided into sections, where each section corresponds to a project. A task created without a section ends up in the board's default and becomes hard to find.
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Fetch the board's sections. **One section:** no question, use it. **Several sections:** list them with name and gid, one per line, and ask which one the requests from this repo should land in.
|
|
59
59
|
|
|
60
|
-
**4
|
|
60
|
+
**4. Collection board?**
|
|
61
61
|
|
|
62
|
-
`1217668109816859` (TECH · Offer pipeline)
|
|
62
|
+
`1217668109816859` (TECH · Offer pipeline) unless told otherwise. Mention it, ask for confirmation, move on.
|
|
63
63
|
|
|
64
|
-
**5
|
|
64
|
+
**5. Check the setup**
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
Fetch the fields on both boards and go through three things:
|
|
67
67
|
|
|
68
|
-
- **
|
|
69
|
-
-
|
|
70
|
-
- **
|
|
68
|
+
- **Do the fields exist?** The customer board needs `Original request`, `Original request title`, `Customer`, `Requested by` and the customer status field (`Kundstatus`). The collection board needs these plus `Offer status`, `Source`, `Request type`, `Funding`, `Score`, `Has conditions`, `Decision`, `Decision reason`, `Spec iterations`, `Score iterations`.
|
|
69
|
+
- **Are the shared fields the same field?** Compare gid, not name. Two fields with the same name and different gids are a duplicate: they drift apart immediately, and the error shows up first with the customer.
|
|
70
|
+
- **Can the options be looked up?** Normalized, per the rule above. Report which ones could not be matched and which options actually exist.
|
|
71
71
|
|
|
72
|
-
**6
|
|
72
|
+
**6. Write the file**
|
|
73
73
|
|
|
74
74
|
```json
|
|
75
75
|
{
|
|
@@ -81,17 +81,17 @@ Hämta fälten på båda brädorna och gå igenom tre saker:
|
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
Write it even if step 5 found problems: the file is about which boards apply, the field setup is a separate problem and must not block. Present the content, and say that the file should be checked in: a wrong board gid then shows up in a diff instead of being discovered after the fact.
|
|
85
85
|
|
|
86
|
-
**7
|
|
86
|
+
**7. Report and move on**
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
Summarize what was written and what is missing, in that order. Then continue with what the user actually asked for: the setup is a prerequisite, not a task in itself.
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
If step 5 found problems that make the next operation impossible, say which one and stop there. Half-created requests are more expensive to untangle than an interruption.
|
|
91
91
|
|
|
92
|
-
##
|
|
92
|
+
## Look up the fields, never hardcode them
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
Fields and options have GIDs that change when someone rebuilds. Fetch them once per session:
|
|
95
95
|
|
|
96
96
|
```
|
|
97
97
|
get_project(project_id, opt_fields="custom_field_settings.custom_field.name,
|
|
@@ -100,63 +100,63 @@ get_project(project_id, opt_fields="custom_field_settings.custom_field.name,
|
|
|
100
100
|
custom_field_settings.custom_field.enum_options.gid")
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
Build a map name → gid, and for enum fields also option name → gid. Writes always use GID.
|
|
104
104
|
|
|
105
|
-
**
|
|
105
|
+
**Match option names normalized.** People edit them in Asana: emoji get added, words get swapped, spaces get moved. Compare on the alphabetic core: strip emoji, symbols and surrounding whitespace, and compare case-insensitively. `✅ Ordered` and `Ordered` are the same option.
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
The value names below are therefore descriptions of meaning, not strings to require exactly. If you find an option whose core corresponds to what you are looking for: use it. If you find several candidates or none: abort and show which options exist.
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
If you cannot find a field you are to write to: abort and say which one. Never create a new field and never guess at a nearby one: a value in the wrong field is worse than no value, because it looks right in a view.
|
|
110
110
|
|
|
111
|
-
##
|
|
111
|
+
## The mapping
|
|
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
|
-
|
|
|
116
|
-
|
|
|
117
|
-
| `request_id`
|
|
118
|
-
| `original_request`
|
|
119
|
-
| `original_request_title`
|
|
120
|
-
| `source`
|
|
121
|
-
| `customer`
|
|
122
|
-
| `requested_by`
|
|
123
|
-
| `type`
|
|
124
|
-
| `funding`
|
|
125
|
-
| `spec`
|
|
126
|
-
| `impact`
|
|
127
|
-
| `bracket` + `drivers`
|
|
128
|
-
| `score`
|
|
129
|
-
| `outcome: DELA_UPP`
|
|
130
|
-
|
|
|
131
|
-
| `customer_rationale`
|
|
132
|
-
| `assumptions_stated`
|
|
133
|
-
| `alternatives`
|
|
134
|
-
| `conditions`
|
|
135
|
-
| `decision`
|
|
136
|
-
| `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
|
+
| `source` | Source | Kunden skrev själv · Mejl · Möte · Haus initierade |
|
|
121
|
+
| `customer` | Customer | |
|
|
122
|
+
| `requested_by` | Requested by | people field, requires a user gid |
|
|
123
|
+
| `type` | Request type | Bug · Improvement · Wear |
|
|
124
|
+
| `funding` | Funding | |
|
|
125
|
+
| `spec` | the description's `## Specifikation` | Only that section. Locked after `Decision`, see below |
|
|
126
|
+
| `impact` | subtask `Påverkansanalys vN` | A new subtask every round, never an edit |
|
|
127
|
+
| `bracket` + `drivers` | subtask `Poängsättning vN` | A new subtask every round, never an edit |
|
|
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 | 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` | 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
|
-
**`Class rationale`
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
**`Class rationale` is no longer written.** The rationale lives in the description's `## Lösning och omfattning`,
|
|
139
|
+
where the customer actually reads it. Writing the same text in two places gave two versions
|
|
140
|
+
that could drift apart, and the field existed only to filter on a text nobody
|
|
141
|
+
filters on. The field may stay in Asana with its old values as history.
|
|
142
142
|
|
|
143
|
-
**
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
**The field names changed on 2026-09-08 (ADR-0121):** `Score iterations` was called `Class iterations`
|
|
144
|
+
and `Has conditions` was called `Conditional classification`. The content is unchanged in substance:
|
|
145
|
+
a counter over scoring rounds and a yes/no flag for conditions. A board that
|
|
146
|
+
still carries the old names is not updated: say so instead of guessing.
|
|
147
147
|
|
|
148
|
-
`Score`
|
|
149
|
-
|
|
148
|
+
`Score` is a number field. Write the score as a number, not as text, so that it can be
|
|
149
|
+
sorted and summed in list views.
|
|
150
150
|
|
|
151
|
-
`Has conditions`
|
|
151
|
+
`Has conditions` is derived: the conditions list is empty or it is not. Always set a value when a score is written, never empty, because empty is reserved for "nobody has scored yet". If the field carries several values at once, it is misconfigured as multi-select; write one value and say so.
|
|
152
152
|
|
|
153
|
-
Asana
|
|
153
|
+
Asana converts markdown to `html_notes`. Use `html_notes` for the description, for the subtasks' content and for comments, not `notes`, otherwise structure is lost: headings, italic text and the horizontal lines between the sections all disappear with `notes`.
|
|
154
154
|
|
|
155
|
-
##
|
|
155
|
+
## The description and the subtasks
|
|
156
156
|
|
|
157
|
-
**
|
|
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.
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
The description has these headings, in this order, separated by a horizontal line:
|
|
160
160
|
|
|
161
161
|
```
|
|
162
162
|
## Ursprunglig förfrågan
|
|
@@ -179,189 +179,190 @@ _Vad vi gör, vad det bygger på och vad det kostar — uppdaterad 2026-08-21_
|
|
|
179
179
|
[kundvänlig motivering, därunder eventuella förutsättningar]
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
-
**
|
|
182
|
+
**The line between the sections** is written as `<hr/>`. Asana renders it in tasks, unlike `<h3>`, which does not exist in the description field. So stick to `<h2>` for the headings (`##` in markdown), never a level deeper.
|
|
183
183
|
|
|
184
|
-
**
|
|
184
|
+
**The line under each heading** says what the section is and when it was last written. It exists because the sections come from separate occasions: without it the description reads as a text written in one sweep, and then it looks as if everything was decided at the same time. The date makes it visible that the spec and the price came after the customer's original words, and that they may have come on different days.
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
The line is in the customer's language, just like the rest of the description. Never skill names, never protocol terms, never "generated by". Date format `YYYY-MM-DD`. `## Ursprunglig förfrågan` gets the creation date and keeps it forever: that section is never rewritten, so the "uppdaterad" (updated) wording does not belong there.
|
|
187
187
|
|
|
188
|
-
**
|
|
188
|
+
**Take the date from the system, not from memory.** A model that writes today's date by feel often writes the wrong year, and a wrong date is worse than none: it looks like a fact.
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
Each section has exactly one source:
|
|
191
191
|
|
|
192
|
-
|
|
|
193
|
-
| --------------------------- | --------------------------------------------------------------------------- |
|
|
194
|
-
| `## Ursprunglig förfrågan` |
|
|
195
|
-
| `## Specifikation` | `request-write-spec` |
|
|
196
|
-
| `## Lösning och omfattning` | `request-score`: `customer_rationale`, `assumptions_stated`, `alternatives` |
|
|
192
|
+
| Section | Source | Written |
|
|
193
|
+
| --------------------------- | --------------------------------------------------------------------------- | ----------------------- |
|
|
194
|
+
| `## Ursprunglig förfrågan` | the customer's words, or the excerpt from the email/brief | at creation, then never |
|
|
195
|
+
| `## Specifikation` | `request-write-spec` | on every spec round |
|
|
196
|
+
| `## Lösning och omfattning` | `request-score`: `customer_rationale`, `assumptions_stated`, `alternatives` | on every scoring round |
|
|
197
197
|
|
|
198
|
-
- **
|
|
199
|
-
-
|
|
200
|
-
-
|
|
198
|
+
- **Each step updates only its own section and leaves the rest untouched.** Previously only one skill wrote to the description; now several do, and a step that replaces the whole field erases the others' work. This also applies to the date line: you update your own section's line, never anyone else's.
|
|
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
|
+
- **`## 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
|
-
|
|
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's subtask.
|
|
203
203
|
|
|
204
|
-
###
|
|
204
|
+
### The description is locked at the customer decision
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
As soon as `Decision` has a value, the description is what the customer has said yes to. **Do not rewrite any section after that point.** This applies to all three sections, regardless of what has changed.
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
The ownership table above describes what applies up to the customer decision.
|
|
209
209
|
|
|
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
|
-
|
|
213
|
-
`Score`.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
If the scoring is rerun on a ticket that already has a `Decision`: do not overwrite
|
|
213
|
+
`Score`. Add the scoring as a new subtask, move the item to ⚖️ and stop there. A
|
|
214
|
+
human decides what the customer gets to see, and in that case it becomes a new decision, not a silent
|
|
215
|
+
edit of an old one. Check `Decision` before you write, regardless of why the
|
|
216
|
+
run was made.
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
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
219
|
|
|
220
|
-
|
|
220
|
+
The lock covers the description and `Score`, which carries the score the price was calculated from. What is internal may always be written: the subtasks (that is the whole point of them: they are numbered sequentially and do not inherit the customer board, so a scoring after a customer decision lands there as usual), `Has conditions` and the counters.
|
|
221
221
|
|
|
222
|
-
###
|
|
222
|
+
### Guard mode is not supported
|
|
223
223
|
|
|
224
|
-
`request-score`
|
|
225
|
-
`mode: "guard"`. **
|
|
226
|
-
|
|
227
|
-
|
|
224
|
+
`request-score` can be run as a gate on a complete implementation plan, with
|
|
225
|
+
`mode: "guard"`. **That output is not written to Asana.** There is no field for a
|
|
226
|
+
revised score, and none may be created until someone has decided how a deviation is to be handled
|
|
227
|
+
toward the customer.
|
|
228
228
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
If you get a run with `mode: "guard"`: abort, say that guard mode is not synced, and
|
|
230
|
+
leave the ticket untouched. Never write the guard score to `Score`: that field carries the
|
|
231
|
+
score the customer got their price on.
|
|
232
232
|
|
|
233
|
-
**Påverkansanalys
|
|
233
|
+
**Impact analysis (`Påverkansanalys vN`) and scoring (`Poängsättning vN`) are added as subtasks, not as comments.**
|
|
234
234
|
|
|
235
235
|
```
|
|
236
236
|
Ticket
|
|
237
|
-
├──
|
|
238
|
-
└── Analys (subtask,
|
|
239
|
-
├── Påverkansanalys vN (under Analys,
|
|
240
|
-
└── Poängsättning vN (under Analys,
|
|
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
241
|
```
|
|
242
242
|
|
|
243
|
-
|
|
244
|
-
Analys
|
|
243
|
+
If the item is split, or the affected area lacks tests, work rows are added above
|
|
244
|
+
`Analys`:
|
|
245
245
|
|
|
246
246
|
```
|
|
247
|
-
Ticket (
|
|
248
|
-
├──
|
|
249
|
-
├── Testtäckning – <yta> (subtask,
|
|
250
|
-
├── <
|
|
251
|
-
├── <
|
|
252
|
-
└── Analys (subtask,
|
|
253
|
-
├── Påverkansanalys vN (under Analys,
|
|
254
|
-
└── Poängsättning vN (under Analys,
|
|
247
|
+
Ticket (no Score of its own; carries the sum of the parts)
|
|
248
|
+
├── description: the three sections above
|
|
249
|
+
├── Testtäckning – <yta> (subtask, own Score)
|
|
250
|
+
├── <subset A> (subtask, own Score)
|
|
251
|
+
├── <subset B> (subtask, own Score)
|
|
252
|
+
└── Analys (subtask, no Score)
|
|
253
|
+
├── Påverkansanalys vN (under Analys, no Score)
|
|
254
|
+
└── Poängsättning vN (under Analys, no Score)
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
See _The `Analys` rows_ further down for what `Analys` is and when it is created.
|
|
258
258
|
|
|
259
|
-
- Subtasks
|
|
260
|
-
-
|
|
261
|
-
-
|
|
262
|
-
-
|
|
259
|
+
- Subtasks do not inherit the customer board and are therefore internal. Check that they are not multi-homed there.
|
|
260
|
+
- **`Analys` and its rows carry English body text under a Swedish title.** Their content is internal, like the analysis and the scoring it holds; the titles `Analys`, `Påverkansanalys vN` and `Poängsättning vN` are lookup keys and are never translated. The description's three sections follow the customer's language instead (_The language contract_ in `request/SKILL.md`).
|
|
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".
|
|
263
264
|
|
|
264
|
-
**
|
|
265
|
+
**Comments are no longer used for impact analysis or scoring.** They remain for what is actually commented on, feedback on a backward move, and there it still applies that they are append-only and never edited.
|
|
265
266
|
|
|
266
|
-
##
|
|
267
|
+
## Split items
|
|
267
268
|
|
|
268
|
-
|
|
269
|
+
If the scoring has answered `DELA_UPP` and the analysis has pointed out the parts, write them like this.
|
|
269
270
|
|
|
270
|
-
**
|
|
271
|
-
tasks
|
|
272
|
-
|
|
271
|
+
**Parts that can be delivered on their own become separate requests** on the customer board, that is, ordinary
|
|
272
|
+
tasks and not subtasks. They have their own spec, their own decision and their own delivery. Link them to
|
|
273
|
+
the original item.
|
|
273
274
|
|
|
274
|
-
**
|
|
275
|
-
|
|
275
|
+
**Parts that cannot be delivered on their own become subtasks** under the item, one per part, with
|
|
276
|
+
its score in `Score` and a line at the top that says which part of the spec the part covers.
|
|
276
277
|
|
|
277
|
-
**
|
|
278
|
-
|
|
279
|
-
|
|
278
|
+
**The parent gets no `Score` of its own.** If the field has a value from before, it is cleared when
|
|
279
|
+
the first scored subtask is created. Asana rolls the children's values up to the
|
|
280
|
+
collapsed parent, so a value of its own there is double-counted or hides the sum.
|
|
280
281
|
|
|
281
|
-
**`Score`
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
**`Score` is written only on work rows.** `Analys` and the rows under it are left empty.
|
|
283
|
+
The `Analys` rows grow in number with every review round, and a value there would count
|
|
284
|
+
rework as scope.
|
|
284
285
|
|
|
285
|
-
**
|
|
286
|
-
|
|
287
|
-
`## Lösning och omfattning`
|
|
286
|
+
**A part that became `OVER_SKALAN` gets no number.** The parent then shows a sum plus a
|
|
287
|
+
row without a value. It is a real outcome, not an error: write in the description's
|
|
288
|
+
`## Lösning och omfattning` that a part is quoted separately, in the customer's language.
|
|
288
289
|
|
|
289
|
-
###
|
|
290
|
+
### Missing test coverage
|
|
290
291
|
|
|
291
|
-
|
|
292
|
-
`Testtäckning – <yta>`
|
|
293
|
-
|
|
292
|
+
If the analysis reports that the affected area lacks automated tests: create a subtask
|
|
293
|
+
`Testtäckning – <yta>` with its own score, and make the implementation's subtask depend on it
|
|
294
|
+
with `add_dependencies`.
|
|
294
295
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
296
|
+
The order carries something. The tests must describe how the area works before the change is made;
|
|
297
|
+
written afterwards, they describe the new behavior and prove nothing about what
|
|
298
|
+
broke. The test row therefore belongs to the build, not to the preparatory work, even though it comes
|
|
299
|
+
first.
|
|
299
300
|
|
|
300
|
-
###
|
|
301
|
+
### The `Analys` rows
|
|
301
302
|
|
|
302
|
-
**
|
|
303
|
-
|
|
304
|
-
|
|
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.
|
|
305
306
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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.
|
|
309
310
|
|
|
310
|
-
**
|
|
311
|
-
|
|
312
|
-
|
|
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.
|
|
313
314
|
|
|
314
|
-
**`Analys`
|
|
315
|
-
|
|
315
|
+
**`Analys` sits last in the list**, below the work rows. The work is what one opens the item
|
|
316
|
+
to see.
|
|
316
317
|
|
|
317
|
-
**
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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.
|
|
321
322
|
|
|
322
|
-
**
|
|
323
|
-
|
|
324
|
-
`Score`,
|
|
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.
|
|
325
326
|
|
|
326
|
-
|
|
327
|
-
(`is_rendered_as_separator`)
|
|
328
|
-
|
|
329
|
-
|
|
327
|
+
Build no grouping on top of this. Name prefixes on the rows and Asana's separators
|
|
328
|
+
(`is_rendered_as_separator`) were used earlier to separate the build from the preparatory work, and
|
|
329
|
+
both are removed: the nesting does the same thing, and the separators could only be set with a
|
|
330
|
+
direct call against Asana's API that most sessions cannot make.
|
|
330
331
|
|
|
331
|
-
##
|
|
332
|
+
## Where a request comes from
|
|
332
333
|
|
|
333
|
-
|
|
334
|
+
The origin decides what you do, and it also decides how the source material is to be interpreted later. Always set `Source`.
|
|
334
335
|
|
|
335
|
-
### Kunden skrev själv
|
|
336
|
+
### The customer wrote on their own board (`Source` = `Kunden skrev själv`)
|
|
336
337
|
|
|
337
|
-
|
|
338
|
+
The customer's words sit in the description and in the task name, and both are overwritten later: the description is turned into section form, the name is changed to the changed state when the spec is written. Those are the **only destructive operations in the entire flow**, and the order is not negotiable:
|
|
338
339
|
|
|
339
|
-
1.
|
|
340
|
-
2. **
|
|
341
|
-
3.
|
|
342
|
-
4. **
|
|
340
|
+
1. Copy the description verbatim to `Original request` and the task name verbatim to `Original request title`
|
|
341
|
+
2. **Read both fields back and verify that the text has landed**
|
|
342
|
+
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.** The rename happens when the spec is written, often in a later run, and by then the customer's title exists only in that field
|
|
343
344
|
|
|
344
|
-
|
|
345
|
+
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`.
|
|
345
346
|
|
|
346
|
-
###
|
|
347
|
+
### Email, meeting or other source material
|
|
347
348
|
|
|
348
|
-
|
|
349
|
+
Here the agent creates both the task and the spec in one sweep, and the customer's words stay outside Asana. Then the reviewer is left with a spec and nothing to hold it against.
|
|
349
350
|
|
|
350
|
-
**
|
|
351
|
+
**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.
|
|
351
352
|
|
|
352
|
-
**
|
|
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.
|
|
353
354
|
|
|
354
|
-
|
|
355
|
+
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.
|
|
355
356
|
|
|
356
|
-
`Source` =
|
|
357
|
+
`Source` = `Mejl` or `Möte`. The statuses are set per the creation sequence below. Note only that a task must not end up in a review state without the spec actually having been written: an empty task in the review queue looks like a bug in the skill when it is a bug in the rule.
|
|
357
358
|
|
|
358
|
-
**
|
|
359
|
+
**Link sibling items.** An email or a meeting often produces several things, and `request-write-spec` decides where the cuts go. The dependency between the items must exist on the tickets, not just as a sentence in a reviewer block that nobody rereads a week later.
|
|
359
360
|
|
|
360
|
-
##
|
|
361
|
+
## Which fields live where
|
|
361
362
|
|
|
362
|
-
|
|
363
|
+
The customer board carries only what the customer is to see. Internal fields exist only on the collection board, and a task cannot carry them until it sits in both projects.
|
|
363
364
|
|
|
364
|
-
|
|
|
365
|
+
| Collection board only | Shared, visible on both |
|
|
365
366
|
| ---------------------------------- | ------------------------------------------ |
|
|
366
367
|
| Offer status · Source | Kundstatus |
|
|
367
368
|
| Spec iterations · Score iterations | Original request · Customer · Requested by |
|
|
@@ -369,85 +370,85 @@ Kundbrädan bär bara det kunden ska se. Interna fält finns enbart på samlings
|
|
|
369
370
|
| | Score · Decision · Decision reason |
|
|
370
371
|
| | Original request title |
|
|
371
372
|
|
|
372
|
-
|
|
373
|
+
A shared field is the **same** field on both boards. If there are two fields with the same name and different gids, it is a duplicate, not a share: they drift apart immediately, and the error shows up only when a customer looks at something that has not been updated. If you discover one: write to neither of them, say so.
|
|
373
374
|
|
|
374
|
-
##
|
|
375
|
+
## At creation
|
|
375
376
|
|
|
376
|
-
|
|
377
|
+
The order matters, because the internal fields do not exist on the customer board.
|
|
377
378
|
|
|
378
|
-
**1
|
|
379
|
+
**1. Create on the customer board** with what exists there: task name, `Original request`, `Original request title`, `Customer`, `Requested by`. Put the task in the right section (see below), and **set `Request status` already here**, explicitly:
|
|
379
380
|
|
|
380
|
-
|
|
|
381
|
-
|
|
|
382
|
-
|
|
|
383
|
-
| Spec
|
|
381
|
+
| State at creation | Request status |
|
|
382
|
+
| ------------------------------------- | --------------- |
|
|
383
|
+
| Nothing written yet, only the request | Mottaget |
|
|
384
|
+
| Spec exists and is up for review | Under bedömning |
|
|
384
385
|
|
|
385
|
-
**2
|
|
386
|
+
**2. Verify that it has landed on the collection board.** The mirroring happens via an Asana rule and is asynchronous: read back the task's projects and wait for it. If it has not arrived, add it there yourself.
|
|
386
387
|
|
|
387
|
-
**3
|
|
388
|
+
**3. Set `Source` and `Offer status`.** Only now do the fields exist. `Offer status` must correspond to the `Request status` you set in step 1:
|
|
388
389
|
|
|
389
|
-
|
|
|
390
|
-
|
|
|
391
|
-
|
|
|
392
|
-
| Spec
|
|
390
|
+
| State | Offer status |
|
|
391
|
+
| ------------------- | --------------------------- |
|
|
392
|
+
| Nothing written yet | 📥 Inkommet |
|
|
393
|
+
| Spec exists | 🔍 Spec redo för granskning |
|
|
393
394
|
|
|
394
|
-
**4
|
|
395
|
+
**4. Verify that both status fields have values.** Read the task back and check `Request status` and `Offer status`. If something has not landed: say so and stop.
|
|
395
396
|
|
|
396
|
-
|
|
397
|
+
If you try to set `Offer status` or `Source` in step 1, the lookup aborts, and you are left with a half-created request.
|
|
397
398
|
|
|
398
|
-
**
|
|
399
|
+
**A task you create never leaves the skill with an empty `Request status` or an empty `Offer status`.** The customer board is grouped on one field, the offer board on the other: a task without a value ends up outside all groups. It exists, but nobody sees it, and the error is discovered only when someone wonders where it went. The gap is real: `Offer status` exists only on the offer board, so between step 1 and step 3 that field is unavoidably empty. That is why `Request status` is set in step 1: it covers the gap.
|
|
399
400
|
|
|
400
|
-
|
|
401
|
+
After creation you never touch `Request status` again. From then on it is derived by the Asana rule from `Offer status`.
|
|
401
402
|
|
|
402
|
-
|
|
403
|
+
The task name must be the changed state, not the job, the same rule as for the spec title. If there is no spec yet: use the request's own words, and rename when the spec is written. But the rename requires that `Original request title` carries the original title and that it has been read back, see _The customer wrote on their own board_.
|
|
403
404
|
|
|
404
|
-
###
|
|
405
|
+
### Section on the customer board
|
|
405
406
|
|
|
406
|
-
- **
|
|
407
|
-
- **
|
|
408
|
-
- **
|
|
409
|
-
- **
|
|
407
|
+
- **If `customer_section` is in the configuration:** use it, but first verify that the section still exists on the board: sections get renamed and removed. If it is gone: ask which one applies now and update `.haus-workflow/request.json`.
|
|
408
|
+
- **If `customer_section` is missing and the board has several sections:** ask **once** and use the answer for the whole batch. If you create ten tasks from an email, you must not ask ten times. Offer to save the answer in the configuration file so the question does not come back.
|
|
409
|
+
- **If the board has only one section:** do not ask.
|
|
410
|
+
- **If the batch spans several sections** (an email often contains requests that belong to different projects), do not guess from the content. Ask once whether all of them go to the same section, and ask for the distribution if the answer is no.
|
|
410
411
|
|
|
411
|
-
|
|
412
|
+
Always match on gid. `customer_section_name` exists only so it can be shown in a confirmation.
|
|
412
413
|
|
|
413
|
-
##
|
|
414
|
+
## The statuses
|
|
414
415
|
|
|
415
|
-
**Offer status**
|
|
416
|
+
**Offer status** drives the work. Each state is an inbox:
|
|
416
417
|
|
|
417
|
-
| Status |
|
|
418
|
+
| Status | Waiting for |
|
|
418
419
|
| ------------------------------------ | ----------------------------------------- |
|
|
419
420
|
| 📥 Inkommet | `request-write-spec` |
|
|
420
|
-
| 🔍 Spec redo för granskning |
|
|
421
|
+
| 🔍 Spec redo för granskning | human |
|
|
421
422
|
| ✅ Spec ok | `request-assess-impact` + `request-score` |
|
|
422
|
-
| ⚖️ Poängsättning redo för granskning |
|
|
423
|
-
| 📤 Klar – till kund |
|
|
423
|
+
| ⚖️ Poängsättning redo för granskning | human |
|
|
424
|
+
| 📤 Klar – till kund | the customer's answer, see `Decision` |
|
|
424
425
|
|
|
425
|
-
**Request status**
|
|
426
|
+
**Request status** is the customer's view and is derived by an Asana rule from `Offer status`. **Set it one single time: at creation, before `Offer status` exists to derive from.** After that you never touch it; the rule owns it. The starting value must be exactly what the rule would have derived, so that the fields can never contradict each other.
|
|
426
427
|
|
|
427
|
-
|
|
428
|
+
The reason: `Request status` is the only thing in the entire flow that a customer sees. Two fields that are both set by hand drift apart, and exactly that drift shows up with someone else.
|
|
428
429
|
|
|
429
|
-
**
|
|
430
|
+
**Fetching a queue** is the most common call: all items with a given Offer status. Always filter on the collection board.
|
|
430
431
|
|
|
431
|
-
##
|
|
432
|
+
## Backward move
|
|
432
433
|
|
|
433
|
-
Feedback
|
|
434
|
+
Feedback is a comment plus a move backward to the nearest AI state. Two rules:
|
|
434
435
|
|
|
435
|
-
**
|
|
436
|
+
**Raise the counter.** Back to 📥 raises `Spec iterations`. Back to ✅ raises `Score iterations` (counts scoring rounds). Read the current value and write the value plus one: never set an absolute number from memory.
|
|
436
437
|
|
|
437
|
-
|
|
438
|
+
**A changed spec invalidates the scoring.** If an item goes back to 📥, it must pass ⚖️ again on its way back. Do not overwrite `Score` with a new value without the item having made that journey.
|
|
438
439
|
|
|
439
|
-
|
|
440
|
+
A backward move produces a new spec, and it is to go into the description: so read `Decision` first. If the item already has a customer decision, the lock rule applies and the description is not touched.
|
|
440
441
|
|
|
441
|
-
##
|
|
442
|
+
## What you never do
|
|
442
443
|
|
|
443
|
-
**
|
|
444
|
+
**Write to the customer.** You set fields and move statuses. Sending the proposal is a human's decision.
|
|
444
445
|
|
|
445
|
-
**
|
|
446
|
+
**Overwrite the description without first having verified `Original request`**, and never overwrite a section that another step owns.
|
|
446
447
|
|
|
447
|
-
**
|
|
448
|
+
**Write to the description without having read `Decision`.** If the ticket has a customer decision, the description is locked, regardless of what has changed.
|
|
448
449
|
|
|
449
|
-
**
|
|
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 is added as a subtask, the item is moved back to ⚖️, and the human decides what the customer gets to see.
|
|
450
451
|
|
|
451
|
-
**
|
|
452
|
+
**Set `Decision` yourself.** It is the customer's answer; it comes from outside.
|
|
452
453
|
|
|
453
|
-
**
|
|
454
|
+
**Delete or edit comments.** The feedback log is append-only.
|