create-pmos 0.48.0 → 0.50.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/payload/KIT-VERSION +2 -2
- package/payload/mods/pmos-cockpit/.claude-plugin/plugin.json +1 -1
- package/payload/mods/pmos-cockpit/README.md +10 -1
- package/payload/mods/pmos-cockpit/hooks/lib/route.mjs +95 -0
- package/payload/mods/pmos-cockpit/hooks/register.tsx +67 -1
- package/payload/mods/pmos-cockpit/types/index.d.ts +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/scripts/status-facts.js +13 -3
- package/payload/scripts/workflow-state.js +7 -2
- package/payload/state/discoveries.md +1 -1
- package/payload/state/log.md +1 -1
- package/workspace/guide/index.md +2 -1
- package/workspace/guide/initiatives-and-next-steps.md +3 -1
- package/workspace/guide/knowledge-graph.md +12 -1
- package/workspace/guide/layout.md +2 -0
- package/workspace/guide/live-notes.md +78 -0
- package/workspace/guide/validate-with-typesafe.md +15 -1
- package/workspace/lib/engine/status-facts.js +13 -3
- package/workspace/lib/engine/workflow-state.js +7 -2
- package/workspace/lib/jev-categorize.js +47 -13
- package/workspace/lib/jev-live.js +37 -0
- package/workspace/lib/jev-route.js +11 -1
- package/workspace/lib/live-notes.js +171 -26
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +8 -6
- package/workspace/public/pm/data.js +21 -1
- package/workspace/public/pm/modes-live.js +68 -5
- package/workspace/public/pm/panel-cockpit.js +3 -1
- package/workspace/public/pm/panel-detail.js +3 -1
- package/workspace/public/pm/panel-tabs.js +112 -10
- package/workspace/public/pm/state.js +3 -0
- package/workspace/public/pm/view-livenotes.js +397 -0
- package/workspace/run-tests.js +148 -0
- package/workspace/server.js +71 -9
- package/workspace/test-files.json +180 -0
package/workspace/server.js
CHANGED
|
@@ -76,6 +76,7 @@ const jevClaims = require('./lib/jev-claims.js'); // D80 — the bridge betwee
|
|
|
76
76
|
const jevCategorize = require('./lib/jev-categorize.js'); // D82 — the bridge between the document categorizer and the one TypeSafe module
|
|
77
77
|
const validateLib = require('./lib/validate.js');
|
|
78
78
|
const liveNotes = require('./lib/live-notes.js'); // jev-live-notes — reads and appends a repository's live-notes ledger; no network
|
|
79
|
+
const jevLive = require('./lib/jev-live.js'); // jev-live-notes Run 7 (D85) — the live view's one bridge to Jev, behind three gates
|
|
79
80
|
const claimsLib = require('./lib/claims.js');
|
|
80
81
|
const docCategory = require('./lib/doc-category.js'); // jev-doc-categorizer Run 1 — the PM-facing kind of a document, derived, local, no model
|
|
81
82
|
const docCatCorrections = require('./lib/doc-category-corrections.js'); // jev-doc-categorizer Run 3 — the PM's own category, per user, outside the repository
|
|
@@ -291,6 +292,10 @@ function sessionEnv() {
|
|
|
291
292
|
// print its environment, so the key is never handed to one, whether it came from settings or the shell.
|
|
292
293
|
delete env[tsSettings.ENV_NAME];
|
|
293
294
|
if (SET_GIT_OPTIONAL_LOCKS) delete env.GIT_OPTIONAL_LOCKS;
|
|
295
|
+
// prompt-model-routing Run 3 — where the cockpit mod asks for a prompt's route: this server, on loopback.
|
|
296
|
+
// An address only; the Jev key stays here (D79).
|
|
297
|
+
const routeUrl = jevRoute.routeUrl(PORT);
|
|
298
|
+
if (routeUrl) env.PMOS_ROUTE_URL = routeUrl; else delete env.PMOS_ROUTE_URL;
|
|
294
299
|
return env;
|
|
295
300
|
}
|
|
296
301
|
|
|
@@ -470,6 +475,12 @@ function homeWorkspace() {
|
|
|
470
475
|
return HOME_ENABLED ? workspaces.get(wsId(SELF_ROOT)) : [...workspaces.values()][0];
|
|
471
476
|
}
|
|
472
477
|
function getWs(id) { return (id && workspaces.get(id)) || homeWorkspace(); }
|
|
478
|
+
/** The registered workspace whose root is this path exactly, or null. Never the home workspace by default. */
|
|
479
|
+
function wsByRoot(root) {
|
|
480
|
+
const want = path.resolve(String(root));
|
|
481
|
+
for (const w of workspaces.values()) if (w && path.resolve(w.root) === want) return w;
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
473
484
|
// R2/R3 — what the sidebar tree needs and cannot derive for itself: which REPOSITORY a registered
|
|
474
485
|
// working tree belongs to, and which branch it is on. `git worktree list` names the main worktree
|
|
475
486
|
// first from any of a repo's trees (git-info.worktreesAt), so index 0 is the group key: it is what
|
|
@@ -1608,8 +1619,8 @@ function annotateJev(root, node, cache, seen = new Set()) {
|
|
|
1608
1619
|
if (node.category === docCategory.UNDETERMINED && typeof node.path === 'string' && /\.md$/i.test(node.path)) {
|
|
1609
1620
|
const c = categoryInput(path.join(root, node.path));
|
|
1610
1621
|
const l = c ? jevCategorize.lookup(cache, { fm: c.fm, text: c.text }) : null;
|
|
1611
|
-
if (l && l.decided) { node.category = l.category; node.category_basis = 'jev'; node.category_why = l.sentence; if (l.ticket_kind) node.
|
|
1612
|
-
else if (l) node.category_why = l.sentence;
|
|
1622
|
+
if (l && l.decided) { node.category = l.category; node.category_basis = 'jev'; node.category_why = l.sentence; if (l.ticket_kind) node.category_kind = l.ticket_kind; }
|
|
1623
|
+
else if (l) { node.category_why = l.sentence; node.category_jev = 'unsure'; }
|
|
1613
1624
|
}
|
|
1614
1625
|
for (const k of Object.keys(node)) if (node[k] && typeof node[k] === 'object') annotateJev(root, node[k], cache, seen);
|
|
1615
1626
|
return node;
|
|
@@ -2285,8 +2296,11 @@ function checkDueSchedules() {
|
|
|
2285
2296
|
} catch {}
|
|
2286
2297
|
return fired;
|
|
2287
2298
|
}
|
|
2288
|
-
// the tick — the persistent server checks on an interval; unref'd so it never blocks exit
|
|
2289
|
-
|
|
2299
|
+
// the tick — the persistent server checks on an interval; unref'd so it never blocks exit.
|
|
2300
|
+
// PMOS_TICK_MS exists for the suites (same footing as PMOS_LIVENESS_MS): proactive-prepare.test.js
|
|
2301
|
+
// used to wait on two real minutes of this clock. Unset, the interval is the 60 seconds it always was.
|
|
2302
|
+
const TICK_MS = Math.max(200, Number(process.env.PMOS_TICK_MS) || 60000);
|
|
2303
|
+
try { const _tick = setInterval(() => { checkDueSchedules(); proactiveTick(); try { prepareTick({ manual: false }); } catch { /* the next tick tries again */ } }, TICK_MS); if (_tick.unref) _tick.unref(); } catch {}
|
|
2290
2304
|
|
|
2291
2305
|
// ── Sessions: real PTYs, owned by the server, with disk-backed scrollback + metadata. ──
|
|
2292
2306
|
/** @type {Map<string, {id,ws,name,agent,cwd,state,note,created,pty,buf,cols,rows,clients:Set}>} */
|
|
@@ -4703,6 +4717,18 @@ function jevGateInfo(w) {
|
|
|
4703
4717
|
const { cacheDir } = claimsPaths(w);
|
|
4704
4718
|
return { switch: f.jev, status_read: f.status_read, open: g.open, missing: g.missing, sentence: g.open ? null : jevClaims.gateSentence(g.missing), ...jevClaims.status(cacheDir) };
|
|
4705
4719
|
}
|
|
4720
|
+
// D85 — the live-notes add route's sender: lib/typesafe.js behind the key, consent and the switch, or none. The key goes to the engine
|
|
4721
|
+
// only when the sender does, and never into a response.
|
|
4722
|
+
function liveSender(w) {
|
|
4723
|
+
const key = tsSettings.getKey(STATE);
|
|
4724
|
+
const flags = { consent: tsSettings.getRepo(STATE, w.root).consent, live: liveNotes.getSwitch(STATE, w.root).live };
|
|
4725
|
+
const snd = jevLive.senderFor(key, flags, jevModelOpts(w.root));
|
|
4726
|
+
return { key: snd.ask ? key : null, flags, ask: snd.ask, askOpts: snd.askOpts };
|
|
4727
|
+
}
|
|
4728
|
+
// D85 — what the live-notes view needs to explain its Jev switch. Booleans and sentences only; the key never leaves the store.
|
|
4729
|
+
function liveJevInfo(w) {
|
|
4730
|
+
return jevLive.infoFor(tsSettings.status(STATE).present, { consent: tsSettings.getRepo(STATE, w.root).consent, live: liveNotes.getSwitch(STATE, w.root).live });
|
|
4731
|
+
}
|
|
4706
4732
|
// D82 — what the page needs to explain the categorize switch: which gates hold, and what the last pass did.
|
|
4707
4733
|
const doccatRunning = new Set();
|
|
4708
4734
|
const doccatAutoAt = new Map(); // root → time of the last automatic pass; at most one a minute
|
|
@@ -5555,11 +5581,13 @@ async function handleRequest(req, res) {
|
|
|
5555
5581
|
}
|
|
5556
5582
|
if (p === '/api/model/route' && req.method === 'POST') {
|
|
5557
5583
|
let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
5558
|
-
|
|
5584
|
+
// A terminal's cockpit mod names its repository by root; the page names a workspace by id.
|
|
5585
|
+
const w = typeof body.root === 'string' ? wsByRoot(body.root) : getWs(body.ws);
|
|
5586
|
+
if (!w) return json(res, { ok: false, error: 'no workspace for that repository' }, 400);
|
|
5559
5587
|
if (typeof body.prompt !== 'string' || !body.prompt.trim() || body.prompt.length > 60000) return json(res, { ok: false, error: 'prompt must be text of up to 60000 characters' }, 400);
|
|
5560
5588
|
const f = tsSettings.getRepo(STATE, w.root);
|
|
5561
5589
|
const out = await jevRoute.routePrompt({ root: w.root, prompt: body.prompt, ctx: { skill: typeof body.skill === 'string' ? body.skill : undefined, source: body.source, running: true },
|
|
5562
|
-
key: tsSettings.getKey(STATE), flags: { consent: f.consent, route: f.route, exclude: f.exclude } });
|
|
5590
|
+
key: tsSettings.getKey(STATE), flags: { consent: f.consent, route: f.route, exclude: f.exclude }, askOpts: body.interactive === true ? jevRoute.INTERACTIVE : {} });
|
|
5563
5591
|
if (!out.ok) return json(res, { ok: false, error: out.error }, 400);
|
|
5564
5592
|
if (out.shadow) return json(res, { ok: true, shadow: true, id: out.id }); // shadow: logged, nothing shown, nothing applied
|
|
5565
5593
|
return json(res, { ok: true, ws: w.id, id: out.id, tier: out.tier, model: out.model, effort: out.effort, stage: out.stage, confidence: out.confidence, why: out.why,
|
|
@@ -6566,12 +6594,14 @@ async function handleRequest(req, res) {
|
|
|
6566
6594
|
return json(res, { ok: true, ws: w.id, name: w.name, ...tsSettings.getRepo(STATE, w.root), key: tsSettings.status(STATE) });
|
|
6567
6595
|
}
|
|
6568
6596
|
/* ── live notes (jev-live-notes, Run 2): the ranked proposals of a conversation, and the PM's accept, change or reject.
|
|
6569
|
-
* Reads the repository's ledger; the
|
|
6597
|
+
* Reads the repository's ledger; the writes are ledger rows and, on close, the day's note. No request leaves this process.
|
|
6598
|
+
* The page is the cockpit's live-notes view (public/pm/view-livenotes.js, Run 6). ── */
|
|
6570
6599
|
if (p === '/api/live-notes' && req.method === 'GET') {
|
|
6571
6600
|
const wsq = url.searchParams.get('ws');
|
|
6572
6601
|
const w = wsq ? workspaces.get(wsq) : homeWorkspace(); // a named workspace the server does not know is refused, never replaced by the home one
|
|
6573
6602
|
if (!w) return json(res, { ok: false, error: 'no such workspace' }, 400);
|
|
6574
|
-
const r = liveNotes.apiList({ root: w.root, session: url.searchParams.get('session') || null });
|
|
6603
|
+
const r = liveNotes.apiList({ root: w.root, session: url.searchParams.get('session') || null, exclude: tsSettings.getRepo(STATE, w.root).exclude });
|
|
6604
|
+
if (r.ok) r.jev = liveJevInfo(w);
|
|
6575
6605
|
return json(res, r, r.ok ? 200 : (r.status || 400));
|
|
6576
6606
|
}
|
|
6577
6607
|
if (p === '/api/live-notes/decide' && req.method === 'POST') {
|
|
@@ -6580,7 +6610,39 @@ async function handleRequest(req, res) {
|
|
|
6580
6610
|
catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
6581
6611
|
const w = body.ws ? workspaces.get(String(body.ws)) : homeWorkspace();
|
|
6582
6612
|
if (!w) return json(res, { ok: false, error: 'no such workspace' }, 400);
|
|
6583
|
-
const r = liveNotes.apiDecide({ root: w.root, runtimeDir: STATE, session: body.session, segment: body.segment, action: body.action, initiative: body.initiative || null, relation: body.relation || null, exclude: tsSettings.getRepo(STATE, w.root).exclude });
|
|
6613
|
+
const r = liveNotes.apiDecide({ root: w.root, runtimeDir: STATE, session: body.session, segment: body.segment, action: body.action, initiative: body.initiative || null, relation: body.relation || null, question: body.question == null ? null : body.question, exclude: tsSettings.getRepo(STATE, w.root).exclude });
|
|
6614
|
+
return json(res, r, r.ok ? 200 : (r.status || 400));
|
|
6615
|
+
}
|
|
6616
|
+
/* Run 7 (D85): the per-repository switch "live notes may use Jev". Default off; it is one of three gates, with the key and consent. */
|
|
6617
|
+
if (p === '/api/live-notes/switch' && req.method === 'POST') {
|
|
6618
|
+
let body = {};
|
|
6619
|
+
try { body = JSON.parse((await readBody(req)) || '{}'); }
|
|
6620
|
+
catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
6621
|
+
const w = body.ws ? workspaces.get(String(body.ws)) : homeWorkspace();
|
|
6622
|
+
if (!w) return json(res, { ok: false, error: 'no such workspace' }, 400);
|
|
6623
|
+
if (typeof body.live !== 'boolean') return json(res, { ok: false, error: 'say live: true or live: false' }, 400);
|
|
6624
|
+
liveNotes.setSwitch(STATE, w.root, { live: body.live });
|
|
6625
|
+
return json(res, { ok: true, ws: w.id, jev: liveJevInfo(w) });
|
|
6626
|
+
}
|
|
6627
|
+
/* Run 6: the live-notes view's writes — start or resume a session, add points, close. A proposal is a local word match made in
|
|
6628
|
+
* this process, unless all three D85 gates hold: then `add` hands each cleaned point to Jev through lib/jev-live.js. */
|
|
6629
|
+
if ((p === '/api/live-notes/session' || p === '/api/live-notes/add' || p === '/api/live-notes/close' || p === '/api/live-notes/private') && req.method === 'POST') {
|
|
6630
|
+
let body = {};
|
|
6631
|
+
try { body = JSON.parse((await readBody(req)) || '{}'); }
|
|
6632
|
+
catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
6633
|
+
const w = body.ws ? workspaces.get(String(body.ws)) : homeWorkspace();
|
|
6634
|
+
if (!w) return json(res, { ok: false, error: 'no such workspace' }, 400);
|
|
6635
|
+
const exclude = tsSettings.getRepo(STATE, w.root).exclude;
|
|
6636
|
+
let r;
|
|
6637
|
+
if (p === '/api/live-notes/session') r = liveNotes.apiOpen({ root: w.root, runtimeDir: STATE, session: body.session || null, anchor: body.anchor || null, exclude });
|
|
6638
|
+
else if (p === '/api/live-notes/add') {
|
|
6639
|
+
const snd = liveSender(w);
|
|
6640
|
+
r = await liveNotes.apiAdd({ root: w.root, runtimeDir: STATE, session: body.session, text: body.text, source: body.source === 'speech' ? 'speech' : 'typed', exclude,
|
|
6641
|
+
key: snd.key, flags: snd.flags, ask: snd.ask, askOpts: snd.askOpts });
|
|
6642
|
+
}
|
|
6643
|
+
else if (p === '/api/live-notes/private') r = liveNotes.apiMarkPrivate({ root: w.root, session: body.session });
|
|
6644
|
+
else r = liveNotes.apiClose({ root: w.root, runtimeDir: STATE, session: body.session, retentionDays: liveNotes.getSwitch(STATE, w.root).retention_days, exclude });
|
|
6645
|
+
if (r.ok && p !== '/api/live-notes/close') { r.jev = liveJevInfo(w); if (r.note) r.note = r.jev.note; }
|
|
6584
6646
|
return json(res, r, r.ok ? 200 : (r.status || 400));
|
|
6585
6647
|
}
|
|
6586
6648
|
if (p === '/api/validate/plan' && req.method === 'POST') {
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "The test files each npm test script runs. run-tests.js reads this file; add a new suite here and every runner, local and CI, picks it up. Every suite claims its own port, state directory and tmux socket (test-run.js or its own equivalent), which is what makes running them side by side safe.",
|
|
3
|
+
"_comment_exclusive": "Suites that write the checkout's own state (workspace/state/, the home workspace's ledgers) while they run. run-tests.js runs each of them with nothing else beside it, because suites that read the checkout as their home workspace (proactive.test.js's scan, for one) would see those writes mid-run.",
|
|
4
|
+
"exclusive": [
|
|
5
|
+
"test.js"
|
|
6
|
+
],
|
|
7
|
+
"unit": [
|
|
8
|
+
"test.js"
|
|
9
|
+
],
|
|
10
|
+
"modules": [
|
|
11
|
+
"proactive.test.js",
|
|
12
|
+
"focus.test.js",
|
|
13
|
+
"handoff.test.js",
|
|
14
|
+
"repo-scan.test.js",
|
|
15
|
+
"profiles.test.js",
|
|
16
|
+
"external-linking.test.js",
|
|
17
|
+
"story-artifacts.test.js",
|
|
18
|
+
"no-network.test.js",
|
|
19
|
+
"typesafe-validation.test.js",
|
|
20
|
+
"claims.test.js",
|
|
21
|
+
"status-facts-panel.test.js",
|
|
22
|
+
"jev-claims.test.js",
|
|
23
|
+
"jev-categorize.test.js",
|
|
24
|
+
"jev-status.test.js",
|
|
25
|
+
"jev-route.test.js",
|
|
26
|
+
"repo-watch.test.js",
|
|
27
|
+
"fixture-parity.test.mjs",
|
|
28
|
+
"markdown.test.mjs",
|
|
29
|
+
"start-policy.test.js",
|
|
30
|
+
"state-isolation.test.js",
|
|
31
|
+
"sync.test.js",
|
|
32
|
+
"schedule-cli.test.js",
|
|
33
|
+
"initiative-surface.test.js",
|
|
34
|
+
"guide.test.js",
|
|
35
|
+
"next-step-actions.test.js",
|
|
36
|
+
"kg.test.js",
|
|
37
|
+
"doc-category.test.js",
|
|
38
|
+
"doc-category-corrections.test.js",
|
|
39
|
+
"proactive-prepare.test.js",
|
|
40
|
+
"learn.test.js",
|
|
41
|
+
"session-flag.test.js",
|
|
42
|
+
"live-notes.test.js"
|
|
43
|
+
],
|
|
44
|
+
"panels": [
|
|
45
|
+
"pm-shell-test.js",
|
|
46
|
+
"pm-repos-test.js",
|
|
47
|
+
"pm-initiatives-test.js",
|
|
48
|
+
"pm-detail-test.js",
|
|
49
|
+
"pm-tabs-test.js",
|
|
50
|
+
"pm-term-test.js",
|
|
51
|
+
"pm-modes-test.js",
|
|
52
|
+
"pm-r1-test.js",
|
|
53
|
+
"pm-r2-test.js",
|
|
54
|
+
"pm-r3-test.js",
|
|
55
|
+
"pm-r4-test.js",
|
|
56
|
+
"pm-r5-test.js",
|
|
57
|
+
"pm-r6-test.js",
|
|
58
|
+
"pm-r7-test.js",
|
|
59
|
+
"pm-r8-test.js",
|
|
60
|
+
"pm-r9-test.js",
|
|
61
|
+
"pm-r10-test.js",
|
|
62
|
+
"pm-r11-test.js",
|
|
63
|
+
"pm-r12-test.js",
|
|
64
|
+
"pm-r13-test.js",
|
|
65
|
+
"pm-r14-test.js",
|
|
66
|
+
"pm-r15-test.js",
|
|
67
|
+
"pm-r16-test.js",
|
|
68
|
+
"pm-r17-test.js",
|
|
69
|
+
"pm-r18-test.js",
|
|
70
|
+
"pm-r19-test.js",
|
|
71
|
+
"pm-r20-test.js",
|
|
72
|
+
"pm-r21-test.js",
|
|
73
|
+
"pm-r22-test.js",
|
|
74
|
+
"pm-r23-test.js",
|
|
75
|
+
"pm-r24-test.js",
|
|
76
|
+
"pm-r25-test.js",
|
|
77
|
+
"pm-r26-test.js",
|
|
78
|
+
"pm-doc-category-test.js",
|
|
79
|
+
"pm-r27-test.js",
|
|
80
|
+
"pm-r28-test.js",
|
|
81
|
+
"pm-r29-today-test.js",
|
|
82
|
+
"pm-r29-portfolio-test.js",
|
|
83
|
+
"pm-r29-knowledge-test.js",
|
|
84
|
+
"pm-r29-memory-test.js",
|
|
85
|
+
"pm-r29-skills-scheduled-test.js",
|
|
86
|
+
"pm-r29-validate-guide-test.js",
|
|
87
|
+
"pm-r29-reports-addrepo-test.js",
|
|
88
|
+
"pm-r29-filespad-test.js",
|
|
89
|
+
"pm-r29-newsession-alignment-test.js",
|
|
90
|
+
"pm-live-notes-test.js",
|
|
91
|
+
"pm-r30-test.js"
|
|
92
|
+
],
|
|
93
|
+
"ui": [
|
|
94
|
+
"ui-test.js"
|
|
95
|
+
],
|
|
96
|
+
"seconds": {
|
|
97
|
+
"test.js": 48,
|
|
98
|
+
"proactive.test.js": 4,
|
|
99
|
+
"focus.test.js": 1,
|
|
100
|
+
"handoff.test.js": 1,
|
|
101
|
+
"repo-scan.test.js": 1,
|
|
102
|
+
"profiles.test.js": 1,
|
|
103
|
+
"external-linking.test.js": 1,
|
|
104
|
+
"story-artifacts.test.js": 1,
|
|
105
|
+
"no-network.test.js": 1,
|
|
106
|
+
"typesafe-validation.test.js": 1,
|
|
107
|
+
"claims.test.js": 1,
|
|
108
|
+
"status-facts-panel.test.js": 1,
|
|
109
|
+
"jev-claims.test.js": 3,
|
|
110
|
+
"jev-categorize.test.js": 1,
|
|
111
|
+
"jev-status.test.js": 1,
|
|
112
|
+
"jev-route.test.js": 1,
|
|
113
|
+
"repo-watch.test.js": 1,
|
|
114
|
+
"fixture-parity.test.mjs": 1,
|
|
115
|
+
"markdown.test.mjs": 1,
|
|
116
|
+
"start-policy.test.js": 1,
|
|
117
|
+
"state-isolation.test.js": 4,
|
|
118
|
+
"sync.test.js": 4,
|
|
119
|
+
"schedule-cli.test.js": 3,
|
|
120
|
+
"initiative-surface.test.js": 3,
|
|
121
|
+
"guide.test.js": 1,
|
|
122
|
+
"next-step-actions.test.js": 5,
|
|
123
|
+
"kg.test.js": 1,
|
|
124
|
+
"doc-category.test.js": 1,
|
|
125
|
+
"doc-category-corrections.test.js": 1,
|
|
126
|
+
"proactive-prepare.test.js": 9,
|
|
127
|
+
"learn.test.js": 3,
|
|
128
|
+
"session-flag.test.js": 1,
|
|
129
|
+
"live-notes.test.js": 3,
|
|
130
|
+
"pm-shell-test.js": 6,
|
|
131
|
+
"pm-repos-test.js": 3,
|
|
132
|
+
"pm-initiatives-test.js": 15,
|
|
133
|
+
"pm-detail-test.js": 16,
|
|
134
|
+
"pm-tabs-test.js": 59,
|
|
135
|
+
"pm-term-test.js": 28,
|
|
136
|
+
"pm-modes-test.js": 18,
|
|
137
|
+
"pm-r1-test.js": 6,
|
|
138
|
+
"pm-r2-test.js": 3,
|
|
139
|
+
"pm-r3-test.js": 3,
|
|
140
|
+
"pm-r4-test.js": 4,
|
|
141
|
+
"pm-r5-test.js": 3,
|
|
142
|
+
"pm-r6-test.js": 2,
|
|
143
|
+
"pm-r7-test.js": 1,
|
|
144
|
+
"pm-r8-test.js": 1,
|
|
145
|
+
"pm-r9-test.js": 2,
|
|
146
|
+
"pm-r10-test.js": 1,
|
|
147
|
+
"pm-r11-test.js": 1,
|
|
148
|
+
"pm-r12-test.js": 6,
|
|
149
|
+
"pm-r13-test.js": 2,
|
|
150
|
+
"pm-r14-test.js": 7,
|
|
151
|
+
"pm-r15-test.js": 35,
|
|
152
|
+
"pm-r16-test.js": 7,
|
|
153
|
+
"pm-r17-test.js": 3,
|
|
154
|
+
"pm-r18-test.js": 4,
|
|
155
|
+
"pm-r19-test.js": 18,
|
|
156
|
+
"pm-r20-test.js": 4,
|
|
157
|
+
"pm-r21-test.js": 4,
|
|
158
|
+
"pm-r22-test.js": 32,
|
|
159
|
+
"pm-r23-test.js": 13,
|
|
160
|
+
"pm-r24-test.js": 4,
|
|
161
|
+
"pm-r25-test.js": 9,
|
|
162
|
+
"pm-r26-test.js": 9,
|
|
163
|
+
"pm-doc-category-test.js": 4,
|
|
164
|
+
"pm-r27-test.js": 26,
|
|
165
|
+
"pm-r28-test.js": 9,
|
|
166
|
+
"pm-r29-today-test.js": 4,
|
|
167
|
+
"pm-r29-portfolio-test.js": 3,
|
|
168
|
+
"pm-r29-knowledge-test.js": 3,
|
|
169
|
+
"pm-r29-memory-test.js": 4,
|
|
170
|
+
"pm-r29-skills-scheduled-test.js": 6,
|
|
171
|
+
"pm-r29-validate-guide-test.js": 20,
|
|
172
|
+
"pm-r29-reports-addrepo-test.js": 4,
|
|
173
|
+
"pm-r29-filespad-test.js": 5,
|
|
174
|
+
"pm-r29-newsession-alignment-test.js": 8,
|
|
175
|
+
"pm-live-notes-test.js": 24,
|
|
176
|
+
"pm-r30-test.js": 4,
|
|
177
|
+
"ui-test.js": 30
|
|
178
|
+
},
|
|
179
|
+
"_comment_seconds": "Measured wall time of each suite run alone (4-CPU sandbox, 2026-10-05). Only the ORDER uses it: the slowest suites start first, and --shard deals suites to the least-loaded shard so the shards finish together. A wrong or missing number never skips a suite; a missing one counts as 10."
|
|
180
|
+
}
|