create-pmos 0.15.0 → 0.16.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/README.md +65 -0
- package/package.json +1 -1
- package/payload/KIT-VERSION +2 -2
- package/workspace/README.md +50 -13
- package/workspace/bin/pmos.js +204 -51
- package/workspace/design-parity.js +358 -0
- package/workspace/doctor.js +54 -0
- package/workspace/lib/find-browser.js +60 -0
- package/workspace/lib/four-panel.js +345 -0
- package/workspace/lib/git-info.js +28 -1
- package/workspace/lib/mobile-nav.js +32 -2
- package/workspace/lib/server-discovery.js +127 -0
- package/workspace/package-lock.json +29 -2
- package/workspace/package.json +13 -5
- package/workspace/public/index.html +79 -409
- package/workspace/public/pm/app.js +242 -0
- package/workspace/public/pm/data.js +905 -0
- package/workspace/public/pm/dom.js +116 -0
- package/workspace/public/pm/fixture.js +979 -0
- package/workspace/public/pm/mobile.css +200 -0
- package/workspace/public/pm/mobile.js +403 -0
- package/workspace/public/pm/modes.js +1076 -0
- package/workspace/public/pm/panel-detail.js +648 -0
- package/workspace/public/pm/panel-initiatives.js +426 -0
- package/workspace/public/pm/panel-repos.js +553 -0
- package/workspace/public/pm/panel-tabs.js +487 -0
- package/workspace/public/pm/state.js +168 -0
- package/workspace/public/pm/tab-doc.js +279 -0
- package/workspace/public/pm/tab-task.js +83 -0
- package/workspace/public/pm/tab-term.js +560 -0
- package/workspace/public/pm/tokens.css +73 -0
- package/workspace/public/vendor/ibm-plex-mono-500.woff2 +0 -0
- package/workspace/server.js +174 -11
- package/workspace/focus.test.js +0 -650
- package/workspace/handoff.test.js +0 -443
- package/workspace/profiles.test.js +0 -362
- package/workspace/public/app.js +0 -5560
- package/workspace/public/styles.css +0 -2258
- package/workspace/repo-scan.test.js +0 -327
package/workspace/focus.test.js
DELETED
|
@@ -1,650 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* Unit suite for lib/focus.js — the Focus queue's pure classifier and its six sections.
|
|
3
|
-
*
|
|
4
|
-
* No framework, no mocks and no server: `buildFocus` does no I/O, so every case here is a real
|
|
5
|
-
* input shape handed straight to the real function. That is the whole reason the module is pure —
|
|
6
|
-
* the domain can be WALKED rather than sampled, and the property this surface lives or dies by is a
|
|
7
|
-
* property about the domain, not about one screenshot.
|
|
8
|
-
*
|
|
9
|
-
* The suite is organised around the one invariant the module exists to hold:
|
|
10
|
-
*
|
|
11
|
-
* A · the ladder domain — every step key × every status, and the two near-misses by name
|
|
12
|
-
* B · the session domain — all twelve cells of session-state's join
|
|
13
|
-
* C · the needs domain, and the exclusions that keep the queue short
|
|
14
|
-
* D · tier order, stable ranking, and the anchor/unanchored dedupe
|
|
15
|
-
* E · the badge — a bare number is impossible over a failed, deriving or absent read
|
|
16
|
-
* F · coverage — the in-flight cap is stated, with real numbers
|
|
17
|
-
* G · handoffs — a STATED absence, no digits, and never "nothing owed by you"
|
|
18
|
-
* H · objectives — four distinguishable states
|
|
19
|
-
* I · upkeep — memory absence stated, budget caveat present, no percentage anywhere
|
|
20
|
-
* J · delegated — verbatim session words, and delegate offers that resolve or are not made
|
|
21
|
-
* K · empty, absent and malformed inputs — nothing throws, nothing is invented
|
|
22
|
-
* L · purity + the grep that pins the one thing a future edit is most likely to "improve"
|
|
23
|
-
*
|
|
24
|
-
* Exits non-zero if any check fails.
|
|
25
|
-
*/
|
|
26
|
-
'use strict';
|
|
27
|
-
const fs = require('fs');
|
|
28
|
-
const path = require('path');
|
|
29
|
-
const assert = require('assert');
|
|
30
|
-
|
|
31
|
-
const focus = require('./lib/focus');
|
|
32
|
-
const sessionState = require('./lib/session-state');
|
|
33
|
-
|
|
34
|
-
const results = [];
|
|
35
|
-
const ok = (name, cond, extra = '') => { results.push({ name, cond }); console.log(`${cond ? '✓' : '✗'} ${name}${extra ? ' — ' + extra : ''}`); };
|
|
36
|
-
|
|
37
|
-
// ── fixture builders ────────────────────────────────────────────────────────────────────────────
|
|
38
|
-
const AS_OF = '2026-07-29T14:02:11.000Z';
|
|
39
|
-
/** A step as `computeWorkflow` emits it. */
|
|
40
|
-
const step = (key, status, evidence = null, next = null) => ({ key, status, evidence, next, blocking: false, rule: null });
|
|
41
|
-
/** A ladder row as the board's `data.live[]` carries it. */
|
|
42
|
-
const row = (id, steps, extra = {}) => Object.assign({
|
|
43
|
-
id, title: `${id} title`, lane: 'feature', stage: 'spec', record: `planning/initiatives/${id}.md`,
|
|
44
|
-
anchor: 'q3-2026:o1:kr1', blockers: [], next: null, steps,
|
|
45
|
-
}, extra);
|
|
46
|
-
/** A board as `boardCache.board(root)` returns it. */
|
|
47
|
-
const board = (rows, extra = {}) => {
|
|
48
|
-
const { data, ...env } = extra;
|
|
49
|
-
return Object.assign({
|
|
50
|
-
ok: true, empty: false, source: 'file-mode',
|
|
51
|
-
data: Object.assign({ live: rows, hidden: 0, total: rows.length, unreadable: [], accepted: 0, blocked: 0 }, data || {}),
|
|
52
|
-
as_of: AS_OF, stale: false, refreshing: false,
|
|
53
|
-
}, env);
|
|
54
|
-
};
|
|
55
|
-
const needsOk = (items) => ({ ok: true, empty: !items.length, data: { items } });
|
|
56
|
-
const spineOk = (extra = {}) => Object.assign({ objectives: [], budgets: [], unanchored: [], unreadable: null, budgets_readable: true }, extra);
|
|
57
|
-
const session = (id, shown, extra = {}) => Object.assign({ id, ws: 'w1', name: id, agent: 'claude', shown, quietMs: 60000 }, extra);
|
|
58
|
-
|
|
59
|
-
const q = (f, kind) => f.queue.filter((i) => i.kind === kind);
|
|
60
|
-
const hasQ = (f, kind) => q(f, kind).length > 0;
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
// ── A · the ladder domain ─────────────────────────────────────────────────────────────────────
|
|
64
|
-
const cells = [];
|
|
65
|
-
for (const key of focus.STEP_KEYS) for (const status of focus.STEP_STATUSES) {
|
|
66
|
-
cells.push({ key, status, c: focus.classifyStep(key, status) });
|
|
67
|
-
}
|
|
68
|
-
const admits = cells.filter((x) => x.c.qualifies).map((x) => `${x.key}/${x.status}`).sort();
|
|
69
|
-
|
|
70
|
-
ok('A1: the whole step domain is walked — 12 keys × 6 statuses = 72 cells, all answered',
|
|
71
|
-
cells.length === 72 && cells.every((x) => typeof x.c.qualifies === 'boolean' && typeof x.c.reason === 'string' && x.c.reason.length > 10),
|
|
72
|
-
`${cells.length} cells`);
|
|
73
|
-
|
|
74
|
-
ok('A2: EXACTLY three combinations admit — contract/blocked, accept/pending, anchor/blocked',
|
|
75
|
-
admits.join(', ') === 'accept/pending, anchor/blocked, contract/blocked', admits.join(', ') || 'none');
|
|
76
|
-
|
|
77
|
-
// The two near-misses, asserted BY NAME. Each is a plausible-looking admission that would make
|
|
78
|
-
// the queue long, comprehensive and worthless.
|
|
79
|
-
const acceptBlocked = focus.classifyStep('accept', 'blocked');
|
|
80
|
-
ok('A3 (near-miss): accept+blocked is REFUSED — the Gate does not open over an unfinished ladder',
|
|
81
|
-
acceptBlocked.qualifies === false && acceptBlocked.reason.includes('unfinished ladder'),
|
|
82
|
-
acceptBlocked.reason.slice(0, 78));
|
|
83
|
-
|
|
84
|
-
const contractPending = focus.classifyStep('contract', 'pending');
|
|
85
|
-
ok('A4 (near-miss): contract+pending is REFUSED — nothing is built yet, so nothing is owed',
|
|
86
|
-
contractPending.qualifies === false && contractPending.reason.includes('nothing has been built'),
|
|
87
|
-
contractPending.reason.slice(0, 78));
|
|
88
|
-
|
|
89
|
-
ok('A5: every agent-act step is refused at EVERY status — these are delegated\'s raw material',
|
|
90
|
-
focus.AGENT_ACT_STEPS.every((k) => focus.STEP_STATUSES.every((s) => !focus.classifyStep(k, s).qualifies)),
|
|
91
|
-
focus.AGENT_ACT_STEPS.join(', '));
|
|
92
|
-
|
|
93
|
-
ok('A6: build and quality never qualify, and quality says why (it is unknown by design)',
|
|
94
|
-
focus.STEP_STATUSES.every((s) => !focus.classifyStep('build', s).qualifies && !focus.classifyStep('quality', s).qualifies)
|
|
95
|
-
&& focus.classifyStep('quality', 'unknown').reason.includes('never claims a test result it did not run'),
|
|
96
|
-
focus.classifyStep('quality', 'unknown').reason.slice(0, 60));
|
|
97
|
-
|
|
98
|
-
// The classifier is TOTAL, not a filter list. This is the check that separates the two: a step key
|
|
99
|
-
// the engine grows next quarter must default OUT, in every status, without anyone editing a list.
|
|
100
|
-
ok('A7: an unknown step key defaults OUT at every status, with a reason that says so',
|
|
101
|
-
focus.STEP_STATUSES.every((s) => focus.classifyStep('triage', s).qualifies === false)
|
|
102
|
-
&& focus.classifyStep('triage', 'blocked').reason.includes('defaults OUT'),
|
|
103
|
-
focus.classifyStep('triage', 'blocked').reason.slice(0, 78));
|
|
104
|
-
|
|
105
|
-
ok('A8: an unknown STATUS on a PM step defaults OUT too (blocked-ish is not blocked)',
|
|
106
|
-
focus.classifyStep('contract', 'stalled').qualifies === false
|
|
107
|
-
&& focus.classifyStep('accept', 'open').qualifies === false
|
|
108
|
-
&& focus.classifyStep('anchor', 'missing').qualifies === false,
|
|
109
|
-
focus.classifyStep('contract', 'stalled').reason.slice(0, 70));
|
|
110
|
-
|
|
111
|
-
ok('A9: null/undefined key and status are answered rather than thrown at',
|
|
112
|
-
focus.classifyStep(null, null).qualifies === false && focus.classifyStep(undefined, undefined).qualifies === false,
|
|
113
|
-
focus.classifyStep(null, null).reason.slice(0, 60));
|
|
114
|
-
|
|
115
|
-
// The end-to-end version of A2/A3: the same fixture the anti-optimism note demands — an `accept`
|
|
116
|
-
// that is blocked, beside a `contract` that is blocked.
|
|
117
|
-
const nearMiss = focus.buildFocus({
|
|
118
|
-
products: [{ id: 'w1', name: 'pmos' }],
|
|
119
|
-
board: board([
|
|
120
|
-
row('unfinished', [step('accept', 'blocked', 'blocked by: spec, anchor', 'the Acceptance Gate is the PM\'s (D12) — but it does not open over an unfinished ladder')]),
|
|
121
|
-
row('owes-contract', [step('contract', 'blocked', '3 run(s) logged, none with pre_run_contract.PM_approved: true', 'agree numbered done_criteria + test_method with the PM and record the approval in planning/runs/runs.jsonl')]),
|
|
122
|
-
]),
|
|
123
|
-
needs: needsOk([]), spine: spineOk(),
|
|
124
|
-
});
|
|
125
|
-
ok('A10: end to end — accept+blocked is absent from the queue while contract+blocked is present',
|
|
126
|
-
nearMiss.queue.length === 1 && nearMiss.queue[0].kind === 'contract' && nearMiss.queue[0].subject === 'owes-contract',
|
|
127
|
-
JSON.stringify(nearMiss.queue.map((i) => `${i.kind}:${i.subject}`)));
|
|
128
|
-
|
|
129
|
-
ok('A11: the qualifying row carries rank, product, a why, and a read: line naming real files',
|
|
130
|
-
nearMiss.queue[0].rank === 1 && nearMiss.queue[0].product === 'w1'
|
|
131
|
-
&& nearMiss.queue[0].why.includes('done_criteria')
|
|
132
|
-
&& nearMiss.queue[0].read.includes(AS_OF)
|
|
133
|
-
&& nearMiss.queue[0].read.includes('planning/initiatives/owes-contract.md')
|
|
134
|
-
&& nearMiss.queue[0].read.includes('ladder step contract (blocked)')
|
|
135
|
-
&& nearMiss.queue[0].read.includes('PM_approved'),
|
|
136
|
-
nearMiss.queue[0].read.slice(0, 110));
|
|
137
|
-
|
|
138
|
-
// ── B · the session domain ────────────────────────────────────────────────────────────────────
|
|
139
|
-
const twelve = sessionState.presentedStates();
|
|
140
|
-
ok('B1: all twelve cells of session-state\'s join are classified',
|
|
141
|
-
twelve.length === 12 && twelve.every((c) => typeof focus.classifySession(c.presented).qualifies === 'boolean'),
|
|
142
|
-
`${twelve.length} cells`);
|
|
143
|
-
|
|
144
|
-
ok('B2: ONLY `needs` admits — and it is reachable only from quiet + turn user',
|
|
145
|
-
twelve.filter((c) => focus.classifySession(c.presented).qualifies).every((c) => c.cadence === 'quiet' && c.turn === 'user')
|
|
146
|
-
&& twelve.some((c) => focus.classifySession(c.presented).qualifies),
|
|
147
|
-
JSON.stringify(twelve.filter((c) => focus.classifySession(c.presented).qualifies)));
|
|
148
|
-
|
|
149
|
-
ok('B3: `working` is refused, and the reason names it as the most tempting thing to count',
|
|
150
|
-
focus.classifySession('working').qualifies === false && focus.classifySession('working').reason.includes('tempting'),
|
|
151
|
-
focus.classifySession('working').reason.slice(0, 70));
|
|
152
|
-
|
|
153
|
-
ok('B4: `blocked` is refused from the queue — a queue promising "you are the blocker" holds no maybes',
|
|
154
|
-
focus.classifySession('blocked').qualifies === false && focus.classifySession('blocked').reason.includes('maybe blocked'),
|
|
155
|
-
focus.classifySession('blocked').reason.slice(0, 80));
|
|
156
|
-
|
|
157
|
-
ok('B5: an unrecognised presented state defaults OUT',
|
|
158
|
-
focus.classifySession('stalled').qualifies === false && focus.classifySession('stalled').reason.includes('defaults OUT'),
|
|
159
|
-
focus.classifySession('stalled').reason.slice(0, 60));
|
|
160
|
-
|
|
161
|
-
const sess = focus.buildFocus({
|
|
162
|
-
products: [{ id: 'w1', name: 'pmos' }],
|
|
163
|
-
board: board([]), needs: needsOk([]), spine: spineOk(),
|
|
164
|
-
sessions: [session('s-work', 'working'), session('s-quiet', 'quiet'), session('s-need', 'needs'),
|
|
165
|
-
session('s-block', 'blocked'), session('s-dead', 'exited')],
|
|
166
|
-
});
|
|
167
|
-
ok('B6: end to end — exactly one session reaches the queue, and it is the `needs` one',
|
|
168
|
-
sess.queue.length === 1 && sess.queue[0].kind === 'session-needs' && sess.queue[0].subject === 's-need',
|
|
169
|
-
JSON.stringify(sess.queue.map((i) => i.subject)));
|
|
170
|
-
|
|
171
|
-
ok('B7: `blocked` reaches DELEGATED carrying session-state\'s hedge verbatim',
|
|
172
|
-
sess.delegated.running.some((r) => r.id === 's-block' && r.word === 'maybe blocked' && r.hedged === true),
|
|
173
|
-
JSON.stringify(sess.delegated.running.map((r) => [r.id, r.word])));
|
|
174
|
-
|
|
175
|
-
ok('B8: delegated lists every non-exited session and drops only the exited one',
|
|
176
|
-
sess.delegated.running.map((r) => r.id).sort().join() === 's-block,s-need,s-quiet,s-work',
|
|
177
|
-
sess.delegated.running.map((r) => r.id).join());
|
|
178
|
-
|
|
179
|
-
ok('B9: every delegated word comes from session-state\'s WORD table, never re-typed here',
|
|
180
|
-
sess.delegated.running.every((r) => r.word === sessionState.WORD[r.shown]),
|
|
181
|
-
JSON.stringify(sess.delegated.running.map((r) => r.word)));
|
|
182
|
-
|
|
183
|
-
// ── C · the needs domain ──────────────────────────────────────────────────────────────────────
|
|
184
|
-
ok('C1: a derived `accept` and a derived `outcome` qualify; an unenumerated derived kind does not',
|
|
185
|
-
focus.classifyNeed('accept', true).qualifies && focus.classifyNeed('outcome', true).qualifies
|
|
186
|
-
&& focus.classifyNeed('question', true).qualifies === false
|
|
187
|
-
&& focus.classifyNeed('triage', true).qualifies === false,
|
|
188
|
-
focus.classifyNeed('question', true).reason.slice(0, 70));
|
|
189
|
-
|
|
190
|
-
ok('C2: a HARNESS-written entry qualifies whatever its kind — someone filed it against the PM',
|
|
191
|
-
focus.classifyNeed('question', false).qualifies && focus.classifyNeed('anything', undefined).qualifies,
|
|
192
|
-
focus.classifyNeed('question', false).reason);
|
|
193
|
-
|
|
194
|
-
const needsFx = focus.buildFocus({
|
|
195
|
-
products: [{ id: 'w1', name: 'pmos' }],
|
|
196
|
-
board: board([]), spine: spineOk(),
|
|
197
|
-
needs: needsOk([
|
|
198
|
-
{ run_id: 'r1', kind: 'accept', initiative_id: 'alpha', derived: true, question: 'Acceptance Gate open — evaluator says PASS (0.9)' },
|
|
199
|
-
{ run_id: 'outcome:beta', kind: 'outcome', initiative_id: 'beta', derived: true, question: 'Outcome check due' },
|
|
200
|
-
{ run_id: 'h1', kind: 'question', initiative_id: 'gamma', derived: false, question: 'which of the two do you want?' },
|
|
201
|
-
{ run_id: 'x1', kind: 'fyi', initiative_id: 'delta', derived: true, question: 'nothing is asked of you' },
|
|
202
|
-
]),
|
|
203
|
-
});
|
|
204
|
-
ok('C3: three of four needs admit — the derived `fyi` is refused',
|
|
205
|
-
needsFx.queue.length === 3 && !needsFx.queue.some((i) => i.subject === 'delta'),
|
|
206
|
-
JSON.stringify(needsFx.queue.map((i) => `${i.kind}:${i.subject}`)));
|
|
207
|
-
|
|
208
|
-
ok('C4: each needs row\'s read: line names the actual jsonl files it was derived from',
|
|
209
|
-
q(needsFx, 'need-accept')[0].read.includes('evals.jsonl') && q(needsFx, 'need-accept')[0].read.includes('acceptances.jsonl')
|
|
210
|
-
&& q(needsFx, 'need-outcome')[0].read.includes('outcome_checks.jsonl')
|
|
211
|
-
&& q(needsFx, 'need-question')[0].read.includes('needs.json'),
|
|
212
|
-
q(needsFx, 'need-accept')[0].read);
|
|
213
|
-
|
|
214
|
-
// ── D · tier order, ranking, dedupe ───────────────────────────────────────────────────────────
|
|
215
|
-
// One item of each tier, in one product. `zzz-anchor` is deliberately first in the fixture and
|
|
216
|
-
// last in the queue: tier order must beat insertion order.
|
|
217
|
-
const tiered2 = focus.buildFocus({
|
|
218
|
-
products: [{
|
|
219
|
-
id: 'w1',
|
|
220
|
-
name: 'pmos',
|
|
221
|
-
board: board([
|
|
222
|
-
row('zzz-anchor', [step('anchor', 'blocked', 'parent_kr is empty — unanchored work is rejected (D59)', 'set parent_kr … (a PM act)')]),
|
|
223
|
-
row('aaa-contract', [step('contract', 'blocked', '1 run(s) logged, none with pre_run_contract.PM_approved: true', 'agree … with the PM')]),
|
|
224
|
-
], { data: { hidden: 0, total: 3, unreadable: [{ id: 'broken', error: 'this record will not parse' }] } }),
|
|
225
|
-
needs: needsOk([]),
|
|
226
|
-
spine: spineOk(),
|
|
227
|
-
sessions: [session('s-need', 'needs')],
|
|
228
|
-
}],
|
|
229
|
-
});
|
|
230
|
-
ok('D1: tier order is board-integrity → contract/gate → session waiting → intake',
|
|
231
|
-
tiered2.queue.map((i) => i.tier).join(' ') === '1 2 2b 4',
|
|
232
|
-
tiered2.queue.map((i) => `${i.tier}:${i.kind}`).join(' '));
|
|
233
|
-
|
|
234
|
-
ok('D2: ranks are 1..n in queue order, and `count` is exactly the queue length',
|
|
235
|
-
tiered2.queue.map((i) => i.rank).join() === '1,2,3,4' && tiered2.count === tiered2.queue.length,
|
|
236
|
-
`count=${tiered2.count} n=${tiered2.queue.length}`);
|
|
237
|
-
|
|
238
|
-
ok('D3: the unreadable-record row leads, above every blocker — nothing below a failed read is trusted',
|
|
239
|
-
tiered2.queue[0].kind === 'record-unreadable' && tiered2.queue[0].subject === 'broken',
|
|
240
|
-
tiered2.queue[0].id);
|
|
241
|
-
|
|
242
|
-
// Within a tier: product name, then subject. Two products, one item each, deliberately named so
|
|
243
|
-
// insertion order and sorted order disagree.
|
|
244
|
-
const two = focus.buildFocus({
|
|
245
|
-
products: [
|
|
246
|
-
{ id: 'w2', name: 'zebra', board: board([row('b', [step('contract', 'blocked', 'e', 'n')])]), needs: needsOk([]), spine: spineOk() },
|
|
247
|
-
{ id: 'w1', name: 'apple', board: board([row('a', [step('contract', 'blocked', 'e', 'n')])]), needs: needsOk([]), spine: spineOk() },
|
|
248
|
-
],
|
|
249
|
-
});
|
|
250
|
-
ok('D4: within a tier the order is product name then id — stable between renders',
|
|
251
|
-
two.queue.map((i) => i.productName).join() === 'apple,zebra',
|
|
252
|
-
two.queue.map((i) => `${i.productName}/${i.subject}`).join(' '));
|
|
253
|
-
|
|
254
|
-
ok('D5: a queue row targets the right product AND the right initiative (R11 landing)',
|
|
255
|
-
two.queue[0].target.product === 'w1' && two.queue[0].target.id === 'a' && two.queue[0].target.kind === 'initiative'
|
|
256
|
-
&& two.queue[1].target.product === 'w2' && two.queue[1].target.id === 'b',
|
|
257
|
-
JSON.stringify(two.queue.map((i) => i.target)));
|
|
258
|
-
|
|
259
|
-
ok('D6: a session row targets the session, not an initiative',
|
|
260
|
-
sess.queue[0].target.kind === 'session' && sess.queue[0].target.id === 's-need',
|
|
261
|
-
JSON.stringify(sess.queue[0].target));
|
|
262
|
-
|
|
263
|
-
// One fact, two views: the ladder's blocked `anchor` and the spine's `unanchored[]` row.
|
|
264
|
-
const dedup = focus.buildFocus({
|
|
265
|
-
products: [{ id: 'w1', name: 'pmos' }],
|
|
266
|
-
board: board([row('drifter', [step('anchor', 'blocked', 'q3-2026:integrity:kr-a — no such KR', 'add the KR (a PM act)')])]),
|
|
267
|
-
needs: needsOk([]),
|
|
268
|
-
spine: spineOk({ unanchored: [{ id: 'drifter', title: 't', file: 'planning/initiatives/drifter.md', raw: 'q3-2026:integrity:kr-a', status: 'unresolvable', reason: 'no such KR' }] }),
|
|
269
|
-
});
|
|
270
|
-
ok('D7: the ladder anchor step and the spine unanchored row are ONE item, not two',
|
|
271
|
-
dedup.queue.length === 1 && dedup.queue[0].kind === 'anchor', JSON.stringify(dedup.queue.map((i) => i.kind)));
|
|
272
|
-
|
|
273
|
-
ok('D8: …and the folded-in second source is still named in the read: line, never dropped',
|
|
274
|
-
dedup.queue[0].read.includes('also') && dedup.queue[0].read.includes('surfaceSpine().unanchored[]'),
|
|
275
|
-
dedup.queue[0].read.slice(-100));
|
|
276
|
-
|
|
277
|
-
ok('D9: an unanchored record with no ladder row still reaches the intake tier (the cap is escaped)',
|
|
278
|
-
(() => {
|
|
279
|
-
const f = focus.buildFocus({
|
|
280
|
-
products: [{ id: 'w1', name: 'pmos' }],
|
|
281
|
-
board: board([], { data: { hidden: 93, total: 105 } }), needs: needsOk([]),
|
|
282
|
-
spine: spineOk({ unanchored: [{ id: 'hidden-one', raw: '', status: 'empty', reason: 'parent_kr is empty' }] }),
|
|
283
|
-
});
|
|
284
|
-
return f.queue.length === 1 && f.queue[0].tier === '4' && f.queue[0].subject === 'hidden-one';
|
|
285
|
-
})(), 'uncapped unanchored[] is the intake source for exactly this reason');
|
|
286
|
-
|
|
287
|
-
ok('D10: no internal join/sort key leaks into a rendered row',
|
|
288
|
-
tiered2.queue.every((i) => !('dedup' in i) && !('sortKey' in i)),
|
|
289
|
-
Object.keys(tiered2.queue[0]).join(','));
|
|
290
|
-
|
|
291
|
-
// ── E · the badge ─────────────────────────────────────────────────────────────────────────────
|
|
292
|
-
const healthy = focus.buildFocus({
|
|
293
|
-
products: [{ id: 'w1', name: 'pmos' }], board: board([]), needs: needsOk([]), spine: spineOk(),
|
|
294
|
-
});
|
|
295
|
-
ok('E1: a successful read of nothing is CALM — zero, exact, and worded as an all-clear',
|
|
296
|
-
healthy.count === 0 && healthy.badge.exact === true && healthy.badge.unreadable === false
|
|
297
|
-
&& healthy.badge.text === 'nothing needs you',
|
|
298
|
-
healthy.badge.text);
|
|
299
|
-
|
|
300
|
-
const brokenNeeds = focus.buildFocus({
|
|
301
|
-
products: [
|
|
302
|
-
{ id: 'w1', name: 'pmos', board: board([row('a', [step('contract', 'blocked', 'e', 'n')])]), needs: { ok: false, error: 'the needs-you queue file is not valid JSON — corrupt state, not empty' }, spine: spineOk() },
|
|
303
|
-
{ id: 'w2', name: 'other', board: board([]), needs: needsOk([]), spine: spineOk() },
|
|
304
|
-
],
|
|
305
|
-
});
|
|
306
|
-
ok('E2: ANY failed read makes the badge read `needs unreadable` — never a partial sum',
|
|
307
|
-
brokenNeeds.badge.text === 'needs unreadable' && brokenNeeds.badge.unreadable === true && brokenNeeds.badge.exact === false,
|
|
308
|
-
brokenNeeds.badge.text);
|
|
309
|
-
|
|
310
|
-
ok('E3: …and the badge text contains NO DIGIT, so a broken product cannot hide inside a number',
|
|
311
|
-
!/\d/.test(brokenNeeds.badge.text), JSON.stringify(brokenNeeds.badge.text));
|
|
312
|
-
|
|
313
|
-
ok('E4: the failure is still reported — the reader\'s own string, verbatim, with its product',
|
|
314
|
-
brokenNeeds.badge.reasons.some((r) => r.includes('w1') && r.includes('not valid JSON')),
|
|
315
|
-
JSON.stringify(brokenNeeds.badge.reasons));
|
|
316
|
-
|
|
317
|
-
ok('E5: a failed needs read also FILES a tier-1 item — a broken honesty surface reads red, not zero',
|
|
318
|
-
hasQ(brokenNeeds, 'needs-unreadable') && q(brokenNeeds, 'needs-unreadable')[0].tier === '1',
|
|
319
|
-
q(brokenNeeds, 'needs-unreadable')[0].detail);
|
|
320
|
-
|
|
321
|
-
const pending = focus.buildFocus({
|
|
322
|
-
products: [
|
|
323
|
-
{ id: 'w1', name: 'pmos', board: board([row('a', [step('contract', 'blocked', 'e', 'n')])]), needs: needsOk([]), spine: spineOk() },
|
|
324
|
-
{ id: 'w2', name: 'other', board: { pending: true, refreshing: true, as_of: null, stale: false }, needs: needsOk([]), spine: spineOk() },
|
|
325
|
-
],
|
|
326
|
-
});
|
|
327
|
-
ok('E6: a DERIVING board is neither empty nor broken — the badge says so beside the number',
|
|
328
|
-
pending.badge.text === '1 need you · 1 product still deriving' && pending.badge.exact === false
|
|
329
|
-
&& pending.badge.unreadable === false && pending.badge.deriving === 1,
|
|
330
|
-
pending.badge.text);
|
|
331
|
-
|
|
332
|
-
ok('E7: a deriving board files NO queue item and NO error — deriving is its own state',
|
|
333
|
-
!pending.queue.some((i) => i.product === 'w2')
|
|
334
|
-
&& pending.coverage.find((c) => c.product === 'w2').state === 'deriving',
|
|
335
|
-
JSON.stringify(pending.coverage.map((c) => [c.product, c.state])));
|
|
336
|
-
|
|
337
|
-
const unsupplied = focus.buildFocus({ products: [{ id: 'w1', name: 'pmos' }] });
|
|
338
|
-
ok('E8: a read that never HAPPENED is `not read`, distinct from one that failed, and suppresses the number',
|
|
339
|
-
unsupplied.badge.exact === false && unsupplied.badge.unreadable === false
|
|
340
|
-
&& unsupplied.badge.text.includes('not read') && unsupplied.badge.not_read === 3,
|
|
341
|
-
unsupplied.badge.text);
|
|
342
|
-
|
|
343
|
-
ok('E9: `count` is always exactly the queue length — there is no second total anywhere',
|
|
344
|
-
[healthy, brokenNeeds, pending, tiered2, needsFx, dedup].every((f) => f.count === f.queue.length && f.badge.n === f.queue.length),
|
|
345
|
-
'count === queue.length in every fixture');
|
|
346
|
-
|
|
347
|
-
const boardBroken = focus.buildFocus({
|
|
348
|
-
products: [{ id: 'w1', name: 'pmos' }],
|
|
349
|
-
board: { ok: false, error: 'the board took longer than 120s to derive and was stopped', as_of: AS_OF, stale: true },
|
|
350
|
-
needs: needsOk([]), spine: spineOk(),
|
|
351
|
-
});
|
|
352
|
-
ok('E10: a failed BOARD leads the queue with the derivation\'s own reason, verbatim',
|
|
353
|
-
boardBroken.queue[0].kind === 'board-unreadable' && boardBroken.queue[0].tier === '1'
|
|
354
|
-
&& boardBroken.queue[0].detail === 'the board took longer than 120s to derive and was stopped'
|
|
355
|
-
&& boardBroken.badge.text === 'needs unreadable',
|
|
356
|
-
boardBroken.queue[0].detail);
|
|
357
|
-
|
|
358
|
-
// ── F · coverage ──────────────────────────────────────────────────────────────────────────────
|
|
359
|
-
const capped = focus.buildFocus({
|
|
360
|
-
products: [{ id: 'w1', name: 'pmos' }],
|
|
361
|
-
board: board(Array.from({ length: 12 }, (_, i) => row(`i${i}`, [step('build', 'done', '1 commit')])), { data: { hidden: 93, total: 105 } }),
|
|
362
|
-
needs: needsOk([]), spine: spineOk(),
|
|
363
|
-
});
|
|
364
|
-
ok('F1: the in-flight cap is stated per product with real numbers (R6\'s exact sentence)',
|
|
365
|
-
capped.coverage[0].line === "12 of 105 scanned · 93 outside the board's in-flight cap."
|
|
366
|
-
&& capped.coverage[0].hidden === 93 && capped.coverage[0].total === 105,
|
|
367
|
-
capped.coverage[0].line);
|
|
368
|
-
|
|
369
|
-
ok('F2: nothing hidden is stated too — "none outside the cap" is a read, not a silence',
|
|
370
|
-
healthy.coverage[0].line.includes('none outside'), healthy.coverage[0].line);
|
|
371
|
-
|
|
372
|
-
const stale = focus.buildFocus({
|
|
373
|
-
products: [{ id: 'w1', name: 'pmos' }],
|
|
374
|
-
board: board([], { stale: true, refreshing: true }), needs: needsOk([]), spine: spineOk(),
|
|
375
|
-
});
|
|
376
|
-
ok('F3: a stale board says when it was derived and that a refresh is running',
|
|
377
|
-
stale.coverage[0].stale === true && stale.coverage[0].line.includes(AS_OF) && stale.coverage[0].line.includes('refresh is running'),
|
|
378
|
-
stale.coverage[0].line);
|
|
379
|
-
|
|
380
|
-
ok('F4: a read: line off a stale board carries the staleness too — provenance is not laundered',
|
|
381
|
-
focus.buildFocus({
|
|
382
|
-
products: [{ id: 'w1', name: 'pmos' }],
|
|
383
|
-
board: board([row('a', [step('contract', 'blocked', 'e', 'n')])], { stale: true }),
|
|
384
|
-
needs: needsOk([]), spine: spineOk(),
|
|
385
|
-
}).queue[0].read.includes('stale, refresh running'), 'stale is named in the row, not only in the header');
|
|
386
|
-
|
|
387
|
-
// ── G · handoffs ──────────────────────────────────────────────────────────────────────────────
|
|
388
|
-
const h = healthy.handoffs;
|
|
389
|
-
ok('G1: handoffs is a STATED not-connected absence, not a calm empty and not a count',
|
|
390
|
-
h.connected === false && h.state === 'not-connected' && h.count === null && h.items.length === 0,
|
|
391
|
-
JSON.stringify({ state: h.state, count: h.count }));
|
|
392
|
-
|
|
393
|
-
ok('G2: the body contains NO DIGIT — no story count and no sync time can be read out of it',
|
|
394
|
-
!/\d/.test(h.body) && !/\d/.test(h.heading), h.body.slice(0, 80));
|
|
395
|
-
|
|
396
|
-
ok('G3: the body never says "nothing owed" — a calm all-clear over an absent connection is the watermelon',
|
|
397
|
-
!/nothing owed/i.test(h.body) && !/all clear/i.test(h.body) && /not connected/i.test(h.body),
|
|
398
|
-
h.body.slice(0, 96));
|
|
399
|
-
|
|
400
|
-
ok('G4: the body names WHY it cannot answer — credentials and an MCP server are a PM decision',
|
|
401
|
-
h.body.includes('No external tracker is configured') && h.body.includes('MCP server'), h.body.slice(-70));
|
|
402
|
-
|
|
403
|
-
ok('G5: tier 3 is declared and structurally empty — no queue item can ever carry it',
|
|
404
|
-
focus.TIERS.some((t) => t.tier === '3') && [healthy, tiered2, brokenNeeds, needsFx].every((f) => !f.queue.some((i) => i.tier === '3')),
|
|
405
|
-
'tier 3 declared, never populated');
|
|
406
|
-
|
|
407
|
-
// ── H · objectives ────────────────────────────────────────────────────────────────────────────
|
|
408
|
-
const objs = focus.buildFocus({
|
|
409
|
-
products: [
|
|
410
|
-
{ id: 'w1', name: 'has', board: board([]), needs: needsOk([]), spine: spineOk({ objectives: [{ id: 'O1', key: 'o1', name: 'ship the loop' }, { id: 'O2', name: 'second' }] }) },
|
|
411
|
-
{ id: 'w2', name: 'none', board: board([]), needs: needsOk([]), spine: spineOk() },
|
|
412
|
-
{ id: 'w3', name: 'broke', board: board([]), needs: needsOk([]), spine: { objectives: [], budgets: [], unanchored: [], unreadable: 'the OKR documents could not be read' } },
|
|
413
|
-
{ id: 'w4', name: 'unread', board: board([]), needs: needsOk([]) },
|
|
414
|
-
],
|
|
415
|
-
}).objectives;
|
|
416
|
-
ok('H1: a product with objectives shows its TOP one only',
|
|
417
|
-
objs[0].state === 'objective' && objs[0].id === 'O1' && objs[0].name === 'ship the loop', JSON.stringify(objs[0]));
|
|
418
|
-
|
|
419
|
-
ok('H2: a successful read of a repo that declares none is CALM and says so',
|
|
420
|
-
objs[1].state === 'none' && objs[1].error === null && objs[1].note === 'this repository declares no objectives',
|
|
421
|
-
JSON.stringify(objs[1]));
|
|
422
|
-
|
|
423
|
-
ok('H3: a FAILED read is its own state and carries surfaceSpine\'s own string, not a paraphrase',
|
|
424
|
-
objs[2].state === 'error' && objs[2].error === 'the OKR documents could not be read', JSON.stringify(objs[2]));
|
|
425
|
-
|
|
426
|
-
ok('H4: a read that never happened is a FOURTH state — "not read" is not "declares none"',
|
|
427
|
-
objs[3].state === 'not-read' && objs[3].error.includes('not the same as none being declared'),
|
|
428
|
-
objs[3].error.slice(0, 80));
|
|
429
|
-
|
|
430
|
-
ok('H5: the failed objective row and the tier-1 queue item are two views of ONE fact',
|
|
431
|
-
(() => {
|
|
432
|
-
const f = focus.buildFocus({
|
|
433
|
-
products: [{ id: 'w3', name: 'broke', board: board([]), needs: needsOk([]), spine: { objectives: [], budgets: [], unanchored: [], unreadable: 'the OKR documents could not be read' } }],
|
|
434
|
-
});
|
|
435
|
-
return f.objectives[0].state === 'error' && hasQ(f, 'spine-unreadable')
|
|
436
|
-
&& q(f, 'spine-unreadable')[0].detail === f.objectives[0].error;
|
|
437
|
-
})(), 'same string in both places');
|
|
438
|
-
|
|
439
|
-
// ── I · upkeep ────────────────────────────────────────────────────────────────────────────────
|
|
440
|
-
const up = focus.buildFocus({
|
|
441
|
-
products: [{
|
|
442
|
-
id: 'w1', name: 'pmos', board: board([]), needs: needsOk([]),
|
|
443
|
-
spine: spineOk({ budgets: [{ id: 'health:tech', name: 'tech', initiatives: [{ id: 'a' }, { id: 'b' }] }] }),
|
|
444
|
-
}],
|
|
445
|
-
budgetRegistry: [{ id: 'health:tech', cap: '20%', covers: 'refactors, debt paydown' }, { id: 'health:ops', cap: '10%', covers: 'pipelines' }],
|
|
446
|
-
}).upkeep;
|
|
447
|
-
|
|
448
|
-
ok('I1: memory grooming is a STATED absence — the mechanism is not built, and the count is null',
|
|
449
|
-
up.memory.state === 'absent' && up.memory.count === null && /not built/i.test(up.memory.body)
|
|
450
|
-
&& !/\d/.test(up.memory.body), up.memory.body.slice(0, 70));
|
|
451
|
-
|
|
452
|
-
ok('I2: the declared budgets appear with their caps, and one that nothing anchors to is still listed',
|
|
453
|
-
up.budgets.rows.map((r) => r.id).join() === 'health:ops,health:tech'
|
|
454
|
-
&& up.budgets.rows.find((r) => r.id === 'health:tech').cap === '20%'
|
|
455
|
-
&& up.budgets.rows.find((r) => r.id === 'health:ops').total === 0,
|
|
456
|
-
JSON.stringify(up.budgets.rows.map((r) => [r.id, r.cap, r.total])));
|
|
457
|
-
|
|
458
|
-
ok('I3: the anchored count is per product and totalled — a count, never a verdict',
|
|
459
|
-
up.budgets.rows.find((r) => r.id === 'health:tech').total === 2
|
|
460
|
-
&& up.budgets.rows.find((r) => r.id === 'health:tech').anchored[0].product === 'w1',
|
|
461
|
-
JSON.stringify(up.budgets.rows.find((r) => r.id === 'health:tech').anchored));
|
|
462
|
-
|
|
463
|
-
ok('I4: the caveat says the ratio is NOT the cap test, and names the missing quarterly denominator',
|
|
464
|
-
up.budgets.caveat === focus.BUDGET_CAVEAT && /QUARTER/.test(up.budgets.caveat)
|
|
465
|
-
&& /NOT the cap test/.test(up.budgets.caveat), up.budgets.caveat.slice(0, 90));
|
|
466
|
-
|
|
467
|
-
ok('I5: NO percentage, share or ratio field exists on a budget row — a percentage implies a test that was never run',
|
|
468
|
-
up.budgets.rows.every((r) => !Object.keys(r).some((k) => /pct|percent|share|ratio|over_?cap/i.test(k))),
|
|
469
|
-
Object.keys(up.budgets.rows[0]).join(','));
|
|
470
|
-
|
|
471
|
-
const noReg = focus.buildFocus({ products: [{ id: 'w1', board: board([]), needs: needsOk([]), spine: spineOk() }] }).upkeep;
|
|
472
|
-
ok('I6: with no registry supplied, no cap is shown and the absence is stated rather than guessed',
|
|
473
|
-
noReg.budgets.state === 'not-read' && noReg.budgets.rows.every((r) => r.cap === null)
|
|
474
|
-
&& noReg.budgets.note.includes('were not supplied to this call'), noReg.budgets.note.slice(0, 80));
|
|
475
|
-
|
|
476
|
-
// ── J · delegated ─────────────────────────────────────────────────────────────────────────────
|
|
477
|
-
ok('J1: skill paths are pulled out of the engine\'s own `next` text, both roots, deduped',
|
|
478
|
-
focus.skillPathsIn('score the build against the rubric (build-skills/evaluator.skill), then append')
|
|
479
|
-
.join() === 'build-skills/evaluator.skill'
|
|
480
|
-
&& focus.skillPathsIn('write skills/core/prd.skill and skills/core/eval-rubric.skill').length === 2
|
|
481
|
-
&& focus.skillPathsIn('the PM names one thing verified BY HAND').length === 0,
|
|
482
|
-
JSON.stringify(focus.skillPathsIn('write skills/core/prd.skill and skills/core/eval-rubric.skill')));
|
|
483
|
-
|
|
484
|
-
const specRow = row('needs-prd', [step('spec', 'blocked', 'no prd_path', 'write the PRD with skills/core/prd.skill and the rubric with skills/core/eval-rubric.skill')]);
|
|
485
|
-
const withIndex = focus.buildFocus({
|
|
486
|
-
products: [{ id: 'w1', name: 'pmos' }],
|
|
487
|
-
board: board([specRow]), needs: needsOk([]), spine: spineOk(),
|
|
488
|
-
skills: ['skills/core/prd.skill', 'build-skills/evaluator.skill'],
|
|
489
|
-
});
|
|
490
|
-
ok('J2: a ▶ delegate is offered ONLY for the skill path that exists in the supplied index',
|
|
491
|
-
withIndex.delegated.starts.length === 1 && withIndex.delegated.starts[0].skill === 'skills/core/prd.skill'
|
|
492
|
-
&& withIndex.delegated.starts[0].initiative === 'needs-prd',
|
|
493
|
-
JSON.stringify(withIndex.delegated.starts.map((s) => s.skill)));
|
|
494
|
-
|
|
495
|
-
ok('J3: the start carries an initialInput naming the initiative AND the skill — no invented agent',
|
|
496
|
-
withIndex.delegated.starts[0].initialInput.includes('skills/core/prd.skill')
|
|
497
|
-
&& withIndex.delegated.starts[0].initialInput.includes('needs-prd'),
|
|
498
|
-
withIndex.delegated.starts[0].initialInput.slice(0, 80));
|
|
499
|
-
|
|
500
|
-
const noIndex = focus.buildFocus({
|
|
501
|
-
products: [{ id: 'w1', name: 'pmos' }], board: board([specRow]), needs: needsOk([]), spine: spineOk(),
|
|
502
|
-
});
|
|
503
|
-
ok('J4: with NO on-disk index, no start is offered at all, and the reason is stated',
|
|
504
|
-
noIndex.delegated.starts.length === 0 && noIndex.delegated.starts_state === 'not-resolved'
|
|
505
|
-
&& noIndex.delegated.starts_note.includes('worse than no offer'),
|
|
506
|
-
noIndex.delegated.starts_note.slice(0, 80));
|
|
507
|
-
|
|
508
|
-
ok('J5: …and what WOULD have been offered is still visible as a candidate, never as a button',
|
|
509
|
-
noIndex.delegated.candidates.length === 2 && noIndex.delegated.starts.length === 0,
|
|
510
|
-
JSON.stringify(noIndex.delegated.candidates.map((c) => c.skill)));
|
|
511
|
-
|
|
512
|
-
const pmStepWithSkill = focus.buildFocus({
|
|
513
|
-
products: [{ id: 'w1', name: 'pmos' }],
|
|
514
|
-
board: board([row('gate', [step('contract', 'blocked', 'e', 'agree with the PM, see skills/core/prd.skill')])]),
|
|
515
|
-
needs: needsOk([]), spine: spineOk(), skills: ['skills/core/prd.skill'],
|
|
516
|
-
});
|
|
517
|
-
ok('J6: a step the PM owns NEVER gets a delegate button, whatever its `next` text happens to say',
|
|
518
|
-
pmStepWithSkill.delegated.starts.length === 0 && pmStepWithSkill.queue.length === 1,
|
|
519
|
-
JSON.stringify(pmStepWithSkill.delegated.starts));
|
|
520
|
-
|
|
521
|
-
ok('J7: the skill index is honoured as a set — a path not on disk is silently not offered',
|
|
522
|
-
focus.buildFocus({
|
|
523
|
-
products: [{ id: 'w1' }], board: board([specRow]), needs: needsOk([]), spine: spineOk(), skills: [],
|
|
524
|
-
}).delegated.starts.length === 0, 'an empty index offers nothing');
|
|
525
|
-
|
|
526
|
-
// The six sections are always present, in order, however empty the inputs are.
|
|
527
|
-
const SECTIONS = ['objectives', 'queue', 'handoffs', 'delegated', 'upkeep'];
|
|
528
|
-
ok('J8: every call returns the six section keys plus a derived count — none is omitted when empty',
|
|
529
|
-
[healthy, brokenNeeds, unsupplied, focus.buildFocus({})].every((f) =>
|
|
530
|
-
SECTIONS.every((k) => f[k] != null) && typeof f.count === 'number' && typeof f.intro === 'string'),
|
|
531
|
-
SECTIONS.concat('count').join(','));
|
|
532
|
-
|
|
533
|
-
ok('J9: the intro line is the contract, printed — it is what the membership rule is accountable to',
|
|
534
|
-
healthy.intro === 'This list holds only items where you are the blocker. Everything else is running on an agent or waiting on another team.',
|
|
535
|
-
healthy.intro);
|
|
536
|
-
|
|
537
|
-
// ── K · empty, absent and malformed inputs ────────────────────────────────────────────────────
|
|
538
|
-
const nothing = focus.buildFocus();
|
|
539
|
-
ok('K1: no argument at all returns the full shape rather than throwing',
|
|
540
|
-
nothing.count === 0 && Array.isArray(nothing.queue) && Array.isArray(nothing.objectives)
|
|
541
|
-
&& nothing.handoffs.connected === false, JSON.stringify({ count: nothing.count }));
|
|
542
|
-
|
|
543
|
-
ok('K2: …and it claims nothing — every read is `not read`, so the badge is not a calm zero',
|
|
544
|
-
nothing.reads.every((r) => r.state === 'not-read') && nothing.badge.exact === false
|
|
545
|
-
&& nothing.badge.text !== 'nothing needs you', nothing.badge.text);
|
|
546
|
-
|
|
547
|
-
const junk = focus.buildFocus({
|
|
548
|
-
products: [{ id: 'w1', name: 'x', board: 'not an object', needs: 42, spine: [], sessions: 'nope', ladders: 7 }],
|
|
549
|
-
skills: 'not a list',
|
|
550
|
-
});
|
|
551
|
-
ok('K3: malformed reads are ERRORS with a stated shape complaint, never treated as empty',
|
|
552
|
-
junk.reads.every((r) => r.state === 'error') && junk.badge.text === 'needs unreadable'
|
|
553
|
-
&& junk.queue.length === 3,
|
|
554
|
-
JSON.stringify(junk.reads.map((r) => r.source + ':' + r.state)));
|
|
555
|
-
|
|
556
|
-
ok('K4: a board whose ok flag is missing entirely is refused as an unrecognised shape',
|
|
557
|
-
focus.readBoard({ data: { live: [] } }).state === 'error'
|
|
558
|
-
&& focus.readBoard({ data: { live: [] } }).error.includes('unrecognised shape'),
|
|
559
|
-
focus.readBoard({ data: { live: [] } }).error);
|
|
560
|
-
|
|
561
|
-
ok('K5: an EMPTY board (a repo with no records) is calm, not red — a successful read of nothing',
|
|
562
|
-
focus.readBoard({ ok: true, empty: true, source: 'file-mode', as_of: AS_OF }).state === 'empty',
|
|
563
|
-
'empty board is calm');
|
|
564
|
-
|
|
565
|
-
ok('K6: rows and steps that are not objects are skipped without throwing',
|
|
566
|
-
focus.buildFocus({
|
|
567
|
-
products: [{ id: 'w1', board: board([null, 'x', row('a', [null, 'y', step('contract', 'blocked', 'e', 'n')])]), needs: needsOk([null, 'z']), spine: spineOk() }],
|
|
568
|
-
}).queue.length === 1, 'one real item survives the junk');
|
|
569
|
-
|
|
570
|
-
ok('K7: an on-demand ladder overrides the board row for the same id — it is never the older read',
|
|
571
|
-
(() => {
|
|
572
|
-
const f = focus.buildFocus({
|
|
573
|
-
products: [{
|
|
574
|
-
id: 'w1',
|
|
575
|
-
board: board([row('a', [step('contract', 'blocked', 'stale evidence', 'n')])]),
|
|
576
|
-
ladders: [{ ok: true, empty: false, row: row('a', [step('contract', 'done', 'PM-approved pre_run_contract on run r9')]) }],
|
|
577
|
-
needs: needsOk([]), spine: spineOk(),
|
|
578
|
-
}],
|
|
579
|
-
});
|
|
580
|
-
return f.queue.length === 0;
|
|
581
|
-
})(), 'the fresher ladder clears the blocker the cached board still shows');
|
|
582
|
-
|
|
583
|
-
ok('K8: a ladder read that FAILED files a tier-1 unreadable-record item with its own reason',
|
|
584
|
-
(() => {
|
|
585
|
-
const f = focus.buildFocus({
|
|
586
|
-
products: [{ id: 'w1', board: board([]), needs: needsOk([]), spine: spineOk(), ladders: { bad: { ok: false, id: 'bad', error: 'the record for bad could not be read' } } }],
|
|
587
|
-
});
|
|
588
|
-
return f.queue.length === 1 && f.queue[0].tier === '1' && f.queue[0].subject === 'bad'
|
|
589
|
-
&& f.queue[0].detail === 'the record for bad could not be read';
|
|
590
|
-
})(), 'a failed ladder read is the same fact as an unreadable board row');
|
|
591
|
-
|
|
592
|
-
ok('K9: a top-level board is NOT shared across several products — one repo\'s ladder is never attributed to all',
|
|
593
|
-
focus.buildFocus({
|
|
594
|
-
products: [{ id: 'w1', name: 'a' }, { id: 'w2', name: 'b' }],
|
|
595
|
-
board: board([row('a', [step('contract', 'blocked', 'e', 'n')])]), needs: needsOk([]), spine: spineOk(),
|
|
596
|
-
}).queue.length === 0, 'the convenience form applies only to a single product');
|
|
597
|
-
|
|
598
|
-
ok('K10: sessions ARE partitioned across products, because sessionList rows carry `ws`',
|
|
599
|
-
(() => {
|
|
600
|
-
const f = focus.buildFocus({
|
|
601
|
-
products: [
|
|
602
|
-
{ id: 'w1', name: 'a', board: board([]), needs: needsOk([]), spine: spineOk() },
|
|
603
|
-
{ id: 'w2', name: 'b', board: board([]), needs: needsOk([]), spine: spineOk() },
|
|
604
|
-
],
|
|
605
|
-
sessions: [session('s1', 'needs', { ws: 'w1' }), session('s2', 'needs', { ws: 'w2' })],
|
|
606
|
-
});
|
|
607
|
-
return f.queue.length === 2 && f.queue[0].product === 'w1' && f.queue[1].product === 'w2';
|
|
608
|
-
})(), 'a session lands on its own product');
|
|
609
|
-
|
|
610
|
-
// ── L · purity + the grep ─────────────────────────────────────────────────────────────────────
|
|
611
|
-
const input = {
|
|
612
|
-
products: [{ id: 'w1', name: 'pmos', board: board([row('a', [step('contract', 'blocked', 'e', 'n')])]), needs: needsOk([{ run_id: 'r', kind: 'accept', derived: true }]), spine: spineOk({ objectives: [{ id: 'O1', name: 'x' }] }), sessions: [session('s', 'needs')] }],
|
|
613
|
-
skills: ['skills/core/prd.skill'], budgetRegistry: [{ id: 'health:tech', cap: '20%' }],
|
|
614
|
-
};
|
|
615
|
-
const frozen = JSON.stringify(input);
|
|
616
|
-
const one = focus.buildFocus(input);
|
|
617
|
-
const twoRuns = focus.buildFocus(input);
|
|
618
|
-
let same = true;
|
|
619
|
-
try { assert.deepStrictEqual(one, twoRuns); } catch { same = false; }
|
|
620
|
-
ok('L1: two calls over one input return an identical shape — no clock, no counter, no order drift', same);
|
|
621
|
-
|
|
622
|
-
ok('L2: the input object is not mutated — the caller\'s reads are still their own afterwards',
|
|
623
|
-
JSON.stringify(input) === frozen, 'input unchanged');
|
|
624
|
-
|
|
625
|
-
const src = fs.readFileSync(path.join(__dirname, 'lib', 'focus.js'), 'utf8');
|
|
626
|
-
ok('L3: the module reads no clock — no Date.now, no new Date, no hrtime anywhere in it',
|
|
627
|
-
!/Date\.now\s*\(/.test(src) && !/new\s+Date\s*\(/.test(src) && !/hrtime/.test(src), 'no clock');
|
|
628
|
-
|
|
629
|
-
ok('L4: the module does no I/O — it requires no fs, path, child_process or http',
|
|
630
|
-
!/require\(['"](fs|path|child_process|http|https|net)['"]\)/.test(src), 'no I/O requires');
|
|
631
|
-
|
|
632
|
-
// Cheap, and it pins the one thing a future edit is most likely to "improve" (PRD Test method).
|
|
633
|
-
ok('L5: the module names NO external tracker — the handoffs section can only ever be not-connected',
|
|
634
|
-
!/jira|confluence|slack|gworkspace|google\s*workspace/i.test(src),
|
|
635
|
-
(src.match(/jira|confluence|slack|gworkspace/i) || ['none'])[0]);
|
|
636
|
-
|
|
637
|
-
ok('L6: nor does it reference a sync, a story count or a connection status it cannot read',
|
|
638
|
-
!/synced|last[_ ]?sync|stories?\s*done|connected\s*:\s*true/i.test(src),
|
|
639
|
-
(src.match(/synced|stories? done|connected\s*:\s*true/i) || ['none'])[0]);
|
|
640
|
-
|
|
641
|
-
ok('L7: `count` has no persisted twin — the only assignment to it is the queue length',
|
|
642
|
-
(src.match(/^\s*const count = queue\.length;/m) || []).length === 1
|
|
643
|
-
&& !/cachedCount|storedCount|lastCount|countCache/.test(src), 'derived in one place');
|
|
644
|
-
} catch (e) {
|
|
645
|
-
ok('no exceptions during suite', false, e && e.stack ? e.stack.split('\n').slice(0, 3).join(' @ ') : String(e));
|
|
646
|
-
} finally {
|
|
647
|
-
const failed = results.filter((r) => !r.cond);
|
|
648
|
-
console.log(`\n${results.length - failed.length}/${results.length} passed${failed.length ? ' — FAILURES: ' + failed.map((f) => f.name).join('; ') : ''}`);
|
|
649
|
-
process.exit(failed.length ? 1 : 0);
|
|
650
|
-
}
|