@brainervirus/workit-core 8.17.1 → 8.18.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/package.json +1 -1
- package/skills/workit-bdd/SKILL.md +1 -1
- package/skills/workit-fanout/SKILL.md +2 -2
- package/skills/workit-fanout/references/orchestration.md +21 -0
- package/skills/workit-retro/SKILL.md +2 -2
- package/skills/workit-review/SKILL.md +4 -1
- package/skills/workit-ship/SKILL.md +1 -1
- package/skills/workit-verify-app/SKILL.md +1 -1
- package/src/check-config.ts +50 -21
- package/src/core/policy/derive.ts +2 -1
- package/src/core/resources.ts +2 -2
- package/src/core/task-evaluation.ts +1 -1
- package/src/fanout-brief.ts +1 -0
- package/src/ledger.ts +17 -4
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@ description: Turn requirements into Given/When/Then scenarios, agree the test se
|
|
|
22
22
|
then take the next scenario. Any edit makes the observation stale; re-run
|
|
23
23
|
before you claim it. A recorded "tests pass" is a note, and an ad-hoc
|
|
24
24
|
`workit check -- <cmd>` never satisfies the gate: only the configured
|
|
25
|
-
`test` check does. No `test` detected? Create
|
|
25
|
+
`test` check does. No `test` detected? Create `.workit/checks.json`, copying
|
|
26
26
|
in every check the repo already runs: once it exists it replaces the
|
|
27
27
|
detected defaults.
|
|
28
28
|
5. **Mock only at system boundaries:** network, clock, randomness, other
|
|
@@ -37,8 +37,8 @@ units of ~100+ lines each. Coupled work stays with one owner. Workit never spawn
|
|
|
37
37
|
6. **Retry once, then escalate.** A stuck or failed slice gets one retry with
|
|
38
38
|
a fresh brief (`workit fanout brief <slice> --mode resume`, same branch);
|
|
39
39
|
if that fails too, re-slice it, take it over, or report the gap. Never
|
|
40
|
-
chain resumes. A
|
|
41
|
-
|
|
40
|
+
chain resumes. A fix round and recheck that both fail: a decider, not a
|
|
41
|
+
third round; product choices go to the user (`references/orchestration.md`).
|
|
42
42
|
7. **Verify by the workspace `verification` setting** (`workit grant show`).
|
|
43
43
|
A report is a pointer, never evidence. `self`: a verifier that wrote none
|
|
44
44
|
of the slices, or you if you wrote none. `independent` or high risk: a
|
|
@@ -43,6 +43,27 @@ rulings). If the retry fails too, escalate instead of a third worker:
|
|
|
43
43
|
re-slice it smaller (`workit fanout plan` again), take it over yourself, or
|
|
44
44
|
report the gap to the user.
|
|
45
45
|
|
|
46
|
+
## Stuck calls: the decider
|
|
47
|
+
|
|
48
|
+
A failed review gets one fix round and one recheck (the changed hunks only).
|
|
49
|
+
If the recheck fails, or a worker and a reviewer disagree, spawn a decider
|
|
50
|
+
instead of a third round. A decider is a fresh read-only agent, not the author
|
|
51
|
+
or the reviewer. It returns three things: the options, a pick by
|
|
52
|
+
reversibility (the option cheapest to undo wins a tie), and the exact change
|
|
53
|
+
list. The fixer applies that list once, then one focused recheck follows.
|
|
54
|
+
|
|
55
|
+
- Hard call (shared contract, security, migration): two deciders on different
|
|
56
|
+
models (Claude Code: a different `model` on the Agent call; OpenCode: a
|
|
57
|
+
child session on another configured agent; Codex, Cursor and Pi: one
|
|
58
|
+
decider, since the host picks no model). If they disagree, you take the
|
|
59
|
+
more reversible option.
|
|
60
|
+
- Record it: `workit ledger decision "<pick>" --by decider --why "<why it is
|
|
61
|
+
the more reversible>" --view "<decider A: view>" --view "<decider B: view>"`.
|
|
62
|
+
The row is listed as a decider call; it is never the user's answer to a
|
|
63
|
+
product choice and never a verdict.
|
|
64
|
+
- A product choice, or authority nobody here has, is not a decider's call:
|
|
65
|
+
ask the user with your recommendation.
|
|
66
|
+
|
|
46
67
|
## Race a hard slice
|
|
47
68
|
|
|
48
69
|
When a hard slice has an uncertain approach and a cheap VERIFY, run 2-3
|
|
@@ -32,7 +32,7 @@ proposes and stops. Nothing changes until the user approves.
|
|
|
32
32
|
more cited occurrences**; a one-off is not a learning.
|
|
33
33
|
4. **Name the strongest enforcer that works**, strongest first: architecture
|
|
34
34
|
or types (the mistake cannot be written) > lint rule > configured check
|
|
35
|
-
(
|
|
35
|
+
(`.workit/checks.json`, `workit check <name>`) or CI job > test >
|
|
36
36
|
`CODING_STANDARDS.md` (judgment the reviewer reads) > AGENTS.md pointer
|
|
37
37
|
(navigation only). A mechanical rule gets a check, not prose: a check can
|
|
38
38
|
fail, a sentence cannot. From types to test, the proof is that the new
|
|
@@ -60,7 +60,7 @@ Bad: "Agents seem lost in the build. Added 'read the build docs carefully' to
|
|
|
60
60
|
AGENTS.md." One vague occurrence, no citation, a no-op line, auto-applied.
|
|
61
61
|
|
|
62
62
|
Good: "Navigation, 3 occurrences (rulings 12, 19; PR #88 thread): agents sought
|
|
63
|
-
check names in package.json, missing
|
|
63
|
+
check names in package.json, missing `.workit/checks.json`. Enforcer: AGENTS.md
|
|
64
64
|
pointer, +74 bytes, minus the stale Commands paragraph (-210). Unstable check,
|
|
65
65
|
2 occurrences (ledger rows 31, 44: `e2e` red then green on one SHA): a debug
|
|
66
66
|
slice to fix the check. Approve?"
|
|
@@ -36,7 +36,10 @@ independent; `type-check-only` never proves a behavior change.
|
|
|
36
36
|
7. Record the verdict:
|
|
37
37
|
`workit ledger verdict verified|failed|blocked --kind review --branch <b> --how "<what you ran and read>"`
|
|
38
38
|
under your own session (the one the lead or the hook gave you).
|
|
39
|
-
8. **
|
|
39
|
+
8. **Bound the loop.** A failed verdict gets one fix round and one recheck of
|
|
40
|
+
the changed hunks. Still failing, or stuck: no third round; the lead spawns
|
|
41
|
+
a decider (workit-fanout). A product choice goes to the user.
|
|
42
|
+
9. **Harness note** (optional): friction caused by the repo or harness, not
|
|
40
43
|
the change (a flaky fixture, a missing check, a misleading doc or skill
|
|
41
44
|
line), goes on its own `Harness note:` line in the report and any PR
|
|
42
45
|
comment, never as a finding or in the verdict, so a later retro finds it.
|
|
@@ -30,7 +30,7 @@ once the verdict is accepted. `merged`: step 7. `null` (closed, not merged):
|
|
|
30
30
|
stop and report. Stop early only for a new consequential choice, a host
|
|
31
31
|
denial, a review comment that needs a product decision, a required update
|
|
32
32
|
that repeats because the base keeps moving, or after 3 failed fix attempts on
|
|
33
|
-
the same check.
|
|
33
|
+
the same check. Fix round and recheck both failed: a decider (workit-fanout).
|
|
34
34
|
|
|
35
35
|
1. **Open.** `workit git push`, then `workit pr create --title "<title>"
|
|
36
36
|
--body-file <f|->` plus `--issue <#n|ID>` per issue (`--refs`: no close; idempotent;
|
|
@@ -35,7 +35,7 @@ way a user would.
|
|
|
35
35
|
never run is a guess; do not hand it off.
|
|
36
36
|
4. **Make the driver a named check** when it is cheap and deterministic. A
|
|
37
37
|
named check is one command without shell syntax (put pipes and setup in a
|
|
38
|
-
script). Add `"verify": "<driver>"` to `checks` in
|
|
38
|
+
script). Add `"verify": "<driver>"` to `checks` in `.workit/checks.json`; if
|
|
39
39
|
the file does not exist yet, first copy in every check the repo already runs
|
|
40
40
|
(test, lint, typecheck), because the file replaces the detected defaults.
|
|
41
41
|
`workit check verify` then records it, and it joins the verification gate
|
package/src/check-config.ts
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
// never does (design §0 #3).
|
|
6
6
|
//
|
|
7
7
|
// Config, first source that exists at the repository top:
|
|
8
|
-
// 1.
|
|
9
|
-
// "gates":{"testing":"test"}}
|
|
10
|
-
// `.workit
|
|
11
|
-
// is
|
|
8
|
+
// 1. `.workit/checks.json`: `{"checks":{"test":"bun test","lint":["bun","run","lint"]},
|
|
9
|
+
// "gates":{"testing":"test"}}`. Per-repo workit config lives in the
|
|
10
|
+
// self-ignoring `.workit/`, never at the repo root. A legacy root
|
|
11
|
+
// `workit.checks.json` is still read when `.workit/checks.json` is absent,
|
|
12
|
+
// with a note that it moved.
|
|
12
13
|
// 2. Built-in ecosystem defaults detected from the repo: package.json scripts
|
|
13
14
|
// `test`, `lint`, `typecheck`, `check` (`<pm> run <script>`, pm from the
|
|
14
15
|
// lockfile); go.mod → `go test ./...`; Cargo.toml → `cargo test`;
|
|
@@ -25,12 +26,30 @@ import { spawnSync } from "node:child_process";
|
|
|
25
26
|
import fs from "node:fs";
|
|
26
27
|
import path from "node:path";
|
|
27
28
|
|
|
28
|
-
/** The package.json scripts that count as named checks when no
|
|
29
|
+
/** The package.json scripts that count as named checks when no checks config exists. */
|
|
29
30
|
export const SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"] as const;
|
|
30
|
-
export const CHECKS_FILE = "workit
|
|
31
|
+
export const CHECKS_FILE = ".workit/checks.json";
|
|
32
|
+
export const LEGACY_CHECKS_FILE = "workit.checks.json";
|
|
31
33
|
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Where the repo's checks config lives under the repository top `root` (the
|
|
36
|
+
* one path resolver): `.workit/checks.json`, else the legacy root file when
|
|
37
|
+
* only that exists, else the `.workit/` path a new config belongs at.
|
|
38
|
+
*/
|
|
39
|
+
export function checksConfigFile(root: string): string {
|
|
40
|
+
const current = path.join(root, CHECKS_FILE);
|
|
41
|
+
const legacy = path.join(root, LEGACY_CHECKS_FILE);
|
|
42
|
+
return !fs.existsSync(current) && fs.existsSync(legacy) ? legacy : current;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Create `<root>/.workit/` with its self-ignoring `.gitignore` (idempotent) and return the dir. */
|
|
46
|
+
export function ensureWorkitDir(root: string): string {
|
|
47
|
+
const dir = path.join(root, ".workit");
|
|
48
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
49
|
+
const ignore = path.join(dir, ".gitignore");
|
|
50
|
+
if (!fs.existsSync(ignore)) fs.writeFileSync(ignore, "*\n");
|
|
51
|
+
return dir;
|
|
52
|
+
}
|
|
34
53
|
|
|
35
54
|
/**
|
|
36
55
|
* `resources.heavyCommands` of the repo's checks config under `root`, read
|
|
@@ -59,15 +78,17 @@ export type NamedCheck = {
|
|
|
59
78
|
|
|
60
79
|
export type CheckConfig = {
|
|
61
80
|
/** Where the config came from; `defaults` = detected ecosystem defaults, `none` = nothing. */
|
|
62
|
-
source:
|
|
81
|
+
source: typeof CHECKS_FILE | typeof LEGACY_CHECKS_FILE | "defaults" | "none";
|
|
63
82
|
/** The directory configured commands run in (the repository top). */
|
|
64
83
|
root: string;
|
|
65
84
|
checks: NamedCheck[];
|
|
66
|
-
/** Explicit gate bindings from
|
|
85
|
+
/** Explicit gate bindings from the checks config (`gates.testing`, `gates.verification`). */
|
|
67
86
|
gates: Partial<Record<"testing" | "verification", string[]>>;
|
|
68
87
|
/** Set when the config exists but cannot be used; gates then fail closed. */
|
|
69
88
|
error: string | null;
|
|
70
|
-
/**
|
|
89
|
+
/** One line for the user when the config was read from the legacy root file. */
|
|
90
|
+
note?: string;
|
|
91
|
+
/** The raw `resources` block of the checks config (heavy-check limits; core/resources.ts reads it). */
|
|
71
92
|
resources?: unknown;
|
|
72
93
|
};
|
|
73
94
|
|
|
@@ -192,19 +213,27 @@ const GATES = ["testing", "verification"] as const;
|
|
|
192
213
|
export function loadCheckConfig(cwd: string): CheckConfig {
|
|
193
214
|
const root = checkRoot(cwd);
|
|
194
215
|
const file = checksConfigFile(root);
|
|
216
|
+
const legacy = path.basename(file) === LEGACY_CHECKS_FILE;
|
|
217
|
+
const label = legacy ? LEGACY_CHECKS_FILE : CHECKS_FILE;
|
|
218
|
+
const note = legacy
|
|
219
|
+
? {
|
|
220
|
+
note: `${LEGACY_CHECKS_FILE} is deprecated; move it to ${CHECKS_FILE} (workit doctor --fix)`,
|
|
221
|
+
}
|
|
222
|
+
: {};
|
|
195
223
|
const none = (error: string | null = null): CheckConfig => ({
|
|
196
|
-
source: error ?
|
|
224
|
+
source: error ? label : "none",
|
|
197
225
|
root,
|
|
198
226
|
checks: [],
|
|
199
227
|
gates: {},
|
|
200
228
|
error,
|
|
229
|
+
...note,
|
|
201
230
|
});
|
|
202
231
|
let text: string | null = null;
|
|
203
232
|
try {
|
|
204
233
|
text = fs.readFileSync(file, "utf8");
|
|
205
234
|
} catch (error) {
|
|
206
235
|
if ((error as NodeJS.ErrnoException).code !== "ENOENT")
|
|
207
|
-
return none(`cannot read ${
|
|
236
|
+
return none(`cannot read ${label}: ${(error as Error).message}`);
|
|
208
237
|
}
|
|
209
238
|
if (text === null) {
|
|
210
239
|
const checks = detectedChecks(root);
|
|
@@ -214,14 +243,14 @@ export function loadCheckConfig(cwd: string): CheckConfig {
|
|
|
214
243
|
try {
|
|
215
244
|
parsed = JSON.parse(text);
|
|
216
245
|
} catch {
|
|
217
|
-
return none(`${
|
|
246
|
+
return none(`${label} is not valid JSON`);
|
|
218
247
|
}
|
|
219
248
|
if (!isRecord(parsed) || !isRecord(parsed.checks))
|
|
220
|
-
return none(`${
|
|
249
|
+
return none(`${label} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
|
|
221
250
|
const checks: NamedCheck[] = [];
|
|
222
251
|
for (const [name, value] of Object.entries(parsed.checks)) {
|
|
223
252
|
if (!CHECK_NAME.test(name))
|
|
224
|
-
return none(`${
|
|
253
|
+
return none(`${label}: check name "${name}" must match ${CHECK_NAME.source}`);
|
|
225
254
|
const argv =
|
|
226
255
|
typeof value === "string"
|
|
227
256
|
? splitCommand(value)
|
|
@@ -230,27 +259,27 @@ export function loadCheckConfig(cwd: string): CheckConfig {
|
|
|
230
259
|
value.every((item) => typeof item === "string" && item.length > 0)
|
|
231
260
|
? (value as string[])
|
|
232
261
|
: new Error("expected a command string or a non-empty argv array");
|
|
233
|
-
if (argv instanceof Error) return none(`${
|
|
262
|
+
if (argv instanceof Error) return none(`${label}: check "${name}": ${argv.message}`);
|
|
234
263
|
checks.push({ name, argv, accepts: [argv] });
|
|
235
264
|
}
|
|
236
265
|
const gates: CheckConfig["gates"] = {};
|
|
237
266
|
if (parsed.gates !== undefined) {
|
|
238
267
|
if (!isRecord(parsed.gates))
|
|
239
|
-
return none(`${
|
|
268
|
+
return none(`${label}: gates must be {"testing":"<name>"|["<name>",…]}`);
|
|
240
269
|
for (const [gate, value] of Object.entries(parsed.gates)) {
|
|
241
270
|
if (!(GATES as readonly string[]).includes(gate))
|
|
242
|
-
return none(`${
|
|
271
|
+
return none(`${label}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
|
|
243
272
|
const names = typeof value === "string" ? [value] : value;
|
|
244
273
|
if (
|
|
245
274
|
!Array.isArray(names) ||
|
|
246
275
|
names.length === 0 ||
|
|
247
276
|
!names.every((name) => typeof name === "string" && checks.some((c) => c.name === name))
|
|
248
277
|
)
|
|
249
|
-
return none(`${
|
|
278
|
+
return none(`${label}: gates.${gate} must name configured checks`);
|
|
250
279
|
gates[gate as (typeof GATES)[number]] = names as string[];
|
|
251
280
|
}
|
|
252
281
|
}
|
|
253
|
-
return { source:
|
|
282
|
+
return { source: label, root, checks, gates, error: null, ...note, resources: parsed.resources };
|
|
254
283
|
}
|
|
255
284
|
|
|
256
285
|
const sameArgv = (left: readonly string[], right: readonly string[]): boolean =>
|
|
@@ -196,7 +196,7 @@ export const ledgerView = (root: string): LedgerView => {
|
|
|
196
196
|
export type RuleStatus = { met: boolean; reason: string; decisionIds: string[] };
|
|
197
197
|
|
|
198
198
|
/** A user decision recorded on the task, or a ledger decision on this branch since the task began. */
|
|
199
|
-
const productDecision = (task: TaskRecord, ledger: LedgerView): RuleStatus => {
|
|
199
|
+
export const productDecision = (task: TaskRecord, ledger: LedgerView): RuleStatus => {
|
|
200
200
|
const decisions = task.decisions.filter(
|
|
201
201
|
(entry) =>
|
|
202
202
|
entry.data.revoked === null &&
|
|
@@ -217,6 +217,7 @@ const productDecision = (task: TaskRecord, ledger: LedgerView): RuleStatus => {
|
|
|
217
217
|
.findLast(
|
|
218
218
|
(item) =>
|
|
219
219
|
item.type === "decision" &&
|
|
220
|
+
item.by !== "decider" &&
|
|
220
221
|
!item.superseded &&
|
|
221
222
|
(item.branch ?? null) === branch &&
|
|
222
223
|
Date.parse(item.at) >= since,
|
package/src/core/resources.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
//
|
|
14
14
|
// Settings: built-in defaults (derived from total RAM and CPUs), then
|
|
15
15
|
// `resources` in the matching workspaces.json entry (the machine owner's
|
|
16
|
-
// word), then `resources` in workit
|
|
16
|
+
// word), then `resources` in .workit/checks.json, which can only tighten them.
|
|
17
17
|
// Only the CLI imports this module; hooks never do.
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
19
19
|
import fs from "node:fs";
|
|
@@ -103,7 +103,7 @@ function cpuValue(value: unknown, host: HostShape): number | Error {
|
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Where a `resources` block comes from. `machine` (the user's workspaces.json)
|
|
106
|
-
* sets anything. `repo` (
|
|
106
|
+
* sets anything. `repo` (the repo's .workit/checks.json) can only tighten: it
|
|
107
107
|
* adds heavy checks, lowers memoryMax and cpuQuota, and sets its own wait; it
|
|
108
108
|
* cannot raise the machine's slots or turn the scope off.
|
|
109
109
|
*/
|
|
@@ -684,7 +684,7 @@ function evaluateObservedGate(
|
|
|
684
684
|
reason:
|
|
685
685
|
requirement.dimension === "testing" && config.checks.length
|
|
686
686
|
? `no \`test\` check is configured; add one (or gates.testing) to ${CHECKS_FILE}, then run ${checkCommand("test", runtimeVersion())}`
|
|
687
|
-
: `no checks are configured or detected; add ${CHECKS_FILE}
|
|
687
|
+
: `no checks are configured or detected; add ${CHECKS_FILE}, e.g. {"checks":{"test":"<your test command>"}}, then run ${checkCommand("test", runtimeVersion())}`,
|
|
688
688
|
};
|
|
689
689
|
const satisfied = fresh.filter(named);
|
|
690
690
|
if (satisfied.length)
|
package/src/fanout-brief.ts
CHANGED
|
@@ -113,6 +113,7 @@ const rules = (
|
|
|
113
113
|
: " 3. Commit with `workit git commit -m <msg> -- <paths in SCOPE>`; push only if this brief says so.",
|
|
114
114
|
" 4. Never record a verdict on your own work.",
|
|
115
115
|
" 5. Run tests, lint, typecheck and builds only through `workit check` (a named check, or `workit check --heavy -- <cmd>`): one machine-wide lease queues every agent, so never a hand-made flock; `workit lock status` shows who holds it.",
|
|
116
|
+
" 6. A failed review gets one fix round and one recheck. Still failing: stop and report the open finding, never a third round (the lead spawns a decider). A product choice is the user's: report it, do not decide it.",
|
|
116
117
|
];
|
|
117
118
|
};
|
|
118
119
|
|
package/src/ledger.ts
CHANGED
|
@@ -130,6 +130,10 @@ export type DecisionRow = RowCommon & {
|
|
|
130
130
|
what: string;
|
|
131
131
|
why: string;
|
|
132
132
|
refs: string[];
|
|
133
|
+
/** A tie-breaker agent's call (`--by decider`): never the user's answer, never a verdict. */
|
|
134
|
+
by?: "decider";
|
|
135
|
+
/** Each decider's view when the call was recorded after weighing more than one. */
|
|
136
|
+
views?: string[];
|
|
133
137
|
};
|
|
134
138
|
export type RulingRow = RowCommon & {
|
|
135
139
|
type: "ruling";
|
|
@@ -1702,8 +1706,12 @@ function checkSupersede(
|
|
|
1702
1706
|
|
|
1703
1707
|
export function recordDecision(
|
|
1704
1708
|
context: RecordContext,
|
|
1705
|
-
input: { what?: string; why?: string; refs?: string[] },
|
|
1709
|
+
input: { what?: string; why?: string; refs?: string[]; by?: string; views?: string[] },
|
|
1706
1710
|
): LedgerResult<DecisionRow> {
|
|
1711
|
+
if (input.by !== undefined && input.by !== "decider")
|
|
1712
|
+
return err("invalid_input", `--by takes "decider", got "${input.by}"`);
|
|
1713
|
+
if (input.by === undefined && input.views?.length)
|
|
1714
|
+
return err("invalid_input", "--view needs --by decider");
|
|
1707
1715
|
const what = required(input.what, "<what>");
|
|
1708
1716
|
if (!what.ok) return what;
|
|
1709
1717
|
const why = required(input.why, "--why");
|
|
@@ -1718,6 +1726,8 @@ export function recordDecision(
|
|
|
1718
1726
|
what: what.value,
|
|
1719
1727
|
why: why.value,
|
|
1720
1728
|
refs: input.refs ?? [],
|
|
1729
|
+
...(input.by === "decider" ? { by: "decider" as const } : {}),
|
|
1730
|
+
...(input.views?.length ? { views: input.views } : {}),
|
|
1721
1731
|
},
|
|
1722
1732
|
{ now: context.now },
|
|
1723
1733
|
);
|
|
@@ -2023,7 +2033,7 @@ export type RowSummary = {
|
|
|
2023
2033
|
branch: string | null;
|
|
2024
2034
|
summary: string;
|
|
2025
2035
|
/** Why a reader should discount the row: superseded, ignored (by the trust rules), self. */
|
|
2026
|
-
labels: Array<"superseded" | "ignored" | "self">;
|
|
2036
|
+
labels: Array<"superseded" | "ignored" | "self" | "decider">;
|
|
2027
2037
|
};
|
|
2028
2038
|
|
|
2029
2039
|
/** Row types that count only when an observing verb wrote them. */
|
|
@@ -2040,6 +2050,7 @@ export function rowLabels(row: ReadRow): RowSummary["labels"] {
|
|
|
2040
2050
|
if (row.supersedeIgnored || (OBSERVED_ONLY.has(row.type) && row.observer !== "workit_cli"))
|
|
2041
2051
|
labels.push("ignored");
|
|
2042
2052
|
if (row.type === "verdict" && row.self === true) labels.push("self");
|
|
2053
|
+
if (row.type === "decision" && row.by === "decider") labels.push("decider");
|
|
2043
2054
|
return labels;
|
|
2044
2055
|
}
|
|
2045
2056
|
|
|
@@ -2047,9 +2058,11 @@ export function summarizeRow(row: ReadRow): RowSummary {
|
|
|
2047
2058
|
const text = (key: string): string => str(row[key]) ?? "";
|
|
2048
2059
|
let summary: string;
|
|
2049
2060
|
switch (row.type) {
|
|
2050
|
-
case "decision":
|
|
2051
|
-
|
|
2061
|
+
case "decision": {
|
|
2062
|
+
const views = Array.isArray(row.views) ? row.views.map(String) : [];
|
|
2063
|
+
summary = `${row.by === "decider" ? "[decider] " : ""}${text("what")} (why: ${text("why")}${views.length ? `; views: ${views.join(" | ")}` : ""})`;
|
|
2052
2064
|
break;
|
|
2065
|
+
}
|
|
2053
2066
|
case "ruling":
|
|
2054
2067
|
summary = `${text("what")} (why: ${text("why")}; cost if wrong: ${text("costIfWrong")})`;
|
|
2055
2068
|
break;
|