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.
- package/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/okf/core/templates/report-rubric-template.md +35 -0
- package/payload/okf/core/templates/report-weekly-template.md +21 -10
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/skills/pm-communication.skill +123 -0
- package/payload/skills/pmos-router.skill +1 -0
- package/payload/skills/technical-writing-ste.skill +5 -3
- package/payload/skills-native/pm-communication/SKILL.md +125 -0
- package/payload/skills-native/pmos-router/SKILL.md +1 -0
- package/payload/skills-native/technical-writing-ste/SKILL.md +5 -3
- package/payload/state/discoveries.md +1 -1
- package/workspace/README.md +35 -0
- package/workspace/bin/pmos.js +10 -1
- package/workspace/lib/initiative-files.js +111 -0
- package/workspace/lib/next-steps.js +113 -0
- package/workspace/lib/prompt-input.js +18 -0
- package/workspace/lib/schedule-cli.js +256 -0
- package/workspace/lib/schedule-logic.js +23 -1
- package/workspace/lib/skill-store.js +11 -3
- package/workspace/lib/status-groups.js +66 -0
- package/workspace/lib/transcripts.js +73 -0
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +3 -3
- package/workspace/public/index.html +2 -0
- package/workspace/public/pm/data.js +23 -2
- package/workspace/public/pm/modes-live.js +19 -0
- package/workspace/public/pm/panel-detail.js +84 -0
- package/workspace/public/pm/panel-initiatives.js +37 -1
- package/workspace/public/pm/panel-tabs.js +38 -2
- package/workspace/public/pm/panel-trees.js +9 -2
- package/workspace/public/pm/state.js +7 -2
- package/workspace/server.js +112 -5
- package/workspace/skills/report-biweekly.skill +5 -5
- package/workspace/skills/report-daily.skill +5 -5
- package/workspace/skills/report-monthly.skill +5 -5
- package/workspace/skills/report-special.skill +3 -3
- package/workspace/skills/report-weekly.skill +5 -5
- package/workspace/skills/weekly-report.skill +3 -2
package/workspace/server.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
|
44
|
-
6. If the definition was a draft, say so in the first line of the
|
|
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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
|
44
|
-
6. If the definition was a draft, say so in the first line of the
|
|
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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
|
44
|
-
6. If the definition was a draft, say so in the first line of the
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
|
44
|
-
6. If the definition was a draft, say so in the first line of the
|
|
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
|
|
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
|
-
-
|
|
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,
|
|
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
|
-
-
|
|
9
|
+
- Delivered — acceptance not recorded
|
|
10
|
+
- Accepted
|
|
10
11
|
- Needs you
|
|
11
12
|
- Metrics pulse
|
|
12
13
|
---
|