create-pmos 0.494.0 → 0.496.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 +75 -27
- package/payload/AGENTS.md +2 -0
- package/payload/KIT-VERSION +2 -2
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/payload/state/log.md +1 -1
- package/workspace/adler-moments-test.js +3 -1
- package/workspace/board-programs-test.js +6 -6
- package/workspace/board-shared-ui-test.js +2 -2
- package/workspace/briefing-premium-adddoc-test.js +3 -49
- package/workspace/briefing-premium-board-test.js +23 -23
- package/workspace/briefing-premium-initiative-test.js +2 -1
- package/workspace/changelog.json +74 -0
- package/workspace/guide/adler.md +1 -1
- package/workspace/guide/briefing-premium.md +3 -1
- package/workspace/guide/claude-proposes.md +6 -2
- package/workspace/guide/data-freshness.md +2 -0
- package/workspace/guide/home.md +1 -1
- package/workspace/guide/initiatives-screen.md +1 -1
- package/workspace/guide/published-board.md +1 -1
- package/workspace/guide/report-slides.md +6 -6
- package/workspace/guide/reports.md +1 -1
- package/workspace/guide/workflows.md +18 -5
- package/workspace/ini-workflow-test.js +2 -1
- package/workspace/lib/board-premium.js +17 -2
- package/workspace/lib/engine/workflows.js +19 -6
- package/workspace/lib/initiative-chat.js +4 -1
- package/workspace/lib/initiative-cli.js +15 -1
- package/workspace/lib/open-mission.js +14 -0
- package/workspace/lib/proposals.js +18 -4
- package/workspace/lib/report-export.js +5 -3
- package/workspace/lib/report-pyramid.js +134 -33
- package/workspace/lib/report-templates.js +33 -3
- package/workspace/lib/routes-board-premium.js +2 -2
- package/workspace/lib/routes-boards.js +35 -2
- package/workspace/lib/routes-brief-page.js +2 -1
- package/workspace/lib/routes-initiatives.js +1 -0
- package/workspace/lib/routes-proposals.js +14 -2
- package/workspace/lib/routes-report-design.js +1 -1
- package/workspace/lib/routes-report-flow.js +11 -4
- package/workspace/lib/routes-report-setups.js +3 -2
- package/workspace/lib/routes-workflows.js +6 -1
- package/workspace/lib/split-merge-suggest.js +37 -3
- package/workspace/lib/split-merge.js +10 -4
- package/workspace/lib/task-cli.js +5 -1
- package/workspace/lib/workflow-cli.js +70 -15
- package/workspace/lib/workflows-store.js +15 -1
- package/workspace/metric-drafts-ui-test.js +2 -1
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/premium-board-actions-test.js +9 -10
- package/workspace/premium-core-doc-issue-test.js +9 -38
- package/workspace/premium-vw-backlog-test.js +3 -2
- package/workspace/premium-vw-item-test.js +3 -2
- package/workspace/premium-vw-jira-test.js +3 -2
- package/workspace/program-panel-test.js +2 -1
- package/workspace/public/pm/cozy-board.css +17 -5
- package/workspace/public/pm/cozy-home.css +1 -0
- package/workspace/public/pm/home-app.js +15 -5
- package/workspace/public/pm/home-chat.js +49 -8
- package/workspace/public/pm/home-today.js +11 -4
- package/workspace/public/pm/ini-view.js +2 -2
- package/workspace/public/pm/load-shapes.css +57 -0
- package/workspace/public/pm/load-shapes.js +57 -0
- package/workspace/public/pm/premium-board.js +82 -57
- package/workspace/public/pm/premium-shell.js +57 -6
- package/workspace/public/pm/premium.css +1 -0
- package/workspace/public/pm/readonly.js +3 -3
- package/workspace/public/pm/split-merge.js +3 -2
- package/workspace/public/pm/wf-page.css +8 -0
- package/workspace/public/pm/wf-page.js +33 -5
- package/workspace/public/premium-board.html +7 -10
- package/workspace/public/premium.html +1 -0
- package/workspace/skills/report-template.skill +4 -2
- package/workspace/split-merge-ui-test.js +4 -3
- package/workspace/stage-card-test.js +2 -1
- package/workspace/task-press-test.js +2 -1
- package/workspace/test-lists/page-open-speed.json +1 -1
- package/workspace/tk-tasks-test.js +2 -1
package/workspace/changelog.json
CHANGED
|
@@ -1,4 +1,78 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "0.496.0",
|
|
4
|
+
"at": "2026-11-03T00:20:24.723Z",
|
|
5
|
+
"f": 3,
|
|
6
|
+
"prs": [
|
|
7
|
+
{
|
|
8
|
+
"n": 1172,
|
|
9
|
+
"t": "Initiative chats find the initiative's workflow through pmos, never a lane",
|
|
10
|
+
"s": "Initiative chats find the initiative's workflow through pmos, never a lane",
|
|
11
|
+
"k": "fix"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"n": 1170,
|
|
15
|
+
"t": "Keep a rejected ✦ suggestion rejected while its facts hold",
|
|
16
|
+
"s": "Keep a rejected ✦ suggestion rejected while its facts hold",
|
|
17
|
+
"k": "fix"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"n": 1171,
|
|
21
|
+
"t": "Tasks keep their mapped stage after a workflow switch on Home and Board",
|
|
22
|
+
"s": "Tasks keep their mapped stage after a workflow switch on Home and Board",
|
|
23
|
+
"k": "fix"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"n": 1173,
|
|
27
|
+
"t": "Calmer Board top bar (batch 24 item 7, design B)",
|
|
28
|
+
"s": "Calmer Board top bar (batch 24 item 7, design B)",
|
|
29
|
+
"k": "new"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"n": 1165,
|
|
33
|
+
"t": "Pages show something at once while they read",
|
|
34
|
+
"s": "Pages show something at once while they read",
|
|
35
|
+
"k": "new"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"v": "0.495.0",
|
|
41
|
+
"at": "2026-11-02T23:26:48.050Z",
|
|
42
|
+
"f": 3,
|
|
43
|
+
"prs": [
|
|
44
|
+
{
|
|
45
|
+
"n": 1164,
|
|
46
|
+
"t": "Report fixes from the live check of 0.494.0",
|
|
47
|
+
"s": "Report fixes from the live check of 0.494.0",
|
|
48
|
+
"k": "fix"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"n": 1167,
|
|
52
|
+
"t": "Keep a live New workflow draft on the Workflows list",
|
|
53
|
+
"s": "Keep a live New workflow draft on the Workflows list",
|
|
54
|
+
"k": "fix"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"n": 1166,
|
|
58
|
+
"t": "Initiative telemetry card starts collapsed (batch 24)",
|
|
59
|
+
"s": "Initiative telemetry card starts collapsed (batch 24)",
|
|
60
|
+
"k": "fix"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"n": 1168,
|
|
64
|
+
"t": "A sent chat message shows at once",
|
|
65
|
+
"s": "A sent chat message shows at once",
|
|
66
|
+
"k": "fix"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"n": 1169,
|
|
70
|
+
"t": "A default workflow, set with a star in Settings › Workflows (canvas DfA)",
|
|
71
|
+
"s": "A default workflow, set with a star in Settings › Workflows (canvas DfA)",
|
|
72
|
+
"k": "new"
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
},
|
|
2
76
|
{
|
|
3
77
|
"v": "0.494.0",
|
|
4
78
|
"at": "2026-11-02T15:32:51.920Z",
|
package/workspace/guide/adler.md
CHANGED
|
@@ -33,7 +33,7 @@ Each mood comes from real state. When nothing happens, Adler is calm or he rests
|
|
|
33
33
|
- **Search.** Adler with a telescope when nothing matches.
|
|
34
34
|
- **Initiative mission.** The telemetry card at the top of an initiative uses the same art: the rocket on its pad (a blueprint, then built, then lift-off), the planet, the dish and the clouds. The rocket flies a path that runs from left to right in time. While Discover is open, Adler stands at the telescope; when Discover is done, he is gone. On a festival, the day's things hang in the sky in place of the clouds. When the news is bad (the initiative is late) the festival goes. With **Face only** or **Off**, neither he nor the festival shows.
|
|
35
35
|
- **Welcome.** Adler says hello once on the first step, and shows his thinking face while PMOS reads your folder.
|
|
36
|
-
- **Board.** His small face
|
|
36
|
+
- **Board.** His small face is the Jira control at the end of the top bar: calm when Jira was read in the last day and a half, honest (with an amber dot) when it was not, and his thinking face while a press reads it again. Hold the pointer on it to see when Jira was read. He heads **New asks**: needs you while asks wait. When a delivery is missed, his honest face stands once in the Deliveries lane's header; hover it to see which. When you open a milestone that was delivered, he celebrates once in the panel (at most once a week); after that his face is calm. The diamond on the timeline stays plain, and nothing is sent to anyone.
|
|
37
37
|
- **Program charter.** His face on the check line, with his thinking face while Claude checks the milestones.
|
|
38
38
|
- **Stage card.** While a session works on a step, that step shows Adler with his thinking face instead of a spinner. When every step is done, Adler stands by the **Move to** button. Your own steps keep their rings.
|
|
39
39
|
- **Sessions.** Only the session that asks you something shows his face in the list. The other sessions keep their dots. In that session's card, he asks the question.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "The premium Briefing (/premium.html)"
|
|
4
4
|
description: "The Briefing page at /premium.html: the hero sentence, What you need to do, Today, the dock, and what each one reads from the repository."
|
|
5
|
-
timestamp: 2026-11-
|
|
5
|
+
timestamp: 2026-11-02
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# The premium Briefing
|
|
@@ -325,6 +325,7 @@ Not drawn, because no record backs them: answers and ideas an agent files by its
|
|
|
325
325
|
Board is the fourth place on the rail, after Chat, at `/premium-board.html`. It reads what the production Initiatives page reads, for one board at a time, and shows a switcher only when more than one board is registered. The first time it takes a few seconds, because the routes derive every initiative. The page says "Reading the board" with the seconds, shows nothing in place of a guess, and asks again.
|
|
326
326
|
|
|
327
327
|
- **The header** counts the board's open initiatives: how many are on track, at risk, late and on hold, with a bar. A zero is shown.
|
|
328
|
+
- **The top bar** has two tabs, **Initiatives** and **Shared**. The Board has no Documents tab: open an initiative's **Documents** fold, or use Search's **Documents** filter. On the Timeline, one chip shows the span (Sprint, Quarter, Half, Year, 2 yr or 5 yr); press it to pick another. One pill shows what needs a look: a dot and a number for new asks, late, at risk, blocked and idle. A count of zero is not shown, and the pill is not shown when all counts are zero. Press the pill to see the rows. Press a row to open the asks or that count's trend, where **Show only** filters the Board; press a row that filters to stop the filter. On the Timeline, the **(i)** icon shows what the bars mean. Jira is Adler's small face at the end of the row. It has an amber dot when Jira was not read in the last day and a half. Hold the pointer on it to see when Jira was read, and press it to read Jira again.
|
|
328
329
|
- **The view chip** in the Initiatives head says which view is shown (List, By stage or Timeline); press it to pick another.
|
|
329
330
|
- **List** shows the late and at-risk initiatives first, then the board's now view, up to 100 loaded and 25 on screen at a time. A row shows the owner and lane, a status chip when it is not on track, how far through the workflow it is (and "derived" when the workflow engine placed it), a progress bar only when a record holds progress, and the planned finish only when an end date was set ("Not planned" otherwise). "Do this first" appears only when the first row is late.
|
|
330
331
|
- **By stage** has one column for each stage of the board's workflow with the board's own count and its first five initiatives.
|
|
@@ -435,6 +436,7 @@ Every popup has this chat, including the ones that have no row of their own: a t
|
|
|
435
436
|
|
|
436
437
|
- **Your first message** in a dialog starts one session for that row, the page or the initiative, in its repository. Claude gets the row as the page shows it: the title, what the page says, the facts and the files to start from. Then it gets your words.
|
|
437
438
|
- **The message box** takes several lines. **Enter** sends; **Shift+Enter** starts a new line. The box grows as you type, up to about eight lines, and then scrolls. The send button stays at its foot, also with a phone keyboard open.
|
|
439
|
+
- **Your message shows at once.** When you send, the box empties and your message appears in the chat straight away, a little lighter with **Sending…** until PMOS takes it, then with **You · HH:MM**. If the message could not be sent, it leaves the chat and your words go back in the box, so you can send them again. This is the same in every chat: Home, an initiative, a new workflow, and the Board and Intelligence chat.
|
|
438
440
|
- **Later messages** in the same dialog go to the same session. Write `/compact`, `/clear` or any other slash command, and Claude Code runs it.
|
|
439
441
|
- **What it may do** is set by your own Claude Code settings. Claude can change files and run commands. When your settings say to ask first, the question shows in the panel with its options; press one to answer.
|
|
440
442
|
- **Claude's questions** show as a card in the conversation. Claude Code's question picker cannot show in the panel, so the session does not open it: the card shows each question with its options as buttons. Press an option to answer a single question. When Claude asks several questions, or a question takes more than one answer, press the options and then **Send answers**. You can also write your own answer in the box. A session that started before this change still opens the picker: end it in the session list, and your next message resumes the same conversation without it.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Claude proposes: the one ✦ on each initiative"
|
|
4
4
|
description: "What Claude finds when you add an initiative that already lives elsewhere, the new dates, done and tidy names it proposes for late tasks, the clean-up of the work that is not in an initiative, how you keep or drop them in the Viewer, Undo, the pmos propose commands, and how Claude grills an initiative by itself (pmos grill)."
|
|
5
|
-
timestamp: 2026-11-
|
|
5
|
+
timestamp: 2026-11-02
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Claude proposes: the one ✦ on each initiative
|
|
@@ -21,7 +21,11 @@ Press the ✦ to open "Claude proposes N" in the Viewer. On a desk the chat stay
|
|
|
21
21
|
|
|
22
22
|
✓ in the Viewer's header keeps everything Claude is sure of. Check these and Not placed are never part of it. Each proposal has its own ✓ and ✕, and ⌄ shows its items. ✕ on one item leaves just that item out. × closes the Viewer and keeps or drops nothing. One toast says what changed, with Undo for the whole batch.
|
|
23
23
|
|
|
24
|
-
✕ means gone: a dropped proposal comes back only when its evidence changes or the date passes again. A
|
|
24
|
+
✕ means gone: a dropped proposal comes back only when its evidence changes or the date passes again. A ✕ stays with the facts, not with how the work is laid out. These do not bring a dropped proposal back: you edit, rename or move a task, you switch the initiative's workflow, Claude words a grill finding again, or a new day starts. A new date proposal for the same late task comes back only when the task misses a new due date. A grill finding comes back only when its sources change.
|
|
25
|
+
|
|
26
|
+
The same is true for Claude's suggestion to split an initiative or to merge it into another. ✕ on "Split off …?" stays while most of the tasks it would move are the tasks you said no to. You can add, edit or re-date tasks and the suggestion stays away. Claude suggests a split again only when a different group of work stands on its own. ✕ on "Same work as …?" stays while the merge is into the same initiative.
|
|
27
|
+
|
|
28
|
+
A Jira ticket keeps Jira's name and dates: a proposal for one becomes a drafted comment in your outbox. A draft plan is never changed.
|
|
25
29
|
|
|
26
30
|
## The commands
|
|
27
31
|
|
|
@@ -111,6 +111,8 @@ A refresh waits for the sources it uses. The briefing waits for work assigned to
|
|
|
111
111
|
- **Quiet start**: a server starts no background run in its first 5 minutes, so the first pages load quickly. A missed refresh catches up after that.
|
|
112
112
|
- **Warm start**: 5 seconds after PMOS starts, it reads what Home and the Board show once, so your first page opens as fast as later ones. This read writes nothing you would see as read or seen. `PMOS_WARM=off` turns it off.
|
|
113
113
|
- **Fresh answers, asked once**: when two parts of a page ask for the same thing at the same moment, the server is asked once and both get the same answer. Nothing is kept after it arrives, so the next look always reads again.
|
|
114
|
+
- **Last view while Home reads**: Home shows the list from your last visit at once, with a hollow ring next to "Good morning". You can read that list, but you cannot use it until the new read is in. Then the ring goes away. Before a section has data, it shows grey shapes with no names and no counts. Each section fills in when its own data arrives. If the Viewer was open on your last visit, its space stays empty, so the chat does not change width. Adler's face next to the greeting thinks while Home reads, and changes to handled when the read is in. If the read fails, it changes to honest.
|
|
115
|
+
- **Board while it reads**: the toolbar and the tabs show at once, with grey rows below them. If the read takes more than 2 seconds, the line "Still reading the board." shows. There is no progress bar and no percent.
|
|
114
116
|
|
|
115
117
|
### The refresh log
|
|
116
118
|
|
package/workspace/guide/home.md
CHANGED
|
@@ -152,7 +152,7 @@ Some features are still not designed. Home keeps each one usable as a plain card
|
|
|
152
152
|
|
|
153
153
|
## The other pages
|
|
154
154
|
|
|
155
|
-
- **Board.** New initiative takes links and files, starts in Triage and asks only the questions that the material does not answer. **Later** keeps the questions as the next step. The Board also has List, By stage
|
|
155
|
+
- **Board.** New initiative takes links and files, starts in Triage and asks only the questions that the material does not answer. **Later** keeps the questions as the next step. The Board also has List, By stage and a span picker. Documents are in each initiative's Documents fold and in Search. On a phone, **Metrics** and **Cancelled and archived** are in every view.
|
|
156
156
|
- **Meetings.** Replay with screen captures and kept points, Focus, a presenter view, Add material, the After view, a Day, Week and Month calendar, and Notes.
|
|
157
157
|
- **Reports.** The Brief, the report states, a document reader and a live page reader.
|
|
158
158
|
- **Insights.** A goal's detail with its chart and readings, targets on each tile, Library flows and a Portfolio view.
|
|
@@ -19,7 +19,7 @@ An open initiative uses one column. At its top is the **mission**: one panel wit
|
|
|
19
19
|
- **To finish this stage.** It shows how many tasks of the stage are done, the stage's open milestone tasks with their dates, and the move to the next stage. The move is blue only when every task of the stage is done and nothing waits on you. At other times it is quiet. Under the rows are the owner, the stakeholders, the workflow and the program. On a wide screen this part stands beside the telemetry card.
|
|
20
20
|
- **About.** It has the description and the success measure. The description is the first paragraph of the PRD's Problem section. Press **Show more** to read all of it, or **Open the PRD** to open the file. The success measure is the one Claude drafts. A proposal opens About until you press its check mark. A kept measure shows each row with its now value and its target. When there is no description or no measure, About gives you a link that asks Claude in Home to write it.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
When you open an initiative, the three parts are hidden at first and only a banner shows: the stage, the dates, the days left, the count of tasks done, the success measure and the move to the next stage. Press the arrow on the banner to show the telemetry card first. Press the arrow on the telemetry card to hide all three parts again. **To finish this stage** and **About** each have their own arrow, and they stay as you left them when you hide and show the mission. The workspace keeps these choices for each initiative in this browser. On a phone, the first screen shows **To finish this stage** and **About** closed.
|
|
23
23
|
|
|
24
24
|
On a phone, the page uses one 16 px margin and has no grey panel around the mission. The telemetry card is shorter: the stage name is on the first line, and the dates, **Time left** and **Elapsed** are on the next line, so the picture does not repeat the Today label. **To finish this stage** and **About** are one white card. The first row shows the next stage and the count of tasks done. Its round arrow opens the move sheet. The second row shows the owner, the stakeholders, the workflow and the program. The third row is **About**. In Tasks, the rows start near the card's edge, and the late and open counts of a stage are on the same line as its dates.
|
|
25
25
|
|
|
@@ -37,7 +37,7 @@ Only you can publish, republish or turn off a link, with a press on the page. An
|
|
|
37
37
|
|
|
38
38
|
## The Shared tab
|
|
39
39
|
|
|
40
|
-
**Shared** is a tab of the Board, beside **Initiatives
|
|
40
|
+
**Shared** is a tab of the Board, beside **Initiatives**. It lists the boards that people published to your teams, and your own links. On a phone it is the last chip. The tab shows a number when there are open copies. Before you read a team, the number is your own open links.
|
|
41
41
|
|
|
42
42
|
**Join a team.** The person who makes a team gets an invite link and sends it to you. It looks like `https://boards.example.com/join#Team.password`. Open **Shared**, paste the link in the field and press **Join**. PMOS asks the host first. It keeps the team only if the password works. You do not need to set the host address first: the invite link has it.
|
|
43
43
|
|
|
@@ -31,13 +31,13 @@ A draft from before the slides has no slides block. Press **Redraft** to add it.
|
|
|
31
31
|
|
|
32
32
|
When you open a drafted report in the viewer, the **Slides** view shows the finished deck. The answer comes first, and the title of each slide is a finding. Read only the titles and you have the story:
|
|
33
33
|
|
|
34
|
-
1. **Title**: the answer. This is your `report.headline` note when you wrote one. If
|
|
35
|
-
2. **The answer**: one sentence
|
|
36
|
-
3. **Scorecard**:
|
|
37
|
-
4. **Timeline**: the dated work, the due dates and any overdue item. The title compares two due dates.
|
|
34
|
+
1. **Title**: the answer. This is the first sentence of your `report.headline` note when you wrote one, so write the answer first. A long sentence is cut so that it fits. If there is no note, PMOS writes a sentence that says which initiatives are off plan.
|
|
35
|
+
2. **The answer**: one sentence, then the rest of the first paragraph of your headline note, and up to three supports, off-plan initiatives first. Your ask is beside them.
|
|
36
|
+
3. **Scorecard**: the title is the verdict on one line, for example "3 of 12 off plan" with up to three names. Each initiative's stage, its status (a shape and a colour), done of total with the change since the last report, and the KR value against its target. The gap is the value minus the target. Only a row that is off plan (at risk or off track) has a comment. A row that is on track has none.
|
|
37
|
+
4. **Timeline**: the dated work, the due dates and any overdue item, for up to six initiatives. The title compares two due dates of the rows on the slide.
|
|
38
38
|
5. **Needs your call**: only when the report has an audience and the `report.asks` note has words. Write each option on its own line that starts with "- ". Write "by" and a date in the note to show a deadline. PMOS adds nothing that you did not write.
|
|
39
|
-
6. **Sources**: the sources that PMOS read, and "What we don't know yet" for each source that it could not read, with the reason.
|
|
39
|
+
6. **Sources**: the sources that PMOS read, and "What we don't know yet" for each source that it could not read, with the reason. PMOS reads these from the draft's "Sources reached and not reached" section, from the cycle's data file and from the "Sources:" paragraph of the notes.
|
|
40
40
|
|
|
41
|
-
Each slide names its source and shows its page number. **Download › PDF**
|
|
41
|
+
Each slide names its source and shows its page number. **Download › PDF** opens the print window with the same slides, one on each page: choose **Save as PDF** there. There is no PDF file to download from PMOS itself, because the browser makes the PDF. **Download › PPTX** gives the same slides as a PowerPoint file. The slides use the report's saved look: its font and its own title or main colour. If the report has no saved look, they use the standard PMOS look. PMOS does not draw KR trend lines, last-quarter bars or forecast ranges, because the draft does not have that data yet.
|
|
42
42
|
|
|
43
43
|
A report that you set up can have its own design, as a deck or an interactive document, with a presenter view, speaker notes and notes on the slide. See [Design how a report looks](report-design.md).
|
|
@@ -47,7 +47,7 @@ The Reports page shows a report as slides. [Report slides](report-slides.md) tel
|
|
|
47
47
|
|
|
48
48
|
When you draft a slot again, every note stays as you wrote it, next to its initiative. The order of the blocks can change. A note for an initiative that is no longer in the window moves to "Notes kept from an earlier draft".
|
|
49
49
|
|
|
50
|
-
When you ask Claude in a chat to change a report, the change goes to the latest draft of that report, in its own file. The report's session
|
|
50
|
+
When you ask Claude in a chat to change a report, the change goes to the latest draft of that report, in its own file. The report's session edits only the notes that the change is about, even when they have text, and keeps every other note and every number as it is. It keeps each caveat (a sentence that says something is provisional, not decided or unverified), also when you ask for shorter text, and it tells you which notes it changed and quotes any sentence it removed. It drafts the same slot again only when you ask for fresh numbers. It never drafts a new slot for today. A change to a number, a date, a section or a slide is not made in the draft: the session tells you which setting to change in the template or the studio. If the latest cycle is issued, it is frozen, and the change is not passed on.
|
|
51
51
|
|
|
52
52
|
## The commands
|
|
53
53
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Workflows: the stages each kind of initiative goes through"
|
|
4
|
-
description: "Settings › Workflows: the workflow list, one numbered list of stages across To do, In progress and Done, the stage panel, Only when conditions, removing a stage in use, New workflow in the chat, moving initiatives to a simpler workflow, and the pmos workflow commands."
|
|
5
|
-
timestamp: 2026-11-
|
|
4
|
+
description: "Settings › Workflows: the workflow list, the default workflow (the star), one numbered list of stages across To do, In progress and Done, the stage panel, Only when conditions, removing a stage in use, New workflow in the chat, moving initiatives to a simpler workflow, and the pmos workflow commands."
|
|
5
|
+
timestamp: 2026-11-02
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Workflows
|
|
@@ -14,11 +14,21 @@ Open it from **Menu › Settings › Workflows**. The address is `/premium-workf
|
|
|
14
14
|
## The page
|
|
15
15
|
|
|
16
16
|
- **The list** on the left shows each workflow and how many initiatives use it. **+** starts a new workflow. An archived workflow shows last, under **Archived**.
|
|
17
|
+
- **The star** on a row marks the **default workflow**: the workflow a new initiative starts on. Point at a row, or tab to it, to see its star, and press it to make that workflow the default. The toast has **Undo**. Product feature is the default until you pick another, and it can be the default again.
|
|
17
18
|
- **The workflow** is one numbered list of stages, in three columns: **To do**, **In progress** and **Done**. Drag a stage to change its place or its column. **Add a stage** adds one at the end of that column. The dashed **Won't do · Archived** column is in every workflow: any initiative can go there from any stage, and come back to Triage.
|
|
18
19
|
- **Triage** is always the first stage, in To do.
|
|
19
20
|
- Tap the name of the workflow to rename it. **⋯** has **Copy** and **Archive**. You cannot archive a workflow that initiatives use.
|
|
20
21
|
- **Product feature** cannot change. **Copy it** to make a workflow of your own from it. A stage of Product feature has **Copy it** too: the copy opens on that stage.
|
|
21
22
|
|
|
23
|
+
## The default workflow
|
|
24
|
+
|
|
25
|
+
A new initiative starts in Triage on the default workflow. It does not matter how you add it: from Triage, from a Horizon scan proposal, from the initiative form, or with `pmos initiative new`. PMOS still reads the initiative's BRD and proposes a switch when the work reads like another workflow. You decide.
|
|
26
|
+
|
|
27
|
+
- The default is per workspace. A workspace that never set a default uses the one you set in Settings.
|
|
28
|
+
- Changing the default does not move initiatives that already exist.
|
|
29
|
+
- If you archive the default workflow, Product feature is the default again. Restoring the workflow does not make it the default again: press its star.
|
|
30
|
+
- The **In:** chip in the chat names where a message goes. It does not show a workflow.
|
|
31
|
+
|
|
22
32
|
## A stage
|
|
23
33
|
|
|
24
34
|
Tap a stage to open it in the side panel. Tap its name at the top to rename it. The panel has:
|
|
@@ -47,6 +57,8 @@ Press **+**. The chat opens, scoped **New workflow**. Tell Claude how the work g
|
|
|
47
57
|
- **Edit by hand** opens the draft on the page, so you can change it before you create it.
|
|
48
58
|
- You can also start **blank** (only Triage) or **from a copy** of a workflow.
|
|
49
59
|
|
|
60
|
+
You can open another workflow while a draft is going. The draft keeps its row at the end of the list, marked **draft**, until you create it. Press that row to go back to the chat and the draft beside it. In the chat list on Home, the same chat shows **In: New workflow**.
|
|
61
|
+
|
|
50
62
|
## Move initiatives to a simpler workflow
|
|
51
63
|
|
|
52
64
|
To use fewer stages, copy Product feature and make the copy simpler. For example, use Discovery, Implementation and Evaluation.
|
|
@@ -54,7 +66,7 @@ To use fewer stages, copy Product feature and make the copy simpler. For example
|
|
|
54
66
|
- **Claude's proposal.** Claude shows where each old stage goes, once, as a ✦ in **New for you**. For example: Triage, Discover, Plan and Prioritize go into Discovery. Each row shows how many initiatives and tasks go into that stage. Press ✓ to move all the initiatives. Press ✕ to leave them where they are.
|
|
55
67
|
- **The bridge.** The page of the new workflow shows **Replaces Product feature**: each old stage, with the stage it goes into. Change a stage there. The change is saved at once, and every later switch from the old workflow uses it.
|
|
56
68
|
- **One initiative.** On the initiative, open the workflow picker and pick the new workflow. The dialog shows the old stages in each new stage. **Change where a stage goes** shows one picker for each old stage. **This initiative** or **All N** sets which initiatives move.
|
|
57
|
-
- **What moves.** An initiative's stage and its tasks move together. Ticked items from the old stages count in the new stage. The new stage counts its days from the day its first old stage began. The stage card shows the days in each old stage.
|
|
69
|
+
- **What moves.** An initiative's stage and its tasks move together. Each task goes into the stage that its old stage goes into, on Home, on the Board and in `pmos task list`. A task goes under **Not in a stage** only when it had no stage before the move. Ticked items from the old stages count in the new stage. The new stage counts its days from the day its first old stage began. The stage card shows the days in each old stage.
|
|
58
70
|
- **Jira tickets.** The tickets go under the stage whose output is Jira tickets, whatever its name.
|
|
59
71
|
- **Undo.** The toast has **Undo**, and the workflow picker keeps **Undo the move** for 7 days. Undo puts back the workflow, each stage and each task together. An initiative that changed stage after the move stays where it is.
|
|
60
72
|
|
|
@@ -62,8 +74,9 @@ To use fewer stages, copy Product feature and make the copy simpler. For example
|
|
|
62
74
|
|
|
63
75
|
An agent writes a draft or a reading with these commands, and moves an initiative along its workflow when you ask. It never saves, copies or archives a workflow: those are your presses on the page.
|
|
64
76
|
|
|
65
|
-
- `pmos workflow list` names the
|
|
66
|
-
- `pmos workflow show <id>` shows one workflow, its stages numbered.
|
|
77
|
+
- `pmos workflow list` names every workflow you can pick and marks the default one: the ones of this repository, then the ones you saved in Settings › Workflows (marked **saved in** and the workspace name) and in your other open workspaces.
|
|
78
|
+
- `pmos workflow show <id>` shows one workflow, its stages numbered. It finds a workflow in any of those libraries.
|
|
79
|
+
- An initiative chat reads workflows only through these commands. It does not search the repository's files for a workflow, and it does not use a lane, such as `feature`, in place of the initiative's workflow. When it cannot find a workflow, it says so and asks you.
|
|
67
80
|
- `pmos workflow draft --file <path>` writes the draft that the New workflow chat shows. `--note` adds a line.
|
|
68
81
|
- `pmos workflow show-draft` shows the draft that is open.
|
|
69
82
|
- `pmos workflow judge --board <id> --id <initiative> --stage <key> --runs yes|no|unsure --why <reason> --cite <where>` records PMOS's reading of an Only when stage. **unsure** makes PMOS ask you. `--undo` takes a reading back.
|
|
@@ -10,6 +10,7 @@ const fs = require('fs');
|
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const { spawn } = require('child_process');
|
|
12
12
|
const { findBrowser } = require('./lib/find-browser');
|
|
13
|
+
const { openMission } = require('./lib/open-mission');
|
|
13
14
|
const T = require('./test-run').configure({ name: 'ini-workflow' });
|
|
14
15
|
const IP = require('./lib/initiative-premium.js');
|
|
15
16
|
const F = require('./fixtures/initiative-premium-facts.js');
|
|
@@ -71,7 +72,7 @@ const BRIEF = BRP.build(BRF.facts());
|
|
|
71
72
|
const shot = async (p, name) => { if (SHOTS) await p.screenshot({ path: path.join(SHOTS, name + '.png') }); };
|
|
72
73
|
|
|
73
74
|
const open = async ({ w = 1440, h = 900, theme = 'light', B = board('triage', { pmos: true }), fresh = true } = {}) => {
|
|
74
|
-
const ctx = await browser.newContext({ viewport: { width: w, height: h } });
|
|
75
|
+
const ctx = await openMission(await browser.newContext({ viewport: { width: w, height: h } }));
|
|
75
76
|
const page = await ctx.newPage();
|
|
76
77
|
const S = { B };
|
|
77
78
|
const errors = [], posts = [];
|
|
@@ -239,7 +239,20 @@ const stageFields = (s) => (s && typeof s === 'object' && s.key ? { key: String(
|
|
|
239
239
|
function wfFields(r) {
|
|
240
240
|
const wf = r.wf && r.wf.id ? { id: String(r.wf.id), name: clip(r.wf.name || r.wf.id, 60), icon: r.wf.icon || 'flag', ...(r.wf.setBy ? { setBy: r.wf.setBy, why: clip(r.wf.why || '', 300) } : {}) } : PF;
|
|
241
241
|
const ended = r.ended && (r.ended.as === 'wontdo' || r.ended.as === 'archived') ? { as: r.ended.as, reason: r.ended.reason || '', note: clip(r.ended.note || '', 300), at: r.ended.at || null } : null;
|
|
242
|
-
|
|
242
|
+
// batch 24 item 5: the stage map of the last workflow switch (old stage key → stage now) and its Undo. Without them a Home or Board view put
|
|
243
|
+
// every task of an old stage under "Not in a stage" after the PM agreed the map (the full row had them; this copy dropped them).
|
|
244
|
+
const wfMap = mapFields(r.wfMap);
|
|
245
|
+
const wfSwitch = r.wfSwitch && typeof r.wfSwitch.batch === 'string' ? { batch: r.wfSwitch.batch, at: r.wfSwitch.at || null, from: r.wfSwitch.from || null } : null;
|
|
246
|
+
return { wf, wfStage: stageFields(r.wfStage), wfStages: Array.isArray(r.wfStages) ? r.wfStages.map(stageFields).filter(Boolean).slice(0, 40) : null, ended,
|
|
247
|
+
...(wfMap ? { wfMap } : {}), ...(wfSwitch ? { wfSwitch } : {}) };
|
|
248
|
+
}
|
|
249
|
+
/** A stage map as the row carries it: { old key: stage key }, short strings only (a key is never cut: a cut key matches nothing), at most 60
|
|
250
|
+
* keys; null when empty. */
|
|
251
|
+
function mapFields(m) {
|
|
252
|
+
if (!m || typeof m !== 'object' || Array.isArray(m)) return null;
|
|
253
|
+
const out = {};
|
|
254
|
+
for (const [k, v] of Object.entries(m).slice(0, 60)) if (k && k.length <= 80 && typeof v === 'string' && v && v.length <= 80) out[k] = v;
|
|
255
|
+
return Object.keys(out).length ? out : null;
|
|
243
256
|
}
|
|
244
257
|
/** The holds of a row as the timeline draws them (feedback batch 9 item 8): from, to (null while open), the return date and the reason. */
|
|
245
258
|
const DAY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
@@ -333,7 +346,9 @@ function build(f) {
|
|
|
333
346
|
}
|
|
334
347
|
// every task, not the first 60: the Board's panel and Home count the same tasks (feedback batch 6 item 1: Board said 56 done, Home 61)
|
|
335
348
|
for (const r of rows.concat(stageRows, gone.cancelled, gone.archived)) if (!r.tasks) r.tasks = (taskBy.get(r.id) || []).slice(0, TASK_MAX);
|
|
336
|
-
|
|
349
|
+
// a milestone under a stage of the workflow the initiative ran before a switch sits in the stage that switch put it in (row.wfMap)
|
|
350
|
+
const msIn = (r, m) => { const keys = (r.wfStages || []).map((s) => s.key); return m.sec && r.wfMap && !keys.includes(m.sec) && keys.includes(r.wfMap[m.sec]) ? { ...m, sec: r.wfMap[m.sec] } : m; };
|
|
351
|
+
for (const r of rows.concat(stageRows, gone.cancelled, gone.archived)) if (!r.milestones) r.milestones = (msBy.get(r.id) || []).map((m) => msIn(r, m)).sort((a, b) => a.date.localeCompare(b.date)).slice(0, 40);
|
|
337
352
|
for (const r of rows.concat(stageRows, gone.cancelled, gone.archived)) if (!r.track) {
|
|
338
353
|
r.track = trackOf(r, tkBy.get(r.id), todayIso, relOfIni.get(r.id)); r.theme = themeOf(r.anchor);
|
|
339
354
|
if (epicBy.has(r.id) && !r.epics.includes(epicBy.get(r.id))) r.epics.unshift(epicBy.get(r.id));
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* CATS · ENDS · OUTPUT_KINDS · WHEN_MODES · DECIDE · ICONS · PRODUCT_FEATURE_ID · PRODUCT_FEATURE
|
|
6
6
|
* check(wf, { existing }) → { ok, workflow } | { ok:false, status, error } validate and normalise one workflow
|
|
7
|
-
* fold(lines) → { byId: Map, draft }
|
|
8
|
-
* list(folded, counts?) → [workflow + { count }]
|
|
7
|
+
* fold(lines) → { byId: Map, draft, defaultId } the library (Product feature first)
|
|
8
|
+
* list(folded, counts?) → [workflow + { count, default? }] Product feature first, archived last
|
|
9
|
+
* defaultOf(folded) → the workflow a new initiative starts on (Product feature when none is set or it is archived)
|
|
9
10
|
* resolve(folded, id) → the workflow (Product feature when the id is unknown or missing)
|
|
10
11
|
* stageByRef(wf, ref) → the stage a key or a name names, or null
|
|
11
12
|
* switchTarget(from, to, stageKey) → the key an initiative lands on when it switches workflow (the bridge's stage, see mapOf)
|
|
@@ -27,6 +28,8 @@
|
|
|
27
28
|
* wf-save { wf } create or edit (Product feature is never saved: it is built in)
|
|
28
29
|
* wf-archive { id, archived }
|
|
29
30
|
* wf-draft { draft | null, note? } the one open draft the chat wrote (null clears it)
|
|
31
|
+
* wf-default { id } the workflow a new initiative starts on, for this workspace (batch 24 item 3, canvas DfA). The last
|
|
32
|
+
* line wins. Product feature can be the default. An unknown or archived id reads as Product feature.
|
|
30
33
|
*/
|
|
31
34
|
|
|
32
35
|
const { STAGES: LIFE, LABELS, OUTPUTS, MILESTONES } = require('./nine-stages');
|
|
@@ -134,7 +137,7 @@ function check(wf) {
|
|
|
134
137
|
/** Fold workflows.jsonl (objects or JSON strings), oldest first. */
|
|
135
138
|
function fold(lines) {
|
|
136
139
|
const byId = new Map([[PRODUCT_FEATURE_ID, PRODUCT_FEATURE]]);
|
|
137
|
-
let draft = null;
|
|
140
|
+
let draft = null, defaultId = null;
|
|
138
141
|
for (const raw of lines || []) {
|
|
139
142
|
let e = raw;
|
|
140
143
|
if (typeof raw === 'string') { try { e = JSON.parse(raw); } catch { continue; } }
|
|
@@ -153,14 +156,24 @@ function fold(lines) {
|
|
|
153
156
|
byId.set(e.id, w);
|
|
154
157
|
} else if (e.kind === 'wf-draft') {
|
|
155
158
|
draft = isObj(e.draft) ? { draft: e.draft, note: typeof e.note === 'string' ? e.note : '', at: e.at || null } : null;
|
|
159
|
+
} else if (e.kind === 'wf-default' && typeof e.id === 'string' && e.id) {
|
|
160
|
+
defaultId = e.id;
|
|
156
161
|
}
|
|
157
162
|
}
|
|
158
|
-
return { byId, draft };
|
|
163
|
+
return { byId, draft, defaultId };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The workflow a new initiative starts on: the last wf-default line's workflow, or Product feature when none is set, the id is not in the
|
|
167
|
+
* library, or that workflow is archived. */
|
|
168
|
+
function defaultOf(folded) {
|
|
169
|
+
const w = folded && folded.defaultId ? folded.byId.get(folded.defaultId) : null;
|
|
170
|
+
return w && !w.archived ? w : PRODUCT_FEATURE;
|
|
159
171
|
}
|
|
160
172
|
|
|
161
173
|
/** The library as a list: Product feature first, then by name, archived last. counts: { [id]: n } initiatives on each. */
|
|
162
174
|
function list(folded, counts = {}) {
|
|
163
|
-
const
|
|
175
|
+
const def = defaultOf(folded).id;
|
|
176
|
+
const all = [...folded.byId.values()].map((w) => ({ ...w, count: counts[w.id] || 0, ...(w.id === def ? { default: true } : {}) }));
|
|
164
177
|
const rank = (w) => (w.id === PRODUCT_FEATURE_ID ? 0 : w.archived ? 2 : 1);
|
|
165
178
|
return all.sort((a, b) => rank(a) - rank(b) || a.name.localeCompare(b.name));
|
|
166
179
|
}
|
|
@@ -269,5 +282,5 @@ const milestonesOf = (wf) => (wf || PRODUCT_FEATURE).stages.filter((s) => s.mile
|
|
|
269
282
|
|
|
270
283
|
module.exports = {
|
|
271
284
|
CATS, CAT_LABEL, ENDS, OUTPUT_KINDS, WHEN_MODES, DECIDE, ICONS, PRODUCT_FEATURE_ID, PRODUCT_FEATURE,
|
|
272
|
-
slug, check, fold, list, resolve, stageByRef, switchTarget, proposeBridge, mapOf, removedInUse, stagesOf, copyOf, milestonesOf,
|
|
285
|
+
slug, check, fold, list, defaultOf, resolve, stageByRef, switchTarget, proposeBridge, mapOf, removedInUse, stagesOf, copyOf, milestonesOf,
|
|
273
286
|
};
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* from lib/routes-boards.js initiativeWorkflow's answer
|
|
14
14
|
* rule(info, id) → the rule's lines, with the workflow and stage when info is known
|
|
15
15
|
* isAsk(text) → whether a message is about a stage, a workflow, a move or a task
|
|
16
|
-
* wrap(text, info, id) → the message as PMOS types it into a live session
|
|
16
|
+
* wrap(text, info, id) → the message as PMOS types it into a live session
|
|
17
|
+
* Batch 24 item 8: a chat in a product repository searched the repo for "Simpler Feature", found nothing (the library is the home
|
|
18
|
+
* workspace's workflows.jsonl, lib/workflow-cli.js) and used the feature LANE (D56) instead. The rule now says where workflows are read. */
|
|
17
19
|
|
|
18
20
|
// The first line of every message PMOS wraps. The page reads it to show the PM's turn as typed (public/pm/goal-chat.js shownUser).
|
|
19
21
|
const MARK = '[PMOS initiative]';
|
|
@@ -38,6 +40,7 @@ function rule(info, id) {
|
|
|
38
40
|
out.push('Its stages: ' + info.stages.map((s, i) => `${i + 1}. ${s.name} [${s.key}]${s.status === 'done' ? ' done' : s.status === 'current' ? ' current' : s.status === 'skipped' ? ' skipped' : ''}`).join('; ') + '.');
|
|
39
41
|
} else out.push(`Run \`pmos workflow of ${ini}\` to see the workflow this initiative runs and its current stage.`);
|
|
40
42
|
out.push(
|
|
43
|
+
`Read workflows only through pmos: \`pmos workflow of ${ini}\` gives this initiative's workflow and its stages, \`pmos workflow show <workflow id>\` gives any workflow's stages, and \`pmos workflow list\` names every workflow the PM can pick, the ones saved in Settings included. The workflows are not files in this repository: never search the files for one. If pmos cannot find a workflow, tell the PM so and ask. A workflow is not a lane: never use a lane (feature, discovery, chore, incident, content, prototype) in place of the initiative's workflow.`,
|
|
41
44
|
`The workflow stage is on the initiative: the stage card and the Board show it. \`pmos workflow of ${ini}\` reads it. When the PM asks to move the initiative, run \`pmos workflow move ${ini} --stage <key> --why "<reason>"\`. When the PM asks to change its workflow, run \`pmos workflow switch ${ini} --to <workflow id>\`. Both write what the page's press writes, under your name.`,
|
|
42
45
|
'There is one kind of task: the ones the PM sees in the initiative\'s Tasks section. The stage card\'s To finish entries and the older breakdown items are tasks too. '
|
|
43
46
|
+ 'A task\'s state is To do, In progress, Done, Won\'t do or Archived. The Tasks section groups the tasks under the stages of this initiative\'s workflow: a task\'s group is its place in the breakdown, never its state. When the PM asks to add, close, group or change a task, '
|
|
@@ -35,6 +35,18 @@ const triage = require('./triage.js');
|
|
|
35
35
|
const lifecycle = require('./lifecycle-stage.js');
|
|
36
36
|
const { parseArgs } = require('./schedule-cli');
|
|
37
37
|
|
|
38
|
+
/** Batch 24 item 3: the new initiative starts on this repository's default workflow (routes-boards startOnDefault), as a page create does. */
|
|
39
|
+
function startOnDefault(root, r, actor) {
|
|
40
|
+
try {
|
|
41
|
+
const D = require('./driver.js');
|
|
42
|
+
const dir = D.stateDirOf(root);
|
|
43
|
+
if (!dir) return;
|
|
44
|
+
const w = { id: D.wsIdOf(root), name: path.basename(root), root };
|
|
45
|
+
const ctx = { getWs: () => w, workspaceList: () => [w], stateDirFor: () => dir, selfRoot: path.resolve(__dirname, '..', '..') };
|
|
46
|
+
require('./routes-boards.js').startOnDefault(ctx, w, r.id, actor, { recordPath: r.path });
|
|
47
|
+
} catch { /* the initiative is made; it runs Product feature */ }
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
const COMMANDS = { initiative: ['new', 'top', 'up', 'down', 'bottom', 'after', 'check-by', 'adopt', 'drop', 'list', 'export'] };
|
|
39
51
|
/** The horizon verbs D105 retired: they answer with what to use instead. */
|
|
40
52
|
const RETIRED = Object.freeze(['now', 'next', 'later']);
|
|
@@ -121,8 +133,10 @@ async function run(argv, { say = (s) => console.log(s), cwd = process.cwd(), isT
|
|
|
121
133
|
}
|
|
122
134
|
if (flags.title) input.title = flags.title;
|
|
123
135
|
for (const k of ['lane', 'anchor', 'intent', 'id']) if (flags[k]) input[k] = flags[k];
|
|
124
|
-
const
|
|
136
|
+
const actor = actorLib.fromCli(who);
|
|
137
|
+
const r = writer.createInitiative(root, input, { actor, now });
|
|
125
138
|
if (!r.ok) return fail(r.error);
|
|
139
|
+
startOnDefault(root, r, actor);
|
|
126
140
|
return done({ id: r.id, path: r.path }, `✓ ${r.id} added at the end of the Roadmap (${r.path}). Commit the record to keep it.`);
|
|
127
141
|
}
|
|
128
142
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/* open-mission: a browser-test helper. The initiative mission (the telemetry card, the way to finish, About) starts hidden in its
|
|
3
|
+
* banner (batch 24 item 1). Suites that read the open card wrap their context with this, as a PM who unfolded it: any initiative
|
|
4
|
+
* with no fold of its own in the browser reads as open. A suite that checks the banner default does not use it. */
|
|
5
|
+
async function openMission(ctx) {
|
|
6
|
+
await ctx.addInitScript(() => {
|
|
7
|
+
try {
|
|
8
|
+
const g = Storage.prototype.getItem;
|
|
9
|
+
Storage.prototype.getItem = function (k) { const v = g.call(this, k); return v == null && /^pmos\.iv\.mc\./.test(String(k)) ? '{"mc":true}' : v; };
|
|
10
|
+
} catch { /* no storage: the default holds */ }
|
|
11
|
+
});
|
|
12
|
+
return ctx;
|
|
13
|
+
}
|
|
14
|
+
module.exports = { openMission };
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
* end { ini, run, ok, stopped? } the run ended (stopped: the PM pressed ■)
|
|
25
25
|
* propose { ini, board, run, id, group, type, section, title, why, sources: [{ label, ref }], items: [item], data }
|
|
26
26
|
* an agent filed it (`pmos propose …`); the last line for an id wins, so a newer run replaces what it files again
|
|
27
|
-
* drop { ini, sig }
|
|
27
|
+
* drop { ini, sig, facts? } ✕ on a proposal or on one item of it: gone until its evidence changes (a new signature) or the date
|
|
28
|
+
* passes again. `facts` (batch 24 item 6) are what the ✕ rests on, not where the work sits: a date proposal's facts are its tasks
|
|
29
|
+
* and the due dates they missed, a grill finding's are its angle and its sources. A ✕ holds while they hold, so a workflow switch
|
|
30
|
+
* (new section keys, so new ids and signatures), a reworded reason or a new day (new computed dates) does not bring it back
|
|
28
31
|
* keep { ini, board, batch, ids, wrote: [write] } ✓: what was written, each write with what it was before, for Undo and History
|
|
29
32
|
* undo { batch }
|
|
30
33
|
* epoch { } the first scan of lib/proposal-runs.js: only initiatives made after it are looked for
|
|
@@ -148,6 +151,16 @@ function clean(group, type, input) {
|
|
|
148
151
|
const idOf = (ini, group, type, key) => 'pr-' + hash([ini, group, type, key || ''].join('|'));
|
|
149
152
|
/** A proposal's signature: what it says and what it rests on. ✕ drops this; new evidence (a new signature) is proposed again. */
|
|
150
153
|
function sigOf(p) { return 's:' + hash([p.id, p.why, JSON.stringify((p.sources || []).map((s) => s.ref || s.label)), JSON.stringify((p.items || []).map((x) => [x.id, x.due, x.start, x.to]))].join('|')); }
|
|
154
|
+
/** What a ✕ rests on, by fact rather than by id (batch 24 item 6): [string]. Dates: each task and the due date it missed (not its section,
|
|
155
|
+
* not the computed new date). Grill: the angle, its key and its sources (not the wording). Other groups: [] (their signature is already
|
|
156
|
+
* their content). */
|
|
157
|
+
function factsOf(p, x) {
|
|
158
|
+
if (!p) return [];
|
|
159
|
+
if (p.group === 'dates') return (x ? [x] : p.items || []).filter((y) => y && y.task).map((y) => factOfTask(p.ini, y.task, y.from && y.from.due));
|
|
160
|
+
if (p.group === 'grill') return ['f:' + hash(['grill', p.ini, p.type, p.id, JSON.stringify((p.sources || []).map((s) => s.ref || s.label).sort())].join('|'))];
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
const factOfTask = (ini, task, due) => 'f:' + hash(['dates', ini, task, due || ''].join('|'));
|
|
151
164
|
function itemSig(p, x) { return 'i:' + hash([p.id, x.id || x.task || x.ref || x.title || '', x.due || '', x.start || '', JSON.stringify(x.to == null ? '' : x.to)].join('|')); }
|
|
152
165
|
|
|
153
166
|
/* ── rule 5: one standard for a task's name ── */
|
|
@@ -182,7 +195,7 @@ function fold(lines) {
|
|
|
182
195
|
else if (l.kind === 'src' && ini) { const L = looks.get(ini); if (L && L.run === l.run && str(l.source)) L.sources.set(l.source, { state: l.state === 'done' ? 'done' : 'reading', n: Number(l.n) || 0, what: str(l.what) }); }
|
|
183
196
|
else if (l.kind === 'end' && ini) { const L = looks.get(ini); if (L && L.run === l.run) { L.ended = true; L.ok = !!l.ok; L.stopped = !!l.stopped; L.endAt = l.at || ''; } }
|
|
184
197
|
else if (l.kind === 'propose' && ini && str(l.id)) props.set(l.id, { id: l.id, ini, board: str(l.board), run: str(l.run), group: l.group, type: l.type, section: str(l.section) || 'sure', title: str(l.title), why: str(l.why), sources: Array.isArray(l.sources) ? l.sources : [], items: Array.isArray(l.items) ? l.items.filter(isObj) : [], data: isObj(l.data) ? l.data : {}, at: l.at || '', by: l.by || null });
|
|
185
|
-
else if (l.kind === 'drop' && str(l.sig)) drops.add(l.sig);
|
|
198
|
+
else if (l.kind === 'drop' && str(l.sig)) { drops.add(l.sig); for (const f of Array.isArray(l.facts) ? l.facts : []) if (/^f:[0-9a-f]{10}$/.test(String(f))) drops.add(f); }
|
|
186
199
|
else if (l.kind === 'keep' && str(l.batch)) kept.push({ batch: l.batch, ini, board: str(l.board), ids: Array.isArray(l.ids) ? l.ids : [], wrote: Array.isArray(l.wrote) ? l.wrote.filter(isObj) : [], at: l.at || '', by: l.by || null });
|
|
187
200
|
else if (l.kind === 'undo' && str(l.batch)) undone.add(l.batch);
|
|
188
201
|
}
|
|
@@ -292,7 +305,7 @@ function datesOf(ini, T, o = {}) {
|
|
|
292
305
|
sources: lead ? lead.p.sources : [], ...(noReason && owners.length ? { ask: { owner: owners[0] } } : {}) };
|
|
293
306
|
p.sig = 'd:' + hash([id, ...items.map((x) => x.task + x.from.due)].join('|')); // rule 7: the date passing again is a new signature
|
|
294
307
|
if (o.drops && o.drops.has(p.sig)) continue;
|
|
295
|
-
p.items = p.items.filter((x) => !(o.drops && o.drops.has(itemSig(p, x))));
|
|
308
|
+
p.items = p.items.filter((x) => !(o.drops && (o.drops.has(itemSig(p, x)) || o.drops.has(factOfTask(ini, x.task, x.from.due)))));
|
|
296
309
|
if (!p.items.length) continue;
|
|
297
310
|
out.push(p);
|
|
298
311
|
}
|
|
@@ -320,6 +333,7 @@ function viewOf(F, ini, o = {}) {
|
|
|
320
333
|
if (p.group === 'status') p.items = p.items.filter((x) => T.has(x.task) && !T.get(x.task).closed);
|
|
321
334
|
if (p.group === 'names') p.items = p.items.filter((x) => T.has(x.task) && T.get(x.task).title !== x.to).map((x) => ({ ...x, from: T.get(x.task).title, ...(T.get(x.task).jira ? { jira: true } : {}) }));
|
|
322
335
|
if (!p.items.length && p.type !== 'stage' && p.group !== 'grill') continue;
|
|
336
|
+
if (p.group === 'grill' && factsOf(p).some((f) => F.drops.has(f))) continue;
|
|
323
337
|
props.push(p);
|
|
324
338
|
}
|
|
325
339
|
// Grill findings (lib/grill.js): 3 show, the best first (rank); the rest wait folded and come up as the PM keeps or drops one
|
|
@@ -373,6 +387,6 @@ function decorate(rows, F, items, o = {}) {
|
|
|
373
387
|
}
|
|
374
388
|
|
|
375
389
|
module.exports = {
|
|
376
|
-
LEDGER, GROUPS, TYPES, SOURCES, SOURCE_NAMES, RISK_DAYS, ledgerFile, read, readLines, append, fold, clean, idOf, sigOf, itemSig, tidyName, taskOf, lateOf,
|
|
390
|
+
LEDGER, GROUPS, TYPES, SOURCES, SOURCE_NAMES, RISK_DAYS, ledgerFile, read, readLines, append, fold, clean, idOf, sigOf, itemSig, factsOf, tidyName, taskOf, lateOf,
|
|
377
391
|
datesOf, viewOf, foundOf, historyOf, decorate, keptIds, newBatch, dn, isoN, md, hash, workday,
|
|
378
392
|
};
|
|
@@ -236,7 +236,7 @@ function pyramidSlideXml(s, L) {
|
|
|
236
236
|
text(1150, 686, 63, 22, String(s.n), { size: 15, color: navy ? '#C9D3EA' : '#8A8478', align: 'r', name: 'Page number' });
|
|
237
237
|
if (navy) {
|
|
238
238
|
text(67, 270, 1000, 22, s.tag.toUpperCase(), { size: 15, bold: true, color: '#C9D3EA' });
|
|
239
|
-
text(67, 300,
|
|
239
|
+
text(67, 300, 1100, 230, s.title, { size: s.size || 64, bold: true, color: '#FFFFFF', anchor: 'b', name: 'Title' });
|
|
240
240
|
text(67, 545, 1000, 70, s.sub, { size: 22, color: '#DDE4F3' });
|
|
241
241
|
return out;
|
|
242
242
|
}
|
|
@@ -248,6 +248,7 @@ function pyramidSlideXml(s, L) {
|
|
|
248
248
|
y += 30;
|
|
249
249
|
if (s.kind === 'answer') {
|
|
250
250
|
const right = s.ask && s.ask.question;
|
|
251
|
+
if (s.lead) { text(67, y - 8, 1120, 60, s.lead, { size: 18, color: L.soft, name: 'Lead' }); y += 64; }
|
|
251
252
|
s.supports.forEach((x, i) => {
|
|
252
253
|
const yy = y + i * 92;
|
|
253
254
|
if (i) shape(67, yy - 10, right ? 640 : 1146, 1, { fill: L.line });
|
|
@@ -473,12 +474,13 @@ const FORMATS = Object.freeze({
|
|
|
473
474
|
});
|
|
474
475
|
const BUILD = { docx, pptx, xlsx, csv: (r) => Buffer.from(csv(r), 'utf8') };
|
|
475
476
|
|
|
476
|
-
/** opts.look: the look lib/report-pyramid.js lookOf read (the route passes it); a cadence draft's .pptx is the Pyramid deck in that look.
|
|
477
|
+
/** opts.look: the look lib/report-pyramid.js lookOf read (the route passes it); a cadence draft's .pptx is the Pyramid deck in that look.
|
|
478
|
+
* opts.data: the structured cycle's <slot>.data.json (report-pyramid dataOf), for the sources not reached. */
|
|
477
479
|
function exportOf(markdown, fmt, opts = {}) {
|
|
478
480
|
if (!Object.prototype.hasOwnProperty.call(FORMATS, fmt)) return { ok: false, error: `fmt must be one of ${Object.keys(FORMATS).join(', ')}` };
|
|
479
481
|
const report = parseReport(markdown);
|
|
480
482
|
let buffer;
|
|
481
|
-
try { buffer = fmt === 'pptx' ? pptx(report, require('./report-pyramid.js').deckOf(markdown, opts.look)) : BUILD[fmt](report); } catch (e) { return { ok: false, error: 'the file could not be built: ' + String((e && e.message) || e).slice(0, 160) }; }
|
|
483
|
+
try { buffer = fmt === 'pptx' ? pptx(report, require('./report-pyramid.js').deckOf(markdown, opts.look, { data: opts.data || null })) : BUILD[fmt](report); } catch (e) { return { ok: false, error: 'the file could not be built: ' + String((e && e.message) || e).slice(0, 160) }; }
|
|
482
484
|
const stem = plain(report.title).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 60) || 'report';
|
|
483
485
|
return { ok: true, fmt, name: `${stem}.${fmt}`, mime: FORMATS[fmt].mime, buffer };
|
|
484
486
|
}
|