create-pmos 0.38.6 → 0.38.8

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.
Files changed (40) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +1 -1
  3. package/payload/okf/core/templates/report-rubric-template.md +35 -0
  4. package/payload/okf/core/templates/report-weekly-template.md +21 -10
  5. package/payload/planning/evals/eval-calibration.md +1 -1
  6. package/payload/planning/okrs/health-budgets.md +1 -1
  7. package/payload/skills/pm-communication.skill +123 -0
  8. package/payload/skills/pmos-router.skill +1 -0
  9. package/payload/skills/technical-writing-ste.skill +5 -3
  10. package/payload/skills-native/pm-communication/SKILL.md +125 -0
  11. package/payload/skills-native/pmos-router/SKILL.md +1 -0
  12. package/payload/skills-native/technical-writing-ste/SKILL.md +5 -3
  13. package/payload/state/discoveries.md +1 -1
  14. package/workspace/README.md +35 -0
  15. package/workspace/bin/pmos.js +10 -1
  16. package/workspace/lib/initiative-files.js +111 -0
  17. package/workspace/lib/next-steps.js +113 -0
  18. package/workspace/lib/prompt-input.js +18 -0
  19. package/workspace/lib/schedule-cli.js +256 -0
  20. package/workspace/lib/schedule-logic.js +23 -1
  21. package/workspace/lib/skill-store.js +11 -3
  22. package/workspace/lib/status-groups.js +66 -0
  23. package/workspace/lib/transcripts.js +73 -0
  24. package/workspace/package-lock.json +1 -1
  25. package/workspace/package.json +3 -3
  26. package/workspace/public/index.html +2 -0
  27. package/workspace/public/pm/data.js +23 -2
  28. package/workspace/public/pm/modes-live.js +19 -0
  29. package/workspace/public/pm/panel-detail.js +84 -0
  30. package/workspace/public/pm/panel-initiatives.js +37 -1
  31. package/workspace/public/pm/panel-tabs.js +38 -2
  32. package/workspace/public/pm/panel-trees.js +9 -2
  33. package/workspace/public/pm/state.js +7 -2
  34. package/workspace/server.js +112 -5
  35. package/workspace/skills/report-biweekly.skill +5 -5
  36. package/workspace/skills/report-daily.skill +5 -5
  37. package/workspace/skills/report-monthly.skill +5 -5
  38. package/workspace/skills/report-special.skill +3 -3
  39. package/workspace/skills/report-weekly.skill +5 -5
  40. package/workspace/skills/weekly-report.skill +3 -2
@@ -63,6 +63,10 @@ ensureSpawnHelperExecutable();
63
63
  const { checkSurface } = require('./lib/surface-contract');
64
64
  const { createSinkGuard } = require('./lib/sink.js');
65
65
  const scheduleLogic = require('./lib/schedule-logic');
66
+ const { safeFirstInput } = require('./lib/prompt-input');
67
+ const transcriptsLib = require('./lib/transcripts');
68
+ const initiativeFiles = require('./lib/initiative-files');
69
+ const { deriveNextSteps } = require('./lib/next-steps');
66
70
  const harness = require('./lib/harness-parse');
67
71
  const transcript = require('./lib/transcript-read');
68
72
  const skillStore = require('./lib/skill-store');
@@ -2040,6 +2044,13 @@ const SEED_SETS = {
2040
2044
  function readSchedules() {
2041
2045
  try { const a = JSON.parse(fs.readFileSync(schedFile(), 'utf8')); return Array.isArray(a) ? a : []; } catch { return []; }
2042
2046
  }
2047
+ // R5 — count a fire against the schedule's own limits. A schedule that has run `max_runs` times, or whose `until`
2048
+ // day is over, turns itself off and says why; nothing about a schedule without either changes.
2049
+ function countRun(cur, now = new Date()) {
2050
+ cur.runs = (+cur.runs || 0) + 1;
2051
+ const why = scheduleLogic.finishReason(cur, now);
2052
+ if (why) { cur.enabled = false; cur.endedReason = why; cur.nextRun = null; }
2053
+ }
2043
2054
  function writeSchedules(list) {
2044
2055
  try { fs.writeFileSync(schedFile(), JSON.stringify(list)); }
2045
2056
  catch (e) { warnWriteFail('schedules.json (scheduled tasks)', e); }
@@ -2088,7 +2099,7 @@ function fireSchedule(sched) {
2088
2099
  modelAsk: { scheduleTier: sched.tier || null, skillTier: task.tier || null, skill: task.skill } });
2089
2100
  const list = readSchedules(); const cur = list.find((x) => x.id === sched.id);
2090
2101
  const started = s.state !== 'exited';
2091
- if (cur) { cur.lastRun = nowISO(); cur.lastStatus = started ? 'started' : 'fail'; cur.nextRun = scheduleLogic.nextRun(cur); if (cur.cadence === 'once') cur.enabled = false; writeSchedules(list); }
2102
+ if (cur) { cur.lastRun = nowISO(); cur.lastStatus = started ? 'started' : 'fail'; cur.nextRun = scheduleLogic.nextRun(cur); if (cur.cadence === 'once') cur.enabled = false; countRun(cur); writeSchedules(list); }
2092
2103
  // A run that started WITHOUT the profile it recorded is reported as such rather than as a
2093
2104
  // clean start: the session is real, the environment is not the one the schedule declared.
2094
2105
  return { ok: started, kind: 'agent', sessionId: s.id,
@@ -2110,7 +2121,7 @@ function fireSchedule(sched) {
2110
2121
  // stamp lastRun/lastStatus/nextRun via the pure module
2111
2122
  const list = readSchedules();
2112
2123
  const cur = list.find((s) => s.id === sched.id);
2113
- if (cur) { cur.lastRun = nowISO(); cur.lastStatus = ok ? 'ok' : 'fail'; cur.nextRun = scheduleLogic.nextRun(cur); writeSchedules(list); }
2124
+ if (cur) { cur.lastRun = nowISO(); cur.lastStatus = ok ? 'ok' : 'fail'; cur.nextRun = scheduleLogic.nextRun(cur); countRun(cur); writeSchedules(list); }
2114
2125
  // surface it — the finished run reaches the PM through needs-you (R5), honest on failure
2115
2126
  try {
2116
2127
  const nf = needsFileFor(w);
@@ -2131,6 +2142,7 @@ function checkDueSchedules() {
2131
2142
  try {
2132
2143
  const list = readSchedules(); let changed = false;
2133
2144
  for (const s of list) if (scheduleLogic.isMissed(s)) { s.enabled = false; s.lastStatus = 'missed'; s.nextRun = null; changed = true; }
2145
+ for (const s of list) { const why = s.enabled && scheduleLogic.finishReason(s); if (why) { s.enabled = false; s.endedReason = why; s.nextRun = null; changed = true; } }
2134
2146
  if (changed) writeSchedules(list);
2135
2147
  } catch {}
2136
2148
  return fired;
@@ -2145,7 +2157,29 @@ const sessions = new Map();
2145
2157
  const allClients = new Set();
2146
2158
  let seq = 0;
2147
2159
 
2160
+ // The initiative a session belongs to, for the conversation record: the PM's or spawn's explicit word first,
2161
+ // else the `initiative/<id>` branch its working tree is on (the same join the initiative detail makes).
2162
+ function initiativeOfSession(s) {
2163
+ if (s && s.initiative) return String(s.initiative);
2164
+ try {
2165
+ const w = wsList().find((x) => x.id === (s && s.ws));
2166
+ const m = w && w.branch ? /^initiative\/(.+)$/.exec(String(w.branch)) : null;
2167
+ return m ? m[1] : null;
2168
+ } catch { return null; }
2169
+ }
2170
+ // Save the conversation of a session that belongs to an initiative, so ending or removing it no longer erases it.
2171
+ // Runtime state only, never the repository (PM ruling 2026-10-03). Quiet when the session is on no initiative.
2172
+ function recordConversation(s) {
2173
+ const initiative = initiativeOfSession(s);
2174
+ if (!initiative) return { ok: false, skipped: true };
2175
+ const r = transcriptsLib.save(STATE, { id: s.id, name: s.name, agent: s.agent, ws: s.ws, initiative,
2176
+ state: s.state, started: s.created ? new Date(s.created).toISOString() : null, text: s.buf });
2177
+ if (!r.ok) warnWriteFail('transcripts (conversation record)', new Error(r.error));
2178
+ return r;
2179
+ }
2148
2180
  function persist(s) {
2181
+ if (s.state === 'exited' && !s._txSaved) { s._txSaved = true; try { recordConversation(s); } catch {} }
2182
+ else if (s.state !== 'exited') s._txSaved = false;
2149
2183
  // note IS persisted — a restored session must still say why it exited. `root` is persisted
2150
2184
  // so a session survives even if the workspace registry is lost (it re-registers its root).
2151
2185
  const meta = { id: s.id, ws: s.ws, root: s.root, name: s.name, agent: s.agent, cwd: s.cwd,
@@ -2872,7 +2906,8 @@ function spawnSession({ name, agent, cwd, ws, origin, initialInput, profile, ini
2872
2906
  try { memoryLib.renderFor(root); } catch { /* memory is never a reason a session fails to start */ }
2873
2907
  // a scheduled run tags the session with its origin, and feeds the skill invocation as first input
2874
2908
  const tag = origin || null;
2875
- const feed = (s) => { if (initialInput && s && s.pty) { try { setTimeout(() => { try { s.pty.write(initialInput); } catch {} }, 700); } catch {} } };
2909
+ // safeFirstInput: a first input that begins with `/` is read by the agent as a slash command, not as the task.
2910
+ const feed = (s) => { if (initialInput && s && s.pty) { try { setTimeout(() => { try { s.pty.write(safeFirstInput(initialInput)); } catch {} }, 700); } catch {} } };
2876
2911
  // shell gets a real fallback to `sh`; agent CLIs run as-is (harness untouched), NO fallback —
2877
2912
  // falling back from `claude` to `sh` would be a lie.
2878
2913
  const cmd = isShell ? (commandExists(SHELL) ? SHELL : 'sh') : agent;
@@ -3302,6 +3337,7 @@ function removeSession(id) {
3302
3337
  const s = sessions.get(id);
3303
3338
  if (!s || s.pty) return false;
3304
3339
  if (s.tmux) tmuxKillSession(s.tmux); // best-effort: never orphan a detached tmux session
3340
+ try { recordConversation(s); } catch {} // the conversation outlives the session (surface-batch R5)
3305
3341
  forgetScratchpad(s); // the tab's staging text goes with the tab
3306
3342
  // Drop a claim this session held. Correctness does not depend on this — treeShare.writerOf only
3307
3343
  // believes a claim whose holder is still live, so a stale entry already reads as vacant — but a
@@ -4229,6 +4265,7 @@ async function handleRequest(req, res) {
4229
4265
  // is the same two-hopper lib/four-panel.js makes per initiative on the server; the board makes it
4230
4266
  // once over every session, from this module, so 178 rows cost no requests.
4231
4267
  if (p === '/lib/working-set.js') return serveFile(res, path.join(__dirname, 'lib', 'working-set.js'));
4268
+ if (p === '/lib/status-groups.js') return serveFile(res, path.join(__dirname, 'lib', 'status-groups.js'));
4232
4269
  // The editor computes its live diff with the SAME module the server saves through, so what the PM
4233
4270
  // sees while typing and what gets written cannot disagree. NB: /lib/ is an explicit allowlist, not
4234
4271
  // a directory route — a <script src> for a file missing from this list 404s silently, and the
@@ -4840,6 +4877,29 @@ async function handleRequest(req, res) {
4840
4877
  if (err) return json(res, { ok: false, error: err }, 500);
4841
4878
  return json(res, { ok: true, prefs });
4842
4879
  }
4880
+ // One recorded conversation (R5): the live session's text, or the saved record once it has ended. Read-only.
4881
+ if (p === '/api/transcript' && req.method === 'GET') {
4882
+ const id = String(url.searchParams.get('id') || '');
4883
+ const live = sessions.get(id);
4884
+ if (live) {
4885
+ const initiative = initiativeOfSession(live);
4886
+ return json(res, { ok: true, id, name: live.name, initiative, state: live.state, live: live.state !== 'exited',
4887
+ stored: 'the runtime state directory — not the repository, and never sent anywhere',
4888
+ text: transcriptsLib.stripAnsi(live.buf).slice(-transcriptsLib.MAX_CHARS) });
4889
+ }
4890
+ const rec = transcriptsLib.read(STATE, id);
4891
+ if (!rec) return json(res, { ok: false, error: 'no recorded conversation with that id' }, 404);
4892
+ return json(res, { ok: true, ...rec, live: false, stored: 'the runtime state directory — not the repository, and never sent anywhere' });
4893
+ }
4894
+ // The tail of one session's terminal text, escape codes stripped, so a CLI can turn "what I just did" into a skill.
4895
+ // Read-only, local, and capped; it names the session it read or says there is none.
4896
+ if (p === '/api/sessions/transcript' && req.method === 'GET') {
4897
+ const sess = sessions.get(String(url.searchParams.get('id') || ''));
4898
+ if (!sess) return json(res, { ok: false, error: 'no such session' }, 404);
4899
+ const max = Math.min(60000, Math.max(500, +url.searchParams.get('max') || 20000));
4900
+ const text = transcriptsLib.stripAnsi(sess.buf).slice(-max);
4901
+ return json(res, { ok: true, id: sess.id, name: sess.name, chars: text.length, text });
4902
+ }
4843
4903
  if (p === '/api/schedules' && req.method === 'GET') {
4844
4904
  // `all=1` (overhaul-r4 D1): every repository's schedules — the drawer lists `scheduled` under "all
4845
4905
  // repositories", so filtering to the home workspace there would hide the PM's other schedules
@@ -4905,8 +4965,32 @@ async function handleRequest(req, res) {
4905
4965
  let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON' }, 400); }
4906
4966
  const list = readSchedules(); const s = list.find((x) => x.id === body.id);
4907
4967
  if (!s) return json(res, { ok: false, error: 'no such schedule' }, 404);
4908
- if (typeof body.enabled === 'boolean') s.enabled = body.enabled;
4968
+ // R5 — `until` and `max_runs` are validated by the same rules a new schedule is, and null/'' clears one. A refused value
4969
+ // returns BEFORE anything is changed, so a bad edit leaves the record as it was.
4970
+ const lim = {};
4971
+ for (const k of ['until', 'max_runs']) if (k in body) lim[k] = body[k];
4972
+ if (Object.keys(lim).length) {
4973
+ const chk = scheduleLogic.normalizeSchedule({ name: s.name, cadence: s.cadence === 'once' ? 'daily' : s.cadence, skill: s.skill, ...lim });
4974
+ if (!chk.ok) return json(res, { ok: false, error: chk.error }, 400);
4975
+ for (const k of Object.keys(lim)) { if (lim[k] == null || lim[k] === '') delete s[k]; else s[k] = chk.schedule[k]; }
4976
+ }
4977
+ if (typeof body.enabled === 'boolean') { s.enabled = body.enabled; if (body.enabled) delete s.endedReason; }
4978
+ // Every field an edit may set is checked BEFORE any is applied (R2): a refused edit names the allowed values and
4979
+ // leaves the record untouched. `skill` must be one that loads for this schedule's repository.
4980
+ const bad = (m) => json(res, { ok: false, error: m }, 400);
4981
+ if (body.cadence != null && !scheduleLogic.CADENCES.includes(body.cadence)) return bad(`cadence must be one of ${scheduleLogic.CADENCES.join(', ')}`);
4982
+ if (body.day != null && !['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].includes(body.day)) return bad('day must be one of Mon, Tue, Wed, Thu, Fri, Sat, Sun');
4983
+ if (body.dayOfMonth != null && !(Number.isInteger(+body.dayOfMonth) && +body.dayOfMonth >= 1 && +body.dayOfMonth <= 28)) return bad('dayOfMonth must be a whole number from 1 to 28');
4984
+ if (body.time != null && !/^([01]?\d|2[0-3]):[0-5]\d$/.test(String(body.time))) return bad('time must be HH:MM, 24-hour, this machine\'s clock');
4985
+ if (body.date != null && !/^\d{4}-\d{2}-\d{2}$/.test(String(body.date))) return bad('date must be YYYY-MM-DD');
4986
+ if (body.name != null && !String(body.name).trim()) return bad('name cannot be empty');
4987
+ if (body.args != null && String(body.args).length > 2000) return bad('args is limited to 2000 characters');
4988
+ for (const k of ['skill', 'task']) if (body[k] != null) {
4989
+ const ws0 = workspaces.get(s.ws);
4990
+ if (!taskById(body[k], ws0 ? ws0.root : null)) return bad(`no skill named "${body[k]}" is available to this schedule`);
4991
+ }
4909
4992
  for (const k of ['name', 'cadence', 'day', 'dayOfMonth', 'time', 'task', 'skill', 'date', 'args']) if (body[k] != null) s[k] = body[k];
4993
+ if ('args' in body && (body.args === null || body.args === '')) delete s.args;
4910
4994
  if ('tier' in body) { if (body.tier === null || body.tier === '') delete s.tier; else if (/^(fast|balanced|deep)$/.test(String(body.tier))) s.tier = String(body.tier); else return json(res, { ok: false, error: 'tier must be fast, balanced or deep' }, 400); }
4911
4995
  // `profile` is settable to null (clear it) as well as to an id, so it is checked for PRESENCE
4912
4996
  // of the key rather than for a non-null value — the other fields above cannot be cleared.
@@ -5189,7 +5273,30 @@ async function handleRequest(req, res) {
5189
5273
  // Discriminated READ results stay 200 — the body carries the reason, the transport code cannot.
5190
5274
  let lanes = null;
5191
5275
  try { if (typeof engine.engine.lanesFor === 'function') lanes = engine.engine.lanesFor(root); } catch { lanes = null; }
5192
- return json(res, { ws: w ? w.id : null, ...out, lanes });
5276
+ // R4/R5/R6 — what remains to reach acceptance, the conversations that belong to this initiative, and the
5277
+ // files git says were changed for it. Each is read, never guessed; a failed read says so.
5278
+ const sessRows = sessionList();
5279
+ const liveConv = sessRows.filter((x) => initiativeOfSession(sessions.get(x.id)) === id)
5280
+ .map((x) => ({ id: x.id, name: x.name, state: x.state === 'exited' ? 'exited' : 'live', live: x.state !== 'exited', ws: x.ws }));
5281
+ const savedConv = transcriptsLib.listFor(STATE, id).filter((c) => !liveConv.some((l) => l.id === c.id))
5282
+ .map((c) => ({ id: c.id, name: c.name, state: 'ended', live: false, ws: c.ws, saved: c.saved, chars: c.chars, cut: c.cut }));
5283
+ const conversations = [...liveConv, ...savedConv];
5284
+ const listed = new Set(((out && out.artifacts) || []).map((a) => a && a.path).filter(Boolean));
5285
+ let files;
5286
+ try { files = initiativeFiles.filesFor(root, id, { exclude: listed }); }
5287
+ catch (e) { files = { ok: false, reason: 'the files could not be read — ' + String((e && e.message) || 'error').slice(0, 120) }; }
5288
+ let nextSteps = null;
5289
+ try {
5290
+ const acc = readAcceptances(w).get(id) || {};
5291
+ const lc = readLifecycle(w).get(id) || null;
5292
+ nextSteps = deriveNextSteps({
5293
+ id, accepted: acc.action === 'accept', returned: acc.action === 'return',
5294
+ lifecycle: lc && lc.state !== 'active' ? lc : null, ladder,
5295
+ needs: (readNeeds(w) || []).filter((n) => n && n.initiative_id === id),
5296
+ sessions: sessRows.filter((x) => (x.initiative === id) || conversations.some((c) => c.id === x.id && c.live)),
5297
+ });
5298
+ } catch (e) { nextSteps = { state: 'unreadable', summary: 'The next steps could not be derived — ' + String((e && e.message) || 'error').slice(0, 120), items: [] }; }
5299
+ return json(res, { ws: w ? w.id : null, ...out, lanes, conversations, files, next_steps: nextSteps });
5193
5300
  }
5194
5301
  if (p === '/api/audit' && req.method === 'GET') {
5195
5302
  /* The workflow audit (D77): the sweep run on one repository, read-only. It reports state that contradicts
@@ -5,7 +5,7 @@ description: "Prepare the biweekly report as a draft, following the PM's own def
5
5
  run: agent
6
6
  agent: claude
7
7
  output:
8
- - Summary
8
+ - Headline
9
9
  - Sections from the definition
10
10
  - Needs your call
11
11
  - Sources reached and not reached
@@ -40,14 +40,14 @@ Procedure:
40
40
  ---
41
41
  ```
42
42
  `definition` says whether the definition used is confirmed (`stable`) or only proposed (`draft`). Do not add a `timestamp` or `verified`.
43
- 5. Under the frontmatter: a short Summary, then the sections the definition names in its order, then "Needs your call" (decisions or answers only the PM can give), then "Sources reached and not reached" (each source kind: reached or not reached and why; private items skipped).
44
- 6. If the definition was a draft, say so in the first line of the Summary so the PM sees it.
43
+ 5. Under the frontmatter: a Headline (one or two sentences: what the reader most needs to know), then the sections the definition names in its order, then "Needs your call" (decisions or answers only the PM can give), then "Sources reached and not reached" (each source kind: reached or not reached and why; private items skipped).
44
+ 6. If the definition was a draft, say so in the first line of the Headline so the PM sees it.
45
45
  7. Finish by saying in one line where the draft is. Do not commit; the PM's Sync carries the file.
46
46
 
47
- Default shape, used ONLY when proposing a definition: a two-week summary: progress, decisions, risks, what is next.
47
+ Default shape, used ONLY when proposing a definition: a two-week report: progress, decisions, risks, what is next. Use the report frame of D78: a Headline, then What moved, Risks and Metrics, then Needs your call and Sources reached and not reached.
48
48
 
49
49
  Output contract — produce EXACTLY these sections, the same shape every run:
50
- - Summary
50
+ - Headline
51
51
  - Sections from the definition
52
52
  - Needs your call
53
53
  - Sources reached and not reached
@@ -5,7 +5,7 @@ description: "Prepare the daily report as a draft, following the PM's own defini
5
5
  run: agent
6
6
  agent: claude
7
7
  output:
8
- - Summary
8
+ - Headline
9
9
  - Sections from the definition
10
10
  - Needs your call
11
11
  - Sources reached and not reached
@@ -40,14 +40,14 @@ Procedure:
40
40
  ---
41
41
  ```
42
42
  `definition` says whether the definition used is confirmed (`stable`) or only proposed (`draft`). Do not add a `timestamp` or `verified`.
43
- 5. Under the frontmatter: a short Summary, then the sections the definition names in its order, then "Needs your call" (decisions or answers only the PM can give), then "Sources reached and not reached" (each source kind: reached or not reached and why; private items skipped).
44
- 6. If the definition was a draft, say so in the first line of the Summary so the PM sees it.
43
+ 5. Under the frontmatter: a Headline (one or two sentences: what the reader most needs to know), then the sections the definition names in its order, then "Needs your call" (decisions or answers only the PM can give), then "Sources reached and not reached" (each source kind: reached or not reached and why; private items skipped).
44
+ 6. If the definition was a draft, say so in the first line of the Headline so the PM sees it.
45
45
  7. Finish by saying in one line where the draft is. Do not commit; the PM's Sync carries the file.
46
46
 
47
- Default shape, used ONLY when proposing a definition: a one-page end-of-day summary: what moved, what is blocked, what needs the PM tomorrow.
47
+ Default shape, used ONLY when proposing a definition: a one-page end-of-day report: what moved, what is blocked, what needs the PM tomorrow. Use the report frame of D78: a Headline, then What moved, Risks and Metrics, then Needs your call and Sources reached and not reached.
48
48
 
49
49
  Output contract — produce EXACTLY these sections, the same shape every run:
50
- - Summary
50
+ - Headline
51
51
  - Sections from the definition
52
52
  - Needs your call
53
53
  - Sources reached and not reached
@@ -5,7 +5,7 @@ description: "Prepare the monthly report as a draft, following the PM's own defi
5
5
  run: agent
6
6
  agent: claude
7
7
  output:
8
- - Summary
8
+ - Headline
9
9
  - Sections from the definition
10
10
  - Needs your call
11
11
  - Sources reached and not reached
@@ -40,14 +40,14 @@ Procedure:
40
40
  ---
41
41
  ```
42
42
  `definition` says whether the definition used is confirmed (`stable`) or only proposed (`draft`). Do not add a `timestamp` or `verified`.
43
- 5. Under the frontmatter: a short Summary, then the sections the definition names in its order, then "Needs your call" (decisions or answers only the PM can give), then "Sources reached and not reached" (each source kind: reached or not reached and why; private items skipped).
44
- 6. If the definition was a draft, say so in the first line of the Summary so the PM sees it.
43
+ 5. Under the frontmatter: a Headline (one or two sentences: what the reader most needs to know), then the sections the definition names in its order, then "Needs your call" (decisions or answers only the PM can give), then "Sources reached and not reached" (each source kind: reached or not reached and why; private items skipped).
44
+ 6. If the definition was a draft, say so in the first line of the Headline so the PM sees it.
45
45
  7. Finish by saying in one line where the draft is. Do not commit; the PM's Sync carries the file.
46
46
 
47
- Default shape, used ONLY when proposing a definition: a monthly review: outcomes against goals, decisions, risks, learning, next month.
47
+ Default shape, used ONLY when proposing a definition: a monthly review: outcomes against goals, decisions, risks, learning, next month. Use the report frame of D78: a Headline, then What moved, Risks and Metrics, then Needs your call and Sources reached and not reached.
48
48
 
49
49
  Output contract — produce EXACTLY these sections, the same shape every run:
50
- - Summary
50
+ - Headline
51
51
  - Sections from the definition
52
52
  - Needs your call
53
53
  - Sources reached and not reached
@@ -5,7 +5,7 @@ description: "Prepare a one-off report as a draft. The PM says what it is for; t
5
5
  run: agent
6
6
  agent: claude
7
7
  output:
8
- - Summary
8
+ - Headline
9
9
  - Sections from the definition
10
10
  - Needs your call
11
11
  - Sources reached and not reached
@@ -26,7 +26,7 @@ Procedure:
26
26
  2. Find the state directory: `pmos/state/` if it exists, otherwise `planning/`. Call it <state>.
27
27
  3. If `<state>/reports/definitions/special.md` exists, read it for the house style and use it where it does not contradict what the PM just asked.
28
28
  4. Gather the material: the requirement notes in `<state>/requirements/`, the initiatives and acceptances, and every connected source you can reach that bears on the topic.
29
- 5. Write the draft to `<state>/reports/special/YYYY-MM-DD-<short-topic-slug>.md`, then the sections the PM asked for, then "Needs your call" and "Sources reached and not reached". Start it with this frontmatter (OKF v0.2, D73). Every key is on ONE line — never indent a key under another:
29
+ 5. Write the draft to `<state>/reports/special/YYYY-MM-DD-<short-topic-slug>.md`, then a Headline (one or two sentences), then the sections the PM asked for (the report frame of D78 by default: What moved, Risks, Metrics), then "Needs your call" and "Sources reached and not reached". Start it with this frontmatter (OKF v0.2, D73). Every key is on ONE line — never indent a key under another:
30
30
  ```
31
31
  ---
32
32
  type: Reference
@@ -43,7 +43,7 @@ Procedure:
43
43
  7. Finish by saying in one line where the draft is. Do not commit; the PM's Sync carries the file.
44
44
 
45
45
  Output contract — produce EXACTLY these sections, the same shape every run:
46
- - Summary
46
+ - Headline
47
47
  - Sections from the definition
48
48
  - Needs your call
49
49
  - Sources reached and not reached
@@ -5,7 +5,7 @@ description: "Prepare the weekly report as a draft, following the PM's own defin
5
5
  run: agent
6
6
  agent: claude
7
7
  output:
8
- - Summary
8
+ - Headline
9
9
  - Sections from the definition
10
10
  - Needs your call
11
11
  - Sources reached and not reached
@@ -40,14 +40,14 @@ Procedure:
40
40
  ---
41
41
  ```
42
42
  `definition` says whether the definition used is confirmed (`stable`) or only proposed (`draft`). Do not add a `timestamp` or `verified`.
43
- 5. Under the frontmatter: a short Summary, then the sections the definition names in its order, then "Needs your call" (decisions or answers only the PM can give), then "Sources reached and not reached" (each source kind: reached or not reached and why; private items skipped).
44
- 6. If the definition was a draft, say so in the first line of the Summary so the PM sees it.
43
+ 5. Under the frontmatter: a Headline (one or two sentences: what the reader most needs to know), then the sections the definition names in its order, then "Needs your call" (decisions or answers only the PM can give), then "Sources reached and not reached" (each source kind: reached or not reached and why; private items skipped).
44
+ 6. If the definition was a draft, say so in the first line of the Headline so the PM sees it.
45
45
  7. Finish by saying in one line where the draft is. Do not commit; the PM's Sync carries the file.
46
46
 
47
- Default shape, used ONLY when proposing a definition: a weekly summary: progress against initiatives, decisions made, risks, what is next.
47
+ Default shape, used ONLY when proposing a definition: a weekly report: progress against initiatives, decisions made, risks, what is next. Use the report frame of D78: a Headline, then What moved, Risks and Metrics, then Needs your call and Sources reached and not reached.
48
48
 
49
49
  Output contract — produce EXACTLY these sections, the same shape every run:
50
- - Summary
50
+ - Headline
51
51
  - Sections from the definition
52
52
  - Needs your call
53
53
  - Sources reached and not reached
@@ -1,12 +1,13 @@
1
1
  ---
2
2
  name: weekly-report
3
3
  label: "Weekly report"
4
- description: "A weekly status report across the workspace — in flight, landed, needs-you, and a metrics pulse."
4
+ description: "A weekly status report across the workspace — in flight, delivered (acceptance not recorded), accepted, needs-you, and a metrics pulse."
5
5
  run: deterministic
6
6
  surface: report
7
7
  output:
8
8
  - In flight
9
- - Landed
9
+ - Delivered — acceptance not recorded
10
+ - Accepted
10
11
  - Needs you
11
12
  - Metrics pulse
12
13
  ---