create-pmos 0.204.0 → 0.206.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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +2 -2
  3. package/payload/planning/evals/eval-calibration.md +1 -1
  4. package/payload/planning/okrs/health-budgets.md +1 -1
  5. package/payload/state/discoveries.md +1 -1
  6. package/payload/state/log.md +1 -1
  7. package/workspace/c3-capture-test.js +206 -0
  8. package/workspace/c3-ib-more-test.js +7 -1
  9. package/workspace/extension/README.md +22 -7
  10. package/workspace/extension/capture.js +114 -0
  11. package/workspace/extension/manifest.json +49 -8
  12. package/workspace/extension/popup.css +28 -0
  13. package/workspace/extension/popup.html +15 -0
  14. package/workspace/extension/popup.js +195 -0
  15. package/workspace/guide/jira.md +1 -0
  16. package/workspace/guide/report-design.md +15 -1
  17. package/workspace/lib/engine/boards.js +45 -2
  18. package/workspace/lib/extension-cli.js +6 -4
  19. package/workspace/lib/jira-cli.js +26 -3
  20. package/workspace/lib/jira-status.js +136 -6
  21. package/workspace/lib/report-design.js +62 -4
  22. package/workspace/lib/routes-boards.js +7 -3
  23. package/workspace/lib/routes-report-design.js +33 -0
  24. package/workspace/package-lock.json +2 -2
  25. package/workspace/package.json +1 -1
  26. package/workspace/public/pm/c3-boot.js +2 -1
  27. package/workspace/public/pm/c3-capture.css +23 -0
  28. package/workspace/public/pm/c3-capture.js +152 -0
  29. package/workspace/public/pm/c3-demo-boards.js +11 -1
  30. package/workspace/public/pm/c3-design-studio.js +17 -5
  31. package/workspace/public/pm/c3-ib-sprints.js +60 -13
  32. package/workspace/public/pm/c3-initiatives.css +5 -0
  33. package/workspace/public/pm/c3-shell.js +2 -0
  34. package/workspace/skills/jira-sync.skill +11 -5
  35. package/workspace/skills/report-design.skill +1 -0
  36. package/workspace/test-lists/report-design-studio-ui.json +3 -3
@@ -1,18 +1,43 @@
1
- /* pm/c3-ib-sprints.js — the Sprints tab of a board with Build and deliver on (mockup v5, 34-initiatives.js tabSprints).
1
+ /* pm/c3-ib-sprints.js — the Sprints tab of a board with Build and deliver on (mockup v5, 34-initiatives.js tabSprints; the sprint goal
2
+ * and team summary from mockup-c3-sprint-goal v3, initiative sprint-goal-summary).
2
3
  * registerBoardTab('sprints', (host, ctx) => ...)
3
- * Reads GET /api/boards/sprints { source: jira|none, readAt, sprints: [{ name, state: active|future|closed, issues: [{ key, summary, status,
4
- * category: todo|doing|done, initiative }], counts }] }. READ-ONLY: the sprints mirror Jira, so there is no create, edit or move control.
5
- * Left: the active sprint's items, its header chip saying Jira, read-only and the read time (c3-board-declutter: no banner), as rows (a linked initiative opens its page), then the
6
- * future and closed sprints in place. Right: the active sprint's bars, and its burndown from GET /api/stage-tools/burndown (built by
7
- * the stage-records change; when that route is not on this server yet, the card says so quietly). source 'none' says there is no tracker. */
4
+ * Reads GET /api/boards/sprints { source: jira|none, readAt, sprints: [{ name, state: active|future|closed, start, end, issues: [{ key, summary,
5
+ * status, category: todo|doing|done, initiative }], counts, goal: { from: jira|none|broad|partial, text, jira, unchecked? } | null, summary,
6
+ * themes: [{ title, line, tickets, done, blocked, people, initiative }], writtenAt, closed: { done, total, carried, carriedTo, points } | null,
7
+ * frozen }] }. READ-ONLY: the sprints mirror Jira, so there is no create, edit or move control.
8
+ * Left: the active sprint's goal and summary (the goal from Jira, or written from the tickets and saying why; what the team is working on,
9
+ * one row per theme), its items (header chip: Jira, read-only and the read time; c3-board-declutter: no banner), then the next sprint
10
+ * with its goal and summary, and each closed sprint with its goal, summary, done and carried counts and the read-once note. Right: the
11
+ * active sprint's card, its burndown from GET /api/stage-tools/burndown (when that route is not on this server yet, the card says so
12
+ * quietly), and how sprints are read. source 'none' says there is no tracker. */
8
13
  import { el } from './dom.js';
9
14
  import { registerBoardTab } from './c3-initiatives.js';
10
- import { icon, lk, chip, tag, st, note, sect, card, list, empty, nrow, cols, hbs, hbr, jiraLabel, day, put } from './c3-ini-ui.js';
15
+ import { icon, lk, chip, tag, st, note, lbl, sect, card, list, empty, nrow, cols, kv, jiraLabel, day, put, plural } from './c3-ini-ui.js';
11
16
  import { loadItems } from './c3-ini-work.js';
12
17
  import { recordCards } from './c3-ini-records.js';
13
18
 
14
19
  const CAT = { todo: 'To do', doing: 'In progress', done: 'Done' };
15
20
  const readTime = (s) => { const m = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})/.exec(String(s || '')); return m ? day(m[1]) + ', ' + m[2] : ''; };
21
+ const WHY = { none: 'Jira has no goal for this sprint.', broad: 'The goal in Jira is too broad to tell what this sprint delivers.', partial: 'The goal in Jira leaves out part of the sprint.' };
22
+ const timeOf = (s) => { const m = /T(\d{2}:\d{2})/.exec(String(s || '')); return m ? m[1] : ''; };
23
+ /* Quiet pages (c3-quiet.css) hide .ini-note, .ini-lhm .dim and a row's third chip. The goal's reason, the theme chips and the
24
+ * read-once note are the content of this tab (mockup v3), so they use their own classes. */
25
+ /* The goal: Jira's when it says what the sprint delivers; else written from the tickets, with why and Jira's own text underneath. */
26
+ function goalBlock(g) {
27
+ if (!g) return el('div', { class: 'lh', 'data-inb': 'sprint-goal', 'data-inb-goal': 'missing' }, el('span', { class: 'ki' }, icon('target')),
28
+ el('div', { class: 'ini-sp-goal' }, lbl('SPRINT GOAL'), el('div', { class: 'dim ini-sp-note' }, 'No goal yet. The Jira status check writes one from the tickets at its next check.')));
29
+ const derived = g.from !== 'jira';
30
+ return el('div', { class: 'lh', 'data-inb': 'sprint-goal', 'data-inb-goal': g.from }, el('span', { class: 'ki' }, icon('target')),
31
+ el('div', { class: 'ini-sp-goal' }, lbl(derived ? 'SPRINT GOAL · written from the tickets' : 'SPRINT GOAL · from Jira'), el('b', null, g.text),
32
+ derived ? el('div', { class: 'dim ini-sp-note', 'data-inb': 'sprint-goal-why' }, (WHY[g.from] || '') + (g.jira ? ' In Jira: “' + g.jira + '”' : '')) : null));
33
+ }
34
+ const themeRow = (t, ctx) => nrow({ ic: t.blocked ? 'alert' : t.tickets && t.done === t.tickets ? 'check' : t.initiative ? 'bolt' : 'task', hot: !t.blocked && t.done < t.tickets && !!t.initiative,
35
+ title: t.title, attrs: { 'data-inb-theme': t.initiative || 'none' },
36
+ after: el('div', null, t.line ? el('div', { class: 'dim ini-sp-note' }, t.line) : null, el('div', { class: 'chips ini-sp-chips' }, ...[chip(plural(t.tickets, 'ticket')), t.done ? st('ok', 'check', t.done + ' done') : null, t.blocked ? st('warn', 'alert', t.blocked + ' blocked') : null,
37
+ t.people ? chip(icon('user', 'sm'), t.people) : null,
38
+ t.initiative ? lk({ onclick: () => ctx.openInitiative(t.initiative), 'data-inb-theme-ini': t.initiative }, icon('init', 'sm'), t.initiative) : el('span', { class: 'chip2 dim' }, 'No initiative')].filter(Boolean))) });
39
+ const summaryBody = (sp, ctx) => [goalBlock(sp.goal), sp.summary ? el('p', { class: 'ini-sp-sum' }, sp.summary) : null,
40
+ sp.themes && sp.themes.length ? [lbl('WHAT THE TEAM IS WORKING ON'), list(sp.themes.map((t) => themeRow(t, ctx)), { 'data-inb': 'sprint-themes' })] : null];
16
41
  const countsOf = (sp) => { const c = sp.counts || {}; const f = (k) => (c[k] != null ? Number(c[k]) : (sp.issues || []).filter((i) => i.category === k).length); return { todo: f('todo'), doing: f('doing'), done: f('done') }; };
17
42
 
18
43
  registerBoardTab('sprints', async (host, ctx) => {
@@ -31,21 +56,43 @@ registerBoardTab('sprints', async (host, ctx) => {
31
56
  i.initiative ? lk({ onclick: () => ctx.openInitiative(i.initiative), 'data-inb-issue-ini': i.initiative }, icon('init', 'sm'), i.initiative) : el('span', { class: 'dim' }, 'No initiative')],
32
57
  right: [el('span', { class: 'chip2', title: 'Status lives in Jira' }, icon('lock', 'sm'))], attrs: { 'data-inb-issue': i.key } });
33
58
  const t = readTime(r.readAt);
59
+ const dates = (s) => (s.start && s.end ? day(s.start) + ' to ' + day(s.end) : '');
60
+ const itemsRow = (s) => { const c = countsOf(s); return nrow({ ic: 'clock', title: c.done + ' done, ' + c.doing + ' in progress, ' + c.todo + ' to do', xp: (s.issues || []).length ? [list(s.issues.map(issueRow))] : [note('No items.')] }); };
61
+ const closedSect = (s) => {
62
+ const c = countsOf(s); const x = s.closed || {}; const n = x.total || (s.issues || []).length; const done = x.total ? x.done : c.done;
63
+ return sect({ ic: 'check', title: s.name, count: n, right: tag('closed'), attrs: { 'data-inb-sprint': s.name, 'data-inb-state': 'closed' } },
64
+ ...summaryBody({ ...s, themes: [] }, ctx),
65
+ el('div', { class: 'chips ini-sp-facts' }, dates(s) ? chip(icon('cal', 'sm'), dates(s)) : null, st('ok', 'check', done + ' of ' + n + ' done'),
66
+ x.points ? chip(x.points + ' points') : null, x.carried ? chip(icon('next', 'sm'), x.carried + ' carried' + (x.carriedTo ? ' to ' + x.carriedTo : '')) : null),
67
+ el('div', { class: 'dim ini-sp-note', 'data-inb': 'sprint-read-once' }, icon('lock', 'sm'), s.frozen ? ' Closed. Read and summarised once on ' + day(String(s.writtenAt || '').slice(0, 10)) + '. PMOS does not read a closed sprint again.'
68
+ : ' Closed. The Jira status check summarises it once at its next check, then does not read it again.'),
69
+ itemsRow(s));
70
+ };
71
+ const n = active ? (active.issues || []).length : 0;
34
72
  const left = [
35
- active ? sect({ ic: 'check', title: active.name + ' items', count: (active.issues || []).length, right: el('span', { class: 'chip2', 'data-inb': 'sprints-source', title: 'Mirrored from Jira ' + jiraLabel(b) + '. Change sprints in Jira.' }, icon('lock', 'sm'), 'Jira, read-only' + (t ? ' · read ' + t : '')), attrs: { 'data-inb-sprint': active.name, 'data-inb-state': 'active' } },
36
- (active.issues || []).length ? list(active.issues.map(issueRow)) : empty('No items in this sprint yet.', { 'data-inb': 'sprint-no-issues' }))
73
+ active ? sect({ ic: 'clock', title: active.name + ' · goal and summary', right: st('ok', null, 'active'), attrs: { 'data-inb': 'sprint-summary' } },
74
+ ...summaryBody(active, ctx),
75
+ el('div', { class: 'dim ini-sp-note', 'data-inb': 'sprint-written' }, active.summary ? 'Goal and summary written by the Jira status check' + (timeOf(active.writtenAt) ? ' at ' + timeOf(active.writtenAt) : '') + ' from all ' + plural(active.summarisedTickets != null ? active.summarisedTickets : n, 'ticket') + ' in the sprint. Read and summarised again at each check while the sprint is active.'
76
+ : 'No summary yet. The Jira status check writes one from the sprint\'s tickets at its next check.')) : null,
77
+ active ? sect({ ic: 'check', title: active.name + ' items', count: n, right: el('span', { class: 'chip2', 'data-inb': 'sprints-source', title: 'Mirrored from Jira ' + jiraLabel(b) + '. Every issue in the sprint, whoever it is assigned to. Change sprints in Jira.' }, icon('lock', 'sm'), 'Jira, read-only' + (t ? ' · read ' + t : '')), attrs: { 'data-inb-sprint': active.name, 'data-inb-state': 'active' } },
78
+ n ? list(active.issues.map(issueRow)) : empty('No items in this sprint yet.', { 'data-inb': 'sprint-no-issues' }))
37
79
  : empty(sprints.length ? 'No sprint is active.' : 'Jira has no sprints for this board.', { 'data-inb': sprints.length ? 'sprints-no-active' : 'sprints-empty' }),
38
- ...others.map((s) => { const c = countsOf(s); return sect({ ic: s.state === 'closed' ? 'check' : 'clock', title: s.name, count: (s.issues || []).length, right: tag(s.state || 'sprint'), attrs: { 'data-inb-sprint': s.name, 'data-inb-state': s.state } },
39
- nrow({ ic: 'clock', title: c.done + ' done, ' + c.doing + ' in progress, ' + c.todo + ' to do', xp: (s.issues || []).length ? [list(s.issues.map(issueRow))] : [note('No items.')] })); }),
80
+ ...others.filter((s) => s.state !== 'closed').map((s) => sect({ ic: 'cal', title: s.name, count: (s.issues || []).length, right: tag(s.state || 'sprint'), attrs: { 'data-inb-sprint': s.name, 'data-inb-state': s.state } },
81
+ goalBlock(s.goal), s.summary ? el('p', { class: 'ini-sp-sum' }, s.summary) : null, itemsRow(s))),
82
+ ...others.filter((s) => s.state === 'closed').map(closedSect),
40
83
  ];
41
84
  const right = [];
42
85
  if (active) {
43
- const c = countsOf(active); const tot = Math.max(1, c.todo + c.doing + c.done);
44
- right.push(card({ ic: 'clock', title: active.name, attrs: { 'data-inb': 'sprint-counts' } }, hbs([hbr('Done', c.done, c.done / tot, 'ok'), hbr('In progress', c.doing, c.doing / tot, 'acc'), hbr('To do', c.todo, c.todo / tot, 'pm')])));
86
+ const c = countsOf(active);
87
+ right.push(card({ ic: 'clock', title: active.name, attrs: { 'data-inb': 'sprint-counts' } }, kv([dates(active) ? ['Dates', dates(active)] : null,
88
+ ['Items', n + ': ' + c.done + ' done, ' + c.doing + ' in progress, ' + c.todo + ' to do']])));
45
89
  const ids = items.ok ? items.initiatives.map((i) => i.id) : [];
46
90
  const pseudo = { ...ctx, row: { id: ids.join(','), title: '' } };
47
91
  right.push(...await recordCards(pseudo, ['burndown']));
48
92
  }
93
+ if (sprints.length) right.push(card({ ic: 'redo', title: 'How sprints are read', attrs: { 'data-inb': 'sprints-how' } }, kv([
94
+ ['Goal', 'From Jira when it says what the sprint delivers, otherwise written from the tickets'], ['Active sprint', 'Read and summarised at each Jira status check'],
95
+ ['Next sprint', 'Read at each check, until it starts'], ['Closed sprints', 'Read and summarised once, then kept'], ['Jira', 'Read-only. PMOS changes nothing there']])));
49
96
  if (dead) return () => {};
50
97
  put(host, cols(left, right));
51
98
  return () => { dead = true; };
@@ -55,6 +55,11 @@
55
55
  .c3 .ini-kv{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:6px 14px;margin:0;font-size:var(--fs-3)}
56
56
  .c3 .ini-kv dt{color:var(--muted-foreground);font:var(--fs-2) var(--mono)}
57
57
  .c3 .ini-kv dd{margin:0;min-width:0;overflow-wrap:anywhere}
58
+ .c3 .ini-sp-sum{margin:var(--sp-3) 0;line-height:1.5} /* a sprint's summary (sprint-goal-summary) */
59
+ .c3 .ini-sp-facts{margin:0 0 var(--sp-2)}
60
+ .c3 .ini-sp-goal{min-width:0}
61
+ .c3 .ini-sp-note{font-size:var(--fs-2);line-height:1.45;margin-top:var(--sp-1)}
62
+ .c3 .ini-sp-chips{margin-top:var(--sp-1)}
58
63
  .c3 .ini-num{width:84px;flex:none}
59
64
  .c3 .ini-up,.c3 .ini-down{display:inline-flex}
60
65
  .c3 .ini-up{transform:rotate(-90deg)}
@@ -624,6 +624,8 @@ async function boot() {
624
624
  const saved = DEMO ? null : await pmGet('/api/settings' + (S.ws ? '?ws=' + encodeURIComponent(S.ws) : '')).catch(() => null); // demo mode's workspace is not registered
625
625
  if (saved && saved.ok) { applyLook(root, saved.settings); if (!start) { const s = (saved.settings || []).find((x) => x.key === 'look.start'); if (s && BUILD[s.value]) start = s.value; } }
626
626
  if (q.get('focus') || q.get('fdate')) import('./c3-focus.js').then((m) => m.openFocus({ event: { id: q.get('focus') || '', ...(q.get('fat') ? { at: q.get('fat'), title: q.get('ftitle') || '' } : {}) }, date: q.get('fdate') || '', pop: q.get('pop') === '1', meet: q.get('meet') || '', mtitle: q.get('mtitle') || '' })).catch(() => { /* the focus page is not available */ }); // a reload keeps the Meeting focus page open; ?pop=1 is its compact form for the Chrome extension (focus=now asks which meeting the Meet call is)
627
+ /* ?studio=<report id> opens that report's design studio over the Reports tab (the PMOS Chrome extension's Open the studio). */
628
+ if (q.get('studio') && /^[a-z][a-z0-9-]{0,39}$/.test(q.get('studio'))) { if (!start) start = 'rep'; import('./c3-design-studio.js').then((m) => m.openStudio({ ws: S.ws, id: q.get('studio'), name: '' })).catch(() => { /* the studio is not available */ }); }
627
629
  if (!S.userNav) showTab(start || 'brief', false); // a press during the read above (the gear, a tab) wins over the start page
628
630
  new ResizeObserver(moveInd).observe(S.tabNav);
629
631
  workCount(); briefCount(); askCount(); S.dock.start();
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: jira-sync
3
3
  label: "Jira status check"
4
- description: "Read, read-only, the status, sprint and subtasks of the Jira tickets each initiative pushed, or of its Jira epics and the issues under them, and every issue in the sprints those tickets are in, and save them in PMOS for the initiative's Jira tickets tab and the board's Sprints tab. It writes nothing to Jira."
4
+ description: "Read, read-only, the status, sprint and subtasks of the Jira tickets each initiative pushed, or of its Jira epics and the issues under them, and every issue in the sprints those tickets are in, write each sprint's goal and a summary of the team's work, and save them in PMOS for the initiative's Jira tickets tab and the board's Sprints tab. It writes nothing to Jira."
5
5
  run: agent
6
6
  agent: claude
7
7
  output:
@@ -14,7 +14,7 @@ output:
14
14
 
15
15
  Rules that always hold:
16
16
  1. READ-ONLY on Jira. Use only the Jira search tool. Never create, edit, transition, assign, comment on, link, close or delete an issue. Never write to Confluence or to any other source.
17
- 2. THE ONLY WRITE is `pmos jira sync`, and `pmos people record` once at the end (step 5). Never edit a repository file. Never run `pmos item`. Never write a ledger by hand. Never commit; the PM's Sync carries the files.
17
+ 2. THE ONLY WRITE is `pmos jira sync`, `pmos jira summary` (step 4b), and `pmos people record` once at the end (step 5). Never edit a repository file. Never run `pmos item`. Never write a ledger by hand. Never commit; the PM's Sync carries the files.
18
18
  3. LIGHT ON CONTENT. Ask Jira only for these fields: summary, status, the issue type, the parent, the sprint field, subtasks, the Flagged field, the story points field, the resolution date, and the display names of the assignee and the reporter. Never ask for or copy a description, a comment, an attachment, an email address, any other detail of a person, or a token.
19
19
  4. TEMPORARY FILES OUTSIDE THE REPOSITORY. Save each search result in this session's scratch folder, never in the repository, and delete it after the sync.
20
20
  5. NOTHING INVENTED. Give `pmos jira sync` exactly what Jira returned. If Jira cannot be reached, list the initiative under "Not reached" and write nothing for it.
@@ -27,7 +27,7 @@ Procedure:
27
27
  - `type`: the issue type's name, for example `Epic`, `Story`, `Task` or `Bug`.
28
28
  - `parent`: the parent issue's key (the epic of a story), or `null`.
29
29
  - `category`: the status category, `new`, `indeterminate` or `done`.
30
- - `sprint`: `{"id": "42", "name": "Sprint 42", "state": "active", "start": "2026-10-01", "end": "2026-10-14"}` for the active sprint, `"future"` for a planned one, or `null` when the issue is in the backlog. Copy `id`, `start`, `end` and `completed` (a closed sprint's completion date) from Jira's sprint field; leave out any that Jira does not give.
30
+ - `sprint`: `{"id": "42", "name": "Sprint 42", "state": "active", "start": "2026-10-01", "end": "2026-10-14"}` for the active sprint, `"future"` for a planned one, or `null` when the issue is in the backlog. Copy `id`, `start`, `end`, `completed` (a closed sprint's completion date) and `goal` (the sprint goal, as Jira writes it) from Jira's sprint field; leave out any that Jira does not give.
31
31
  - `points`: the story points as a number, or `null` when none are set.
32
32
  - `resolved`: the resolution date as `YYYY-MM-DD`, or `null` when the issue is not resolved.
33
33
  - `subtasks`: `{"done": 2, "total": 5}`.
@@ -35,10 +35,16 @@ Procedure:
35
35
  - `url`: the issue's browse link, `https://<site>.atlassian.net/browse/<KEY>`.
36
36
  The shape Jira returned (`{"issues": [{"key", "self", "fields": {...}}]}`) is also accepted.
37
37
  4. Run `pmos jira sync <initiative> --from <the file> --query "<the JQL>"`. Then delete the file.
38
- 4a. Read the whole sprints, after every initiative is synced. Run `pmos jira query --sprints`. It prints one Jira search for every issue in the sprints the tickets above are in, whoever the issue is assigned to. Do this even when the task names one initiative. If it prints "no Jira reading has a sprint yet", skip this step. Otherwise run that search with the same fields as step 2, fetch every page, save the result in the scratch folder in the same shape as step 3 (for example `<scratch>/jira-sprints.json`), run `pmos jira sync --sprints --from <the file> --query "<the JQL>"`, and delete the file. Do not record people from this search (step 5 is for the initiatives' own issues).
38
+ 4a. Read the whole sprints, after every initiative is synced. Run `pmos jira query --sprints`. It prints one Jira search for every issue in the sprints the tickets above are in, whoever the issue is assigned to. Do this even when the task names one initiative. If it prints "no Jira reading has a sprint yet" or "every sprint is closed and summarised once", skip to step 4b. Otherwise run that search with the same fields as step 2, fetch every page, save the result in the scratch folder in the same shape as step 3 (for example `<scratch>/jira-sprints.json`), run `pmos jira sync --sprints --from <the file> --query "<the JQL>"`, and delete the file. Do not record people from this search (step 5 is for the initiatives' own issues).
39
+ 4b. Write each sprint's goal and summary, after step 4a. Run `pmos jira summary`. Each line is a sprint that needs one: its name, its state, its number of issues and Jira's goal. A closed sprint that is already summarised is listed as kept: do not write it again. If it prints "no sprint needs a summary", skip this step. For each listed sprint, read its issues in the sprint search result of step 4a and write:
40
+ - `goal`: `{"from": "jira", "text": "<Jira's goal>"}` when Jira's goal says what the sprint delivers. Otherwise write the goal from the sprint's tickets in one sentence (at most 240 characters), and say why: `"from": "none"` when Jira has no goal, `"broad"` when Jira's goal is too broad to tell what the sprint delivers (for example "Q4 delivery"), `"partial"` when it leaves out a large part of the sprint's tickets. With `broad` or `partial`, put Jira's own goal in `"jira"`.
41
+ - `summary`: two to four plain sentences on what the team is working on: what is done, what is moving, what is blocked. For a closed sprint, say if the goal was met and what moved to the next sprint.
42
+ - `themes`: one entry per group of related tickets, at most 8, largest first: `{"title", "line", "tickets", "done", "blocked", "people", "initiative"}`. Group by initiative when the tickets belong to one (the initiative id from step 1); put tickets with no initiative in one theme such as "Fixes and upkeep" with `"initiative": null`. `line` is one sentence; `people` is the assignees' first names, comma-separated.
43
+ - For a closed sprint, also `closed`: `{"done", "total", "carried", "carriedTo", "points"}` (`points` as "31 of 38" when the tickets have points).
44
+ Use only what the tickets say. Never invent work, a date or a person. Save `{"sprints": [{"name", "id", "state", "goal", "summary", "themes", "closed"}]}` in the scratch folder, run `pmos jira summary --from <the file>`, and delete the file.
39
45
  5. Record the people in People. For each assignee and reporter other than the PM, write one sighting per issue (People adds each new person at once): {"source": "jira", "handle": {"kind": "jira", "value": "<their Jira display name>"}, "name": "<display name>", "interaction": {"date": "<the issue's last update, YYYY-MM-DD>", "kind": "Jira", "text": "<Assignee|Reporter> of <KEY>: <summary, at most 150 characters>", "pointer": "jira · <KEY> · <url>", "initiative": "<the initiative id>"}}. Put all of them in one file {"sightings": [...]} in the scratch folder, run `pmos people record --file <that file>` once, and delete the file. An issue with no assignee gives no assignee sighting.
40
46
  6. Finish with the three sections of the output contract, one short line each:
41
- - Checked: the initiative, the number of tickets read, and each change (status, sprint, blocked). If nothing changed, say "no change". Add how many people you recorded, and how many issues the sprint read saved per sprint.
47
+ - Checked: the initiative, the number of tickets read, and each change (status, sprint, blocked). If nothing changed, say "no change". Add how many people you recorded, how many issues the sprint read saved per sprint, and which sprints got a goal and summary (and whose goal you wrote from the tickets).
42
48
  - Not reached: each initiative whose search failed, with the reason, and the sprint read if it failed.
43
49
  - Needs you: tickets newly blocked, tickets the search did not return, and any refusal.
44
50
 
@@ -38,6 +38,7 @@ The modes:
38
38
 
39
39
  How to read a sample:
40
40
  - A PDF or an image: read the file with Read and look at every page. Follow its look closely: the grid and the place of each part, the colours, the header and footer bands, the type sizes, how dense each slide or page is, and the slide kinds and their order. Say in "took" what you followed.
41
+ - Pictures of a web page (a sample the PM added from Chrome): the lines name every picture file, in order, top to bottom. Read every one with Read, as you would the pages of a PDF, and follow the look closely in the same way. They are one long page cut into screens, so do not read them as separate slides. Say in "took" what you followed.
41
42
  - A .pptx or a .docx: read its words in the .txt file named for it. You cannot see its look, only its words and order.
42
43
  - A report document: read the .md file named for it.
43
44
  - A link: when the lines name a .txt copy for it, read that file. It holds the words and the structure of the linked file (slide titles, order, sections, tables), copied through the PM's connectors. It does not show the look, so take the look from a PDF or an image sample, and say so in "took". When the lines name no copy, you have no web tool: say so with "readable": false.
@@ -1,5 +1,5 @@
1
1
  {
2
- "_comment": "report-design-studio UI slice: the design studio, the deck of a cycle in the Read tab, Present, speaker notes and notes on the slide, Print / PDF, the document design and the Slide deck output, in demo mode; and the same flows against the real backend with a stub claude (c3-report-design-real-test.js).",
3
- "panels": ["c3-report-design-test.js", "c3-report-design-real-test.js"],
4
- "seconds": { "c3-report-design-test.js": 75, "c3-report-design-real-test.js": 40 }
2
+ "_comment": "report-design-studio UI slice: the design studio, the deck of a cycle in the Read tab, Present, speaker notes and notes on the slide, Print / PDF, the document design and the Slide deck output, in demo mode; and the same flows against the real backend with a stub claude (c3-report-design-real-test.js); and the PMOS Chrome extension's popup page against the real backend, with a parent page that plays the extension (c3-capture-test.js).",
3
+ "panels": ["c3-report-design-test.js", "c3-report-design-real-test.js", "c3-capture-test.js"],
4
+ "seconds": { "c3-report-design-test.js": 75, "c3-report-design-real-test.js": 40, "c3-capture-test.js": 40 }
5
5
  }