create-pmos 0.72.1 → 0.74.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 +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/notes.md +1 -1
- package/workspace/guide/today.md +23 -2
- package/workspace/lib/brief.js +88 -2
- package/workspace/lib/day-marks.js +163 -0
- package/workspace/lib/day-run.js +38 -4
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +1 -1
- package/workspace/public/pm/app.js +2 -0
- package/workspace/public/pm/components.css +99 -8
- package/workspace/public/pm/note-dock.js +137 -0
- package/workspace/public/pm/notes-links.js +16 -37
- package/workspace/public/pm/today-copy.js +82 -4
- package/workspace/public/pm/today-day.js +349 -19
- package/workspace/public/pm/view-notes.js +39 -9
- package/workspace/server.js +81 -3
- package/workspace/skills/day-task.skill +1 -0
- package/workspace/skills/morning-brief.skill +13 -2
- package/workspace/test-lists/today-act-prepare.json +5 -0
package/workspace/server.js
CHANGED
|
@@ -98,6 +98,7 @@ const briefLib = require('./lib/brief');
|
|
|
98
98
|
const calMemLib = require('./lib/calendar-memory');
|
|
99
99
|
const goalsLib = require('./lib/goals');
|
|
100
100
|
const dayRunLib = require('./lib/day-run');
|
|
101
|
+
const dayMarksLib = require('./lib/day-marks');
|
|
101
102
|
const todayLib = require('./lib/today');
|
|
102
103
|
const prepareQueue = require('./lib/prepare-queue');
|
|
103
104
|
const harness = require('./lib/harness-parse');
|
|
@@ -3842,7 +3843,7 @@ function startDayTask(run, t, now) {
|
|
|
3842
3843
|
}
|
|
3843
3844
|
/** My work's Run agent (initiative my-work, PRD R5): add the briefed task to today's run and start what may start. A day
|
|
3844
3845
|
* with no run gets one, marked opened by My work, in the repository the PM pressed from. Returns { ok, run } or why not. */
|
|
3845
|
-
function myWorkBrief(spec, wsId) {
|
|
3846
|
+
function myWorkBrief(spec, wsId, add) {
|
|
3846
3847
|
const now = Date.now(); const date = briefLib.localDate(now);
|
|
3847
3848
|
let run = readDayRun(date);
|
|
3848
3849
|
if (!run) {
|
|
@@ -3850,7 +3851,8 @@ function myWorkBrief(spec, wsId) {
|
|
|
3850
3851
|
if (!w) return { ok: false, status: 400, error: 'no workspace to run the agent in' };
|
|
3851
3852
|
run = { ...dayRunLib.newRun(date, now), ws: w.id, opened_by: 'my-work' };
|
|
3852
3853
|
}
|
|
3853
|
-
|
|
3854
|
+
// `add` is how the task joins the run: briefed by default; Send from a Your day card adds the PM's own text (addSend)
|
|
3855
|
+
const a = add ? add(run, spec, now) : dayRunLib.addBriefed(run, spec);
|
|
3854
3856
|
if (!a.ok) return { ok: false, status: 409, error: a.error };
|
|
3855
3857
|
const next = startDayTasks(a.run, now);
|
|
3856
3858
|
if (!writeDayRun(next)) return { ok: false, status: 500, error: 'could not save — nothing changed' };
|
|
@@ -3940,6 +3942,30 @@ function writeCalMemory(m) {
|
|
|
3940
3942
|
fs.mkdirSync(STATE, { recursive: true }); fs.writeFileSync(tmp, JSON.stringify(m) + '\n'); fs.renameSync(tmp, f);
|
|
3941
3943
|
}
|
|
3942
3944
|
|
|
3945
|
+
/* The PM's marks on today's items and meetings (initiative today-act-prepare, PRD M5): done or ready, snoozed, waiting on
|
|
3946
|
+
* someone, not mine, and the steps checked. goals/<date>.marks.json, written only by POST /api/proactive/day/mark. Kept outside the
|
|
3947
|
+
* proactive, prepare and day-run blocks, whose boundary tests list the only files they write. */
|
|
3948
|
+
const dayMarksFile = (date) => path.join(GOALS_DIR(), date + '.marks.json');
|
|
3949
|
+
function readDayMarks(date) {
|
|
3950
|
+
let raw = null;
|
|
3951
|
+
try { raw = JSON.parse(fs.readFileSync(dayMarksFile(date), 'utf8')); } catch { /* none yet */ }
|
|
3952
|
+
return dayMarksLib.readMarks(raw, date);
|
|
3953
|
+
}
|
|
3954
|
+
function writeDayMarks(rec) {
|
|
3955
|
+
try {
|
|
3956
|
+
fs.mkdirSync(GOALS_DIR(), { recursive: true });
|
|
3957
|
+
const f = dayMarksFile(rec.date); const tmp = `${f}.${process.pid}.tmp`;
|
|
3958
|
+
fs.writeFileSync(tmp, JSON.stringify(rec) + '\n'); fs.renameSync(tmp, f); return true;
|
|
3959
|
+
} catch (e) { warnWriteFail('day marks', e); return false; }
|
|
3960
|
+
}
|
|
3961
|
+
/** The ids a mark may name: today's brief items ('item') and calendar events ('event'). */
|
|
3962
|
+
function dayMarkIds(brief) {
|
|
3963
|
+
const known = new Map();
|
|
3964
|
+
if (!brief) return known;
|
|
3965
|
+
for (const x of [...(brief.priorities || []), ...(brief.worth_asking || [])]) known.set(x.id, 'item');
|
|
3966
|
+
for (const e of brief.calendar || []) known.set(e.id, 'event');
|
|
3967
|
+
return known;
|
|
3968
|
+
}
|
|
3943
3969
|
// ── Background prepare, slice D3 (initiative proactive-engine) ──────────────────────────────────────────────────
|
|
3944
3970
|
// The PM approves ONE plan hash from Today. The 60-second tick later starts that session through the SAME path a next-step
|
|
3945
3971
|
// `go` uses (startWork with origin next-step and the prompt that was shown), only while background prepare is on, outside quiet
|
|
@@ -7774,7 +7800,7 @@ async function handleRequest(req, res) {
|
|
|
7774
7800
|
: !bs ? { session: null, why: 'the morning-brief session did not start.' }
|
|
7775
7801
|
: (!!bs.ended_at || !bsess || bsess.state === 'exited') ? { session: bs.id, why: (bsess && bsess.note) || 'the session ended without submitting a brief.' }
|
|
7776
7802
|
: briefSessionDone(run, date, now) === 'idle' ? { session: bs.id, idle: true, why: 'the session has printed nothing for two minutes and has not submitted a brief.' } : null;
|
|
7777
|
-
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,
|
|
7803
|
+
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, marks: dayMarksLib.effective(readDayMarks(date), now), cards_error: cardsError, read_failed: readFailed,
|
|
7778
7804
|
settings: { briefSources: st.briefSources, scopes: st.scopes, placeRepos: st.placeRepos, dayCheck: st.dayCheck, websites: st.websites }, sources,
|
|
7779
7805
|
// one day for every repository: the repositories it can be narrowed to, and the repository of each tagged item
|
|
7780
7806
|
repos: [...workspaces.values()].map((x) => ({ id: x.id, name: x.name || path.basename(x.root) })),
|
|
@@ -7797,6 +7823,58 @@ async function handleRequest(req, res) {
|
|
|
7797
7823
|
if (!writeCalMarks(r.marks)) return json(res, { ok: false, error: 'could not save — nothing changed' }, 500);
|
|
7798
7824
|
return json(res, { ok: true });
|
|
7799
7825
|
}
|
|
7826
|
+
/* The PM's mark on an item or a meeting of today's brief (initiative today-act-prepare, PRD M1 to M6). It needs no day
|
|
7827
|
+
* run: a mark is about the brief, and only this press writes goals/<date>.marks.json. The answer carries `prev`, the
|
|
7828
|
+
* mark before the press, so the page's Undo can put it back with { action: 'restore', mark: prev }. */
|
|
7829
|
+
if (p === '/api/proactive/day/mark' && req.method === 'POST') {
|
|
7830
|
+
let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
7831
|
+
const now = Date.now(); const date = briefLib.localDate(now);
|
|
7832
|
+
const stored = briefOfDay(date);
|
|
7833
|
+
const brief = stored && stored.date === date ? stored : null;
|
|
7834
|
+
if (!brief) return json(res, { ok: false, error: 'no brief today — press Prepare my day first' }, 409);
|
|
7835
|
+
const r = dayMarksLib.edit(readDayMarks(date), body, { now, known: dayMarkIds(brief) });
|
|
7836
|
+
if (!r.ok) return json(res, { ok: false, error: r.error }, /no item or meeting with that id/.test(r.error) ? 404 : 400);
|
|
7837
|
+
if (!writeDayMarks(r.rec)) return json(res, { ok: false, error: 'could not save — nothing changed' }, 500);
|
|
7838
|
+
return json(res, { ok: true, mark: r.mark, prev: r.prev, marks: dayMarksLib.effective(r.rec, now) });
|
|
7839
|
+
}
|
|
7840
|
+
/* "Have the agent draft it" on a job the PM owns (PRD C2): one briefed task, kind approve, tied to the item, through the
|
|
7841
|
+
* My work path. The day-task session drafts and sends nothing; the PM approves or revises the draft as for any job. */
|
|
7842
|
+
if (p === '/api/proactive/day/draft' && req.method === 'POST') {
|
|
7843
|
+
let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
7844
|
+
const now = Date.now(); const date = briefLib.localDate(now);
|
|
7845
|
+
const stored = briefOfDay(date);
|
|
7846
|
+
const brief = stored && stored.date === date ? stored : null;
|
|
7847
|
+
const x = brief ? [...(brief.priorities || []), ...(brief.worth_asking || [])].find((y) => y.id === String(body.item || '')) : null;
|
|
7848
|
+
if (!x) return json(res, { ok: false, error: 'no item with that id in today\'s brief' }, 404);
|
|
7849
|
+
if (x.owner !== 'pm') return json(res, { ok: false, error: 'the agent already does this job: approve or revise its draft' }, 409);
|
|
7850
|
+
const verb = { reply: 'reply', review: 'review notes', decide: 'answer', 'sign-off': 'sign-off comment', read: 'summary', do: 'draft' }[x.verb] || (x.reply_needed ? 'reply' : 'draft');
|
|
7851
|
+
const spec = { id: 't-' + x.id, item_id: x.id, title: x.title, source: x.source, link: x.link || null,
|
|
7852
|
+
instruction: `Draft the PM's ${verb} for this item: ${x.todo || x.how || x.ask || x.title}. Draft only. Send nothing: the PM approves it first.`,
|
|
7853
|
+
context: { from: x.who || null, where: x.where || null, about: x.about || null, ask: x.ask || null, how: x.how || null, why_now: x.why_now || null } };
|
|
7854
|
+
const r = myWorkBrief(spec, body.ws);
|
|
7855
|
+
if (!r.ok) return json(res, { ok: false, error: r.error }, r.status || 400);
|
|
7856
|
+
return json(res, { ok: true, task: r.task });
|
|
7857
|
+
}
|
|
7858
|
+
/* Send from the card (PRD F1 to F3): the PM wrote the reply or the review in the card and pressed Send. The press is the
|
|
7859
|
+
* approval, so the task goes straight to sending with the PM's text, and one send session posts it as written to the
|
|
7860
|
+
* place the item names. Nothing is sent without this press (D65 propose-only). */
|
|
7861
|
+
if (p === '/api/proactive/day/send' && req.method === 'POST') {
|
|
7862
|
+
let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
7863
|
+
const now = Date.now(); const date = briefLib.localDate(now);
|
|
7864
|
+
const stored = briefOfDay(date);
|
|
7865
|
+
const brief = stored && stored.date === date ? stored : null;
|
|
7866
|
+
const x = brief ? [...(brief.priorities || []), ...(brief.worth_asking || [])].find((y) => y.id === String(body.item || '')) : null;
|
|
7867
|
+
if (!x) return json(res, { ok: false, error: 'no item with that id in today\'s brief' }, 404);
|
|
7868
|
+
if (x.owner !== 'pm') return json(res, { ok: false, error: 'the agent already does this job: approve or revise its draft' }, 409);
|
|
7869
|
+
if (!x.link) return json(res, { ok: false, error: 'this item has no link, so there is no place to send to — open it and answer there' }, 409);
|
|
7870
|
+
const review = !!(x.send && x.send.review);
|
|
7871
|
+
if (review && !['approve', 'changes'].includes(body.verdict)) return json(res, { ok: false, error: 'a review needs a verdict: approve or changes' }, 400);
|
|
7872
|
+
const spec = { id: 't-' + x.id, item_id: x.id, title: x.title, source: x.source, link: x.link, to: (x.send && x.send.to) || x.where || null,
|
|
7873
|
+
text: body.text, verdict: review ? body.verdict : null };
|
|
7874
|
+
const r = myWorkBrief(spec, body.ws, dayRunLib.addSend);
|
|
7875
|
+
if (!r.ok) return json(res, { ok: false, error: r.error }, r.status || 400);
|
|
7876
|
+
return json(res, { ok: true, task: r.task });
|
|
7877
|
+
}
|
|
7800
7878
|
if (p.startsWith('/api/proactive/day/') && req.method === 'POST') {
|
|
7801
7879
|
const verb = p.slice('/api/proactive/day/'.length);
|
|
7802
7880
|
if (!['deliver', 'sent', 'fail', 'decide', 'check'].includes(verb)) return json(res, { ok: false, error: 'unknown day-run action' }, 404);
|
|
@@ -35,6 +35,7 @@ Procedure:
|
|
|
35
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
36
|
4. If MODE is send:
|
|
37
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
|
+
A draft whose summary starts "The PM's own" was written by the PM in the Your day card. Post it exactly as written, as the PM, to the place the summary and the link name. Do not add a greeting, a signature or a line of your own.
|
|
38
39
|
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
40
|
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
41
|
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.
|
|
@@ -50,11 +50,18 @@ Procedure:
|
|
|
50
50
|
- "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.
|
|
51
51
|
- "initiative": {"id": "<initiative id>", "why": "<why this meeting belongs to it>"}, only when it truly does.
|
|
52
52
|
- "links": up to 5 of {"label", "url"}, such as the agenda, the doc or the issue. A link starts with https:// or http://.
|
|
53
|
+
- "refs": up to 3 sources the PM reads to prepare, each {"label", "source", "link", "lines"}. "lines" is 1 to 5 lines of at most 200 characters: what the source says, in your own words, so the PM does not have to open it. Never copy the source.
|
|
54
|
+
- A Before step that reads a source has "ref": the index of that source in "refs" (0 for the first). A Before step of the PM that asks for a written view, such as a recommendation, has "note": true: the PM writes it in the card and it stays on this machine.
|
|
53
55
|
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.
|
|
54
56
|
Then fill the item fields that tell the PM what to do, so the card needs no second look:
|
|
55
57
|
- "link": the link to the conversation, issue, pull request, file or page itself, not to a list or a search.
|
|
56
58
|
- "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.
|
|
57
59
|
- "reply_needed": true only when someone waits for an answer from the PM.
|
|
60
|
+
- For an item the PM owns, say what the PM does, once: "verb" (one of reply, review, decide, sign-off, read, do), "todo" (one sentence of at most 200 characters: what to do, for whom, and by when, for example "Reply to Dana in #launch-plan: confirm Oct 14, or give the new date."), "due" (a date and time, only when the source gives one) and "steps" (at most 4 short steps, in order). Your day shows the verb and the sentence first, and the PM checks the steps off.
|
|
61
|
+
- Give the PM what they need to finish in the card, without opening another file:
|
|
62
|
+
- "thread": up to 4 messages of the conversation, oldest first, each {"who", "at", "text"}. "text" is at most 280 characters: the message in your words when it is longer.
|
|
63
|
+
- "refs": up to 3 sources the item points to, as for a meeting. A step that reads one is {"t": "…", "ref": 0} in "steps".
|
|
64
|
+
- "send": where the PM's own answer goes, {"to", "suggest", "review"}. "to" names the place in a few words, for example "Dana, in #launch-plan". "suggest" is up to 3 short answers the PM can start from, at most 280 characters each, in the PM's voice, with no greeting or signature. Set "review": true when the answer is a review that approves or asks for changes. Give "send" only when the item has a link to the conversation, issue or page itself: the PM's answer is posted there, as written, after the PM presses Send.
|
|
58
65
|
- "how": how you suggest to deal with it, at most 480 characters.
|
|
59
66
|
- "kind": who does the job.
|
|
60
67
|
- "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.
|
|
@@ -69,9 +76,13 @@ Procedure:
|
|
|
69
76
|
"purpose": "…", "prep": "…",
|
|
70
77
|
"steps": {"before": [{"t": "…", "who": "agent"}], "during": [{"t": "…", "who": "pm"}], "after": [{"t": "…", "who": "agent"}]},
|
|
71
78
|
"initiative": {"id": "<initiative id>", "why": "…"},
|
|
72
|
-
"links": [{"label": "Agenda", "url": "https://…"}]
|
|
79
|
+
"links": [{"label": "Agenda", "url": "https://…"}],
|
|
80
|
+
"refs": [{"label": "…", "source": "confluence", "link": "https://…", "lines": ["…"]}]}],
|
|
73
81
|
"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": "…",
|
|
74
|
-
"link": "https://…", "where": "#pm-team", "who": "…", "about": "…", "ask": "…", "reply_needed": true, "how": "…", "kind": "you|approve|auto"
|
|
82
|
+
"link": "https://…", "where": "#pm-team", "who": "…", "about": "…", "ask": "…", "reply_needed": true, "how": "…", "kind": "you|approve|auto",
|
|
83
|
+
"verb": "reply", "todo": "…", "due": "2026-10-07T15:00:00", "steps": [{"t": "…", "ref": 0}, "…"],
|
|
84
|
+
"thread": [{"who": "…", "at": "09:12", "text": "…"}], "refs": [{"label": "…", "source": "jira", "link": "https://…", "lines": ["…"]}],
|
|
85
|
+
"send": {"to": "…", "suggest": ["…"], "review": false}}],
|
|
75
86
|
"digests": [{"source": "slack|jira|confluence", "name": "#pm-team", "link": "https://…", "decisions": ["…"], "open": ["…"], "goals": ["…"]}],
|
|
76
87
|
"places": [{"source": "slack|jira|confluence", "name": "#pm-team", "link": "https://…"}],
|
|
77
88
|
"memory": {"from": "YYYY-MM-DD", "to": "YYYY-MM-DD",
|