create-pmos 0.526.0 → 0.527.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.526.0",
3
+ "version": "0.527.0",
4
4
  "description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -32,6 +32,19 @@
32
32
  "audit"
33
33
  ],
34
34
  "pmosChanges": [
35
+ {
36
+ "v": "0.527.0",
37
+ "at": "2026-11-04T11:15:45.443Z",
38
+ "f": 4,
39
+ "prs": [
40
+ {
41
+ "n": 1237,
42
+ "t": "Chat refinements keep drafting the open report",
43
+ "s": "Chat refinements keep drafting the open report",
44
+ "k": "fix"
45
+ }
46
+ ]
47
+ },
35
48
  {
36
49
  "v": "0.526.0",
37
50
  "at": "2026-11-04T10:37:51.951Z",
@@ -400,21 +413,6 @@
400
413
  "d": "The record of what the decision guard checked went to a shared place. Now it stays in your own folder."
401
414
  }
402
415
  ]
403
- },
404
- {
405
- "v": "0.507.0",
406
- "at": "2026-11-03T12:20:44.537Z",
407
- "f": 4,
408
- "prs": [
409
- {
410
- "n": 1194,
411
- "t": "Rehearse a plan before you approve it (pmos rehearse)",
412
- "s": "Rehearse a plan before you approve it (pmos rehearse)",
413
- "k": "new",
414
- "h": "Rehearse a plan",
415
- "d": "Moving a date or cutting a task can quietly break a milestone. Now, before you approve a plan, Claude walks through the change and shows what it breaks or helps. Nothing changes until you say yes."
416
- }
417
- ]
418
416
  }
419
417
  ]
420
418
  }
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@dbaab974
1
+ kernel-source: pmos@e3e4aeb7
2
2
  assembled: 2026-11-04
3
3
  mode: vendor
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Eval calibration log"
4
4
  description: "Append-only record of every PM override of an evaluator verdict."
5
- generated: {by: process:create-pmos, at: "2026-11-04T10:37:49Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-04T11:15:42Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Health budgets"
4
4
  description: "Closed registry of health-budget anchors (D59), edited only by the PM."
5
- generated: {by: process:create-pmos, at: "2026-11-04T10:37:49Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-04T11:15:42Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Discoveries and friction log"
4
4
  description: "Append-only record of what runs discovered and where the process rubbed."
5
- generated: {by: process:create-pmos, at: "2026-11-04T10:37:49Z"}
5
+ generated: {by: process:create-pmos, at: "2026-11-04T11:15:42Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -1,4 +1,17 @@
1
1
  [
2
+ {
3
+ "v": "0.527.0",
4
+ "at": "2026-11-04T11:15:45.443Z",
5
+ "f": 4,
6
+ "prs": [
7
+ {
8
+ "n": 1237,
9
+ "t": "Chat refinements keep drafting the open report",
10
+ "s": "Chat refinements keep drafting the open report",
11
+ "k": "fix"
12
+ }
13
+ ]
14
+ },
2
15
  {
3
16
  "v": "0.526.0",
4
17
  "at": "2026-11-04T10:37:51.951Z",
@@ -58,6 +58,7 @@ Each cycle is a document: Draft or Issued, with its output, its copy state and w
58
58
  - PMOS writes to Google or to Confluence only when you press **Create the copy** or **Update the copy**.
59
59
  - A download happens only when you press a download item.
60
60
  - You issue a report. An issued report is frozen.
61
+ - When a report has an open draft, keep talking in the chat. Each change you ask for, such as a date, a wording or a look, goes to that draft by itself. You do not press anything. The draft updates beside the chat, and Claude says in one line what it passed. An issued report does not change. Change its template for the next cycle.
61
62
 
62
63
  ## Send a dashboard as a report
63
64
 
@@ -16,6 +16,7 @@ const crypto = require('crypto');
16
16
  const headless = require('./headless.js');
17
17
  const Goal = require('./goal-chat.js');
18
18
  const ReportChat = require('./report-chat.js');
19
+ const REPORT_KEYS = new Set(); // chats that carried the report rule: their later messages carry the short reminder (item 9, batch 27)
19
20
  const ProgramChat = require('./program-chat.js');
20
21
  const PastChat = require('./past-chat.js');
21
22
  const WorkflowChat = require('./workflow-chat.js');
@@ -200,7 +201,7 @@ function openingFor(row, text, extra = []) {
200
201
  }
201
202
  /** sessions: { alive(id) → bool, spawn(spec) → session row, type(id, text) → bool (Enter after, `/` left as typed) }.
202
203
  * iniInfo: () → lib/initiative-chat.js infoOf answer | null, read only when an init: chat needs it (its opening, a message about a stage). */
203
- function chat({ runtimeDir, key, text, model, context, sessions, wsId, iniInfo, now = new Date() }) {
204
+ function chat({ runtimeDir, key, text, model, context, sessions, wsId, iniInfo, reportInfo, now = new Date() }) {
204
205
  if (!runtimeDir) return fail(409, 'this workspace has no state folder to keep the conversation in');
205
206
  if (!KEY_RE.test(String(key || ''))) return fail(400, 'key must name a row');
206
207
  const q = String(text == null ? '' : text).trim();
@@ -210,6 +211,8 @@ function chat({ runtimeDir, key, text, model, context, sessions, wsId, iniInfo,
210
211
  const live = chatSessionOf(runtimeDir, key);
211
212
  const alive = !!(live && sessions.alive(live));
212
213
  let infoRead = false, infoVal = null;
214
+ let linked = null;
215
+ const reportOf = () => { try { return typeof reportInfo === 'function' ? reportInfo() : null; } catch { return null; } };
213
216
  const info = () => { if (!infoRead) { infoRead = true; try { infoVal = typeof iniInfo === 'function' ? iniInfo() : null; } catch { infoVal = null; } } return infoVal; };
214
217
  // slice 4 (PRD L2): an ended chat resumes its own Claude Code conversation when the file for it is still there
215
218
  const prev = alive ? null : chatRecordOf(runtimeDir, key);
@@ -227,7 +230,11 @@ function chat({ runtimeDir, key, text, model, context, sessions, wsId, iniInfo,
227
230
  // bug 2026-10-25: in a live initiative chat, a message about a stage, a workflow or a move carries the initiative's workflow and stage, read now
228
231
  else if (alive && /^init:/.test(key) && IniChat.isAsk(q)) msg = IniChat.wrap(q, info(), iniId(key));
229
232
  // premium-reports-from-chat R1: a message about a report, a chart or a dashboard carries the pmos-report rule (lib/report-chat.js)
230
- else if (ReportChat.isAsk(q)) msg = ReportChat.wrap(q);
233
+ // Wawan, 2026-11-04: a chat with a set-up report open beside it carries that report on every message, so feedback in any words reaches the draft (lib/report-chat.js)
234
+ else if (!q.startsWith('/') && (linked = reportOf())) { msg = ReportChat.wrap(q, linked); }
235
+ else if (ReportChat.isAsk(q)) { msg = ReportChat.wrap(q); REPORT_KEYS.add(runtimeDir + '|' + key); }
236
+ // a refinement later in a chat that carried the report rule (it need not say "report") is still about that report's draft
237
+ else if (REPORT_KEYS.has(runtimeDir + '|' + key) && !q.startsWith('/')) msg = ReportChat.remind(q);
231
238
  // bug 2026-10-24: a message about a program carries the program rule (lib/program-chat.js), so the chat names the real press
232
239
  else if (ProgramChat.isAsk(q)) msg = ProgramChat.wrap(q);
233
240
  const extra = goal ? { goal } : {};
@@ -26,8 +26,13 @@ const RULE = [
26
26
  ' When the PM says how it should look, or asks it to follow a file (a draft deck, a sample), add "design": "the look, in the PM\'s words" and, for a file, "sample": "<its path in the workspace>" (a .pdf, .pptx, .docx, .png or .jpg). Once the PM sets it up, the tab adds the sample and starts that look.',
27
27
  ' The report view asks a few questions first, with your picks filled in: the format, who reads it, when it goes out, the look and the sources. The PM changes any of them, then presses Draft it. Say it like that: "A few questions before I draft. My picks are filled in on the right. Change any, then press Draft it." Never say Create, Save or any other button.',
28
28
  ' The report then drafts in its own session, not in this chat. Never describe contents the block does not carry.',
29
+ ' Once the PM has pressed Draft it, the report is SET UP (its setup line is in report-setups.jsonl, found by name) and the proposal is closed. From then on follow the next rule, never "press Draft it".',
29
30
  '- A report that is set up already: {"kind":"report","id":"<its id>"} and, only when the PM asked for it, "schedule": {…} or "off", "sections": ["the new section titles, in order"] or "structure": "the change to its sections, in the PM\'s words", "design": "the change to its look, in the PM\'s words".',
30
- ' A change to the draft of a set-up report (a date, a slide, a wording): {"kind":"report","id":"<its id>","change":"the change, in the PM\'s words"}. The PM\'s press on the chip passes it to the report\'s own session, which changes the draft; nothing is issued. Do not change the draft yourself.',
31
+ ' A change to the draft of a set-up report (a date, a slide, a wording): {"kind":"report","id":"<its id>","change":"the change, in the PM\'s words"}. The page passes it to the report\'s own session by itself, with no press, and the draft updates beside this chat; nothing is issued. Do not change the draft yourself.',
32
+ ' Every refinement the PM gives about a set-up report (the tab shows "Draft · <date>"), in this chat or later in it, is such a change: end with the change block, with the whole change in it, and say in ONE line what you passed, e.g. "Passed to the Nov 9 draft: tender stays open with no award date; Feature implementation not started."',
33
+ ' Never say "press Draft it", "the proposal is unchanged" or "the report drafts in its own session" for a set-up report. If the draft needs something you do not know (a date), ask it once in the same answer and pass the change with what is known: the draft marks the unknown, it does not wait.',
34
+ ' A look change to the open draft (a palette, tables, a Gantt style) is such a change too. Put the paths of any file the PM attached (a sample PDF, a screenshot) in the change text, so the report\'s session reads them. Several changes in a row each pass; say the draft is updating, never "the draft stays as it is".',
35
+ ' An issued report (its latest cycle is issued) is frozen: pass nothing, and say the change goes into the template for the next cycle.',
31
36
  ' The set-up reports are the setup lines of report-setups.jsonl (planning/ or pmos/state/); a later change line wins, a remove line deletes one.',
32
37
  '- A chart of one measure: {"kind":"chart","proposal":{"measure":"<KR metric id>","title":"short title","question":"what it answers","form":"line|bars|number|meter","range":null,"target":null,"better":"higher|lower","note":""}}',
33
38
  ' measure is the id of a KR metric record (planning/records/kr-metrics.jsonl). range is how many of the latest readings to draw, or null for all. A meter needs a target.',
@@ -38,7 +43,29 @@ const RULE = [
38
43
  'If the message is not about a report, a chart or a dashboard, answer it and add no block.',
39
44
  ].join('\n');
40
45
 
41
- /** The message as PMOS types it into the session: the rule, then the PM's words. */
42
- function wrap(text) { return MARK + ' ' + RULE + '\n\n' + SAYS + String(text == null ? '' : text); }
46
+ const REMIND = 'This chat is about a report. If it is set up (report-setups.jsonl, by name) every refinement is a change to its open draft: end with {"kind":"report","id":"<its id>","change":"the whole change, in the PM\'s words"} in a pmos-report block, the page passes it by itself, and say in one line what you passed and that the draft is updating (put the paths of attached files in the change). Never say "the draft stays as it is" or "press Draft it" or "the proposal is unchanged" for a set-up report; ask anything unknown once and pass what is known. An issued report is frozen: pass nothing. Same rules as before.';
43
47
 
44
- module.exports = { MARK, isAsk, wrap, RULE };
48
+ /** A later message in a chat that already carried the rule (the PM refines without saying "report"): the short reminder, then the PM's words. */
49
+ function remind(text) { return MARK + ' ' + REMIND + '\n\n' + SAYS + String(text == null ? '' : text); }
50
+
51
+ /** The report this chat set up (Wawan, 2026-11-04: "I expect the drafting continue when the feedback continue from chat"). Once the PM pressed
52
+ * Draft it, the report is set up and its own session drafts; a later message in the same chat is about that draft whatever words it uses, so it
53
+ * carries this note with the report's id. Without it, "keep the tender open" went as typed, and Claude sent the PM back to Draft it. */
54
+ const ID_RE = /^[a-z0-9][a-z0-9-]{0,62}$/;
55
+ function linkedNote(rep) {
56
+ const id = String(rep.id), name = String(rep.name || id).replace(/\s+/g, ' ').slice(0, 80);
57
+ return [
58
+ `This chat set up the report "${name}" (report id ${id}). The PM pressed Draft it already: the report is set up and its own session drafts it.${rep.draft ? ' Its latest draft: ' + String(rep.draft).slice(0, 200) + '.' : ''}`,
59
+ 'Read every message here as feedback on that draft unless it is plainly about something else. Never tell the PM to press Draft it again, and never say the proposal is unchanged.',
60
+ `When the PM asks for a change to the draft in any words (a date, a status, a row, a slide, a wording, a point to keep open), answer in a few lines and end with {"kind":"report","id":"${id}","change":"the change, in the PM's words, complete"}. Put every point of the PM's message in "change"; it is all the report's session reads.`,
61
+ 'PMOS passes the block to the report\'s session as your answer lands, and the draft beside this chat changes. Say so in one line. When you need an answer from the PM first, ask it and add no block.',
62
+ ].join('\n');
63
+ }
64
+ /** The message as PMOS types it into the session: the rule, then the PM's words. With rep ({ id, name, draft? }), the note on the report this
65
+ * chat set up comes between them. */
66
+ function wrap(text, rep) {
67
+ const linked = rep && ID_RE.test(String(rep.id || '')) ? '\n' + linkedNote(rep) : '';
68
+ return MARK + ' ' + RULE + linked + '\n\n' + SAYS + String(text == null ? '' : text);
69
+ }
70
+
71
+ module.exports = { MARK, isAsk, wrap, remind, RULE, REMIND, linkedNote };
@@ -83,6 +83,23 @@ function iniInfoOf(ctx, key, wsId) {
83
83
  return null;
84
84
  }
85
85
 
86
+ /** The set-up report open beside this chat, for lib/report-chat.js wrap: { id, name, draft } | null. Wawan, 2026-11-04: feedback in the chat,
87
+ * in any words, is about the draft beside it. The page names the report (pm/premium-report-tab.js reportBeside); it counts only while it is set
88
+ * up, read in the chat's own workspace, then the others. */
89
+ function reportInfoOf(ctx, wsId, rid) {
90
+ const id = String(rid || ''); if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(id)) return null;
91
+ const RS = require('./report-setups.js');
92
+ const list = ctx.workspaceList ? ctx.workspaceList() : [];
93
+ const first = wsId && ctx.getWs ? ctx.getWs(wsId) : null;
94
+ for (const w of [first, ...list.filter((x) => !first || x.id !== first.id)].filter((x) => x && x.root)) {
95
+ let st; try { st = RS.stateOf(w.root); } catch { continue; }
96
+ const s = st.setups.get(id); if (!s) continue;
97
+ const last = (s.drafted || []).filter((d) => d && d.path).slice(-1)[0];
98
+ return { id, name: s.name || id, draft: last ? last.path : null };
99
+ }
100
+ return null;
101
+ }
102
+
86
103
  async function post(req, res, url, ctx) {
87
104
  const act = url.pathname.slice('/api/briefing-premium/'.length);
88
105
  const got = await ctx.readJson(req);
@@ -125,7 +142,7 @@ async function post(req, res, url, ctx) {
125
142
  if (!att.ok) { ctx.json(res, { ok: false, error: att.error }, att.status); return true; }
126
143
  // batch 20 item 13: the PM can start a grill any time by asking for it in an initiative's chat; it also runs by itself
127
144
  const g2 = grillAsk(ctx, b.key, b.text, wsId);
128
- const a = (ctx.chatSessions ? Ag.chat : Ag.ask)({ runtimeDir, key: b.key, text: Files.withFiles(g2 ? g2.text : b.text, att.paths), context: b.context, sessions: ctx.chatSessions, model: /^work:/.test(String(b.key || '')) ? WP.peekModel() : /^board:/.test(String(b.key || '')) ? BoardP.peekModel() : /^ins:/.test(String(b.key || '')) ? InsP.peekModel() : /^(init|doc):/.test(String(b.key || '')) ? InitP.peekModel() : sk || (/^src:/.test(String(b.key || '')) && !(b.key === 'src:this-page' && url.searchParams.get('ws'))) ? Prod.peekModel() : BP.peekModel(root), spawnSession: ctx.spawnSession, wsId, iniInfo: () => iniInfoOf(ctx, b.key, wsId) });
145
+ const a = (ctx.chatSessions ? Ag.chat : Ag.ask)({ runtimeDir, key: b.key, text: Files.withFiles(g2 ? g2.text : b.text, att.paths), context: b.context, sessions: ctx.chatSessions, reportInfo: () => reportInfoOf(ctx, wsId, b.report), model: /^work:/.test(String(b.key || '')) ? WP.peekModel() : /^board:/.test(String(b.key || '')) ? BoardP.peekModel() : /^ins:/.test(String(b.key || '')) ? InsP.peekModel() : /^(init|doc):/.test(String(b.key || '')) ? InitP.peekModel() : sk || (/^src:/.test(String(b.key || '')) && !(b.key === 'src:this-page' && url.searchParams.get('ws'))) ? Prod.peekModel() : BP.peekModel(root), spawnSession: ctx.spawnSession, wsId, iniInfo: () => iniInfoOf(ctx, b.key, wsId) });
129
146
  ctx.json(res, a.body, a.status);
130
147
  return true;
131
148
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.526.0",
3
+ "version": "0.527.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.526.0",
9
+ "version": "0.527.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@anthropic-ai/sdk": "^1.3.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.526.0",
3
+ "version": "0.527.0",
4
4
  "private": true,
5
5
  "description": "PMOS Terminal Workspace — local companion hosting real PTYs beside the library and gates (D62)",
6
6
  "license": "MIT",
@@ -36,9 +36,10 @@ import { adler } from './cozy-adler.js';
36
36
  import { snapOf, turnEnded } from './chat-turn-end.js'; // a finished turn re-reads what the Viewer shows (batch 25 item 3)
37
37
  import { nextPromptUi } from './next-prompt-ui.js'; // the suggested next prompt and the Sharper card above the box (batch 26 item 2)
38
38
  /* The views a chat opens go in the Viewer: on a page with no dialog the old viewer's views draw in the cozy card (premium-viewer.js). */
39
+ let REPORT_TAB = false; // a report tab was opened from a chat on this page: only then is premium-report-tab.js read at send
39
40
  const views = {
40
41
  ctx: () => import('./premium-vw-ctx.js'), file: () => import('./premium-vw-file.js'), attach: () => import('./premium-attach.js'),
41
- block: (b, o) => (b.kind === 'report' ? import('./premium-report-tab.js').then((M) => M.openReportTab(b, o)) : import('./premium-chart-tab.js').then((M) => M.openChartTab(b))),
42
+ block: (b, o) => (b.kind === 'report' ? (REPORT_TAB = true, import('./premium-report-tab.js').then((M) => M.openReportTab(b, o))) : import('./premium-chart-tab.js').then((M) => M.openChartTab(b))),
42
43
  };
43
44
 
44
45
  const SVG = (d, w = 16, sw = 2) => `<svg aria-hidden="true" width="${w}" height="${w}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round"><path d="${d}"/></svg>`;
@@ -477,7 +478,10 @@ export function mountChat(el, host) {
477
478
  C.pend.push(p); C.files = []; C.jump = true; draw();
478
479
  if (t.key) {
479
480
  const files = sentFiles.map((f) => f.saved);
480
- r = await host.pmPost('/api/briefing-premium/ask' + (t.ws ? '?ws=' + encodeURIComponent(t.ws) : ''), files.length ? { key: t.key, text, files, ...(t.context ? { context: t.context } : {}) } : { key: t.key, text, ...(t.context ? { context: t.context } : {}) }).catch(() => null);
481
+ // the set-up report beside this chat goes with the message, so feedback in any words reaches its draft (Wawan, 2026-11-04)
482
+ const report = REPORT_TAB ? await import('./premium-report-tab.js').then((M) => M.reportBeside(t.key)).catch(() => null) : null;
483
+ const extra = { ...(t.context ? { context: t.context } : {}), ...(report ? { report } : {}) };
484
+ r = await host.pmPost('/api/briefing-premium/ask' + (t.ws ? '?ws=' + encodeURIComponent(t.ws) : ''), files.length ? { key: t.key, text, files, ...extra } : { key: t.key, text, ...extra }).catch(() => null);
481
485
  } else r = await host.pmPost('/api/session/say', { id: t.sid, text }).catch(() => null);
482
486
  C.sending = false;
483
487
  if (!r || r.ok !== true) {
@@ -76,6 +76,10 @@ export function openReportTab(block, o = {}) {
76
76
  vzStyle(); rpStyle();
77
77
  const id = 'report:' + (block.id || block.proposal.name).toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 60);
78
78
  let t = T.get(id);
79
+ // Wawan, 2026-11-04 ("I expect the drafting continue when the feedback continue from chat"): a change Claude writes in the report's own chat, or
80
+ // in the chat the report is open beside, goes to the report's session as the answer lands. The PM's message is the press. An answer that was
81
+ // there when the chat opened opens nothing (home-chat.js autoOpen), so a reload never passes a change twice.
82
+ const linked = RF.passesBySelf(o, block.id, t ? t.chat : null);
79
83
  // a proposal that is set up already opens its report, never the questions again (Wawan, 2026-11-01, batch 22 item 13: "it goes back here again")
80
84
  if (t && block.proposal && t.block.id && !t.gone) return current(t);
81
85
  if (!t && block.proposal) { setTimeout(() => adoptSetUp(id, block.proposal.name), 0); }
@@ -85,12 +89,19 @@ export function openReportTab(block, o = {}) {
85
89
  T.set(id, t);
86
90
  if (block.id && t.panel === 'manage') load(t);
87
91
  } else if (o.chat) t.chat = o.chat;
88
- // a change passes on the PM's press of the chip only; an answer that opens the view by itself shows the report as it is
89
- if (block.id && block.change && !t.passed && !o.auto && !PASSED.has(JSON.stringify(block))) { PASSED.add(JSON.stringify(block)); t.passed = true; passChange(t); return show(t); }
92
+ // a change passes on the PM's press of the chip, or by itself in a chat linked to this report (above); any other answer that opens the view by
93
+ // itself shows the report as it is
94
+ if (block.id && block.change && !t.passed && (!o.auto || linked) && !PASSED.has(JSON.stringify(block))) { PASSED.add(JSON.stringify(block)); t.passed = true; passChange(t, !!o.auto); return show(t); }
90
95
  if (block.id && t.panel === 'current') return current(t);
91
96
  if (block.id && t.panel === 'template') { const v = show(t); loadFlow(t).then(() => { redraw(t); if (!o.auto) makeLook(t); }); return v; }
92
97
  return show(t);
93
98
  }
99
+ /** The set-up report shown beside the chat `chatKey`, by id, or null (Wawan, 2026-11-04). The chat sends it with each message, so feedback in any
100
+ * words reaches that report's draft (lib/report-chat.js wrap). Only a tab opened from that chat counts, and only while it is the view on screen. */
101
+ export function reportBeside(chatKey) {
102
+ const c = vCur(); const t = c && T.get(c.id);
103
+ return t && chatKey && t.chat === chatKey && t.block && t.block.id && !t.block.proposal && !t.gone ? t.block.id : null;
104
+ }
94
105
  /** A chat change to a set-up report's sections or look opens its template (canvas TplA), where it waits as one ✦. */
95
106
  const tplAsk = (b) => !!(b && b.id && !b.change && ((Array.isArray(b.sections) && b.sections.length) || (typeof b.design === 'string' && b.design.trim())));
96
107
  /** The look the PM asked for in words is made first (a design job, as Design's turn), once per chat change, on the PM's press of the chip; when
@@ -147,9 +158,10 @@ async function waitFor(t) {
147
158
  }
148
159
  /** A change Claude passed from the chat: to the report's own session, on the latest draft by its file, then the report opens (RfH2).
149
160
  * An issued latest cycle is frozen: the change is not passed, and the PM is told to change the template instead. */
150
- async function passChange(t) {
161
+ async function passChange(t, auto) {
151
162
  const F = await loadFlow(t);
152
163
  const to = F && F.ok ? RF.changeTargetOf(F) : {};
164
+ if (auto && !to.path && !to.frozen) { PASSED.delete(JSON.stringify(t.block)); t.passed = false; return current(t); } // no draft yet: the chip's press starts the first one
153
165
  let said;
154
166
  if (to.frozen) said = `The ${to.frozen} report is issued, so it is frozen. The change was not passed on: change the template for the next cycle.`;
155
167
  else {
@@ -38,10 +38,16 @@ export function chatOf(st = {}, id = '') {
38
38
  placeholder: 'Change the report',
39
39
  context: { title: name, subject: 'A report set up in PMOS (report id ' + id + ')' + (settings ? ': ' + settings : ''),
40
40
  say: 'When the PM asks to change the draft, answer in a few lines and end with ONE fenced code block with the language tag pmos-report holding {"kind":"report","id":"' + id
41
- + '","change":"the change, in the PM\'s words"}. The PM presses its chip to pass it to the report\'s own session. Do not change the draft yourself. The format, readers, schedule, look and sources change with the template icon in the report\'s header.',
41
+ + '","change":"the change, in the PM\'s words, complete"}. PMOS passes it to the report\'s own session as your answer lands, and the draft beside the chat changes; say so in one line. Do not change the draft yourself. The format, readers, schedule, look and sources change with the template icon in the report\'s header.',
42
42
  facts: [['Report id', id]].concat(draft ? [['Latest draft', draft]] : []), files: draft ? [draft] : [] } };
43
43
  }
44
44
 
45
+ /** Whether a change Claude wrote passes to the report's session as the answer lands, without the chip (Wawan, 2026-11-04): only for an answer
46
+ * that opened by itself (o.auto), in the report's own chat or in the chat its tab was opened from (tabChat). Pure. */
47
+ export function passesBySelf(o = {}, id = '', tabChat = null) {
48
+ return !!(o.auto && o.chat && id && (o.chat === chatOf({}, id).key || (tabChat && tabChat === o.chat)));
49
+ }
50
+
45
51
  export const FORMAT_NAMES = { 'google-slides': ['Slides', 'Read aloud or presented.'], 'google-doc': ['Document', 'Read alone, with tables and footnotes.'],
46
52
  pdf: ['PDF', 'A fixed page to read or print.'], 'google-sheets': ['Sheets', 'The numbers as a table.'], confluence: ['Confluence page', 'A page in your wiki.'] };
47
53
  export const CADENCES = [['weekly', 'Every week'], ['bi-weekly', 'Every two weeks'], ['monthly', 'Every month'], ['daily', 'Every day']];