create-pmos 0.178.0 → 0.180.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 +1 -1
- package/payload/KIT-VERSION +1 -1
- 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/workspace/c3-report-setup-test.js +190 -0
- package/workspace/c3-reports-test.js +30 -97
- package/workspace/guide/console-calendar.md +17 -2
- package/workspace/guide/document-sheet.md +14 -0
- package/workspace/guide/index.md +3 -2
- package/workspace/guide/reports-dashboards.md +17 -28
- package/workspace/guide/reports-page.md +10 -5
- package/workspace/guide/reports-set-up.md +56 -0
- package/workspace/guide/reports.md +13 -1
- package/workspace/guide/settings.md +11 -2
- package/workspace/lib/doc-sync.js +28 -6
- package/workspace/lib/docs-cli.js +7 -11
- package/workspace/lib/four-panel.js +1 -1
- package/workspace/lib/headless.js +21 -2
- package/workspace/lib/notes-series.js +357 -0
- package/workspace/lib/notes.js +53 -4
- package/workspace/lib/report-checks.js +103 -0
- package/workspace/lib/report-cli.js +89 -4
- package/workspace/lib/report-export.js +347 -0
- package/workspace/lib/report-finder.js +156 -0
- package/workspace/lib/report-needs.js +1 -1
- package/workspace/lib/report-setups.js +743 -0
- package/workspace/lib/route-modules.js +1 -0
- package/workspace/lib/routes-c3-reports.js +35 -17
- package/workspace/lib/routes-doc-health.js +15 -1
- package/workspace/lib/routes-notes.js +27 -2
- package/workspace/lib/routes-report-setups.js +368 -0
- package/workspace/lib/schedule-logic.js +5 -2
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-demo-rep2.js +15 -0
- package/workspace/public/pm/c3-demo-rs.js +281 -0
- package/workspace/public/pm/c3-demo.js +1 -1
- package/workspace/public/pm/c3-doc-formats.js +152 -0
- package/workspace/public/pm/c3-doc-sheet.js +115 -25
- package/workspace/public/pm/c3-notes.js +299 -34
- package/workspace/public/pm/c3-quiet.js +2 -2
- package/workspace/public/pm/c3-report-print.css +20 -0
- package/workspace/public/pm/c3-report-setup.css +62 -0
- package/workspace/public/pm/c3-report-setup.js +260 -0
- package/workspace/public/pm/c3-report-store.js +208 -0
- package/workspace/public/pm/c3-reports.css +4 -0
- package/workspace/public/pm/c3-reports.js +47 -181
- package/workspace/public/pm/c3-settings.js +4 -3
- package/workspace/public/pm/c3-shell.js +15 -31
- package/workspace/public/pm/console3.css +13 -0
- package/workspace/run-tests.js +25 -1
- package/workspace/server.js +66 -5
- package/workspace/skills/doc-sync.skill +17 -8
- package/workspace/skills/report-find.skill +75 -0
- package/workspace/skills/report-setup.skill +67 -0
- package/workspace/test-files.json +12 -0
- package/workspace/test-lists/c3-notes-series.json +5 -0
- package/workspace/test-lists/report-workspace-ui.json +5 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Reports: dashboards of reports"
|
|
4
|
-
description: "How the console Reports page shows dashboards at portfolio, board and initiative scope, how to edit one, how to
|
|
5
|
-
timestamp: 2026-10-
|
|
4
|
+
description: "How the console Reports page shows dashboards at portfolio, board and initiative scope, how to edit one, how to send one as a report, and what the Charts tab holds."
|
|
5
|
+
timestamp: 2026-10-12
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Reports: dashboards of reports
|
|
@@ -11,9 +11,13 @@ A report answers one question. A dashboard is a set of reports for one scope. Op
|
|
|
11
11
|
|
|
12
12
|
## The three tabs
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- **
|
|
14
|
+
Each tab has one job. A line under the tabs repeats it: **Live** goes to Dashboards, **Sent** goes to Reports, **Building blocks** goes to Charts.
|
|
15
|
+
|
|
16
|
+
- **Dashboards.** The charts of one scope, drawn from the repository's records. A dashboard is live: it changes when your work changes. The tab shows how many dashboards there are.
|
|
17
|
+
- **Reports.** The documents you send, with their set-ups. See [Set up a report by asking](reports-set-up.md). The tab shows how many drafts wait for you.
|
|
18
|
+
- **Charts.** Every chart the workspace can draw, with the question it answers, the dashboards that use it and the reports that use it. The tab shows how many charts there are.
|
|
19
|
+
|
|
20
|
+
The old tab names still work. A link to Library opens Charts. A link to Drafts opens Reports.
|
|
17
21
|
|
|
18
22
|
The headline deck of the Reports page is above the tabs. It shows only report signals: claims that block sending, ticket keys in a leadership report, a digest that is due, and people who hold an old date. It never repeats the Briefing's cards. When there is no report signal, the deck says that nothing needs you. The scope buttons are on the same row as the tabs.
|
|
19
23
|
|
|
@@ -50,32 +54,17 @@ The Calendar page no longer carries these. They moved here.
|
|
|
50
54
|
|
|
51
55
|
The workspace keeps your layout for each scope in its runtime state. It does not write to the repository. **Reset to default** returns a scope to its first layout.
|
|
52
56
|
|
|
53
|
-
##
|
|
54
|
-
|
|
55
|
-
Press **Share**. A sheet shows the dashboard as a report: a headline, one section per report, what needs your call, and the sources. Choose **Leadership** for one sentence per report. Choose **Team** to add the numbers behind each sentence.
|
|
56
|
-
|
|
57
|
-
**Make a draft now** puts one draft in the outbox. Nothing is sent. With no channel connected, the outbox says so.
|
|
58
|
-
|
|
59
|
-
## Put a dashboard on a schedule
|
|
60
|
-
|
|
61
|
-
A schedule makes a ready draft of a dashboard and tells you on the bell. Nothing is sent. No sender channel is connected yet, so you send the draft yourself.
|
|
62
|
-
|
|
63
|
-
1. Press **Share**.
|
|
64
|
-
2. Choose the audience: **Leadership** or **Team**.
|
|
65
|
-
3. Choose **Weekly, Mondays 08:00** or **Monthly, 1st at 08:00**. The sheet shows the next draft.
|
|
66
|
-
4. Press **Save schedule**.
|
|
57
|
+
## Send a dashboard as a report
|
|
67
58
|
|
|
68
|
-
|
|
59
|
+
A dashboard is live. A report is a document that you send on a day. To send a dashboard as a report, press **Send this as a report…** in the head of the dashboard.
|
|
69
60
|
|
|
70
|
-
|
|
61
|
+
1. A sheet shows the proposed report: its name, what it is built from, the output, the schedule and the audience.
|
|
62
|
+
2. Change the name. Choose the output: **PDF** or **Google Slides**. Choose the schedule: **Weekly**, **Every 2 weeks** or **Monthly**. Choose the audience: **Leadership** or **Team**.
|
|
63
|
+
3. Press **Set up**.
|
|
71
64
|
|
|
72
|
-
|
|
65
|
+
PMOS shows the first draft date. Each cycle is a snapshot document of the dashboard as it was on that day. The document is in Reports, under Documents, and the set-up is under Set up. The dashboard stays live.
|
|
73
66
|
|
|
74
|
-
|
|
67
|
+
PMOS drafts only. It sends nothing. It writes to Google only when you press **Create the copy** in the document.
|
|
75
68
|
|
|
76
|
-
|
|
69
|
+
The Share sheet and the schedule control of a dashboard are gone. Each schedule is now the schedule of a report. See and change it in Reports, under Set up.
|
|
77
70
|
|
|
78
|
-
- **Drafts.** One row for each dashboard draft. A row says **Made by schedule** or **Made by you**. Press **Preview** to read the text. Press **Discard** to remove it from the list. **Undo** in the message brings it back. The log keeps the line.
|
|
79
|
-
- **Scheduled.** One row for each schedule: the dashboard, how often, the audience and the next draft.
|
|
80
|
-
- **Handled for you**, **What I could stop** and **Data you can lean on.** The Briefing used to show these. They come from the same briefing data. A card with no data does not show.
|
|
81
|
-
- **Reports to send.** The four report families, the claims check and the outbox. They work as before.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Reports: draft, read and issue"
|
|
4
4
|
description: "How to draft a report, add your notes, issue it, and print or copy it. How the report schedules and the Today cards for reports work."
|
|
5
|
-
timestamp: 2026-10-
|
|
5
|
+
timestamp: 2026-10-12
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Reports: draft, read and issue
|
|
@@ -15,11 +15,16 @@ The workspace keeps each report in a file of the repository, for example `planni
|
|
|
15
15
|
|
|
16
16
|
Open the Reports tab with the shortcut `g r`, from the command palette, or from the Today card of a report. Reports shows the repository that is open.
|
|
17
17
|
|
|
18
|
-
The page has three
|
|
18
|
+
The Reports page has three tabs: Dashboards, Reports and Charts. See [Reports: dashboards of reports](reports-dashboards.md) and [Set up a report by asking](reports-set-up.md).
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **
|
|
20
|
+
The Reports tab is for the documents that you issue. It has four parts:
|
|
21
|
+
|
|
22
|
+
- **Ask PMOS.** Describe a report. PMOS drafts it.
|
|
23
|
+
- **Found in your work.** The reports PMOS found that you already issue.
|
|
24
|
+
- **Set up.** One row for each report with a schedule.
|
|
25
|
+
- **Documents.** One row for each cycle of each report. A draft or an issued document opens in the [document sheet](document-sheet.md).
|
|
26
|
+
|
|
27
|
+
The parts that this guide describes below, such as Draft now, Edit notes and the report schedules, are the controls of the cadence report. The Reports tab does not show them. The `pmos report` commands in "In a terminal" do the same acts.
|
|
23
28
|
|
|
24
29
|
## Draft a report
|
|
25
30
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Guide
|
|
3
|
+
title: "Set up a report by asking, and the report finder"
|
|
4
|
+
description: "How to ask PMOS to draft a report you send, how the report finder finds reports you already send, how to set one up, and how a cycle becomes a document you issue."
|
|
5
|
+
timestamp: 2026-10-12
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Set up a report by asking, and the report finder
|
|
9
|
+
|
|
10
|
+
Many PMs send the same report again and again, for example an update for a standing meeting. PMOS can draft that report on its day. You read it, change it and issue it. PMOS drafts only. It never sends a report.
|
|
11
|
+
|
|
12
|
+
Open the **Reports** tab of the console. It has four parts: Ask PMOS, Found in your work, Set up and Documents.
|
|
13
|
+
|
|
14
|
+
## Ask PMOS
|
|
15
|
+
|
|
16
|
+
1. Write what the report is in the Ask box. For example: "My TSC update every month, due a week before TSC".
|
|
17
|
+
2. Press **Set up**. PMOS reads your earlier copies and learns the sections. This takes a minute. You can leave the page. **Cancel** stops it.
|
|
18
|
+
3. PMOS shows its proposal: what it found, the proposed report, what it learned from, and the fixes it will apply.
|
|
19
|
+
4. Press **Set up** to accept the proposal. Press **Change** to say what to change, for example "Draft it on Tuesday instead". PMOS revises the proposal. Press **Cancel** to drop it.
|
|
20
|
+
|
|
21
|
+
A proposal shows the name, the purpose, the audience, the output, the schedule, the sources and the sections in order. Change the output with the buttons: Google Slides, Google Doc, Google Sheets, Confluence or PDF. PMOS shows the report in the same format in the sheet.
|
|
22
|
+
|
|
23
|
+
If PMOS finds no earlier copy, the proposal says so and works from your words only.
|
|
24
|
+
|
|
25
|
+
## Found in your work
|
|
26
|
+
|
|
27
|
+
PMOS looks for reports that you send already. It reads your calendar, Slack, Drive and the subjects of mail you sent. It never reads a message body. Each finding says why PMOS thinks it is a report, shows the evidence, and says **Likely** or **Not sure**.
|
|
28
|
+
|
|
29
|
+
For each finding you can:
|
|
30
|
+
|
|
31
|
+
- Press **Set up** to accept the proposal as it is.
|
|
32
|
+
- Press **Change first** to open the proposal and change it.
|
|
33
|
+
- Press **Not a report**. The finding goes away at once, and PMOS says it will not suggest it again. Press **Undo** in the next five seconds to bring it back. After five seconds the choice is saved.
|
|
34
|
+
|
|
35
|
+
The most likely finding also shows on the Briefing as **Report found**. Press **See what PMOS found** to open it on the Reports tab. Turn the finder off, or change its sources, in [Settings](settings.md), under Connectors.
|
|
36
|
+
|
|
37
|
+
## Set up and Documents
|
|
38
|
+
|
|
39
|
+
**Set up** has one row for each report: its source, output, schedule, next draft and last issue. Each row has these buttons:
|
|
40
|
+
|
|
41
|
+
- **Open** opens the current draft.
|
|
42
|
+
- **All cycles** lists every cycle of the report.
|
|
43
|
+
- **Pause** stops the drafts. **Resume** starts them again.
|
|
44
|
+
|
|
45
|
+
**Documents** has one row for each cycle: Draft or Issued, the output, the copy state and who made it. Filter by state or by source. Press **Open** to read a document in the [document sheet](document-sheet.md).
|
|
46
|
+
|
|
47
|
+
## What PMOS does and does not do
|
|
48
|
+
|
|
49
|
+
- PMOS drafts on the day of the schedule. It sends nothing.
|
|
50
|
+
- PMOS writes to Google or to Confluence only when you press **Create the copy** or **Update the copy**.
|
|
51
|
+
- A download happens only when you press a download item.
|
|
52
|
+
- You issue a report. An issued report is frozen.
|
|
53
|
+
|
|
54
|
+
## Send a dashboard as a report
|
|
55
|
+
|
|
56
|
+
To make a report from a dashboard, use **Send this as a report…** on the dashboard. See [Reports: dashboards of reports](reports-dashboards.md).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Reports: draft and issue a cadence report"
|
|
4
4
|
description: "How pmos report drafts a cadence report with every number, how the notes are kept when you draft again, and how you issue and freeze a report."
|
|
5
|
-
timestamp: 2026-10-
|
|
5
|
+
timestamp: 2026-10-12
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Reports: draft and issue a cadence report
|
|
@@ -64,6 +64,14 @@ pmos report issue biweekly 2026-10-16
|
|
|
64
64
|
|
|
65
65
|
`pmos report list [<cadence>]` shows each draft and each issued report, newest first, with who issued it.
|
|
66
66
|
|
|
67
|
+
`pmos report propose` is for an agent run. It files one report proposal or finding. It refuses a pattern that you dismissed with Not a report.
|
|
68
|
+
|
|
69
|
+
`pmos report definitions` lists the report definitions. It only reads.
|
|
70
|
+
|
|
71
|
+
`pmos report setups` lists the recurring reports that are set up.
|
|
72
|
+
|
|
73
|
+
`pmos report finder` shows the report finder setting, or changes it.
|
|
74
|
+
|
|
67
75
|
Every command accepts `--root <dir>` to name the repository. `draft` and `list` also accept `--json`.
|
|
68
76
|
|
|
69
77
|
## The window
|
|
@@ -73,3 +81,7 @@ The window starts at the time of the last issued report of the same cadence, so
|
|
|
73
81
|
## The report lane
|
|
74
82
|
|
|
75
83
|
An initiative in the report lane completes its last step when you issue its report. The workflow engine reads the issued line for the initiative's `doc_path` in `issues.jsonl`.
|
|
84
|
+
|
|
85
|
+
## Reports of your own
|
|
86
|
+
|
|
87
|
+
The cadence report is the report that PMOS defines. For a report that you send already, such as an update for a standing meeting, use the Reports tab of the console. Describe the report, or let the report finder find it. See [Set up a report by asking](reports-set-up.md).
|
|
@@ -27,10 +27,10 @@ Settings is not a tab. While it is open, no tab is selected. Press a tab to go b
|
|
|
27
27
|
| About you | Your name, your role, what you do and your job description. The Welcome page asks for the same four on first run. |
|
|
28
28
|
| Workspaces | Your workspaces: rename, remove, add one by its folder. Which workspace opens on start, and what the Briefing covers. |
|
|
29
29
|
| Boards and workflows | The workflow templates for new boards and the default template. The rules that move a card. To change the workflow of one board, open that board in Initiatives. |
|
|
30
|
-
| Connectors | How your Claude session reaches Google Calendar, Jira, Wispr Flow and a sender. The import window, the refresh time, how private events show, and attendee names. |
|
|
30
|
+
| Connectors | How your Claude session reaches Google Calendar, Jira, Wispr Flow and a sender. The import window, the refresh time, how private events show, and attendee names. The report finder is the last block. |
|
|
31
31
|
| Recording | The live transcript: what to listen to, the Whisper model, the language, the echo removal, the final pass after Stop, the words to recognize, and how long kept audio stays. A card at the top says what recording uses now. |
|
|
32
32
|
| Day and time | The time zone, the first day of the week, your working hours, the free time before a meeting with prep, the buffer between meetings, and the time-of-day tint and daylight strip. |
|
|
33
|
-
| Sharing | How a
|
|
33
|
+
| Sharing | How a report is shared: off, a link, or sent by the sender. The audiences of the workspace. Schedules are not here. Each report has its own schedule, in Reports, under Set up. |
|
|
34
34
|
| Access and devices | Use from a phone or another computer: the host, the tunnel and the people who may open it. |
|
|
35
35
|
| Claude sessions | What a new session starts with: the CLI, the model, the Claude profile and the launch flags. The session limits. Validate consent for this workspace. |
|
|
36
36
|
| Privacy and data | A list of what leaves this computer and when. Whether a TypeSafe key is saved, the local model that answers when there is no key (Off, Winnow or Laya), and the data folder. |
|
|
@@ -74,3 +74,12 @@ The other settings are kept in the PMOS state directory, in `settings.json` for
|
|
|
74
74
|
- A setting changes what the page stores. Some features read their setting in a later release. Until then, the feature uses its default.
|
|
75
75
|
- Settings changes templates and defaults for boards. It does not change an existing board.
|
|
76
76
|
- Nothing on this page sends a message or shares a dashboard by itself.
|
|
77
|
+
|
|
78
|
+
## The report finder
|
|
79
|
+
|
|
80
|
+
The report finder is the last block of **Connectors**. It looks for reports that you send already, and shows what it found on the Reports tab and on the Briefing.
|
|
81
|
+
|
|
82
|
+
- **Look for reports you send.** Choose **Weekly** or **Off**. Weekly looks every Monday. Off stops it.
|
|
83
|
+
- **Sources.** The finder reads Calendar, Slack and Drive. It reads the subjects and the recipient counts of mail you sent, only when you turn **Sent mail subjects** on. It never reads a message body. Sent mail subjects are off until you turn them on.
|
|
84
|
+
|
|
85
|
+
See [Set up a report by asking](reports-set-up.md).
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
* readingsByLink(rows) → Map "<path>|<kind>" → readings, oldest first (refused lines skipped)
|
|
12
12
|
* runsOf(rows) → { runs, open, last, syncs } checks, and the syncs the PM pressed
|
|
13
13
|
* signal(link, readings, repo) → { signal, words, basis, checked_at, … }
|
|
14
|
-
* remoteIdOf(kind, target) → the Google
|
|
14
|
+
* remoteIdOf(kind, target) → the Google file id or Confluence page id in a pasted link
|
|
15
|
+
* kindOfTarget(target) → 'google' | 'google-slides' | 'google-sheets' | 'confluence' | null, from a pasted address
|
|
16
|
+
* copyKindOf(output) → the copy kind a report output makes (pdf: null)
|
|
15
17
|
*
|
|
16
18
|
* ── How a signal is decided ────────────────────────────────────────────────────────────────────────────────────
|
|
17
19
|
* A READING is what a check session saw for one link: whether the copy exists, its version and modified time, and
|
|
@@ -36,8 +38,10 @@
|
|
|
36
38
|
* writes nothing: a session holding the PM's connectors does the write and records it through `pmos docs`.
|
|
37
39
|
*/
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
+
// `google` is a Google Doc (the first kind, so its name stays); Slides and Sheets joined it with report-workspace.
|
|
42
|
+
const KINDS = Object.freeze(['google', 'google-slides', 'google-sheets', 'confluence']);
|
|
43
|
+
const KIND_LABEL = Object.freeze({ google: 'Google Docs', 'google-slides': 'Google Slides', 'google-sheets': 'Google Sheets', confluence: 'Confluence' });
|
|
44
|
+
const GOOGLE_KINDS = Object.freeze(['google', 'google-slides', 'google-sheets']);
|
|
41
45
|
const EVENTS = Object.freeze(['run', 'reading']);
|
|
42
46
|
const SIGNALS = Object.freeze(['same', 'ahead', 'behind', 'diverged', 'unchecked', 'unreadable', 'missing-remote', 'missing-repo']);
|
|
43
47
|
// What needs the PM's eye: everything but `same`. `unchecked` is included — a copy nobody read is not known to match.
|
|
@@ -56,11 +60,29 @@ function okPath(p) {
|
|
|
56
60
|
return !(p.startsWith('/') || p.includes('\\') || p.split('/').some((s) => s === '..' || s === '.' || s === ''));
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The copy kind a pasted address names, or null: Google Slides (docs.google.com/presentation), Google Sheets
|
|
65
|
+
* (docs.google.com/spreadsheets), a Google Doc (any other Google address) or Confluence. The server decides; a page may guess too.
|
|
66
|
+
*/
|
|
67
|
+
function kindOfTarget(target) {
|
|
68
|
+
const t = String(target || '').trim();
|
|
69
|
+
if (!t) return null;
|
|
70
|
+
if (remoteIdOf('confluence', t) && /atlassian|confluence|\/wiki\/|pageId=/i.test(t)) return 'confluence';
|
|
71
|
+
if (!remoteIdOf('google', t) || !/google|docs\.|drive\./i.test(t)) return null;
|
|
72
|
+
if (/\/presentation\//i.test(t)) return 'google-slides';
|
|
73
|
+
if (/\/spreadsheets\//i.test(t)) return 'google-sheets';
|
|
74
|
+
return 'google';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The copy kind a report's output makes: a Google Doc, Slides or Sheets, a Confluence page; a PDF has no live copy (null). */
|
|
78
|
+
const COPY_KIND_OF_OUTPUT = Object.freeze({ 'google-doc': 'google', 'google-slides': 'google-slides', 'google-sheets': 'google-sheets', confluence: 'confluence', pdf: null });
|
|
79
|
+
const copyKindOf = (output) => (Object.prototype.hasOwnProperty.call(COPY_KIND_OF_OUTPUT, output) ? COPY_KIND_OF_OUTPUT[output] : null);
|
|
80
|
+
|
|
59
81
|
/** The id inside a pasted link, so the same copy pasted in two shapes is one copy. */
|
|
60
82
|
function remoteIdOf(kind, target) {
|
|
61
83
|
const t = String(target || '').trim();
|
|
62
84
|
if (!t) return null;
|
|
63
|
-
if (kind
|
|
85
|
+
if (GOOGLE_KINDS.includes(kind)) {
|
|
64
86
|
const m = /\/d\/([A-Za-z0-9_-]{10,})/.exec(t) || /[?&]id=([A-Za-z0-9_-]{10,})/.exec(t);
|
|
65
87
|
if (m) return m[1];
|
|
66
88
|
return /^[A-Za-z0-9_-]{10,}$/.test(t) ? t : null;
|
|
@@ -220,6 +242,6 @@ function signal(link, readings, repo) {
|
|
|
220
242
|
}
|
|
221
243
|
|
|
222
244
|
module.exports = {
|
|
223
|
-
KINDS, KIND_LABEL, EVENTS, SIGNALS, ATTENTION, RUN_ID, USES,
|
|
224
|
-
checkRow, readingsByLink, runsOf, signal, remoteIdOf, okPath, keyOf,
|
|
245
|
+
KINDS, KIND_LABEL, GOOGLE_KINDS, EVENTS, SIGNALS, ATTENTION, RUN_ID, USES,
|
|
246
|
+
checkRow, readingsByLink, runsOf, signal, remoteIdOf, kindOfTarget, copyKindOf, okPath, keyOf,
|
|
225
247
|
};
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* pmos docs [list] [--attention] [--json] each linked document and its signal per copy
|
|
6
6
|
* pmos docs link <file> <google-or-confluence-link> link a repository file to a copy
|
|
7
|
-
* pmos docs unlink <file> google|confluence remove that link (the copy is not touched)
|
|
7
|
+
* pmos docs unlink <file> google|google-slides|google-sheets|confluence remove that link (the copy is not touched)
|
|
8
8
|
* pmos docs check-start [--json] open a check: what to read, with each file's hash
|
|
9
9
|
* pmos docs record <check> --file <readings.json> record what the session read
|
|
10
10
|
* pmos docs check-finish <check> [--summary "…"] close the check
|
|
11
11
|
* pmos docs find [words] the repository's markdown files that match, with or without a copy
|
|
12
|
-
* pmos docs sync-start <file> google|confluence --use pmos|copy [--json] open a sync of one copy (the sync session)
|
|
12
|
+
* pmos docs sync-start <file> google|google-slides|google-sheets|confluence --use pmos|copy [--json] open a sync of one copy (the sync session)
|
|
13
13
|
* pmos docs sync-finish <sync> --version <v> [--modified-at <t>] [--url <u>] close it: both sides now the same
|
|
14
14
|
* pmos docs sync-fail <sync> --reason "…" close it: nothing was written, and why
|
|
15
15
|
*
|
|
@@ -33,12 +33,12 @@ const USAGE = [
|
|
|
33
33
|
'usage: pmos docs [list|link|unlink|find|check-start|record|check-finish|sync-start|sync-finish|sync-fail] — is each linked document the same as its Google Docs and Confluence copy',
|
|
34
34
|
' pmos docs [list] [--attention] [--json] each linked document and its signal: same, ahead, behind, edited in both',
|
|
35
35
|
' pmos docs link <file> <link> link a repository file to a Google Doc or a Confluence page',
|
|
36
|
-
' pmos docs unlink <file> google|confluence remove that link; the copy is not touched',
|
|
36
|
+
' pmos docs unlink <file> google|google-slides|google-sheets|confluence remove that link; the copy is not touched',
|
|
37
37
|
' pmos docs check-start [--json] (check session) open a check and list what to read',
|
|
38
38
|
' pmos docs record <check> --file <readings.json> (check session) record what was read',
|
|
39
39
|
' pmos docs check-finish <check> [--summary "…"] (check session) close the check',
|
|
40
40
|
' pmos docs find [words] the repository\'s markdown files that match',
|
|
41
|
-
' pmos docs sync-start <file> google|confluence --use pmos|copy (sync session) open a sync of one copy',
|
|
41
|
+
' pmos docs sync-start <file> google|google-slides|google-sheets|confluence --use pmos|copy (sync session) open a sync of one copy',
|
|
42
42
|
' pmos docs sync-finish <sync> --version <v> [--modified-at <t>] [--url <u>] (sync session) both sides are now the same',
|
|
43
43
|
' pmos docs sync-fail <sync> --reason "…" (sync session) nothing was written, and why',
|
|
44
44
|
'Nothing here writes a document. Press Check now, or Use … version on one copy, in Docs.',
|
|
@@ -66,11 +66,7 @@ function whoOf(flags, env) {
|
|
|
66
66
|
if (flags.by) return 'profile:' + String(flags.by).slice(0, 60);
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
69
|
-
const kindOf = (target) =>
|
|
70
|
-
if (D.remoteIdOf('confluence', target) && /atlassian|confluence|\/wiki\/|pageId=/i.test(target)) return 'confluence';
|
|
71
|
-
if (D.remoteIdOf('google', target) && /google|docs\.|drive\./i.test(target)) return 'google';
|
|
72
|
-
return null;
|
|
73
|
-
};
|
|
69
|
+
const kindOf = (target) => D.kindOfTarget(target);
|
|
74
70
|
const ago = (iso, now) => {
|
|
75
71
|
if (!iso) return 'never';
|
|
76
72
|
const m = Math.round((now - Date.parse(iso)) / 60000);
|
|
@@ -125,7 +121,7 @@ async function run(argv, { say = console.log, cwd = process.cwd(), env = process
|
|
|
125
121
|
|
|
126
122
|
if (sub === 'unlink') {
|
|
127
123
|
const [file, kind] = [pos[1], pos[2]];
|
|
128
|
-
if (!file || !D.KINDS.includes(kind)) return fail('usage: pmos docs unlink <file> google|confluence', 2);
|
|
124
|
+
if (!file || !D.KINDS.includes(kind)) return fail('usage: pmos docs unlink <file> google|google-slides|google-sheets|confluence', 2);
|
|
129
125
|
const r = rel(file);
|
|
130
126
|
if (!r) return fail(`${file} is not inside this repository`);
|
|
131
127
|
const w = store.removeLink(root, { path: r, kind });
|
|
@@ -186,7 +182,7 @@ async function run(argv, { say = console.log, cwd = process.cwd(), env = process
|
|
|
186
182
|
}
|
|
187
183
|
if (sub === 'sync-start') {
|
|
188
184
|
const [file, kind] = [pos[1], pos[2]];
|
|
189
|
-
if (!file || !D.KINDS.includes(kind) || !D.USES.includes(flags.use)) return fail('usage: pmos docs sync-start <file> google|confluence --use pmos|copy', 2);
|
|
185
|
+
if (!file || !D.KINDS.includes(kind) || !D.USES.includes(flags.use)) return fail('usage: pmos docs sync-start <file> google|google-slides|google-sheets|confluence --use pmos|copy', 2);
|
|
190
186
|
if (!by) return fail('a sync must name who writes: run it in a workspace session, or add --by <profile>');
|
|
191
187
|
const r = rel(file);
|
|
192
188
|
if (!r) return fail(`${file} is not inside this repository`);
|
|
@@ -68,7 +68,7 @@ const INITIATIVE_LINKS = 'initiative-links.jsonl';
|
|
|
68
68
|
const ARTIFACT_SYNC = 'artifact-sync.jsonl';
|
|
69
69
|
|
|
70
70
|
/** The destinations the surface can declare. A kind outside this set is refused, not stored. */
|
|
71
|
-
const LINK_KINDS = ['confluence', 'jira', 'google'];
|
|
71
|
+
const LINK_KINDS = ['confluence', 'jira', 'google', 'google-slides', 'google-sheets'];
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* The closed vocabulary a link row may use about itself. Three states, and the word `connected` is
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* (initiative workspace-headless-jobs; PM decision 2026-10-11: keep Jev, no direct Claude API, background
|
|
4
4
|
* jobs that need no human run headless).
|
|
5
5
|
*
|
|
6
|
-
* kindFor({ agent, origin, skill, env }) → 'notes' | 'skill' | 'ask' | 'drive' | 'scan-web' | 'scan-repo' | null (null: open a pane, as before)
|
|
6
|
+
* kindFor({ agent, origin, skill, env }) → 'notes' | 'skill' | 'ask' | 'drive' | 'scan-web' | 'scan-repo' | 'report-setup' | 'report-find' | null (null: open a pane, as before)
|
|
7
7
|
* argsFor(kind, { modelArgs, addDirs }) → the argv after `claude`
|
|
8
8
|
* parseResult(stdout) → { ok, text, isError, turns, durationMs, costUsd, denied[] }
|
|
9
9
|
* logLines(result, exitCode) → the lines the read-only log pane shows at the end
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
* `pmos scan file`, and nothing else: it reads untrusted pages, so it holds no private data (no Read, Glob or Grep, no repository
|
|
26
26
|
* path in its prompt, no --add-dir). `scan-repo` reads the repository and the findings the web run filed, so it has no web tool and
|
|
27
27
|
* nothing that sends. Neither kind ever runs in a pane.
|
|
28
|
+
*
|
|
29
|
+
* The `report-setup` and `report-find` kinds are the report workspace (initiative report-workspace). `report-setup` (origin `report-setup:<ask>`)
|
|
30
|
+
* turns the PM's request in the Ask box into ONE proposal: it reads the repository, the report definitions and (where the PM's own settings allow
|
|
31
|
+
* it) the PM's earlier copies through read-only connectors, and its only write is `pmos report propose`. `report-find` (origin `report-find:<run>`)
|
|
32
|
+
* is the weekly look for reports the PM already sends. It reads calendar, Slack, mail SUBJECTS and file names, which is untrusted text, so it holds
|
|
33
|
+
* no Write, no Edit, no web fetch and no tool that sends or posts, and its only write is `pmos report propose`. A connector tool is named in neither
|
|
34
|
+
* list: a connector read runs only where the PM's own Claude Code settings already allow it, and a connector write is denied and reported. A proposal
|
|
35
|
+
* is data; only the PM's press on the Reports page sets a report up. Neither kind ever runs in a pane.
|
|
28
36
|
*/
|
|
29
37
|
|
|
30
38
|
// The skills whose every start runs headless: they read, file a result for PMOS, and send nothing out.
|
|
@@ -43,6 +51,9 @@ const DRIVE_ORIGINS = Object.freeze(['drive:']);
|
|
|
43
51
|
// the repository and fetches nothing. Neither has both untrusted input and a way out (PRD: trust boundary).
|
|
44
52
|
const SCAN_WEB_ORIGINS = Object.freeze(['scan-web:']);
|
|
45
53
|
const SCAN_REPO_ORIGINS = Object.freeze(['scan-repo:', 'scan-suggest:']);
|
|
54
|
+
// report-workspace: the Ask box run (origin `report-setup:<ask>`) and the weekly finder run (origin `report-find:<run>`).
|
|
55
|
+
const REPORT_SETUP_ORIGINS = Object.freeze(['report-setup:']);
|
|
56
|
+
const REPORT_FIND_ORIGINS = Object.freeze(['report-find:']);
|
|
46
57
|
|
|
47
58
|
// What each kind may use. Read-only search is always allowed. The notes passes write their JSON (and rename the .tmp
|
|
48
59
|
// file) in their notes folder, which --add-dir opens to them; the skills also run `pmos` verbs and repository scripts.
|
|
@@ -56,6 +67,12 @@ const TOOLS = Object.freeze({
|
|
|
56
67
|
// The repository run: read and search the repository, `pmos scan` (its PM-only verbs refuse an agent session), the initiative list and git log.
|
|
57
68
|
// No WebSearch, no WebFetch, no curl, no git push: it reads the web run's findings, which are untrusted text, so it has nothing that sends or fetches.
|
|
58
69
|
'scan-repo': Object.freeze(['Read', 'Glob', 'Grep', 'Bash(pmos scan *)', 'Bash(pmos initiative list *)', 'Bash(git log *)']),
|
|
70
|
+
// The Ask box run: read and search, the one verb that files a proposal, the read-only list of definitions and git log. No Write, no Edit, no web,
|
|
71
|
+
// nothing that sends. The PM's earlier copies are read through a connector only where the PM's own settings allow it (as for `drive`).
|
|
72
|
+
'report-setup': Object.freeze(['Read', 'Glob', 'Grep', 'Bash(pmos report propose *)', 'Bash(pmos report definitions *)', 'Bash(git log *)']),
|
|
73
|
+
// The finder run: read and search, and the one verb that files a finding. Calendar, Slack, mail subjects and Drive are read through the PM's own
|
|
74
|
+
// connectors where the PM's settings allow it. No web fetch, no git, no Write or Edit, nothing that sends or posts.
|
|
75
|
+
'report-find': Object.freeze(['Read', 'Glob', 'Grep', 'Bash(pmos report propose *)']),
|
|
59
76
|
skill: Object.freeze(['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash(pmos *)', 'Bash(node *)',
|
|
60
77
|
'Bash(git log *)', 'Bash(git status *)', 'Bash(git diff *)', 'Bash(ls *)', 'Bash(date *)']),
|
|
61
78
|
});
|
|
@@ -74,6 +91,8 @@ function kindFor({ agent, origin, skill, env } = {}) {
|
|
|
74
91
|
if (DRIVE_ORIGINS.some((p) => o.startsWith(p))) return 'drive';
|
|
75
92
|
if (SCAN_WEB_ORIGINS.some((p) => o.startsWith(p))) return 'scan-web';
|
|
76
93
|
if (SCAN_REPO_ORIGINS.some((p) => o.startsWith(p))) return 'scan-repo';
|
|
94
|
+
if (REPORT_SETUP_ORIGINS.some((p) => o.startsWith(p))) return 'report-setup';
|
|
95
|
+
if (REPORT_FIND_ORIGINS.some((p) => o.startsWith(p))) return 'report-find';
|
|
77
96
|
if (skill && HEADLESS_SKILLS.includes(String(skill))) return 'skill';
|
|
78
97
|
return null;
|
|
79
98
|
}
|
|
@@ -125,4 +144,4 @@ function logLines(r, exitCode) {
|
|
|
125
144
|
return out;
|
|
126
145
|
}
|
|
127
146
|
|
|
128
|
-
module.exports = { HEADLESS_SKILLS, HEADLESS_ORIGINS, ASK_ORIGINS, DRIVE_ORIGINS, SCAN_WEB_ORIGINS, SCAN_REPO_ORIGINS, TOOLS, DEFAULT_TIMEOUT_MS, kindFor, argsFor, timeoutMs, parseResult, logLines, switchedOff };
|
|
147
|
+
module.exports = { HEADLESS_SKILLS, HEADLESS_ORIGINS, ASK_ORIGINS, DRIVE_ORIGINS, SCAN_WEB_ORIGINS, SCAN_REPO_ORIGINS, REPORT_SETUP_ORIGINS, REPORT_FIND_ORIGINS, TOOLS, DEFAULT_TIMEOUT_MS, kindFor, argsFor, timeoutMs, parseResult, logLines, switchedOff };
|