create-pmos 0.54.1 → 0.55.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/bin/create-pmos.js +3 -1
- package/package.json +1 -1
- 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/guide/proactive-engine.md +73 -9
- package/workspace/guide/today.md +27 -2
- package/workspace/lib/brief.js +268 -8
- package/workspace/lib/day-run.js +297 -0
- package/workspace/lib/goals.js +25 -3
- package/workspace/lib/proactive-cli.js +119 -6
- package/workspace/lib/proactive.js +53 -7
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +1 -1
- package/workspace/public/pm/components.css +135 -23
- package/workspace/public/pm/dest-today.js +10 -8
- package/workspace/public/pm/today-copy.js +185 -0
- package/workspace/public/pm/today-day.js +766 -0
- package/workspace/public/pm/today-goals.js +96 -161
- package/workspace/replaced-tests.json +10 -0
- package/workspace/server.js +213 -10
- package/workspace/skills/day-check.skill +56 -0
- package/workspace/skills/day-task.skill +47 -0
- package/workspace/skills/morning-brief.skill +47 -10
- package/workspace/test-files.json +2 -0
package/workspace/server.js
CHANGED
|
@@ -95,6 +95,7 @@ const proactiveLib = require('./lib/proactive');
|
|
|
95
95
|
const learnLib = require('./lib/learn');
|
|
96
96
|
const briefLib = require('./lib/brief');
|
|
97
97
|
const goalsLib = require('./lib/goals');
|
|
98
|
+
const dayRunLib = require('./lib/day-run');
|
|
98
99
|
const todayLib = require('./lib/today');
|
|
99
100
|
const prepareQueue = require('./lib/prepare-queue');
|
|
100
101
|
const harness = require('./lib/harness-parse');
|
|
@@ -155,6 +156,13 @@ const EMBEDDED_SELF = !INSTALLED_COPY && SELF_ROOT === PARENT_DIR &&
|
|
|
155
156
|
// launcher named a real repo via PMOS_SELF_ROOT. An installed copy launched WITHOUT that env
|
|
156
157
|
// (unsupported direct run) has no home — the client must open a repo to do anything.
|
|
157
158
|
const HOME_ENABLED = EMBEDDED_SELF || (!!process.env.PMOS_SELF_ROOT && dirExists(SELF_ROOT));
|
|
159
|
+
// An installed copy runs from the global npm folder, and the launcher starts it there. An upgrade (`npm install -g`)
|
|
160
|
+
// deletes and rewrites that folder, so a process that inherits it as its working directory is left in a deleted
|
|
161
|
+
// directory: the tmux daemon this server starts, and every agent CLI that daemon or a PTY starts, inherit it, and an
|
|
162
|
+
// agent CLI that reads its working directory at start exits ("the current working directory was deleted"). Every path
|
|
163
|
+
// in this server is absolute (__dirname, STATE, a workspace root), so an installed copy moves to the home folder,
|
|
164
|
+
// which no upgrade removes.
|
|
165
|
+
if (INSTALLED_COPY) { try { process.chdir(os.homedir()); } catch { /* keep the launch directory */ } }
|
|
158
166
|
const PUB = path.join(__dirname, 'public');
|
|
159
167
|
const PM_DIR = path.join(PUB, 'pm'); // the four-panel front end — served as a directory, guarded below
|
|
160
168
|
const XROOT = path.join(__dirname, 'node_modules', '@xterm');
|
|
@@ -2325,7 +2333,7 @@ function checkDueSchedules() {
|
|
|
2325
2333
|
// PMOS_TICK_MS exists for the suites (same footing as PMOS_LIVENESS_MS): proactive-prepare.test.js
|
|
2326
2334
|
// used to wait on two real minutes of this clock. Unset, the interval is the 60 seconds it always was.
|
|
2327
2335
|
const TICK_MS = Math.max(200, Number(process.env.PMOS_TICK_MS) || 60000);
|
|
2328
|
-
try { const _tick = setInterval(() => { checkDueSchedules(); proactiveTick(); try { prepareTick({ manual: false }); } catch { /* the next tick tries again */ } autoSync.tick().catch(() => {}); }, TICK_MS); if (_tick.unref) _tick.unref(); } catch {}
|
|
2336
|
+
try { const _tick = setInterval(() => { checkDueSchedules(); proactiveTick(); try { prepareTick({ manual: false }); } catch { /* the next tick tries again */ } try { dayTick(); } catch (e) { warnWriteFail('day run tick', e); } autoSync.tick().catch(() => {}); }, TICK_MS); if (_tick.unref) _tick.unref(); } catch {}
|
|
2329
2337
|
|
|
2330
2338
|
// ── Sessions: real PTYs, owned by the server, with disk-backed scrollback + metadata. ──
|
|
2331
2339
|
/** @type {Map<string, {id,ws,name,agent,cwd,state,note,created,pty,buf,cols,rows,clients:Set}>} */
|
|
@@ -2848,7 +2856,10 @@ function writeTmuxConf() {
|
|
|
2848
2856
|
// `env` is an OPT-IN override and only `new-session` ever passes one (profiles R3). has-session /
|
|
2849
2857
|
// kill-session / list-sessions / display-message have no session context, so handing them a
|
|
2850
2858
|
// profile's overlay would apply it to whichever pane the command happened to touch — a silent bug.
|
|
2851
|
-
|
|
2859
|
+
// The tmux daemon is born from the first of these calls and keeps that working directory for its whole life, across
|
|
2860
|
+
// workspace upgrades. It is started in the home folder, never in this server's install folder (see the chdir above).
|
|
2861
|
+
function tmuxCtl(args, env) { return spawnSync(TMUX, ['-L', TMUX_SOCK, ...args], { env: env || sessionEnv(), encoding: 'utf8', cwd: tmuxCwd() }); }
|
|
2862
|
+
function tmuxCwd() { const h = os.homedir(); return dirExists(h) ? h : undefined; }
|
|
2852
2863
|
function tmuxHasSession(name) { return !!TMUX && tmuxCtl(['has-session', '-t', name]).status === 0; }
|
|
2853
2864
|
function tmuxKillSession(name) { if (TMUX && name) tmuxCtl(['kill-session', '-t', name]); }
|
|
2854
2865
|
// Create the tmux session a FRESH spawn runs in. It REFUSES a name that already exists rather
|
|
@@ -3108,6 +3119,11 @@ function spawnSession({ name, agent, cwd, ws, origin, initialInput, profile, ini
|
|
|
3108
3119
|
const cmd = isShell ? (commandExists(SHELL) ? SHELL : 'sh') : agent;
|
|
3109
3120
|
// a session's working directory is confined to its OWN workspace root (R3)
|
|
3110
3121
|
const workdir = cwd && insideRoot(root, path.resolve(root, cwd)) ? path.resolve(root, cwd) : root;
|
|
3122
|
+
// A repository folder that was moved or deleted after it was registered: tmux would start the agent in its own folder
|
|
3123
|
+
// instead, and the agent would run in the wrong place or not at all. Refuse here and name the folder.
|
|
3124
|
+
if (!dirExists(workdir)) return exitedSession({ id, ws: w ? w.id : wsId(SELF_ROOT), root, name: (name && String(name).trim()) || agent, agent,
|
|
3125
|
+
origin: tag, profile: null, profileRefusal: null, plan_item: planItem || null, token: sessionToken },
|
|
3126
|
+
workdir, `the folder ${workdir} is not there. Open the repository again from its current folder, then try again.`);
|
|
3111
3127
|
// Resolved once, here, so every path below spawns under the SAME environment the record claims.
|
|
3112
3128
|
// `pres.id` is null whenever the overlay was not applied — including when a profile was asked for
|
|
3113
3129
|
// and could not be read — so the pane line can never name a profile that did nothing (R3/R9).
|
|
@@ -3572,6 +3588,103 @@ function prepareHandoff(body) {
|
|
|
3572
3588
|
};
|
|
3573
3589
|
}
|
|
3574
3590
|
|
|
3591
|
+
// ── Today day run (G13, G14, G21, G22; initiative today-goals) ───────────────────────────────────────────────────
|
|
3592
|
+
// The PM's press of "Prepare my day" records a run in goals/<date>.run.json beside the goals file. These helpers are
|
|
3593
|
+
// the only code that writes that file and the only code that starts its sessions: one per task through the `day-task`
|
|
3594
|
+
// skill (mode do, revise or send) and one per check through the `day-check` skill, both through spawnSession as
|
|
3595
|
+
// fireSchedule starts a scheduled agent skill. Every rule (which task, how many at once, when a check is due) is in
|
|
3596
|
+
// lib/day-run.js; this only reads, starts and writes. Nothing here runs for a day the PM did not press.
|
|
3597
|
+
const dayRunFile = (date) => path.join(GOALS_DIR(), date + '.run.json');
|
|
3598
|
+
function readDayRun(date) {
|
|
3599
|
+
let raw = null;
|
|
3600
|
+
try { raw = JSON.parse(fs.readFileSync(dayRunFile(date), 'utf8')); } catch { /* no run that day */ }
|
|
3601
|
+
const run = dayRunLib.readRun(raw, date);
|
|
3602
|
+
if (run && raw && typeof raw.ws === 'string') run.ws = raw.ws; // the repository the PM pressed in; the sessions run there
|
|
3603
|
+
return run;
|
|
3604
|
+
}
|
|
3605
|
+
function writeDayRun(run) {
|
|
3606
|
+
try { fs.mkdirSync(GOALS_DIR(), { recursive: true }); fs.writeFileSync(dayRunFile(run.date), JSON.stringify(run) + '\n'); return true; }
|
|
3607
|
+
catch (e) { warnWriteFail('day run (goals/<date>.run.json)', e); return false; }
|
|
3608
|
+
}
|
|
3609
|
+
function briefOfDay(date) {
|
|
3610
|
+
try { return JSON.parse(fs.readFileSync(path.join(STATE, 'briefs', date + '.json'), 'utf8')); } catch { return null; }
|
|
3611
|
+
}
|
|
3612
|
+
const daySessionAlive = (id) => { const s = id && sessions.get(id); return !!s && s.state !== 'exited'; };
|
|
3613
|
+
// The skill's own body is the prompt, as fireSchedule hands it over; the line after it names this run's task and mode.
|
|
3614
|
+
function daySkillPrompt(run, skillId) {
|
|
3615
|
+
const w = getWs(run.ws);
|
|
3616
|
+
const task = taskById(skillId, w ? w.root : null);
|
|
3617
|
+
if (!task || task.kind !== 'agent' || !task.prompt) return { error: `the ${skillId} skill is not installed for this repository` };
|
|
3618
|
+
return { w, task, prompt: skillStore.retargetFiling(task.prompt, filingContract(w)) };
|
|
3619
|
+
}
|
|
3620
|
+
/** Start one task session; a start that fails marks the task failed with the reason. Returns the new run. */
|
|
3621
|
+
function startDayTask(run, t, now) {
|
|
3622
|
+
const sk = daySkillPrompt(run, 'day-task');
|
|
3623
|
+
let s = null;
|
|
3624
|
+
if (!sk.error) {
|
|
3625
|
+
const prompt = `${sk.prompt}\n\nThis run: /day-task ${t.id} ${t.mode} — read the task first with: pmos proactive day show ${t.id}`;
|
|
3626
|
+
s = spawnSession({ name: `${t.title} · day task`, agent: sk.task.agent || 'claude', ws: sk.w ? sk.w.id : run.ws, origin: 'day-task', initialInput: prompt + '\r',
|
|
3627
|
+
modelAsk: { scheduleTier: null, skillTier: sk.task.tier || null, skill: sk.task.skill } });
|
|
3628
|
+
}
|
|
3629
|
+
const live = s && s.state !== 'exited';
|
|
3630
|
+
const st = dayRunLib.started(run, t.id, live ? s.id : null, now);
|
|
3631
|
+
if (!st.ok) return run;
|
|
3632
|
+
if (live) return st.run;
|
|
3633
|
+
const f = dayRunLib.fail(st.run, t.id, String(sk.error || (s && s.note) || 'the session could not start').slice(0, 240), now);
|
|
3634
|
+
return f.ok ? f.run : st.run;
|
|
3635
|
+
}
|
|
3636
|
+
/** Start what lib/day-run.js says may start now (at most 5 sessions at once). */
|
|
3637
|
+
function startDayTasks(run, now) {
|
|
3638
|
+
for (const t of dayRunLib.toStart(run)) run = startDayTask(run, t, now);
|
|
3639
|
+
return run;
|
|
3640
|
+
}
|
|
3641
|
+
/** Start one check session. @returns {{ok:true, run, session}|{ok:false, error}} */
|
|
3642
|
+
function startDayCheck(run, reason, now) {
|
|
3643
|
+
if (run.checks.session_id && daySessionAlive(run.checks.session_id)) return { ok: false, error: 'a check is already running' };
|
|
3644
|
+
if (run.checks.session_id) run = dayRunLib.sessionEnded(run, run.checks.session_id, now).run;
|
|
3645
|
+
const sk = daySkillPrompt(run, 'day-check');
|
|
3646
|
+
if (sk.error) return { ok: false, error: sk.error };
|
|
3647
|
+
const prompt = `${sk.prompt}\n\nThis run: /day-check ${reason} — the last check was at ${run.checks.last || 'none yet'}; read what changed since ${run.checks.last || run.started_at}.`;
|
|
3648
|
+
const s = spawnSession({ name: 'Daytime check', agent: sk.task.agent || 'claude', ws: sk.w ? sk.w.id : run.ws, origin: 'day-check', initialInput: prompt + '\r',
|
|
3649
|
+
modelAsk: { scheduleTier: null, skillTier: sk.task.tier || null, skill: sk.task.skill } });
|
|
3650
|
+
if (s.state === 'exited') return { ok: false, error: s.note || 'the check session could not start' };
|
|
3651
|
+
const c = dayRunLib.checkStarted(run, s.id, reason, now);
|
|
3652
|
+
return c.ok ? { ok: true, run: c.run, session: s.id } : c;
|
|
3653
|
+
}
|
|
3654
|
+
/** A brief for a day with a run arrived: its agent priorities become tasks and start. */
|
|
3655
|
+
function dayAfterBrief(date, merged) {
|
|
3656
|
+
const now = Date.now();
|
|
3657
|
+
let run = readDayRun(date);
|
|
3658
|
+
if (!run) return null;
|
|
3659
|
+
const brief = briefOfDay(date);
|
|
3660
|
+
if (merged && run.checks.session_id) { const c = dayRunLib.checkDone(run, merged, now); if (c.ok) run = c.run; }
|
|
3661
|
+
const a = dayRunLib.addTasks(run, brief);
|
|
3662
|
+
if (a.ok) run = a.run;
|
|
3663
|
+
run = startDayTasks(run, now);
|
|
3664
|
+
writeDayRun(run);
|
|
3665
|
+
return run;
|
|
3666
|
+
}
|
|
3667
|
+
/** The tick: mark ended sessions, start what may start, and start a check when one is due. */
|
|
3668
|
+
function dayTick() {
|
|
3669
|
+
const now = Date.now();
|
|
3670
|
+
const date = briefLib.localDate(now);
|
|
3671
|
+
let run = readDayRun(date);
|
|
3672
|
+
if (!run) return;
|
|
3673
|
+
const before = JSON.stringify(run);
|
|
3674
|
+
const ids = new Set([...run.tasks.filter((t) => (t.status === 'working' || t.status === 'sending') && t.session_id).map((t) => t.session_id),
|
|
3675
|
+
run.brief_session && !run.brief_session.ended_at ? run.brief_session.id : null, run.checks.session_id].filter(Boolean));
|
|
3676
|
+
for (const id of ids) if (!daySessionAlive(id)) run = dayRunLib.sessionEnded(run, id, now).run;
|
|
3677
|
+
const brief = briefOfDay(date);
|
|
3678
|
+
const a = dayRunLib.addTasks(run, brief);
|
|
3679
|
+
if (a.ok) run = a.run;
|
|
3680
|
+
run = startDayTasks(run, now);
|
|
3681
|
+
const due = dayRunLib.checkDue(run, { settings: proSettings(), now, events: brief ? brief.calendar || [] : [] });
|
|
3682
|
+
if (due.due) { const c = startDayCheck(run, due.reason, now); if (c.ok) run = c.run; }
|
|
3683
|
+
run.checks.next = due.next;
|
|
3684
|
+
if (JSON.stringify(run) !== before) writeDayRun(run);
|
|
3685
|
+
}
|
|
3686
|
+
// ── end of the today day run helpers ─────────────────────────────────────────────────────────────────────────────
|
|
3687
|
+
|
|
3575
3688
|
// ── Background prepare, slice D3 (initiative proactive-engine) ──────────────────────────────────────────────────
|
|
3576
3689
|
// The PM approves ONE plan hash from Today. The 60-second tick later starts that session through the SAME path a next-step
|
|
3577
3690
|
// `go` uses (startWork with origin next-step and the prompt that was shown), only while background prepare is on, outside quiet
|
|
@@ -6998,13 +7111,21 @@ async function handleRequest(req, res) {
|
|
|
6998
7111
|
return json(res, { ok: true, day: r.day, goal: r.goal });
|
|
6999
7112
|
}
|
|
7000
7113
|
if (p === '/api/proactive/goals/plan' && req.method === 'POST') {
|
|
7001
|
-
// A PM press: make sure the morning brief is scheduled for this
|
|
7002
|
-
// goals with `pmos proactive goals` and submits a brief whose
|
|
7114
|
+
// A PM press ("Prepare my day"): record the day run (G13), make sure the morning brief is scheduled for this
|
|
7115
|
+
// repository, then run it now. The session reads the goals with `pmos proactive goals` and submits a brief whose
|
|
7116
|
+
// items name them. No goal is needed: the brief plans the day either way. A second press while the brief session
|
|
7117
|
+
// still runs starts nothing and answers that session.
|
|
7003
7118
|
let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
7004
7119
|
const w = getWs(body.ws);
|
|
7005
7120
|
if (!w) return json(res, { ok: false, error: 'no workspace' }, 400);
|
|
7006
|
-
const
|
|
7007
|
-
|
|
7121
|
+
const pnow = Date.now(); const pdate = briefLib.localDate(pnow);
|
|
7122
|
+
const open = readGoalsDay(pdate).goals.filter((g) => g.status === 'open');
|
|
7123
|
+
let run = readDayRun(pdate) || dayRunLib.newRun(pdate, pnow);
|
|
7124
|
+
run.ws = w.id;
|
|
7125
|
+
if (run.brief_session && !run.brief_session.ended_at) {
|
|
7126
|
+
if (daySessionAlive(run.brief_session.id)) { writeDayRun(run); return json(res, { ok: true, running: true, session: run.brief_session.id, run, goals: open.length }); }
|
|
7127
|
+
run = dayRunLib.sessionEnded(run, run.brief_session.id, pnow).run;
|
|
7128
|
+
}
|
|
7008
7129
|
let sched = readSchedules().find((x) => x.ws === w.id && x.skill === 'morning-brief');
|
|
7009
7130
|
let seeded = false;
|
|
7010
7131
|
if (!sched) {
|
|
@@ -7016,9 +7137,76 @@ async function handleRequest(req, res) {
|
|
|
7016
7137
|
const list = readSchedules(); list.unshift(sched); writeSchedules(list); seeded = true;
|
|
7017
7138
|
}
|
|
7018
7139
|
const result = fireSchedule(sched);
|
|
7019
|
-
|
|
7140
|
+
// A session that could not start is recorded as already ended, so the page says why instead of waiting on a read.
|
|
7141
|
+
if (result.sessionId) run.brief_session = { id: result.sessionId, started_at: new Date(pnow).toISOString(), ended_at: result.ok ? null : new Date(pnow).toISOString() };
|
|
7142
|
+
writeDayRun(run);
|
|
7143
|
+
return json(res, { ok: !!result.ok, started: !!result.ok, session: result.sessionId || null, run, seeded, schedule: { id: sched.id, time: sched.time }, result, goals: open.length,
|
|
7020
7144
|
...(result.ok ? {} : { error: result.reason || 'the morning-brief session did not start' }) }, result.ok ? 200 : 500);
|
|
7021
7145
|
}
|
|
7146
|
+
/* ── today day run (G13, G14, G21, G22): the day view, the task sessions' reports and the PM's decisions. These
|
|
7147
|
+
* routes write only goals/<date>.run.json (writeDayRun) and start sessions only through the day-run helpers, for
|
|
7148
|
+
* the run of a day the PM pressed. Deliver, sent and fail are called by the sessions through `pmos proactive day`;
|
|
7149
|
+
* decide and check are the PM's presses. ── */
|
|
7150
|
+
if (p === '/api/proactive/day' && req.method === 'GET') {
|
|
7151
|
+
const now = Date.now();
|
|
7152
|
+
const date = briefLib.localDate(now);
|
|
7153
|
+
const w = getWs(url.searchParams.get('ws'));
|
|
7154
|
+
const st = proSettings();
|
|
7155
|
+
const run = readDayRun(date);
|
|
7156
|
+
const stored = briefOfDay(date);
|
|
7157
|
+
const brief = stored && stored.date === date ? stored : null;
|
|
7158
|
+
let cards = []; let cardsError = null;
|
|
7159
|
+
if (w) {
|
|
7160
|
+
const memo = goalsCardsMemo.get(w.id);
|
|
7161
|
+
if (memo && now - memo.at < 30000) ({ cards, error: cardsError } = memo);
|
|
7162
|
+
else {
|
|
7163
|
+
let sess = []; try { sess = sessionList(); } catch { /* no session cards */ }
|
|
7164
|
+
const t = todayLib.todayFor(w, { stateDir: stateDirFor(w), sessions: sess, registered: (root) => { const x = workspaces.get(wsId(root)); return !!x && path.resolve(x.root) === path.resolve(root); } });
|
|
7165
|
+
if (t.ok) cards = t.cards; else cardsError = t.error;
|
|
7166
|
+
goalsCardsMemo.set(w.id, { at: now, cards, error: cardsError });
|
|
7167
|
+
}
|
|
7168
|
+
}
|
|
7169
|
+
const plan = goalsLib.planDay({ day: readGoalsDay(date), brief, cards, now });
|
|
7170
|
+
const due = dayRunLib.checkDue(run, { settings: st, now, events: brief ? brief.calendar || [] : [] });
|
|
7171
|
+
const items = brief ? [...(brief.priorities || []), ...(brief.worth_asking || [])] : [];
|
|
7172
|
+
const sources = proactiveLib.BRIEF_SOURCES.map((k) => ({ k, on: st.briefSources[k] === true, state: brief ? brief.sources[k] || null : null, got: items.filter((x) => x.source === k).length }));
|
|
7173
|
+
// The brief session ended and no brief arrived: say so, with the session's own reason, so the page does not wait on a
|
|
7174
|
+
// read that will never finish (it reads as "stuck on Read" otherwise).
|
|
7175
|
+
const bs = run && run.brief_session; const bsess = bs ? sessions.get(bs.id) : null;
|
|
7176
|
+
const readFailed = !run || brief ? null
|
|
7177
|
+
: !bs ? { session: null, why: 'the morning-brief session did not start.' }
|
|
7178
|
+
: (!!bs.ended_at || !bsess || bsess.state === 'exited') ? { session: bs.id, why: (bsess && bsess.note) || 'the session ended without submitting a brief.' } : null;
|
|
7179
|
+
return json(res, { ok: true, ws: w ? w.id : null, today: date, now: new Date(now).toISOString(), run, stage: dayRunLib.stage(run, brief), brief, plan, cards_error: cardsError, read_failed: readFailed,
|
|
7180
|
+
settings: { briefSources: st.briefSources, scopes: st.scopes, dayCheck: st.dayCheck, websites: st.websites }, sources,
|
|
7181
|
+
check: { every: st.dayCheck.every, last: run ? run.checks.last : null, next: due.next, running: !!(run && run.checks.session_id), log: run ? run.checks.log : [] } });
|
|
7182
|
+
}
|
|
7183
|
+
if (p.startsWith('/api/proactive/day/') && req.method === 'POST') {
|
|
7184
|
+
const verb = p.slice('/api/proactive/day/'.length);
|
|
7185
|
+
if (!['deliver', 'sent', 'fail', 'decide', 'check'].includes(verb)) return json(res, { ok: false, error: 'unknown day-run action' }, 404);
|
|
7186
|
+
let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
7187
|
+
const now = Date.now();
|
|
7188
|
+
let run = readDayRun(briefLib.localDate(now));
|
|
7189
|
+
if (!run) return json(res, { ok: false, error: 'no day run today — press Prepare my day first' }, 409);
|
|
7190
|
+
if (verb === 'check') {
|
|
7191
|
+
// "Check now": one check at once, whatever the hour. The PM's press; reason 'manual'.
|
|
7192
|
+
if (body.ws && !run.ws) run.ws = getWs(body.ws) ? getWs(body.ws).id : run.ws;
|
|
7193
|
+
const c = startDayCheck(run, 'manual', now);
|
|
7194
|
+
if (!c.ok) return json(res, { ok: false, error: c.error }, 409);
|
|
7195
|
+
writeDayRun(c.run);
|
|
7196
|
+
return json(res, { ok: true, session: c.session, run: c.run });
|
|
7197
|
+
}
|
|
7198
|
+
const id = String(body.task || '');
|
|
7199
|
+
if (!dayRunLib.findTask(run, id)) return json(res, { ok: false, error: `no task ${id || '(none given)'} in today's run` }, 404);
|
|
7200
|
+
const r = verb === 'deliver' ? dayRunLib.deliver(run, id, { summary: body.summary, body: body.body, link: body.link }, now)
|
|
7201
|
+
: verb === 'sent' ? dayRunLib.sent(run, id, { link: body.link }, now)
|
|
7202
|
+
: verb === 'fail' ? dayRunLib.fail(run, id, body.reason, now)
|
|
7203
|
+
: dayRunLib.decide(run, id, { action: body.action, note: body.note }, now);
|
|
7204
|
+
if (!r.ok) return json(res, { ok: false, error: r.error }, 400);
|
|
7205
|
+
// A delivered, sent or failed task frees a place, and a decision may make a task ready: start what may start now.
|
|
7206
|
+
run = startDayTasks(r.run, now);
|
|
7207
|
+
if (!writeDayRun(run)) return json(res, { ok: false, error: 'could not save — nothing changed' }, 500);
|
|
7208
|
+
return json(res, { ok: true, run, task: dayRunLib.findTask(run, id) });
|
|
7209
|
+
}
|
|
7022
7210
|
/* ── background prepare (D3): plan is read-only; queue records an approval; the runner starts it later ── */
|
|
7023
7211
|
if (p === '/api/proactive/prepare/plan' && req.method === 'POST') {
|
|
7024
7212
|
let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
@@ -7095,9 +7283,20 @@ async function handleRequest(req, res) {
|
|
|
7095
7283
|
const bday = body && typeof body.date === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(body.date) ? body.date : briefLib.localDate(Date.now());
|
|
7096
7284
|
const v = briefLib.validateBrief(body, { now: Date.now(), settings: proSettings(), goalIds: readGoalsDay(bday).goals.map((g) => g.id) });
|
|
7097
7285
|
if (!v.ok) return json(res, { ok: false, error: v.error }, 400);
|
|
7098
|
-
|
|
7286
|
+
// `merge: true` (a daytime check, G22) merges into the stored brief of that day; with none stored it is stored as it is.
|
|
7287
|
+
let out = { ...v.brief, resolved: [] }; let merged = null;
|
|
7288
|
+
if (body.merge === true) {
|
|
7289
|
+
let stored = null; try { stored = JSON.parse(fs.readFileSync(path.join(dir, v.brief.date + '.json'), 'utf8')); } catch { /* none yet */ }
|
|
7290
|
+
const m = briefLib.mergeBrief(stored, v.brief, { now: Date.now() });
|
|
7291
|
+
if (!m.ok) return json(res, { ok: false, error: m.error }, 400);
|
|
7292
|
+
out = m.brief; merged = { found: m.added, resolved: m.removed };
|
|
7293
|
+
}
|
|
7294
|
+
try { fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(path.join(dir, out.date + '.json'), JSON.stringify(out) + '\n'); }
|
|
7099
7295
|
catch (e) { warnWriteFail('briefs', e); return json(res, { ok: false, error: 'could not save — nothing changed' }, 500); }
|
|
7100
|
-
|
|
7296
|
+
// A brief for a day the PM pressed "Prepare my day": its agent priorities become tasks and start (G13), and a merge
|
|
7297
|
+
// closes the check that sent it (G22).
|
|
7298
|
+
try { dayAfterBrief(out.date, merged); } catch (e) { warnWriteFail('day run after the brief', e); }
|
|
7299
|
+
return json(res, { ok: true, brief: out, ...(merged ? { merged } : {}) });
|
|
7101
7300
|
}
|
|
7102
7301
|
if (req.method === 'GET') {
|
|
7103
7302
|
const now = Date.now(); const st = proSettings();
|
|
@@ -7113,7 +7312,11 @@ async function handleRequest(req, res) {
|
|
|
7113
7312
|
if (req.method === 'POST') {
|
|
7114
7313
|
let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
7115
7314
|
const cur = proSettings();
|
|
7116
|
-
|
|
7315
|
+
// A patch: triggers, sources, the check and each service's levels merge over what is stored (a level of null clears it).
|
|
7316
|
+
const bs = body.scopes && typeof body.scopes === 'object' && !Array.isArray(body.scopes) ? body.scopes : null;
|
|
7317
|
+
const scopes = bs ? { ...cur.scopes, ...Object.fromEntries(Object.entries(bs).map(([k, v]) => [k, v && typeof v === 'object' && !Array.isArray(v) ? { ...(cur.scopes[k] || {}), ...v } : v])) } : cur.scopes;
|
|
7318
|
+
const dayCheck = body.dayCheck && typeof body.dayCheck === 'object' && !Array.isArray(body.dayCheck) ? { ...cur.dayCheck, ...body.dayCheck } : (body.dayCheck === undefined ? cur.dayCheck : body.dayCheck);
|
|
7319
|
+
const n = proactiveLib.normalizeSettings({ ...cur, ...body, triggers: { ...cur.triggers, ...(body.triggers || {}) }, briefSources: { ...cur.briefSources, ...(body.briefSources || {}) }, scopes, dayCheck });
|
|
7117
7320
|
if (!n.ok) return json(res, { ok: false, error: n.error }, 400);
|
|
7118
7321
|
try { fs.mkdirSync(STATE, { recursive: true }); fs.writeFileSync(PRO_SETTINGS(), JSON.stringify(n.settings) + '\n'); }
|
|
7119
7322
|
catch (e) { warnWriteFail('proactive-settings.json', e); return json(res, { ok: false, error: 'could not save — nothing changed' }, 500); }
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: day-check
|
|
3
|
+
label: "Day check"
|
|
4
|
+
description: "The daytime check of today's run: read only what changed since the last check, and merge new items, resolved items, meeting notes and updated digests into the stored brief. Read-only on every source; it sends nothing."
|
|
5
|
+
run: agent
|
|
6
|
+
agent: claude
|
|
7
|
+
output:
|
|
8
|
+
- What changed
|
|
9
|
+
- Items added and resolved
|
|
10
|
+
- Sources reached and not reached
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
/day-check — look once at what changed since the last check of today's run, and merge it into today's brief.
|
|
14
|
+
|
|
15
|
+
Rules that always hold:
|
|
16
|
+
1. READ-ONLY on every source. Never send, reply, forward, post, react, comment, create, edit, accept, decline, transition, label, archive, mark read or delete anything in mail, Slack, Jira, Confluence, GitHub, Drive, Figma, a website, calendar, Wispr Flow or any other source.
|
|
17
|
+
2. ONLY WHAT CHANGED. Do not read the day again. Read only what is new since the last check. The time of the last check is in `pmos proactive day`.
|
|
18
|
+
3. SETTINGS DECIDE THE SOURCES AND THE SCOPES, as in the morning brief. Read a source only if it is on. In Slack, Jira and Confluence, read a place with scope watch since the last check, look in a place with scope search only for mentions of the PM and the words of the goals, and do not touch a place with scope ignore.
|
|
19
|
+
4. LIGHT ON CONTENT. Never copy a message body, an attachment, a token or a credential. Paraphrase in the limits of each field. Skip anything marked private, personal, confidential or HR.
|
|
20
|
+
5. NOTHING INVENTED. Add an item only for something you read. A check that finds nothing is a correct check.
|
|
21
|
+
6. THE ONLY WRITE is `pmos proactive brief submit --merge`. Do not edit repository files and do not commit. Do not start, approve or revise a task.
|
|
22
|
+
7. AT MOST 30 ITEMS in the stored brief after the merge. Resolve what is done before you add. The workspace refuses a merge that would pass 30.
|
|
23
|
+
|
|
24
|
+
Procedure:
|
|
25
|
+
1. Run `pmos proactive day`. It shows the stage, the tasks, and the check state (when the last check was, and why this one runs). Run `pmos proactive brief` to read the stored brief: its items with their ids, its events with their ids, and its digests. Run `pmos proactive settings` for the sources, the scopes and the websites. Run `pmos proactive goals` for the goal ids.
|
|
26
|
+
2. Read what changed, from the sources that are on and reachable:
|
|
27
|
+
- Calendar: today's events. Note a new event, a moved one, a cancelled one, and a new link or agenda in one.
|
|
28
|
+
- Mentions of the PM since the last check, in Slack, Jira and Confluence, in every place that is not ignored. Keep where, who, the link to the conversation itself and a short paraphrase of what is asked.
|
|
29
|
+
- Places with scope watch: read every message or comment since the last check. Update the digest of the place (decisions, open questions, what touches a goal). A digest you submit replaces the stored digest of that place, so carry over what still holds.
|
|
30
|
+
- GitHub, Drive, Figma and websites, if on: only what changed since the last check and matters to a goal or a meeting.
|
|
31
|
+
- Wispr Flow: notes for meetings that ended since the last check. Read the summary, the decisions and the actions. Read the related mail for the same meeting only if mail is on, and keep the subject, the sender and a gist.
|
|
32
|
+
3. Decide what the brief needs.
|
|
33
|
+
- New items: a thing the PM should act on that is not in the stored brief. Fill the fields as the morning-brief skill does: link, where, who, about, ask, reply_needed, how and kind (you, approve or auto, with the same meaning). Set "mention": true on an item that comes from a mention. Only give an item a source that is "read".
|
|
34
|
+
- Resolved items: an item in the stored brief that is now done, answered or no longer true, for example a thread the PM answered, an issue that was closed, a meeting that was cancelled. List its id in "resolved". Never resolve an item only because it is old.
|
|
35
|
+
- Event notes: for a meeting that ended, add "notes": {"wispr": {"link", "summary", "decisions", "actions"}, "mail": {"subject", "from", "gist", "link"}} to that event, and mark the "after" steps that are now done with "done": true. Give the event by the same "start", "end" and "title" it has in the stored brief, because the workspace derives its id from them.
|
|
36
|
+
- Updated digests: only for the places you read.
|
|
37
|
+
4. Write one JSON file in the scratch folder of this session, in the shape of the morning brief, with only what is new or changed, and a list of resolved ids:
|
|
38
|
+
```
|
|
39
|
+
{"date": "YYYY-MM-DD",
|
|
40
|
+
"sources": {"calendar": "read", "slack": "read", "jira": "read", "wispr": "read"},
|
|
41
|
+
"calendar": [{"start": "<ISO>", "end": "<ISO>", "title": "…", "notes": {"wispr": {"summary": "…", "decisions": ["…"], "actions": ["…"]}}}],
|
|
42
|
+
"items": [{"title": "…", "why_now": "…", "source": "slack", "owner": "pm|agent", "effort": "minutes", "confidence": 0.0, "evidence": "…",
|
|
43
|
+
"link": "https://…", "where": "…", "who": "…", "about": "…", "ask": "…", "reply_needed": true, "how": "…", "kind": "you|approve|auto"}],
|
|
44
|
+
"digests": [{"source": "slack", "name": "#pm-team", "decisions": ["…"], "open": ["…"], "goals": ["…"]}],
|
|
45
|
+
"resolved": ["<item id>"],
|
|
46
|
+
"judgment": {"used": false}}
|
|
47
|
+
```
|
|
48
|
+
Report in "sources" only the sources you read or could not reach in this check. Use the date of today.
|
|
49
|
+
5. Run `pmos proactive brief submit --file <that file> --merge`. If it is refused, read the reason, fix the file and submit once more. Do not work around a refusal.
|
|
50
|
+
6. If nothing changed, still submit a merge with no new items, no resolved ids and no notes: `{"date": "YYYY-MM-DD", "sources": {}, "calendar": [], "items": [], "resolved": [], "judgment": {"used": false}}`. The workspace counts an empty check and waits longer before the next one. Do not skip this step.
|
|
51
|
+
7. Finish with the output contract. Say in three lines what changed, how many items you added and resolved, and which sources were not reached.
|
|
52
|
+
|
|
53
|
+
Output contract — produce EXACTLY these sections in your final message:
|
|
54
|
+
- What changed
|
|
55
|
+
- Items added and resolved
|
|
56
|
+
- Sources reached and not reached
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: day-task
|
|
3
|
+
label: "Day task"
|
|
4
|
+
description: "Do one task of the PM's day for one task id and one mode (do, revise or send). In do and revise it prepares ONE draft and sends nothing. In send it carries out the one outward step of the draft the PM approved."
|
|
5
|
+
run: agent
|
|
6
|
+
agent: claude
|
|
7
|
+
output:
|
|
8
|
+
- Task and mode
|
|
9
|
+
- What was done
|
|
10
|
+
- Draft or step result
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
/day-task TASK MODE — work one task of today's run. TASK is a task id (t-xxxxxxxx). MODE is do, revise or send.
|
|
14
|
+
|
|
15
|
+
Rules that always hold:
|
|
16
|
+
1. READ-ONLY while you work. In do and revise mode, read sources and write a draft. Never send, post, reply, comment, forward, react, create, edit, transition, merge, push, label, archive or delete anything in mail, Slack, Jira, Confluence, GitHub, Drive, Figma, calendar or any other system. Do not open a pull request. Do not commit.
|
|
17
|
+
2. ONE DRAFT. Submit exactly one draft in do and revise mode, with `pmos proactive day deliver`. The PM reads it and decides.
|
|
18
|
+
3. THE DRAFT IS WHAT LEAVES. In send mode, do exactly the one outward step of the approved draft, and nothing else. Do not improve the text. Do not add a recipient. Do not do a second step.
|
|
19
|
+
4. LIGHT ON CONTENT. Quote no more than one short line from a source. Write the rest in your own words. Never copy a token or a credential. Skip anything marked private, personal, confidential or HR.
|
|
20
|
+
5. NOTHING INVENTED. If a fact is not in a source you read, leave it out or write "unverified".
|
|
21
|
+
6. NEVER APPROVE FOR THE PM. Do not run `pmos proactive day approve`, `revise` or `retry`. Those commands are the PM's.
|
|
22
|
+
7. YOU CANNOT FINISH WITHOUT A REPORT. End every run with exactly one of `day deliver` (do, revise), `day sent` (send) or `day fail`.
|
|
23
|
+
|
|
24
|
+
Procedure:
|
|
25
|
+
1. Run `pmos proactive day show TASK`. It prints the task: the title, the kind (approve or auto), the source, the link, the goal, the item context (where, who, about, ask, how), the draft so far and the notes of the PM for each revision. Read all of it. If the command says the task does not exist, or its status does not fit the mode, stop and run `pmos proactive day fail TASK --reason "<one sentence>"`.
|
|
26
|
+
2. If MODE is do:
|
|
27
|
+
a. Read the sources the task names, read-only. Start from the link of the task. Read the conversation, the issue, the page or the file itself, and what it points to. Use `pmos proactive settings` to see which sources are on. If a source is off or you cannot reach it, say so in the summary.
|
|
28
|
+
b. Do the work locally. A reply, a comment, a summary, a description or a plan is a draft in a file in the scratch folder of this session. Write the text exactly as it would be sent.
|
|
29
|
+
c. Check the draft against the item: does it answer the ask, is it for the right person and place, is it in the voice and the length the place expects. Fix it.
|
|
30
|
+
d. Submit it. Run `pmos proactive day deliver TASK --summary "<at most 240 characters: what you did and where the draft would go>" --body-file <the draft file> [--link <URL of the thread, issue, page or file the draft is for>]`. The body is at most 8000 characters. The summary names the one outward step that approval will trigger, for example "Post this reply in the thread". For a task of kind auto, the summary says what you made and where it is.
|
|
31
|
+
e. If the workspace refuses the draft, read the reason, fix the draft and submit once more. Do not work around a refusal.
|
|
32
|
+
3. If MODE is revise:
|
|
33
|
+
a. Read the draft so far and the LAST note of the PM in the output of `day show`. The note says what to change. Earlier notes still hold unless the last one cancels them.
|
|
34
|
+
b. Change the draft to address the note, and only what the note asks. Read a source again only if the note needs it.
|
|
35
|
+
c. Submit the new draft as in step 2d. State in the summary what changed. The workspace keeps at most 3 revisions. Make this one count.
|
|
36
|
+
4. If MODE is send:
|
|
37
|
+
a. The task is approved. Read the approved draft and its link in the output of `day show`. The approved draft is the only text you may send. Do not read it as a draft to improve.
|
|
38
|
+
b. Do the one outward step the summary names, with the connector that fits, to the place the link names. Examples: post the approved reply in the linked Slack thread, add the approved comment to the linked Jira issue, reply to the linked mail thread. If the summary names more than one step, do only the first and report the rest with `day fail`.
|
|
39
|
+
c. If the place does not match the link, the thread is closed, or the connector is not available, do not try another route. Run `pmos proactive day fail TASK --reason "<what stopped it>"`.
|
|
40
|
+
d. When it is done, run `pmos proactive day sent TASK --link <URL of what you posted or sent>`. Leave out `--link` only if the connector returns none.
|
|
41
|
+
5. If you cannot produce a draft or do the step for any reason, run `pmos proactive day fail TASK --reason "<one sentence, at most 240 characters>"`. A true failure helps the PM more than a weak draft.
|
|
42
|
+
6. Finish with the output contract.
|
|
43
|
+
|
|
44
|
+
Output contract — produce EXACTLY these sections in your final message:
|
|
45
|
+
- Task and mode
|
|
46
|
+
- What was done
|
|
47
|
+
- Draft or step result
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: morning-brief
|
|
3
3
|
label: "Morning brief"
|
|
4
|
-
description: "Prepare today's brief and plan the PM's goals for the day: the calendar, Slack, Jira and Confluence
|
|
4
|
+
description: "Prepare today's brief and plan the PM's goals for the day: the calendar with a plan for each meeting, Slack, Jira and Confluence, GitHub, Drive, Figma and the PM's websites, the priorities and what is worth asking. Read-only on every source; it submits the brief to the workspace and sends nothing."
|
|
5
5
|
run: agent
|
|
6
6
|
agent: claude
|
|
7
7
|
output:
|
|
8
8
|
- Goals
|
|
9
9
|
- Calendar
|
|
10
|
+
- Digests
|
|
10
11
|
- Priorities
|
|
11
12
|
- Worth asking
|
|
12
13
|
- Sources reached and not reached
|
|
@@ -15,36 +16,72 @@ output:
|
|
|
15
16
|
/morning-brief — prepare today's brief for the PM, and plan the goals they set for today.
|
|
16
17
|
|
|
17
18
|
Rules that always hold:
|
|
18
|
-
1. READ-ONLY on every source. Never send, reply, forward, post, react, comment, create, edit, accept, decline, transition, label, archive, mark read or delete anything in mail, Slack, Jira, Confluence, calendar, Wispr Flow or any other source.
|
|
19
|
-
2. SETTINGS DECIDE THE SOURCES. Run `pmos proactive settings`. Read a source only if its line under "brief sources" says on. A source that is off is reported as "off". Mail is off unless the PM turned it on.
|
|
20
|
-
3. LIGHT ON CONTENT.
|
|
19
|
+
1. READ-ONLY on every source. Never send, reply, forward, post, react, comment, create, edit, accept, decline, transition, label, archive, mark read or delete anything in mail, Slack, Jira, Confluence, GitHub, Drive, Figma, a website, calendar, Wispr Flow or any other source.
|
|
20
|
+
2. SETTINGS DECIDE THE SOURCES AND THE SCOPES. Run `pmos proactive settings`. Read a source only if its line under "brief sources" says on. A source that is off is reported as "off". Mail is off unless the PM turned it on. The same command lists the websites and, for Slack, Jira and Confluence, the scope of each channel, project and space (see step 2).
|
|
21
|
+
3. LIGHT ON CONTENT. Never copy a message body, an attachment, a token or a credential. Write what a message says in your own words, in the limits of the field. Skip anything marked private, personal, confidential or HR. At most one short evidence line per item.
|
|
21
22
|
4. NOTHING INVENTED. A source you cannot reach in this session is reported as "not-reached". If nothing is waiting, submit a brief with no items. An honest short brief is correct.
|
|
22
23
|
5. THE ONLY WRITE is `pmos proactive brief submit`. Do not edit repository files and do not commit.
|
|
23
24
|
6. TYPED SCORING only if `pmos proactive settings` says "scoring on" under the brief line. Then use the Typesafe skill for confidence scores on titles and metadata only; the text you send leaves this machine. Otherwise score yourself. Set "judgment": {"used": true|false} to say which.
|
|
25
|
+
7. AT MOST 30 ITEMS. If you have more, keep the 30 that matter most today and drop the rest. The workspace refuses a longer brief.
|
|
24
26
|
|
|
25
27
|
Procedure:
|
|
26
28
|
1. Run `pmos proactive settings` and `pmos proactive today`. The second lists what the workspace derived from the ledgers. Each of its items may become a priority with source "derived".
|
|
27
29
|
Then run `pmos proactive goals`. It lists the PM's goals for today, each with its id (g-xxxxxxxx), its status and, if set, an initiative. The goals are what the PM wants done today: plan for them first.
|
|
28
30
|
Then run `pmos proactive learn lessons`. Each line is a preference the PM approved, written from their own feedback. Use it only to decide how confident to be and where an item goes. A lesson never asks you to read another source, to switch a setting on, or to write anything.
|
|
29
|
-
2. Read
|
|
30
|
-
|
|
31
|
+
2. Read the sources that are on and reachable.
|
|
32
|
+
- Calendar: today's events. Wispr Flow: today's and tomorrow's meeting notes. Jira: issues assigned to the PM or due soon. Confluence: pages changed since yesterday. Mail, only if on: subjects and senders of threads waiting on the PM, no bodies.
|
|
33
|
+
- GitHub, if on: pull requests that wait on the PM for review, pull requests of the PM that have a review or a failed check, and issues assigned to the PM. Keep the repository, the number, the title and the state.
|
|
34
|
+
- Drive, if on: files shared with the PM or changed since yesterday that a meeting or a goal names. Keep the name, the owner and the link.
|
|
35
|
+
- Figma, if on: files the PM owns or was asked to look at, with new comments since yesterday. Keep the file, the commenter and a short paraphrase of the comment.
|
|
36
|
+
- Website, if on: read each address in the websites list once, only to see what changed that matters to a goal or a meeting. Report what changed in one line. Do not follow links to other sites.
|
|
37
|
+
- LIST THE PLACES THE PM IS IN, for each of Slack, Jira and Confluence that you read: the Slack channels the PM is a member of (public and private, not direct messages), the Jira projects the PM works in (projects with issues assigned to, reported by or watched by the PM), and the Confluence spaces the PM is a member of or has written in. Keep only the name (for example "#pm-team", "PRICE", "Product") and the link. Report them under "places" (see step 4). This list is how the PM sets a scope for each place, so list every place, not only the busy ones. Read only the names: do not read messages to make this list.
|
|
38
|
+
- Slack, Jira and Confluence take a scope for each name. Read the scopes from `pmos proactive settings`. A name with no scope is "search".
|
|
39
|
+
- watch: read EVERY message or comment since the last read (yesterday morning if there is none). Then write one digest (see step 4) with the decisions made, the open questions, and what touches a goal. Write the digest, not the messages.
|
|
40
|
+
- search: do not read the place. Look only for MENTIONS of the PM and for the words of the PM's goals and initiatives.
|
|
41
|
+
- ignore: do not read the place. Do not search it. Do not report it.
|
|
42
|
+
- MENTIONS of the PM since yesterday morning, in every place that is not ignored: Slack messages that @-mention the PM or are direct messages to them; Jira issues and comments that mention the PM; Confluence pages and comments that mention the PM. For each, keep only where it is (channel, issue key, page title), who wrote it, the link to the conversation itself, and a short paraphrase of what is asked. Never copy the message itself.
|
|
31
43
|
3. PLAN EACH OPEN GOAL. For each open goal, find what moves it today in what you read and in `pmos proactive today`: a meeting where it is decided, a mention that blocks it, an issue or page it needs, a step an agent can prepare. Write each as an item with "goal": "<the goal id>". An agent step names the initiative it advances, so the PM can prepare it from Today. A calendar event that belongs to a goal also gets "goal". If you find nothing for a goal, write nothing for it: the workspace shows that goal as needing a plan. Never invent a step.
|
|
32
44
|
Then ANTICIPATE: for each other thing the PM should act on today (a mention waiting on them, a meeting to prepare for, an issue due), write one item with no goal.
|
|
45
|
+
PLAN EACH MEETING. For each calendar event that is a meeting, write what it is for and how to be ready. Use the Wispr Flow notes of the earlier meetings in the series, the related mail (only if mail is on) and the related Jira, Confluence, Drive and Slack items you read. Do not guess: leave a field out when you have no basis for it.
|
|
46
|
+
- "purpose": why the meeting exists, at most 240 characters.
|
|
47
|
+
- "prep": what the PM must read or decide before it, at most 240 characters.
|
|
48
|
+
- "steps": before, during and after, each a list of at most 6 steps. A step has "t" (at most 140 characters), "who" ("pm" or "agent") and, if it is already done, "done": true. Give a step to "agent" only when an agent session can do it without sending anything.
|
|
49
|
+
- "initiative": {"id": "<initiative id>", "why": "<why this meeting belongs to it>"}, only when it truly does.
|
|
50
|
+
- "links": up to 5 of {"label", "url"}, such as the agenda, the doc or the issue. A link starts with https:// or http://.
|
|
33
51
|
For each item, write: a short title, why it is for today, its source, the initiative id if one applies, who acts (pm or agent), effort (minutes, an-hour, half-day, day), your confidence from 0 to 1 that it belongs on today's list, and one short evidence line. Set "mention": true on an item that comes from a mention of the PM. Give a low confidence when you are guessing. The workspace shows an item under 0.5 as "worth asking", not as a priority.
|
|
52
|
+
Then fill the item fields that tell the PM what to do, so the card needs no second look:
|
|
53
|
+
- "link": the link to the conversation, issue, pull request, file or page itself, not to a list or a search.
|
|
54
|
+
- "where": the channel, issue key, repository or page title. "who": who wrote it or who waits. "about": what it is about, in your words, at most 480 characters. "ask": what is asked of the PM, at most 240 characters.
|
|
55
|
+
- "reply_needed": true only when someone waits for an answer from the PM.
|
|
56
|
+
- "how": how you suggest to deal with it, at most 480 characters.
|
|
57
|
+
- "kind": who does the job.
|
|
58
|
+
- "you": only the PM can do it, for example a decision, a conversation or a judgment. This is the default when the owner is pm. Use it also when a connector call would be easy but the content is the PM's to choose.
|
|
59
|
+
- "approve": an agent prepares one draft, such as a reply, a comment, a summary or a pull request description. The PM reads it and presses approve before anything leaves. The owner is agent. Use it for anything that goes to another person.
|
|
60
|
+
- "auto": an agent does it, nothing leaves the machine and nothing is shared, for example a local summary, a reading list or a checked draft file. The owner is agent. Never use it for a message, a comment, a status change or a merge.
|
|
61
|
+
A task for the PM is never "approve" or "auto": the workspace refuses it.
|
|
34
62
|
4. Write the brief as a JSON file in the scratch folder of this session, in this shape, and nothing else:
|
|
35
63
|
```
|
|
36
|
-
{"date": "YYYY-MM-DD",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
64
|
+
{"date": "YYYY-MM-DD",
|
|
65
|
+
"sources": {"calendar": "read", "wispr": "not-reached", "mail": "off", "slack": "read", "jira": "read", "confluence": "read", "github": "read", "drive": "off", "figma": "off", "website": "off"},
|
|
66
|
+
"calendar": [{"start": "<ISO date and time>", "end": "<ISO>", "title": "…", "with": "…", "goal": "<goal id or omit>",
|
|
67
|
+
"purpose": "…", "prep": "…",
|
|
68
|
+
"steps": {"before": [{"t": "…", "who": "agent"}], "during": [{"t": "…", "who": "pm"}], "after": [{"t": "…", "who": "agent"}]},
|
|
69
|
+
"initiative": {"id": "<initiative id>", "why": "…"},
|
|
70
|
+
"links": [{"label": "Agenda", "url": "https://…"}]}],
|
|
71
|
+
"items": [{"title": "…", "why_now": "…", "source": "calendar|wispr|mail|slack|jira|confluence|github|drive|figma|website|derived", "initiative_id": "<id or omit>", "goal": "<goal id or omit>", "mention": false, "owner": "pm|agent", "effort": "minutes|an-hour|half-day|day", "confidence": 0.0, "evidence": "…",
|
|
72
|
+
"link": "https://…", "where": "#pm-team", "who": "…", "about": "…", "ask": "…", "reply_needed": true, "how": "…", "kind": "you|approve|auto"}],
|
|
73
|
+
"digests": [{"source": "slack|jira|confluence", "name": "#pm-team", "link": "https://…", "decisions": ["…"], "open": ["…"], "goals": ["…"]}],
|
|
74
|
+
"places": [{"source": "slack|jira|confluence", "name": "#pm-team", "link": "https://…"}],
|
|
39
75
|
"judgment": {"used": false}}
|
|
40
76
|
```
|
|
41
|
-
Only report calendar events if calendar is "read". Only give an item a source that is "read" (or "derived"). Only use a goal id that `pmos proactive goals` listed today: the workspace refuses any other.
|
|
77
|
+
"places" holds at most 200 entries, each a name of at most 120 characters, only for a source that is "read". A digest has at most 5 decisions, 5 open questions and 5 goal lines, each at most 140 characters, and at most 30 digests. Write a digest only for a place with scope watch, and only for a source that is "read". Only report calendar events if calendar is "read". Only give an item a source that is "read" (or "derived"). Only use a goal id that `pmos proactive goals` listed today: the workspace refuses any other. Leave out a field you cannot fill: an empty string is worse than no field.
|
|
42
78
|
5. Run `pmos proactive brief submit --file <that file>`. If it is refused, read the reason, fix the file and submit once more. Do not work around a refusal.
|
|
43
79
|
6. Finish by saying in three lines how many goals have a step and which have none, how many priorities and worth-asking items you submitted, and which sources were not reached.
|
|
44
80
|
|
|
45
81
|
Output contract — produce EXACTLY these sections in your final message:
|
|
46
82
|
- Goals
|
|
47
83
|
- Calendar
|
|
84
|
+
- Digests
|
|
48
85
|
- Priorities
|
|
49
86
|
- Worth asking
|
|
50
87
|
- Sources reached and not reached
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"modules": [
|
|
11
11
|
"proactive.test.js",
|
|
12
12
|
"goals.test.js",
|
|
13
|
+
"day-run.test.js",
|
|
13
14
|
"focus.test.js",
|
|
14
15
|
"handoff.test.js",
|
|
15
16
|
"repo-scan.test.js",
|
|
@@ -108,6 +109,7 @@
|
|
|
108
109
|
"test.js": 48,
|
|
109
110
|
"proactive.test.js": 4,
|
|
110
111
|
"goals.test.js": 6,
|
|
112
|
+
"day-run.test.js": 6,
|
|
111
113
|
"focus.test.js": 1,
|
|
112
114
|
"handoff.test.js": 1,
|
|
113
115
|
"repo-scan.test.js": 1,
|