create-pmos 0.505.0 → 0.506.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 +32 -14
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/changelog.json +31 -0
- package/workspace/fixtures/decision-pairs.js +94 -0
- package/workspace/guide/ask-the-past.md +46 -0
- package/workspace/guide/claude-proposes.md +29 -2
- package/workspace/guide/index.md +2 -0
- package/workspace/guide/what-you-missed.md +42 -0
- package/workspace/lib/ai-cli.js +63 -0
- package/workspace/lib/ai-guard.js +314 -0
- package/workspace/lib/briefing-premium-agent.js +3 -0
- package/workspace/lib/decision-ledger.js +236 -0
- package/workspace/lib/decision-memory.js +237 -0
- package/workspace/lib/drift-cli.js +98 -0
- package/workspace/lib/drift-facts.js +151 -0
- package/workspace/lib/drift-runs.js +149 -0
- package/workspace/lib/drift.js +227 -0
- package/workspace/lib/grill-cli.js +22 -1
- package/workspace/lib/grill-runs.js +20 -3
- package/workspace/lib/grill.js +15 -8
- package/workspace/lib/past-chat.js +32 -0
- package/workspace/lib/past-cli.js +147 -0
- package/workspace/lib/past-repeat.js +126 -0
- package/workspace/lib/past.js +353 -0
- package/workspace/lib/pmos-verbs.js +9 -0
- package/workspace/lib/proposal-runs.js +20 -0
- package/workspace/lib/route-modules.js +1 -0
- package/workspace/lib/routes-drift.js +62 -0
- package/workspace/lib/settings-store.js +6 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/grill-words.js +4 -2
- package/workspace/public/pm/home-app.js +14 -2
- package/workspace/public/pm/home-drift.js +76 -0
- package/workspace/server.js +19 -0
- package/workspace/skills/grill.skill +4 -2
- package/workspace/test-files.json +1 -1
- package/workspace/test-lists/ask-the-past.json +5 -0
- package/workspace/test-lists/decision-memory.json +6 -0
- package/workspace/test-lists/weekly-drift-check.json +5 -0
|
@@ -101,6 +101,10 @@ const DEFS = Object.freeze({
|
|
|
101
101
|
// and the monthly credit cap in US dollars. At the cap Claude pauses. `pmos grill mode|cap` reads and sets them.
|
|
102
102
|
'grill.mode': one('checks', 'computer', ['full', 'exits', 'off'], 'full'),
|
|
103
103
|
'grill.cap': { section: 'checks', scope: 'computer', type: 'dollars', min: 0, max: 500, def: 10 },
|
|
104
|
+
// The AI jobs' kill switch (lib/ai-guard.js): 'ai.on' false stops every AI job; 'ai.off' lists the jobs that are off one by one.
|
|
105
|
+
// `pmos ai off [job]` and `pmos ai on [job]` set them; PMOS_AI=off wins over both.
|
|
106
|
+
'ai.on': bool('checks', 'computer', true),
|
|
107
|
+
'ai.off': { section: 'checks', scope: 'computer', type: 'jobs', max: 40, def: [] },
|
|
104
108
|
});
|
|
105
109
|
|
|
106
110
|
/* The environment variables that win over a setting, here or in a setting's own store. `key` names the setting the page
|
|
@@ -112,6 +116,7 @@ const ENV = Object.freeze([
|
|
|
112
116
|
{ key: 'rec.final', env: 'PMOS_NOTES_FINAL', off: 'off', effect: 'The pass after Stop is off on this computer.' },
|
|
113
117
|
{ key: 'notes.wispr', env: 'PMOS_NOTES_WISPR', off: 'off', effect: 'Wispr Flow notes are not fetched on this computer.' },
|
|
114
118
|
{ key: 'notes.engine', env: 'PMOS_NOTES_QWEN', off: 'off', effect: 'Qwen3-ASR is not used on this computer.' },
|
|
119
|
+
{ key: 'ai.on', env: 'PMOS_AI', off: 'off', effect: 'Every AI job is off on this computer: nothing is filed under ✦.' },
|
|
115
120
|
]);
|
|
116
121
|
|
|
117
122
|
const WS_ID = /^[A-Za-z0-9_-]{1,80}$/;
|
|
@@ -163,6 +168,7 @@ function validate(key, value) {
|
|
|
163
168
|
}
|
|
164
169
|
// ['*'] alone is every workspace (the product picker's All, lib/briefing-premium-products.js)
|
|
165
170
|
case 'ids': return value === null || Array.isArray(value) && (value.length === 1 && value[0] === '*' || value.length <= d.max && value.every((x) => typeof x === 'string' && WS_ID.test(x)) && new Set(value).size === value.length) ? null : `${key} must list at most ${d.max} workspace ids, each once, or ['*'] for every one`;
|
|
171
|
+
case 'jobs': return Array.isArray(value) && value.length <= d.max && value.every((x) => typeof x === 'string' && /^[a-z][a-z0-9-]{1,40}$/.test(x)) && new Set(value).size === value.length ? null : `${key} must list at most ${d.max} job ids, each once`;
|
|
166
172
|
// Dress for the day (pm/cozy-seasons.js): a day's key is its charm key, as in DAYS there
|
|
167
173
|
case 'country': return value === '' || typeof value === 'string' && /^[A-Z]{2}$/.test(value) ? null : `${key} must be a two-letter country code, or empty for this computer's`;
|
|
168
174
|
case 'colors': return Array.isArray(value) && value.length === 2 && value.every((c) => DRESS_COLORS.includes(c)) ? null : `${key} must be two of ${DRESS_COLORS.join(', ')}`;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.506.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pmos-workspace",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.506.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@anthropic-ai/sdk": "^1.3.0",
|
package/workspace/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* pm/grill-words.js — the words and marks every page uses for Claude's checks (Grill and Forecast screens; canvas GrillResult, GrillLenses,
|
|
2
2
|
* FcIntel, FcPhone). Pure: Node can import it.
|
|
3
3
|
*
|
|
4
|
-
* ANGLE { [angle]: { label, icon (a 24-unit path) } }: premortem · redteam · voices · reframe · scope · whatif
|
|
4
|
+
* ANGLE { [angle]: { label, icon (a 24-unit path) } }: premortem · redteam · voices · reframe · scope · whatif · decision · past
|
|
5
5
|
* findingOf(p) a grill proposal (lib/grill.js finding) → { angle, label, icon, title, why, next, simulated }
|
|
6
6
|
* the title and why without the angle prefix and the "Next:" tail lib/grill.js writes into them
|
|
7
7
|
* whenWord(iso, now) 'overnight' · 'today' · 'yesterday' · 'on Oct 30'
|
|
@@ -16,6 +16,8 @@ export const ANGLE = Object.freeze({
|
|
|
16
16
|
reframe: { label: 'Reframe', icon: 'M4 8V4h4M20 8V4h-4M4 16v4h4M20 16v4h-4M9 9h6v6H9z' },
|
|
17
17
|
scope: { label: 'Scope', icon: 'M4 6h16M7 12h10M10 18h4' },
|
|
18
18
|
whatif: { label: 'What-if', icon: 'M4 18c4 0 4-12 8-12s4 12 8 12M4 12c4 0 4-6 8-6' },
|
|
19
|
+
decision: { label: 'Earlier decision', icon: 'M6 3h9l3 3v15H6zM9 9h6M9 13h6M9 17h3' },
|
|
20
|
+
past: { label: 'You decided this before', icon: 'M3 12a9 9 0 1 0 3-6.7M3 4v4h4M12 7v5l3 2' },
|
|
19
21
|
});
|
|
20
22
|
const MON = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
21
23
|
|
|
@@ -44,7 +46,7 @@ export function costWord(usd) {
|
|
|
44
46
|
if (usd == null || !Number.isFinite(n)) return '';
|
|
45
47
|
return n < 1 ? Math.max(1, Math.round(n * 100)) + '¢' : '$' + n.toFixed(2);
|
|
46
48
|
}
|
|
47
|
-
const WHY = { exit: 'before it leaves its stage', forecast: 'when its forecast turned', move: 'after its scope or dates moved', monthly: 'in the monthly look' };
|
|
49
|
+
const WHY = { exit: 'before it leaves its stage', forecast: 'when its forecast turned', move: 'after its scope or dates moved', plan: 'when its PRD changed', monthly: 'in the monthly look' };
|
|
48
50
|
export const whyWord = (r) => (r && WHY[r.why]) || '';
|
|
49
51
|
export function runLine(r, now = Date.now()) {
|
|
50
52
|
if (!r) return '';
|
|
@@ -58,6 +58,7 @@ import * as CW from './ctx-wedge.js'; // how full a session's context is, and
|
|
|
58
58
|
import * as SO from './sort-order.js'; // the sort button and ONE order with the Board (batch 14 item 8, canvas SoAHomePhone)
|
|
59
59
|
import * as PO from './prog-order.js'; // a program's order on Home: the grip, hold-then-drag on a phone, ✦ Claude's order from the dates (canvas RoAPhone)
|
|
60
60
|
import * as IMP from './home-impact.js'; // Your impact: the weekly check in Home's own chat (batch 16 item 7, GmP1Ledger)
|
|
61
|
+
import * as DRIFT from './home-drift.js'; // What you might have missed: the weekly drift ✦ in Home's own chat (weekly-drift-check)
|
|
61
62
|
import * as LPV from './loose-props.js'; // Claude's clean-up of "Not in an initiative": the ✦ on its header and the Viewer (batch 13 item 3)
|
|
62
63
|
import * as IM from './impact.js';
|
|
63
64
|
import { mountPull } from './cozy-pull.js'; // pull to refresh on Your day, Adler peeking up (batch 17 item 4-B, AdP2) // Your impact (batch 16 item 7): Adler's sprout on the greeting, the week in the Viewer, the Friday chapter
|
|
@@ -214,6 +215,7 @@ async function load() {
|
|
|
214
215
|
draw();
|
|
215
216
|
NEED.refresh(); // an open ask: its agent finished, or it left Home while it was open
|
|
216
217
|
loadImpact();
|
|
218
|
+
loadDrift();
|
|
217
219
|
readImpact();
|
|
218
220
|
loadGrillNews();
|
|
219
221
|
// the boards of the other products Home reads, each its own closed group: read after the list is drawn, so a slow one never holds it up
|
|
@@ -347,7 +349,7 @@ function homeListHtml() {
|
|
|
347
349
|
S.lo = { sel, ses: S.ses, restOpen: (k) => S.rest.has(k), pin: S.pin, phone: false }; // the folded rail's card draws Home's rows with these
|
|
348
350
|
return TD.listHtml(M, todayModel(), {
|
|
349
351
|
sel, selHead: S.head, ses: S.ses, pOpen, here: isPhone() ? null : TD.groupOfOpen(M, sel) || S.head, restOpen: (k) => S.rest.has(k), cap: S.cap, pin: S.pin, avHtml, ready: S.ready, err: S.D.err.length ? S.D.err[0] : '',
|
|
350
|
-
pick: S.D.pick, tones: pickTones(), review: R, mic: canDictate(), phone: isPhone(), day: S.day, news: S.news && S.news.ok ? S.news.count : 0, top: S.ready ? RECROW.html() + MEN.rowHtml(S.D, { sel: S.sel }) + (S.impact ? IMP.rowHtml(S.impact.c, { esc, sel: S.sel === 'impact' }) : '') : S.menIn ? '<div class="ls-stale" inert>' + LS.inert(MEN.rowHtml(S.D, {})) + '</div>' : LS.menShape(), // Mentions on its own clock, seen but not pressed until Home is fresh (SpG)
|
|
352
|
+
pick: S.D.pick, tones: pickTones(), review: R, mic: canDictate(), phone: isPhone(), day: S.day, news: S.news && S.news.ok ? S.news.count : 0, top: S.ready ? RECROW.html() + MEN.rowHtml(S.D, { sel: S.sel }) + (S.impact ? IMP.rowHtml(S.impact.c, { esc, sel: S.sel === 'impact' }) : '') + (S.drift ? DRIFT.rowHtml(S.drift.c, { esc, sel: S.sel === 'drift' }) : '') : S.menIn ? '<div class="ls-stale" inert>' + LS.inert(MEN.rowHtml(S.D, {})) + '</div>' : LS.menShape(), // Mentions on its own clock, seen but not pressed until Home is fresh (SpG)
|
|
351
353
|
last: S.ready ? '' : LS.last(LAST_KEY), keep: S.ready && S.filter === 'today' && !S.ini ? (b) => LS.keepLast(LAST_KEY, b) : null,
|
|
352
354
|
imFace: IM.faceHtml(S.im, { joy: S.imJoy }), grill: S.ready ? grillNews() : null,
|
|
353
355
|
});
|
|
@@ -1072,7 +1074,7 @@ function programTarget(id, task) {
|
|
|
1072
1074
|
function homeTarget() {
|
|
1073
1075
|
return { key: 'chat:main', ws: WS || undefined, title: 'Home', scope: { text: 'All work', icon: I.home }, rich: false, placeholder: 'Message Claude', role: CLAUDE,
|
|
1074
1076
|
empty: 'Ask anything about your work. Claude reads your repositories and works in a real Claude Code session.', also: alsoOf(null),
|
|
1075
|
-
intro: S.impact ? S.impact.intro : undefined };
|
|
1077
|
+
intro: S.sel === 'drift' && S.drift ? S.drift.intro : S.impact ? S.impact.intro : S.drift ? S.drift.intro : undefined };
|
|
1076
1078
|
}
|
|
1077
1079
|
/* Your impact (batch 16 item 7; batch 23 item 4, IcA): while last week's check is open, Home's own chat opens on Claude's thank-you card.
|
|
1078
1080
|
* Off in Settings, or nothing to check: no card. Read after the list is drawn, so it never holds Home up. */
|
|
@@ -1084,6 +1086,15 @@ async function loadImpact() {
|
|
|
1084
1086
|
if ((was || S.impact) && cur && cur.key === 'chat:main') { chat.open(homeTarget()); }
|
|
1085
1087
|
if (was || S.impact) drawList();
|
|
1086
1088
|
}
|
|
1089
|
+
/* What you might have missed (weekly-drift-check): last week's drift ✦ in Home's own chat. A quiet week, or the check off: no card, no row. */
|
|
1090
|
+
async function loadDrift() {
|
|
1091
|
+
const c = await DRIFT.readCheck(pmGet).catch(() => null);
|
|
1092
|
+
const was = !!S.drift;
|
|
1093
|
+
S.drift = c ? { c, intro: DRIFT.checkIntro(c, { esc, pmPost }) } : null;
|
|
1094
|
+
const cur = chat.current && chat.current();
|
|
1095
|
+
if ((was || S.drift) && cur && cur.key === 'chat:main') { chat.open(homeTarget()); }
|
|
1096
|
+
if (was || S.drift) drawList();
|
|
1097
|
+
}
|
|
1087
1098
|
// the switch in Settings › General (pm/cozy-settings.js) turns the check off and on at once
|
|
1088
1099
|
window.addEventListener('pmos:impact', () => { loadImpact(); readImpact(); }); // the switch: the weekly check row and Adler's sprout follow at once
|
|
1089
1100
|
function openTarget(t) {
|
|
@@ -1792,6 +1803,7 @@ $('#hmList').onclick = (e) => { // delegated: every press on the list is a <bu
|
|
|
1792
1803
|
if (S.ses && !b.closest('.hv-spp, .hv-sps, .hv-rcs')) sesPill(null, false); // a press anywhere else in the list closes it
|
|
1793
1804
|
if (d.hmSel) { if (S.ses) sesPill(null, false); if (S.day) S.day = false; select(d.hmSel); return; }
|
|
1794
1805
|
if ('hrImpact' in d) { if (S.day) S.day = false; S.sel = 'impact'; if (isPhone()) S.phoneChat = true; openTarget(homeTarget()); drawList(); return; } // Your impact: Home's own chat, on the weekly check
|
|
1806
|
+
if ('hrDrift' in d) { if (S.day) S.day = false; S.sel = 'drift'; if (isPhone()) S.phoneChat = true; openTarget(homeTarget()); drawList(); return; } // What you might have missed: Home's own chat, on the weekly ✦
|
|
1795
1807
|
if ('hrMentions' in d) { if (S.day) S.day = false; S.sel = 'mentions'; drawList(); MEN.openMentions(menCtx()); return; }
|
|
1796
1808
|
if ('hmImpact' in d) { if (S.day) day(false); IM.openWeek(); return; }
|
|
1797
1809
|
if (d.hmGn) { openGrillNews(d.hmGn); return; }
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* pm/home-drift.js — "What you might have missed" in Home's chat (initiative weekly-drift-check; lib/routes-drift.js).
|
|
2
|
+
* Each Monday PMOS checks last week's records for quiet drift. When it found something, Claude shows at most three items as one ✦ card in
|
|
3
|
+
* Home's own chat, with the records each came from. A quiet week shows nothing: no card and no row. One quiet ⋯ per item holds "Not right,
|
|
4
|
+
* drop it"; a dropped item says so with Undo. Nothing else is a button: the check runs by itself. It reuses the Your impact card's look
|
|
5
|
+
* (pm/home-impact.css, canvas IcA).
|
|
6
|
+
*
|
|
7
|
+
* readCheck(pmGet) → the check of GET /api/drift/check, else null
|
|
8
|
+
* rowHtml(c, { esc, sel }) → the one row under Mentions until the card is seen: a tap opens Home's chat on it
|
|
9
|
+
* checkIntro(c, { esc, pmPost }) → the intro of Home's chat: { role, mood, text, card: { html, press } }
|
|
10
|
+
* Writes: POST /api/drift/drop { id }, /undo { id }, /looked { week }. Nothing leaves PMOS. */
|
|
11
|
+
|
|
12
|
+
const SV = (d, w = 18) => `<svg aria-hidden="true" width="${w}" height="${w}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${d}"/></svg>`;
|
|
13
|
+
const P = {
|
|
14
|
+
stalled: 'M12 7v5l3 2M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z',
|
|
15
|
+
slipped: 'M4 12h12M12 6l6 6-6 6',
|
|
16
|
+
jira: 'M8 7h11M8 12h11M8 17h11M4 7h.01M4 12h.01M4 17h.01',
|
|
17
|
+
spark: 'M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8z',
|
|
18
|
+
no: 'M6 6l12 12M18 6L6 18', dots: 'M5 12h.01M12 12h.01M19 12h.01', lock: 'M7 11V8a5 5 0 0 1 10 0v3M5 11h14v9H5z',
|
|
19
|
+
};
|
|
20
|
+
const WORD = ['', 'One thing', 'Two things', 'Three things'];
|
|
21
|
+
|
|
22
|
+
export async function readCheck(pmGet) {
|
|
23
|
+
const r = await pmGet('/api/drift/check');
|
|
24
|
+
return r && r.ok && r.on && r.check && r.check.items && r.check.items.length ? r.check : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function rowHtml(c, { esc, sel }) {
|
|
28
|
+
if (!c || c.looked) return '';
|
|
29
|
+
return `<div class="hv-card hr-card hr-men-card"><div class="hm-r hv-r hr-men${sel ? ' sel' : ''}" data-id="drift">`
|
|
30
|
+
+ `<button type="button" class="hm-rm" data-hr-drift${sel ? ' aria-current="true"' : ''} aria-label="What you might have missed last week">`
|
|
31
|
+
+ `<span class="hv-mk hr-at" aria-hidden="true">${SV(P.spark, 16)}</span><span class="hm-rt"><b>What you might have missed</b><small class="hr-sub">Last week</small></span>`
|
|
32
|
+
+ '<span class="hv-xc" aria-hidden="true"></span></button></div></div>';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function checkIntro(c, { esc, pmPost }) {
|
|
36
|
+
const st = { c, msg: '', menu: null, told: !!c.looked };
|
|
37
|
+
const row = (x) => {
|
|
38
|
+
if (x.dropped) {
|
|
39
|
+
return `<li class="im-q im-gone"><span class="im-gt">Dropped: ${esc(x.title)}</span>`
|
|
40
|
+
+ `<button type="button" class="im-undo" data-dr-undo="${esc(x.id)}">Undo</button></li>`;
|
|
41
|
+
}
|
|
42
|
+
const open = st.menu === x.id;
|
|
43
|
+
const facts = (x.claims || []).map((k) => k.text).filter(Boolean).join(' · ');
|
|
44
|
+
return `<li class="im-q"><span class="im-k" aria-hidden="true">${SV(P[x.kind] || P.spark)}</span>`
|
|
45
|
+
+ `<span class="im-tx"><span class="im-t">${esc(x.title)}</span><span class="im-w">${esc(x.say)}</span>`
|
|
46
|
+
+ (facts ? `<span class="im-w">${esc(facts)}</span>` : '') + (x.product ? `<span class="im-w">${esc(x.product)}</span>` : '') + '</span>'
|
|
47
|
+
+ `<span class="im-mw"><button type="button" class="ut-ib im-more" data-dr-more="${esc(x.id)}" aria-haspopup="menu" aria-expanded="${open}" aria-label="More" title="More">${SV(P.dots, 16)}</button>`
|
|
48
|
+
+ (open ? `<span class="im-pop" role="menu"><button type="button" role="menuitem" data-dr-drop="${esc(x.id)}">${SV(P.no, 15)} Not right, drop it</button></span>` : '')
|
|
49
|
+
+ '</span></li>';
|
|
50
|
+
};
|
|
51
|
+
const html = () => {
|
|
52
|
+
if (!st.told) { st.told = true; st.c.looked = true; Promise.resolve(pmPost('/api/drift/looked', { week: st.c.week })).catch(() => {}); }
|
|
53
|
+
return `<div class="im-card" role="group" aria-label="What you might have missed last week">`
|
|
54
|
+
+ `<ul class="im-list">${st.c.items.map(row).join('')}</ul>`
|
|
55
|
+
+ `<p class="im-lock">${SV(P.lock, 13)} Read from your records. Nothing was changed or sent.</p>`
|
|
56
|
+
+ `<p class="im-msg" role="status">${esc(st.msg)}</p></div>`;
|
|
57
|
+
};
|
|
58
|
+
const press = async (b) => {
|
|
59
|
+
const d = b.dataset;
|
|
60
|
+
if (d.drMore) { st.menu = st.menu === d.drMore ? null : d.drMore; return true; }
|
|
61
|
+
let r = null;
|
|
62
|
+
if (d.drDrop) r = await pmPost('/api/drift/drop', { id: d.drDrop });
|
|
63
|
+
else if (d.drUndo) r = await pmPost('/api/drift/undo', { id: d.drUndo });
|
|
64
|
+
else return false;
|
|
65
|
+
st.menu = null;
|
|
66
|
+
if (!r || !r.ok) { st.msg = (r && r.error) || 'Not saved.'; return true; }
|
|
67
|
+
const x = st.c.items.find((y) => y.id === (d.drDrop || d.drUndo));
|
|
68
|
+
if (x) x.dropped = !!d.drDrop;
|
|
69
|
+
st.msg = '';
|
|
70
|
+
return true;
|
|
71
|
+
};
|
|
72
|
+
const n = c.items.length;
|
|
73
|
+
const unread = c.notRead && c.notRead.length ? ' I could not read ' + c.notRead.join(', ') + ', so this may not be all.' : '';
|
|
74
|
+
return { role: { key: 'claude', name: 'Claude', initial: 'C' }, mood: 'honest',
|
|
75
|
+
text: `✦ ${WORD[n] || n + ' things'} quietly drifted last week. The facts come from your records. Whether they matter is yours to judge.${unread}`, card: { html, press } };
|
|
76
|
+
}
|
package/workspace/server.js
CHANGED
|
@@ -3073,6 +3073,25 @@ const impactRuns = require('./lib/impact-runs.js').create({
|
|
|
3073
3073
|
warn: (what, e) => warnWriteFail(what, e),
|
|
3074
3074
|
});
|
|
3075
3075
|
const impactTick = () => { try { impactRuns.tick({}); } catch (e) { warnWriteFail('impact tick', e); } };
|
|
3076
|
+
// weekly-drift-check (What would I have missed): each Monday the server reads every open product's records for quiet drift (lib/drift-runs.js).
|
|
3077
|
+
// Structured signals only, no session and no model. Its only write is the person's own ledger <runtime>/drift/drift.jsonl, through the shared
|
|
3078
|
+
// guard (lib/ai-guard.js). Off with PMOS_DRIFT=off or `pmos ai off drift`. Nothing is sent.
|
|
3079
|
+
const driftRuns = require('./lib/drift-runs.js').create({
|
|
3080
|
+
runtime: () => STATE,
|
|
3081
|
+
products: () => {
|
|
3082
|
+
const IR = require('./lib/initiative-rows.js'), out = [];
|
|
3083
|
+
for (const w of workspaces.values()) {
|
|
3084
|
+
const r = IR.rowsFor(w.root);
|
|
3085
|
+
if (r && r.pending) return null; // the rows are being read in the background: the next tick runs the week
|
|
3086
|
+
out.push({ w: { id: w.id, name: w.name || w.id, root: w.root }, stateDir: stateDirFor(w), rows: r && r.ok ? r.rows : null });
|
|
3087
|
+
}
|
|
3088
|
+
return out;
|
|
3089
|
+
},
|
|
3090
|
+
open: (now) => !proactiveLib.inQuietHours(now, proSettings().quiet),
|
|
3091
|
+
env: process.env,
|
|
3092
|
+
every: TICK_MS, // its own tick, as often as the server's; only the server that owns the schedules runs it
|
|
3093
|
+
mayRun: () => { try { return scheduleOwner.mayRun(); } catch { return true; } },
|
|
3094
|
+
warn: (what, e) => warnWriteFail(what, e) });
|
|
3076
3095
|
// batch 16 item 6 ("Claude runs the OKRs"): Claude reads each goal with its recipe (lib/metric-read-runs.js) when its cadence comes round, files the
|
|
3077
3096
|
// value with its source and time, says honestly what it could not read and drafts the fix. A broken limit opens one alert: a bell line only when
|
|
3078
3097
|
// the goal's notify is push. At most 2 runs at once, one per product. PMOS_METRIC_READ=off.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grill
|
|
3
3
|
label: "Claude grills an initiative"
|
|
4
|
-
description: "One short background run for one initiative, started by Claude itself before a stage ends, when the forecast turns at risk, when scope or dates move a lot, or monthly on running work with late tasks. It picks the angles that fit the stage and the evidence (premortem, red team, voices, reframe, scope, what-if) and files each finding with a source and a next step. Everything waits under the initiative's ✦ for the PM's ✓. It writes no task, document or stage and sends nothing."
|
|
4
|
+
description: "One short background run for one initiative, started by Claude itself before a stage ends, when the forecast turns at risk, when scope or dates move a lot, when its PRD changes, or monthly on running work with late tasks. It picks the angles that fit the stage and the evidence (premortem, red team, voices, reframe, scope, what-if, earlier decision) and files each finding with a source and a next step. Everything waits under the initiative's ✦ for the PM's ✓. It writes no task, document or stage and sends nothing."
|
|
5
5
|
run: agent
|
|
6
6
|
agent: claude
|
|
7
7
|
output:
|
|
@@ -28,11 +28,13 @@ The angles. Pick the ones that fit the stage and why this run runs; you do not n
|
|
|
28
28
|
- `reframe`: the problem is framed wrong, or a smaller problem would win more.
|
|
29
29
|
- `scope`: what to cut, split or defer to keep the date (good when scope or dates moved).
|
|
30
30
|
- `whatif`: one other order or date for the work, and what it would change (good when the forecast turned).
|
|
31
|
+
- `decision`: the plan or the PRD goes against an earlier decision that still stands. Check this angle on EVERY run, first when the PRD changed. The decisions that stand are listed after this procedure, one line each with its `path:line`. Read the ones the plan touches for their words. Quote both sides verbatim: the decision's own words, and the plan's words at `path:line`. Never push back with a superseded decision: the server refuses it and names the one that replaced it. Say what you recommend: `keep` (keep the decision, change the plan), `change` (the plan is right: a new decision amends the old one), or `drop` (the old decision no longer holds). File it only when you would bet on it (`confidence` high or medium). A plan that only repeats a decision is not a finding.
|
|
31
32
|
|
|
32
33
|
Procedure:
|
|
33
34
|
1. Read the initiative: `pmos initiative export <id>`, its PRD and stage documents, `pmos task list <id>`, and the stored reads (`pmos source status`, `pmos source show <source>`). For Voices, look in the stored research and meeting notes.
|
|
34
35
|
2. Choose 2 to 4 angles. For each, find what the evidence says.
|
|
35
|
-
3. File
|
|
36
|
+
3. File an earlier decision with `pmos grill find decision --json -` and `{ "decision": "D79", "quote": "<the decision's words>", "plan": { "label": "Checkout PRD", "ref": "planning/prd/checkout-prd.md:42", "quote": "<the plan's words at that line>" }, "conflict": "<how the plan goes against it, one line>", "recommend": "keep", "next": "<one next step>", "confidence": "high" }`. The server checks both quotes and files it under the ✦ only after the real supersede pairs replayed.
|
|
37
|
+
4. File each other finding: `pmos grill find <angle> --json -` with `{ "title": "<the finding, one short line>", "why": "<the evidence, one line>", "next": "<one next step>", "sources": [{ "label": "Kickoff notes, Oct 3", "ref": "<path or link>" }], "rank": 1 }`.
|
|
36
38
|
|
|
37
39
|
Output contract: produce EXACTLY these sections in your final message:
|
|
38
40
|
- Filed (the finding ids and their angles, or "nothing: the plan holds")
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Decision memory (initiative decision-memory): the decision ledger, the grill's earlier-decision angle, the replay of the real supersede pairs through lib/ai-guard.js, and the plan trigger. No browser, no model.",
|
|
3
|
+
"modules": [
|
|
4
|
+
"decision-memory.test.js"
|
|
5
|
+
]
|
|
6
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "weekly-drift-check: What would I have missed. The signals, the cap and silence, facts from ledger files with path:line citations, the replay, the weekly run through the shared guard, pmos drift, the routes and Home's card (lib/drift.js, lib/drift-facts.js, lib/drift-runs.js, lib/drift-cli.js, lib/routes-drift.js, public/pm/home-drift.js).",
|
|
3
|
+
"modules": ["drift.test.js"],
|
|
4
|
+
"seconds": { "drift.test.js": 2 }
|
|
5
|
+
}
|