create-pmos 0.38.7 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/package.json +1 -1
  2. package/payload/KIT-VERSION +1 -1
  3. package/payload/planning/evals/eval-calibration.md +1 -1
  4. package/payload/planning/okrs/health-budgets.md +1 -1
  5. package/payload/state/discoveries.md +1 -1
  6. package/workspace/README.md +64 -1
  7. package/workspace/bin/pmos.js +10 -1
  8. package/workspace/guide/index.md +38 -0
  9. package/workspace/guide/initiatives-and-next-steps.md +72 -0
  10. package/workspace/guide/layout.md +59 -0
  11. package/workspace/guide/scheduled-tasks-and-cli.md +41 -0
  12. package/workspace/guide/statuses.md +53 -0
  13. package/workspace/guide/validate-with-typesafe.md +47 -0
  14. package/workspace/lib/initiative-files.js +134 -0
  15. package/workspace/lib/judgment.js +65 -0
  16. package/workspace/lib/mobile-nav.js +1 -1
  17. package/workspace/lib/next-step-actions.js +139 -0
  18. package/workspace/lib/next-steps.js +133 -0
  19. package/workspace/lib/prompt-input.js +18 -0
  20. package/workspace/lib/schedule-cli.js +259 -0
  21. package/workspace/lib/schedule-logic.js +23 -1
  22. package/workspace/lib/secret-scan.js +28 -0
  23. package/workspace/lib/session-budget.js +35 -0
  24. package/workspace/lib/skill-store.js +11 -3
  25. package/workspace/lib/status-groups.js +109 -0
  26. package/workspace/lib/transcripts.js +73 -0
  27. package/workspace/lib/typesafe-settings.js +119 -0
  28. package/workspace/lib/typesafe.js +124 -0
  29. package/workspace/lib/validate.js +304 -0
  30. package/workspace/package-lock.json +1 -1
  31. package/workspace/package.json +3 -3
  32. package/workspace/public/index.html +2 -0
  33. package/workspace/public/pm/app.js +53 -18
  34. package/workspace/public/pm/data.js +41 -5
  35. package/workspace/public/pm/mobile.js +1 -1
  36. package/workspace/public/pm/modes-live.js +171 -0
  37. package/workspace/public/pm/modes.js +25 -0
  38. package/workspace/public/pm/next-step-ui.js +185 -0
  39. package/workspace/public/pm/panel-detail.js +121 -1
  40. package/workspace/public/pm/panel-initiatives.js +61 -3
  41. package/workspace/public/pm/panel-repos.js +2 -2
  42. package/workspace/public/pm/panel-tabs.js +38 -2
  43. package/workspace/public/pm/panel-trees.js +9 -2
  44. package/workspace/public/pm/state.js +9 -4
  45. package/workspace/public/pm/status-banner.js +87 -0
  46. package/workspace/public/pm/tab-doc.js +11 -6
  47. package/workspace/server.js +434 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.38.7",
3
+ "version": "0.39.0",
4
4
  "description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@8c38e46
1
+ kernel-source: pmos@063ee3c
2
2
  assembled: 2026-10-03
3
3
  mode: vendor
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Eval calibration log"
4
4
  description: "Append-only record of every PM override of an evaluator verdict."
5
- generated: {by: process:create-pmos, at: "2026-10-03T01:20:42Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-03T07:05:43Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Health budgets"
4
4
  description: "Closed registry of health-budget anchors (D59), edited only by the PM."
5
- generated: {by: process:create-pmos, at: "2026-10-03T01:20:42Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-03T07:05:43Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -2,7 +2,7 @@
2
2
  type: Reference
3
3
  title: "Discoveries and friction log"
4
4
  description: "Append-only record of what runs discovered and where the process rubbed."
5
- generated: {by: process:create-pmos, at: "2026-10-03T01:20:42Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-03T07:05:43Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -46,6 +46,69 @@ npm run test:ui # browser checks: terminal geometry, reader scroll, markdow
46
46
  ```
47
47
  </details>
48
48
 
49
+ ## Status clarity and the user guide (`status-clarity`)
50
+
51
+ The initiative list can say what each status means. Choose **status**, then **what do these mean?**: one row per group with what it means, the fact it is read from, who acts, what moves it and what you can do. The text comes from `lib/status-groups.js`, the table that also files the rows, so the legend and the grouping cannot disagree.
52
+
53
+ The initiative detail opens with a **status banner**: the group, why (the evidence the server derived, for example the open item in its own words), who acts, the next thing to do, and what changes the status. Next steps are split into **yours** and **mine**; each item has an `id`, an `owner` (`pm` or `agent`) and `act` (`null` for now). Hold, close, cancel and reopen are explained under the control and every change is listed. No other status can be set by hand: they are read from git and the ledgers. Acceptance is the PM's and is recorded in a session, so the banner says how and the workspace never records one.
54
+
55
+ Files listed under **files from git** open in a read-only tab. A file that exists only on the initiative's branch is read from that branch through `GET /api/doc?ref=initiative/<id>` (an initiative branch only, text under 2 MB). A binary file says it cannot be previewed.
56
+
57
+ The user guide is `workspace/guide/*.md`. The **guide** item in the settings and tools drawer opens its pages in a read-only tab (`GET /api/guide`). A change to `workspace/public/pm/**` must change the guide in the same pull request (a workspace CI step), and `guide.test.js` fails when a status group, a drawer item or a `pmos` command is not mentioned.
58
+
59
+ ## Three columns (`column-layout`)
60
+
61
+ The desktop has three columns: **navigation**, **detail** and **tabs**. Navigation is the repository list with the list of the current mode under it (the initiatives of the selected repository, or memory, skills, knowledge, scheduled tasks, focus, portfolio, the guide). The repository list is capped at 42% of the height and scrolls inside its cap, so the list under it stays on screen. The detail column shows an initiative. The tabs column holds terminals, documents, conversations and guide pages and is never rebuilt by a layout change.
62
+
63
+ The DOM still holds four hosts in the same order (repositories, list, detail, tabs); `placeHosts` in `public/pm/app.js` puts the list host under the repositories by grid placement. About a hundred test assertions and the phone layout depend on that order, so it is not changed. The list folds to a one-line bar, the navigation column folds to a 36px rail (the list folds with it), and the detail column folds to its own rail. Drag the navigation column's right edge to resize it (220 to 560px); a double click restores 280px. `state.js` no longer stores `col1W`; an old stored layout loads and keeps its tabs.
64
+
65
+ `MODE_PLACE` in `public/pm/modes.js` is the rule for where things open, and `ROW_OPENS` says what a row in each list opens: an initiative opens in the detail column, a skill, a knowledge note or a guide page opens in a tab, and a schedule or a memory entry is used inside the list. A list or a detail kind missing from these tables fails `pm-r19-test.js`.
66
+
67
+ ## Next-step actions (`next-step-actions`)
68
+
69
+ Each next step carries an `act` (`open`, `session`, `work`, `gate`, `lifecycle`; `lib/next-step-actions.js`). For a `work` step the PM presses **prepare**, which returns a plan and creates nothing (`POST /api/next-step/prepare`): the branch and tree (`startPreview`: an existing `initiative/<id>` worktree is reused, otherwise one is created on go), the sessions already running on the initiative, the read-only sources the chosen profile declares and that suit the step, whether the typed-judgment helper is on, the session budget, and the exact first input with its hash. **Go** (`POST /api/next-step/go`, `confirm: true`, the hash) re-prepares, refuses if anything differs from what was shown, and starts the session through `startWork` with `origin: next-step` and the prompt as `initialInput`. The socket's `start` message still cannot carry a prompt.
70
+
71
+ Only the step the ladder has reached can be prepared; the build is refused until the pre-run contract is PM-approved; the contract, acceptance and open items are `gate` acts and have no start control; a stopped initiative must be reopened. The prompt states the propose-only boundary (`RULES`): commit to the local branch or write drafts; no push, pull request, send, post, ticket, acceptance, contract approval or merge. `lib/session-budget.js` limits sessions started from steps (3 at once, 8 a day, `GET`/`POST /api/next-step/settings`); sessions started by hand are never counted.
72
+
73
+ `lib/judgment.js` owns the instruction that tells a session to use the Typesafe skill (off by default, per run; it needs `TYPESAFE_API_KEY` and sends the question text to an outside service) and the shape check for `JUDGMENT {json}` lines (classify, score, yes or no). The next-step routes and modules make no model or network call (the one named egress in the workspace is D79's repository validation, a separate feature).
74
+
75
+ After the session ends, `GET /api/next-step/summary` returns the files the branch changed against trunk, the end of the conversation and the judgments, checked; `POST /api/next-step/review` records the PM's verdict (`accept` or `send-back` with a note). Runs and verdicts are `next-step-runs.jsonl` and `step-reviews.jsonl` in the runtime state directory. A verdict never changes the initiative's status.
76
+
77
+ ## Scheduled tasks from the terminal (`schedule-cli`)
78
+
79
+ `pmos schedule` and `pmos skill` talk to the running workspace server, so the terminal and the workspace share one schedule store and one validator. Every change is saved the moment the command succeeds. A refused value names what is allowed and changes nothing. With no server running the command says so and exits 1.
80
+
81
+ ```
82
+ pmos schedule add --skill gather-requirements --cadence daily --time 07:30 --max-runs 20 --until 2026-12-31
83
+ pmos schedule add --from-session <session-id> --skill-name my-check --output "Summary,Risks" --cadence weekly --day Fri --time 16:00
84
+ pmos schedule add --from-file notes.txt --skill-name my-check --cadence daily # same, from a text file
85
+ pmos schedule list [--all] · show <id|name> · run · enable · disable · rm
86
+ pmos schedule edit <id|name> --time 08:00 --args "focus on billing" --tier deep --until none
87
+ pmos skill list · show <name> · new --name N --description T [--body-file F] · edit <name> --file F · copy <name>
88
+ ```
89
+
90
+ | To change | Use |
91
+ |---|---|
92
+ | Period | `--cadence daily\|weekly\|bi-weekly\|monthly\|once` with `--time HH:MM`, `--day`, `--day-of-month` or `--date` |
93
+ | Duration | `--until YYYY-MM-DD` (that day still runs) and `--max-runs N`. When either is reached the schedule turns itself off and records why. `none` clears one. |
94
+ | Details and input | `--name`, `--args` (up to 2000 characters, added to each run) |
95
+ | How it runs | `--skill`, `--tier fast\|balanced\|deep`, `--profile` |
96
+ | Structure and output | The skill: `pmos skill edit`. Its `output:` list is the contract. Built-in skills are read-only; `pmos skill copy` makes an editable one. |
97
+
98
+ `--from-session` and `--from-file` write a new **user** skill from the captured work (the last 6000 characters of a session's terminal text, escape codes removed) and schedule it. Read the skill before its first run: `pmos skill show <name>`. A session's text can hold anything you typed or printed. The command never overwrites a skill and never uses a built-in's name.
99
+
100
+ **A schedule fires only while the workspace server is running.** A slot missed while it is stopped is skipped, never caught up. A one-off gets a 15-minute grace window. A skill's text is never typed into a session starting with `/`, because Claude Code would read that as a slash command.
101
+
102
+ ## The initiative list and detail: status groups, next steps, conversations, files (`initiative-surface-batch`)
103
+
104
+ **Group by status.** Above the initiative list, `group by objective | status` switches the grouping, and `active only` (in status mode) shows only live work. The groups, in this order: **Needs you** (returned at acceptance, or open items in the needs queue) · **In progress** (runs logged, nothing merged) · **Planning** (recorded, no run yet) · **Awaiting acceptance** (delivered, not accepted) · **Accepted** · **On hold** · **Closed** · **Cancelled** · **Unknown** (the history could not be read, so the status cannot be derived). "Active" is the first four. The status is **derived** from the ledgers and git, never typed into a record. On hold, closed and cancelled are the PM's: the `on hold` / `close` / `cancel` buttons in the initiative detail append one line to `lifecycle.jsonl`, and `reopen` appends another. An active search shows every match whatever its status.
105
+
106
+ **Next steps to done.** The initiative detail lists everything between where it is and accepted, in order: the remaining workflow steps (blocked first), open needs items, a returned acceptance, the sessions running on it, then the PM acceptance (D12). The first is marked `next`. Every item names the fact it came from. It is read from the workflow ladder and the ledgers, not written by a model, so it never names a step the engine does not require. A stopped initiative says so and leads with `reopen`; an accepted one says nothing remains.
107
+
108
+ **Conversations and files in the artifacts.** A session that belongs to an initiative — one you bound, one started on it, or one on its `initiative/<id>` branch — is listed under its artifacts as a conversation. It opens a read-only transcript tab. The transcript is saved when the session ends or is removed, so ending a session no longer erases it. It is kept in this machine's **runtime state directory** (`transcripts/`), not in the repository, and is never sent anywhere; a transcript can contain anything you typed or an agent printed, including a secret. A session on no initiative is not recorded. Below the documents, **files from git** lists what was changed for the initiative: commits on its `initiative/<id>` branch and commits whose subject names it, grouped as code, tests, documents or other, once each, only files that still exist. A failed git read is stated, never shown as an empty list.
109
+
110
+ **Ending a session from the repository navigation** closes its tab. A refused end, or "asked it to end, not exited yet", leaves the tab open.
111
+
49
112
  ## Layout — four panels (PM-directed design, built by `workspace-four-panel`)
50
113
  The three-column layout is retired. The surface is now **four collapsible panels** under a 44px top bar and a 26px status bar, on the oklch token layer, in both themes:
51
114
 
@@ -64,7 +127,7 @@ The three-column layout is retired. The surface is now **four collapsible panels
64
127
  **Fixture mode.** `?fixture=proto` renders the whole surface from a bundled seed file, with the server unable to read any repository — that is how the design comparison is run (`npm run test:parity`), and it is never the default.
65
128
 
66
129
  ## Artifact links are declarations, not integrations
67
- The PMOS server makes no outbound request and holds no credential, and the workspace makes no request you did not ask for. Its git network verbs are named and press-only: `Sync` runs `fetch`, a fast-forward or an own-commit rebase, a commit of record paths, and `push` — only when you press it, never on a timer or on load, never forced ([D70](/okf/products/pmos/adr/d70-stopped-initiatives-and-sync.md)). A web page opens in a tab only when you type or click its address, and a restored web tab loads only when you press "load this page" ([D69](/okf/products/pmos/adr/d69-web-tab.md)). Linking an artifact to an external destination therefore records a **declaration** — `{artifact_path, kind, target}` appended to `pmos/state/artifact-links.jsonl`. The sync panel renders as designed, and with no connector configured its summary line says exactly that: *no connector configured — nothing was read, and nothing will be written*, with the approval buttons disabled. A difference summary it never read would be the watermelon this repo has a word for.
130
+ The PMOS server makes no outbound request and holds no credential, with one named exception ([D79](/okf/products/pmos/adr/d79-typesafe-validation-egress.md)): when you save a TypeSafe key and turn validation on for a repository, the **validate** page sends that repository's document text to `api.typesafe.ai`, from one module, only after you confirm the plan. With no key, nothing is sent. The key stays on this machine, is never shown again, and is never given to a terminal. The workspace makes no request you did not ask for. Its git network verbs are named and press-only: `Sync` runs `fetch`, a fast-forward or an own-commit rebase, a commit of record paths, and `push` — only when you press it, never on a timer or on load, never forced ([D70](/okf/products/pmos/adr/d70-stopped-initiatives-and-sync.md)). A web page opens in a tab only when you type or click its address, and a restored web tab loads only when you press "load this page" ([D69](/okf/products/pmos/adr/d69-web-tab.md)). Linking an artifact to an external destination therefore records a **declaration** — `{artifact_path, kind, target}` appended to `pmos/state/artifact-links.jsonl`. The sync panel renders as designed, and with no connector configured its summary line says exactly that: *no connector configured — nothing was read, and nothing will be written*, with the approval buttons disabled. A difference summary it never read would be the watermelon this repo has a word for.
68
131
 
69
132
  ## The gates are real, never automatic (R5)
70
133
  `approve` / `return` / `accept` write the PMOS file-mode vocabulary to `acceptances.jsonl` and clear the matching needs entry — only ever on your click.
@@ -389,6 +389,13 @@ function doctor() {
389
389
  else if (cmd === 'ls' || cmd === 'list') code = await ls();
390
390
  else if (cmd === 'stop') code = await stop();
391
391
  else if (cmd === 'doctor') code = doctor();
392
+ else if (cmd === 'schedule' || cmd === 'skill') {
393
+ // Thin client of the running server's own routes (lib/schedule-cli.js): one store, one validator.
394
+ const live = await discover();
395
+ const port = live.length ? live[0].port : null;
396
+ code = await require('../lib/schedule-cli').run(process.argv.slice(2), {
397
+ say, call: async (m, p, b) => (port ? api(port, m, p, b) : null) });
398
+ }
392
399
  else if (cmd === 'upgrade') {
393
400
  // Escape hatch: pull the latest bundled app and force-reinstall over this global copy. Delegates
394
401
  // to the scaffolder (which owns the install path) rather than reinstalling itself in place.
@@ -400,9 +407,11 @@ function doctor() {
400
407
  if (r.error) { say('✗ could not run ' + npx + ' — ' + r.error.message); code = 1; }
401
408
  else code = r.status === null ? 1 : r.status;
402
409
  } else if (cmd === 'help' || cmd === '--help' || cmd === '-h') {
403
- say('usage: pmos [<path>|status|ls|stop|doctor|upgrade]');
410
+ say('usage: pmos [<path>|status|ls|stop|doctor|upgrade|schedule|skill]');
404
411
  say(' pmos open the current repo as a workspace (starts the server if needed)');
405
412
  say(' pmos <path> open another repo as a workspace');
413
+ say(' pmos schedule … create, edit and run scheduled tasks (pmos schedule --help)');
414
+ say(' pmos skill … list, read, create and edit skills');
406
415
  say(' pmos upgrade reinstall the workspace app from the latest published create-pmos');
407
416
  code = 0;
408
417
  } else if (cmd === 'up' || cmd === 'start' || cmd === 'open') {
@@ -0,0 +1,38 @@
1
+ ---
2
+ type: Guide
3
+ title: "Guide: start here"
4
+ timestamp: 2026-10-03
5
+ ---
6
+
7
+ # Guide: start here
8
+
9
+ This guide describes the PMOS workspace as it works today. It ships with the app. When a change alters how the workspace looks or behaves, the same change updates this guide.
10
+
11
+ ## The screen
12
+
13
+ The workspace has three columns. [Layout](layout.md) explains where things open.
14
+
15
+ 1. **Navigation.** The repositories you added, with the list of the current mode under them. The default list is the initiatives of the selected repository. The settings and tools drawer at the bottom opens the other lists here.
16
+ 2. **The detail.** The item you selected: an initiative with its status, next steps and artifacts, or a skill, a schedule or a memory item.
17
+ 3. **Tabs.** Terminals, documents, conversations and guide pages.
18
+
19
+ ## The settings and tools drawer
20
+
21
+ | Item | What it opens |
22
+ |---|---|
23
+ | memory | What the repository remembers: decisions and notes kept in its memory ledger. |
24
+ | knowledge | The decisions, concepts and skills of the repository. |
25
+ | portfolio | Every initiative of every repository, grouped by domain. |
26
+ | guide | This guide. |
27
+ | validate | Checks one repository's documents with TypeSafe. It sends text only after you turn it on. |
28
+ | focus | What is waiting on you, across all repositories. |
29
+ | skills | Built-in and user skills. A built-in is read-only. |
30
+ | scheduled tasks | Skills that run on a schedule. |
31
+
32
+ ## Where to read next
33
+
34
+ - [Layout](layout.md): the three columns and the rule for where a list or a detail opens.
35
+ - [Statuses](statuses.md): what each status means, where it comes from, and what you can change.
36
+ - [Initiatives and next steps](initiatives-and-next-steps.md): the detail, the next steps, and the artifacts.
37
+ - [Scheduled tasks and the command line](scheduled-tasks-and-cli.md): how to make, edit and run scheduled tasks.
38
+ - [Validate a repository with TypeSafe](validate-with-typesafe.md): what leaves your machine, how to run a validation, and how to read the findings.
@@ -0,0 +1,72 @@
1
+ ---
2
+ type: Guide
3
+ title: "Guide: initiatives and next steps"
4
+ timestamp: 2026-10-03
5
+ ---
6
+
7
+ # Guide: initiatives and next steps
8
+
9
+ Select an initiative in the list. The detail opens in the detail column.
10
+
11
+ ## What the detail shows, top to bottom
12
+
13
+ 1. **Status.** The status, why it has that status, who acts now, the next thing to do, and when the status changes. If the status is Needs you, each open item is listed in its own words. If the status cannot be read, the detail says so and shows no status.
14
+ 2. **Lifecycle control.** Hold, close, cancel or reopen. Beneath it, the history of every change.
15
+ 3. **Next steps to done.** Everything between now and acceptance, in order. See below.
16
+ 4. **Phases and links.** The workflow phases and the external links.
17
+ 5. **Artifacts.** Documents, conversations and files.
18
+
19
+ ## Next steps
20
+
21
+ The list comes from the workflow ladder, the ledgers and the running sessions. The workspace does not write it with a model. Every step names the fact it came from.
22
+
23
+ The list has two parts:
24
+
25
+ - **Yours.** Steps only you can do. Acceptance, approval of the contract and answers to open items belong here.
26
+ - **Mine.** Steps the agent does.
27
+
28
+ The numbers run along the whole path. The step marked **next** is the one the workflow names first. A stopped initiative lists a reopen step first. An accepted initiative has none.
29
+
30
+ ## Acting on a step
31
+
32
+ Each step says what you can do with it. The control sits under the step.
33
+
34
+ | Act | What it is | The control |
35
+ |---|---|---|
36
+ | `work` | An agent session can do this step. | **prepare**, then **go** |
37
+ | `open` | The step is yours, and it points at a file you should read. | **open** the file |
38
+ | `session` | A session is already running on it. | **switch to it** |
39
+ | `gate` | The step is yours alone: accepting the initiative, approving the contract, answering an open item. | Words only. There is no start button. |
40
+ | `lifecycle` | The initiative is stopped. | **reopen** |
41
+
42
+ ### Prepare, then go
43
+
44
+ **Prepare** shows a plan and starts nothing. The plan names the branch and the folder, says whether the initiative's branch already exists (it is reused) or would be created, lists the sessions already running on the initiative, names the read-only sources the session may use, and shows the budget. It also shows the exact first input the session would receive. Preparing creates no branch, folder or session.
45
+
46
+ **Go** starts one session with exactly that input. The workspace refuses if the plan changed after you read it. In that case, prepare again.
47
+
48
+ A step the workflow has not reached cannot be prepared. The build cannot be prepared until you approve the contract. A stopped initiative must be reopened first. The workspace says which step comes first.
49
+
50
+ ### What the session may do
51
+
52
+ The session proposes. It commits to the local initiative branch or writes draft files. It does not push, open a pull request, send or post anything, create a ticket, record an acceptance, approve a contract or merge. Those are yours.
53
+
54
+ ### The budget
55
+
56
+ At most 3 prepared sessions run at once and 8 start each day. Go refuses a start over either limit and says the number. Sessions you start by hand are never counted or blocked. Change the limits with `POST /api/next-step/settings`.
57
+
58
+ ### Sources and the typed judgment option
59
+
60
+ Choose a profile in the plan. The session may read only the sources that profile declares and that suit the step: Wispr Flow notes, Gmail, Google Calendar, Confluence, Jira and Slack. They are read-only.
61
+
62
+ You can turn on the Typesafe skill for a run. It helps the session categorise, score and decide yes or no with a typed answer. It sends the question text to an outside service and needs `TYPESAFE_API_KEY` in the session. It is off by default. The session prints each answer as a `JUDGMENT` line, and the review checks its shape.
63
+
64
+ ### Review
65
+
66
+ After the session ends, the step offers **review what it did**. The review lists the files the branch changed against trunk, shows the end of the conversation, and lists any judgments the session reported, with those that failed the shape check marked. Then choose **accept the step** or **send it back**. A send-back needs a note. Your verdict is recorded in the runtime state folder. Accepting a step does not accept the initiative. Acceptance stays at the Acceptance Gate.
67
+
68
+ ## Artifacts
69
+
70
+ - **Documents** open in a tab. You can edit them.
71
+ - **Conversations** are sessions bound to the initiative. A conversation is saved when its session ends. It opens read-only. It stays on this computer, in the runtime state folder. It is not in the repository.
72
+ - **Files from git** are the files changed for the initiative. They open read-only in a tab. A file that exists only on the initiative branch opens from that branch. A file that cannot be previewed, such as a binary file, says so.
@@ -0,0 +1,59 @@
1
+ ---
2
+ type: Guide
3
+ title: "Guide: layout"
4
+ timestamp: 2026-10-03
5
+ ---
6
+
7
+ # Guide: layout
8
+
9
+ The workspace has three columns. They are, from left to right, navigation, detail and tabs.
10
+
11
+ ## Navigation
12
+
13
+ The first column holds the list of repositories. Under it is the list for the current mode. When you choose a repository, its initiatives appear there. When you choose an item in the settings and tools drawer, its list replaces the initiatives. Choose a repository again to return to its initiatives.
14
+
15
+ If you have many repositories, the repository list scrolls inside its own space. The list under it stays on screen.
16
+
17
+ Drag the right edge of the column to change its width. Double-click the edge to restore the default width.
18
+
19
+ ## Detail
20
+
21
+ The second column shows one item in full. Choose an initiative, a skill, a schedule or a memory item, and its detail opens here.
22
+
23
+ ## Tabs
24
+
25
+ The third column holds your terminals, documents, conversations and guide pages. Folding or resizing the other columns does not touch your terminals.
26
+
27
+ ## The rule for where things open
28
+
29
+ A list or a directory opens in the navigation column, under the repository list. A detail opens in the detail column. A file-like row opens in a tab, as any document does.
30
+
31
+ | Item | Kind | Opens in |
32
+ |---|---|---|
33
+ | Initiatives | list | navigation |
34
+ | Memory | list | navigation |
35
+ | Knowledge | list | navigation |
36
+ | Portfolio | list | navigation |
37
+ | Guide | list | navigation |
38
+ | Validate | list | navigation |
39
+ | Focus | list | navigation |
40
+ | Skills | list | navigation |
41
+ | Scheduled tasks | list | navigation |
42
+
43
+ What a row in a list opens:
44
+
45
+ - A row in Initiatives, Portfolio or Focus opens the initiative in the detail column.
46
+ - A row in Skills, Knowledge, Guide or Validate opens the file in a tab, beside your terminals.
47
+ - Scheduled tasks and Memory are used inside the list. They have no separate detail.
48
+
49
+ ## Folding
50
+
51
+ - Choose the arrow in the header of the list to fold it to one line under the repositories. Choose the line to open it again.
52
+ - Fold the navigation column to a thin strip. The list folds with it. Choose the strip to open it again.
53
+ - Fold the detail column to its strip in the same way.
54
+
55
+ The workspace remembers which columns are open and how wide the navigation column is.
56
+
57
+ ## On a phone
58
+
59
+ A phone shows one panel at a time. The bottom bar moves between them.
@@ -0,0 +1,41 @@
1
+ ---
2
+ type: Guide
3
+ title: "Guide: scheduled tasks and the command line"
4
+ timestamp: 2026-10-03
5
+ ---
6
+
7
+ # Guide: scheduled tasks and the command line
8
+
9
+ A scheduled task runs a skill at a set time. The workspace starts a terminal session in the repository and types the skill into it.
10
+
11
+ ## Limits
12
+
13
+ A schedule fires only while the workspace server runs. If the server is stopped, the workspace skips the missed slot. It does not catch up later. A one-time task has a 15-minute grace window.
14
+
15
+ ## The commands
16
+
17
+ The commands talk to the running server. A change is saved when the command succeeds. A refused value names what is allowed and changes nothing. If no server runs, the command says so and exits with code 1.
18
+
19
+ - `pmos schedule list` shows the schedules. Add `--all` for every repository.
20
+ - `pmos schedule show <id|name>` shows one schedule, its next run, its last run and its status.
21
+ - `pmos schedule add` makes a schedule. Use `--skill` and `--cadence`. Add `--from-session` or `--from-file` to make a new user skill from your work and schedule it.
22
+ - `pmos schedule edit <id|name>` changes any field.
23
+ - `pmos schedule run <id|name>` runs it now.
24
+ - `pmos schedule enable <id|name>` and `pmos schedule disable <id|name>` turn it on and off.
25
+ - `pmos schedule rm <id|name>` deletes it.
26
+ - `pmos skill list` shows the skills.
27
+ - `pmos skill show <name>` shows one skill.
28
+ - `pmos skill new` makes a user skill.
29
+ - `pmos skill edit <name> --file F` replaces a user skill's text.
30
+ - `pmos skill copy <name>` makes an editable copy of a built-in skill.
31
+
32
+ ## The fields
33
+
34
+ | Field | Meaning |
35
+ |---|---|
36
+ | `--cadence`, `--day`, `--day-of-month`, `--time`, `--date` | When it runs. Times are local to the computer. |
37
+ | `--until`, `--max-runs` | When it stops. The schedule turns itself off when either is reached and records why. `none` clears one. |
38
+ | `--args` | Extra instructions added to the skill. |
39
+ | `--tier`, `--profile` | How it runs. |
40
+
41
+ A skill's text never starts a session with `/`, because Claude Code would read that as a command. The workspace puts a short sentence before it.
@@ -0,0 +1,53 @@
1
+ ---
2
+ type: Guide
3
+ title: "Guide: statuses"
4
+ timestamp: 2026-10-03
5
+ ---
6
+
7
+ # Guide: statuses
8
+
9
+ Nobody types a status. The workspace works it out again each time you look. It reads the ledgers in the repository and git. Because of this, a status cannot become stale. It also means you cannot edit most statuses by hand.
10
+
11
+ ## The nine statuses
12
+
13
+ | Status | Who acts | What it means |
14
+ |---|---|---|
15
+ | Needs you | You | Work is stopped on a decision or an answer only you can give. |
16
+ | In progress | The agent | An agent run is logged and nothing is merged yet. |
17
+ | Planning | The agent | The initiative is recorded and no run has started. |
18
+ | Awaiting acceptance | You | The work is delivered. It waits for your acceptance. |
19
+ | Accepted | Nobody | You accepted it. Nothing remains. |
20
+ | On hold | You | You paused it. It is off the board. |
21
+ | Closed | You | You closed it. It is off the board. |
22
+ | Cancelled | You | You cancelled it. It is off the board. |
23
+ | Unknown | Nobody | The status could not be worked out, so none is shown. |
24
+
25
+ The first four are the active statuses. Choose **active only** in the list to hide the others.
26
+
27
+ To read the same table in the app, choose **status** in the list, then **what do these mean?**. The app shows, for each status, the fact it is read from, what moves an initiative out of it, and what you can do.
28
+
29
+ ## Where each status comes from
30
+
31
+ - **Needs you** comes from your latest acceptance row (a return), or from an open item in the needs queue that names the initiative.
32
+ - **In progress** comes from `agent-runs.jsonl`: at least one run, and no merged `initiative/<id>` branch.
33
+ - **Planning** means the record exists and `agent-runs.jsonl` has no run for it.
34
+ - **Awaiting acceptance** comes from git: an `initiative/<id>` branch was merged, or a trunk commit names the initiative and changes build files. No acceptance row exists yet.
35
+ - **Accepted** comes from your latest acceptance row.
36
+ - **On hold**, **Closed** and **Cancelled** come from your latest line in `lifecycle.jsonl`.
37
+ - **Unknown** appears when the trunk history or a ledger cannot be read. The detail gives the reason.
38
+
39
+ ## What you can change by hand
40
+
41
+ You can hold, close, cancel or reopen an initiative. Each action writes one line to `lifecycle.jsonl` with your reason. A held, closed or cancelled initiative leaves the board and stops gating. Reopen puts it back. The detail lists every change.
42
+
43
+ You cannot set any other status. To move an initiative from Awaiting acceptance to Accepted, record an acceptance. Acceptance is yours and nobody can do it for you. Tell the session that holds the initiative to record it, or run `scripts/record-acceptance.js`. The workspace never records an acceptance for you.
44
+
45
+ ## How a status changes
46
+
47
+ A status changes when a fact changes:
48
+
49
+ 1. An agent logs its first run: Planning becomes In progress.
50
+ 2. A branch is merged: In progress becomes Awaiting acceptance.
51
+ 3. You return the work at acceptance: it becomes Needs you.
52
+ 4. You accept: it becomes Accepted.
53
+ 5. You hold, close or cancel: it moves to that status, whatever it was. Reopen returns it to the status its facts give.
@@ -0,0 +1,47 @@
1
+ ---
2
+ type: Guide
3
+ title: "Guide: validate a repository with TypeSafe"
4
+ timestamp: 2026-10-03
5
+ ---
6
+
7
+ # Guide: validate a repository with TypeSafe
8
+
9
+ The validate item checks the documents of one repository. It looks for documents that are unreliable, that disagree with each other, or that are out of date. It uses TypeSafe's Jev model for the checks that need judgement.
10
+
11
+ ## What leaves your machine
12
+
13
+ Validation is off until you turn it on. Until then, the workspace sends nothing.
14
+
15
+ When you turn it on, the workspace sends document text to `api.typesafe.ai`. It sends nothing else. It does not send binary files. It does not send a document that you excluded. It does not send a document that matches a credential pattern, such as a private key or a token. It lists that document as withheld.
16
+
17
+ The workspace reads the TypeSafe key only on this machine. After you save the key, the screen does not show it again. The workspace does not give the key to a terminal or to an agent.
18
+
19
+ ## Set it up
20
+
21
+ 1. Open **validate** in the settings and tools drawer.
22
+ 2. Under **TypeSafe key**, paste the key and press **save**. Press **remove** to delete a saved key.
23
+ 3. Under **this repository**, press **turn on**. This choice applies to the selected repository only.
24
+ 4. Enter the paths that the workspace must never send, one for each line. A path ends with `/` for a folder. A `*` matches any characters. Press **save excluded paths**.
25
+
26
+ ## Run a validation
27
+
28
+ 1. Press **show what would be sent**. The workspace shows the number of requests, the number of characters, and the documents it withholds. It sends nothing at this step.
29
+ 2. Press **confirm and send**. If the repository changed since the plan, the workspace refuses and asks you to read the plan again.
30
+
31
+ The workspace does the local checks first. It finds links that point to a file that does not exist. It finds documents whose `stale_after` date has passed. It reads the workflow sweep. These checks send nothing.
32
+
33
+ Then it asks Jev these questions about each document: Does the document contradict itself? Can a reader tell what it is for? Is the writing succinct? For an old document, it also asks if the document is out of date. For documents that link to each other, it asks if they state conflicting facts. For a document that names its sources, it asks if the source supports the claims.
34
+
35
+ ## Read the findings
36
+
37
+ A finding has a severity, a rule, a confidence and the files it concerns. Press a finding to open the first file.
38
+
39
+ - **Confidence** shows how sure the answer is. For a yes-or-no question, the workspace derives it from the probability that Jev returns. A finding with low confidence says **for your judgement**.
40
+ - **Coverage** is the first line under **findings**. Read it first. It shows how many requests Jev answered, and how many documents were withheld, too large, excluded or failed. A short list of findings does not mean the repository is clean.
41
+ - A finding is advice. It does not change a file. It is not an acceptance. You decide what to do.
42
+
43
+ The workspace keeps the findings in `validation-findings.jsonl`. In a PMOS repository, this file is in the runs folder. In another repository, it is in the runtime folder of this machine. The file holds the answers. It does not hold document text.
44
+
45
+ ## Limits
46
+
47
+ Jev's verdicts are advice until a person compares them with real decisions. The workspace does not yet measure how often Jev is right. Treat a high-confidence finding as a reason to look, not as proof.
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+ /* initiative-files.js — the files changed FOR an initiative, read from git.
3
+ *
4
+ * The artifact index lists only documents that carry the initiative's name in their header or filename.
5
+ * A source file, a test or an unlabelled note created for the work is therefore invisible there. Git
6
+ * already knows: the commits on the initiative's `initiative/<id>` branch, and trunk commits whose
7
+ * subject names the initiative (PMOS's `type(scope): subject (<id>)` convention). This reads both.
8
+ *
9
+ * It lists a file once, only if it still exists, skips what the document index already shows, and says
10
+ * so when git could not be read — a failed read is never an empty list. Local git only; no network.
11
+ */
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { spawnSync } = require('child_process');
15
+ const { previewKind } = require('./preview-kind');
16
+
17
+ const MAX_FILES = 200;
18
+ const cache = new Map(); // root|id → {head, at, result}
19
+ const TTL_MS = 30000;
20
+
21
+ function git(root, args) {
22
+ const r = spawnSync('git', ['-C', root, ...args], { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024, timeout: 20000 });
23
+ if (r.error || r.status !== 0) return { ok: false, error: String((r.error && r.error.message) || r.stderr || 'git failed').trim().slice(0, 160) };
24
+ return { ok: true, out: r.stdout };
25
+ }
26
+
27
+ // An id must match as a whole token, so `overhaul-r1` does not claim `overhaul-r12`.
28
+ function namesId(subject, id) {
29
+ const esc = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
30
+ return new RegExp('(?<![\\w.-])' + esc + '(?![\\w.-])').test(subject);
31
+ }
32
+
33
+ function groupOf(p) {
34
+ if (/(^|\/)(tests?|__tests__|spec)(\/|$)|\.(test|spec)\.[a-z]+$|(^|\/)[\w-]*-test\.[a-z]+$/i.test(p)) return 'tests';
35
+ if (/\.(md|mdx|txt|rst|html)$/i.test(p)) return 'documents';
36
+ if (/\.(js|mjs|cjs|ts|tsx|jsx|py|sh|rb|go|rs|java|css|sql|ya?ml|json)$/i.test(p)) return 'code';
37
+ return 'other';
38
+ }
39
+
40
+ function parseLog(out, keep) {
41
+ // records: \x1e<sha>\x1f<subject>\n<name-status lines>
42
+ const commits = [];
43
+ for (const rec of out.split('\x1e')) {
44
+ if (!rec.trim()) continue;
45
+ const nl = rec.indexOf('\n');
46
+ const head = nl < 0 ? rec : rec.slice(0, nl);
47
+ const [sha, subject] = head.split('\x1f');
48
+ if (!keep(subject || '')) continue;
49
+ const files = [];
50
+ for (const line of (nl < 0 ? '' : rec.slice(nl + 1)).split('\n')) {
51
+ const m = /^([AMRC]\d*)\t(?:.+\t)?(.+)$/.exec(line.trim());
52
+ if (m) files.push(m[2]);
53
+ }
54
+ commits.push({ sha, subject, files });
55
+ }
56
+ return commits;
57
+ }
58
+
59
+ /**
60
+ * @param {string} root repository root
61
+ * @param {string} id initiative id
62
+ * @param {{exclude?:Set<string>}} opts repo-relative paths the document index already lists
63
+ * @returns {{ok:true, items:Array, total:number, truncated:boolean, via:string[]}|{ok:false, reason:string}}
64
+ */
65
+ function filesFor(root, id, opts) {
66
+ const exclude = (opts && opts.exclude) || new Set();
67
+ if (!/^[A-Za-z0-9][\w.-]*$/.test(String(id || ''))) return { ok: false, reason: 'not an initiative id' };
68
+ const headR = git(root, ['rev-parse', 'HEAD']);
69
+ if (!headR.ok) return { ok: false, reason: 'git could not be read here — ' + headR.error };
70
+ const head = headR.out.trim();
71
+ const key = root + '|' + id;
72
+ const hit = cache.get(key);
73
+ let base = hit && hit.head === head && Date.now() - hit.at < TTL_MS ? hit.result : null;
74
+ if (!base) {
75
+ const commits = []; const via = [];
76
+ // 1. trunk and every other ref: commits whose subject names the initiative
77
+ const named = git(root, ['log', '--all', '-F', '--grep=' + id, '-n', '2000', '--no-merges', '--name-status', '--format=%x1e%H%x1f%s']);
78
+ if (!named.ok) return { ok: false, reason: 'git history could not be read — ' + named.error };
79
+ const byName = parseLog(named.out, (s) => namesId(s, id));
80
+ if (byName.length) { via.push('commits naming it'); commits.push(...byName); }
81
+ // 2. the initiative's own branch, local or remote, relative to the trunk
82
+ const branch = ['initiative/' + id, 'origin/initiative/' + id].find((b) => git(root, ['rev-parse', '--verify', '--quiet', b]).ok);
83
+ const trunk = ['origin/main', 'main', 'origin/master', 'master'].find((b) => git(root, ['rev-parse', '--verify', '--quiet', b]).ok);
84
+ if (branch && trunk) {
85
+ const own = git(root, ['log', trunk + '..' + branch, '--no-merges', '--name-status', '--format=%x1e%H%x1f%s']);
86
+ if (own.ok) { const rows = parseLog(own.out, () => true); if (rows.length) { via.push('its branch ' + branch); commits.push(...rows); } }
87
+ }
88
+ // What still exists: the checked-out tree plus the initiative's own branch tip, so a file that lives only on an
89
+ // unmerged branch is listed, and a file deleted since is not. `ls-tree` once per ref, never one spawn per file.
90
+ const present = new Set(); const onHead = new Set();
91
+ let treeOk = false;
92
+ for (const ref of ['HEAD', branch].filter(Boolean)) {
93
+ const t = git(root, ['ls-tree', '-r', '--name-only', '-z', ref]);
94
+ if (t.ok) { treeOk = true; for (const f of t.out.split('\0')) if (f) { present.add(f); if (ref === 'HEAD') onHead.add(f); } }
95
+ }
96
+ base = { commits, via, branch: branch || null, present: treeOk ? present : null, onHead: treeOk ? onHead : null };
97
+ cache.set(key, { head, at: Date.now(), result: base });
98
+ }
99
+ const seen = new Map();
100
+ for (const c of base.commits) for (const f of c.files) {
101
+ if (exclude.has(f)) continue;
102
+ const prev = seen.get(f);
103
+ if (prev) { if (!prev.shas.includes(c.sha)) prev.shas.push(c.sha); } else seen.set(f, { path: f, shas: [c.sha] });
104
+ }
105
+ const exists = (f) => { if (base.present) return base.present.has(f); try { return fs.existsSync(path.join(root, f)); } catch { return false; } };
106
+ const items = [...seen.values()].filter((x) => exists(x.path))
107
+ // `where` says which copy a reader gets: `head` is in the checked-out tree; `branch` exists only on the
108
+ // initiative's own branch tip, so it has to be read from git, not from the working tree.
109
+ .map((x) => ({ path: x.path, group: groupOf(x.path), commits: x.shas.length, last: x.shas[0].slice(0, 7),
110
+ where: base.onHead ? (base.onHead.has(x.path) ? 'head' : 'branch') : 'head', kind: previewKind(x.path) }))
111
+ .sort((a, b) => (a.group + a.path).localeCompare(b.group + b.path));
112
+ return { ok: true, total: items.length, truncated: items.length > MAX_FILES, items: items.slice(0, MAX_FILES), via: base.via, branch: base.branch };
113
+ }
114
+
115
+ /**
116
+ * Read one file as it is at `ref` (the initiative's own branch), for a file that is not in the checked-out tree.
117
+ * Only `initiative/<id>` and `origin/initiative/<id>` are readable, and only text under 2 MB; anything else is a
118
+ * reason, never a guess.
119
+ */
120
+ const MAX_BYTES = 2 * 1024 * 1024;
121
+ function readAtRef(root, ref, rel) {
122
+ if (!/^(origin\/)?initiative\/[A-Za-z0-9][\w.-]*$/.test(String(ref || ''))) return { ok: false, reason: 'only an initiative branch can be read this way' };
123
+ const p = String(rel || '');
124
+ if (!p || p.startsWith('/') || p.split('/').some((seg) => seg === '..' || seg === '')) return { ok: false, reason: 'not a path inside the repository' };
125
+ const kind = previewKind(p);
126
+ if (kind !== 'markdown' && kind !== 'text' && kind !== 'html') return { ok: false, reason: 'this kind of file cannot be previewed from a branch' };
127
+ const r = spawnSync('git', ['-C', root, 'show', ref + ':' + p], { encoding: 'buffer', maxBuffer: MAX_BYTES + 1024, timeout: 20000 });
128
+ if (r.error || r.status !== 0) return { ok: false, reason: 'git has no such file on ' + ref };
129
+ if (r.stdout.length > MAX_BYTES) return { ok: false, reason: 'the file is larger than 2 MB' };
130
+ if (r.stdout.indexOf(0) !== -1) return { ok: false, reason: 'the file is binary' };
131
+ return { ok: true, kind, text: r.stdout.toString('utf8') };
132
+ }
133
+
134
+ module.exports = { filesFor, groupOf, namesId, readAtRef };