create-pmos 0.38.8 → 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.
- 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/README.md +29 -1
- package/workspace/guide/index.md +38 -0
- package/workspace/guide/initiatives-and-next-steps.md +72 -0
- package/workspace/guide/layout.md +59 -0
- package/workspace/guide/scheduled-tasks-and-cli.md +41 -0
- package/workspace/guide/statuses.md +53 -0
- package/workspace/guide/validate-with-typesafe.md +47 -0
- package/workspace/lib/initiative-files.js +29 -6
- package/workspace/lib/judgment.js +65 -0
- package/workspace/lib/mobile-nav.js +1 -1
- package/workspace/lib/next-step-actions.js +139 -0
- package/workspace/lib/next-steps.js +27 -7
- package/workspace/lib/schedule-cli.js +5 -2
- package/workspace/lib/secret-scan.js +28 -0
- package/workspace/lib/session-budget.js +35 -0
- package/workspace/lib/status-groups.js +53 -10
- package/workspace/lib/typesafe-settings.js +119 -0
- package/workspace/lib/typesafe.js +124 -0
- package/workspace/lib/validate.js +304 -0
- package/workspace/package-lock.json +1 -1
- package/workspace/package.json +3 -3
- package/workspace/public/pm/app.js +53 -18
- package/workspace/public/pm/data.js +18 -3
- package/workspace/public/pm/mobile.js +1 -1
- package/workspace/public/pm/modes-live.js +152 -0
- package/workspace/public/pm/modes.js +25 -0
- package/workspace/public/pm/next-step-ui.js +185 -0
- package/workspace/public/pm/panel-detail.js +47 -11
- package/workspace/public/pm/panel-initiatives.js +26 -4
- package/workspace/public/pm/panel-repos.js +2 -2
- package/workspace/public/pm/state.js +3 -3
- package/workspace/public/pm/status-banner.js +87 -0
- package/workspace/public/pm/tab-doc.js +11 -6
- package/workspace/server.js +323 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
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": {
|
package/payload/KIT-VERSION
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-03T07:05:43Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
package/workspace/README.md
CHANGED
|
@@ -46,6 +46,34 @@ 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
|
+
|
|
49
77
|
## Scheduled tasks from the terminal (`schedule-cli`)
|
|
50
78
|
|
|
51
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.
|
|
@@ -99,7 +127,7 @@ The three-column layout is retired. The surface is now **four collapsible panels
|
|
|
99
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.
|
|
100
128
|
|
|
101
129
|
## Artifact links are declarations, not integrations
|
|
102
|
-
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.
|
|
103
131
|
|
|
104
132
|
## The gates are real, never automatic (R5)
|
|
105
133
|
`approve` / `return` / `accept` write the PMOS file-mode vocabulary to `acceptances.jsonl` and clear the matching needs entry — only ever on your click.
|
|
@@ -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.
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const { spawnSync } = require('child_process');
|
|
15
|
+
const { previewKind } = require('./preview-kind');
|
|
15
16
|
|
|
16
17
|
const MAX_FILES = 200;
|
|
17
18
|
const cache = new Map(); // root|id → {head, at, result}
|
|
@@ -86,13 +87,13 @@ function filesFor(root, id, opts) {
|
|
|
86
87
|
}
|
|
87
88
|
// What still exists: the checked-out tree plus the initiative's own branch tip, so a file that lives only on an
|
|
88
89
|
// unmerged branch is listed, and a file deleted since is not. `ls-tree` once per ref, never one spawn per file.
|
|
89
|
-
const present = new Set();
|
|
90
|
+
const present = new Set(); const onHead = new Set();
|
|
90
91
|
let treeOk = false;
|
|
91
92
|
for (const ref of ['HEAD', branch].filter(Boolean)) {
|
|
92
93
|
const t = git(root, ['ls-tree', '-r', '--name-only', '-z', ref]);
|
|
93
|
-
if (t.ok) { treeOk = true; for (const f of t.out.split('\0')) if (f) present.add(f); }
|
|
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); } }
|
|
94
95
|
}
|
|
95
|
-
base = { commits, via, present: treeOk ? present : null };
|
|
96
|
+
base = { commits, via, branch: branch || null, present: treeOk ? present : null, onHead: treeOk ? onHead : null };
|
|
96
97
|
cache.set(key, { head, at: Date.now(), result: base });
|
|
97
98
|
}
|
|
98
99
|
const seen = new Map();
|
|
@@ -103,9 +104,31 @@ function filesFor(root, id, opts) {
|
|
|
103
104
|
}
|
|
104
105
|
const exists = (f) => { if (base.present) return base.present.has(f); try { return fs.existsSync(path.join(root, f)); } catch { return false; } };
|
|
105
106
|
const items = [...seen.values()].filter((x) => exists(x.path))
|
|
106
|
-
|
|
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) }))
|
|
107
111
|
.sort((a, b) => (a.group + a.path).localeCompare(b.group + b.path));
|
|
108
|
-
return { ok: true, total: items.length, truncated: items.length > MAX_FILES, items: items.slice(0, MAX_FILES), via: base.via };
|
|
112
|
+
return { ok: true, total: items.length, truncated: items.length > MAX_FILES, items: items.slice(0, MAX_FILES), via: base.via, branch: base.branch };
|
|
109
113
|
}
|
|
110
114
|
|
|
111
|
-
|
|
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 };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/* judgment.js — typed answers to classify, score and yes-or-no questions.
|
|
3
|
+
*
|
|
4
|
+
* The workspace makes no network call and runs no model. When the PM turns judgment on for a run, the PROMPT tells the
|
|
5
|
+
* session to use the Typesafe skill (https://docs.typesafe.ai/agent-skill; needs TYPESAFE_API_KEY) for these three kinds
|
|
6
|
+
* of decision, and to print each answer on one line as `JUDGMENT {json}`. This module owns the fragment that says so and
|
|
7
|
+
* the check of the shape that comes back. An answer in any other shape is rejected, never repaired.
|
|
8
|
+
*
|
|
9
|
+
* Off, or without a key, the session is asked for the SAME shape from plain judgment, and the fragment says so — the
|
|
10
|
+
* PM never gets a different answer format depending on a setting.
|
|
11
|
+
*/
|
|
12
|
+
const KINDS = ['classify', 'score', 'yesno'];
|
|
13
|
+
const LINE = /^JUDGMENT\s+(\{.*\})\s*$/;
|
|
14
|
+
const clip = (s, n) => String(s).slice(0, n);
|
|
15
|
+
|
|
16
|
+
function fragment(on) {
|
|
17
|
+
const shape = [
|
|
18
|
+
'When a step needs you to categorise, score, or decide yes or no, give the answer on its own line, exactly like this, one JSON object per line:',
|
|
19
|
+
// The examples start with "e.g." so a transcript that echoes this prompt is never read as an answer.
|
|
20
|
+
' e.g. JUDGMENT {"kind":"classify","question":"…","label":"<one of your labels>","labels":["…","…"],"confidence":0.0-1.0}',
|
|
21
|
+
' e.g. JUDGMENT {"kind":"score","question":"…","score":0.0-1.0,"reason":"…"}',
|
|
22
|
+
' e.g. JUDGMENT {"kind":"yesno","question":"…","answer":true|false,"confidence":0.0-1.0,"reason":"…"}',
|
|
23
|
+
];
|
|
24
|
+
if (on) return ['Use the Typesafe skill (/typesafe:typesafe-ai) for those decisions. The question text you send leaves this machine for the Typesafe service and needs TYPESAFE_API_KEY. If the skill or the key is missing, say so and decide yourself.', ...shape].join('\n');
|
|
25
|
+
return ['Decide those yourself; no outside service is used for this run.', ...shape].join('\n');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const unit = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 1;
|
|
29
|
+
|
|
30
|
+
/** @returns {{ok:true,value:object}|{ok:false,error:string}} */
|
|
31
|
+
function validate(o) {
|
|
32
|
+
if (!o || typeof o !== 'object' || Array.isArray(o)) return { ok: false, error: 'not an object' };
|
|
33
|
+
if (!KINDS.includes(o.kind)) return { ok: false, error: `kind must be one of ${KINDS.join(', ')}` };
|
|
34
|
+
if (typeof o.question !== 'string' || !o.question.trim()) return { ok: false, error: 'question is required' };
|
|
35
|
+
const base = { kind: o.kind, question: clip(o.question.trim(), 300) };
|
|
36
|
+
if (o.kind === 'classify') {
|
|
37
|
+
if (!Array.isArray(o.labels) || o.labels.length < 2 || o.labels.some((l) => typeof l !== 'string' || !l.trim())) return { ok: false, error: 'labels must be a list of at least two strings' };
|
|
38
|
+
if (typeof o.label !== 'string' || !o.labels.includes(o.label)) return { ok: false, error: 'label must be one of labels' };
|
|
39
|
+
if (!unit(o.confidence)) return { ok: false, error: 'confidence must be a number from 0 to 1' };
|
|
40
|
+
return { ok: true, value: { ...base, label: o.label, labels: o.labels.map((l) => clip(l, 60)), confidence: o.confidence } };
|
|
41
|
+
}
|
|
42
|
+
if (o.kind === 'score') {
|
|
43
|
+
if (!unit(o.score)) return { ok: false, error: 'score must be a number from 0 to 1' };
|
|
44
|
+
if (typeof o.reason !== 'string' || !o.reason.trim()) return { ok: false, error: 'reason is required' };
|
|
45
|
+
return { ok: true, value: { ...base, score: o.score, reason: clip(o.reason.trim(), 300) } };
|
|
46
|
+
}
|
|
47
|
+
if (typeof o.answer !== 'boolean') return { ok: false, error: 'answer must be true or false' };
|
|
48
|
+
if (!unit(o.confidence)) return { ok: false, error: 'confidence must be a number from 0 to 1' };
|
|
49
|
+
return { ok: true, value: { ...base, answer: o.answer, confidence: o.confidence, ...(typeof o.reason === 'string' ? { reason: clip(o.reason.trim(), 300) } : {}) } };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Every `JUDGMENT {json}` line in a transcript, checked. Returns { valid:[…], invalid:[{line,error}] }. */
|
|
53
|
+
function extract(text) {
|
|
54
|
+
const valid = []; const invalid = [];
|
|
55
|
+
for (const raw of String(text == null ? '' : text).split(/\r?\n/)) {
|
|
56
|
+
const m = LINE.exec(raw.trim());
|
|
57
|
+
if (!m) continue;
|
|
58
|
+
let o; try { o = JSON.parse(m[1]); } catch { invalid.push({ line: clip(raw.trim(), 200), error: 'not valid JSON' }); continue; }
|
|
59
|
+
const v = validate(o);
|
|
60
|
+
if (v.ok) valid.push(v.value); else invalid.push({ line: clip(raw.trim(), 200), error: v.error });
|
|
61
|
+
}
|
|
62
|
+
return { valid, invalid };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { KINDS, fragment, validate, extract };
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
// silently renders a key where a title belongs.
|
|
50
50
|
const MODE_TITLE = {
|
|
51
51
|
product: 'initiatives', scheduled: 'scheduled tasks', workflow: 'workflows', skills: 'skills',
|
|
52
|
-
knowledge: 'knowledge', agent: 'agents', memory: 'memory',
|
|
52
|
+
knowledge: 'knowledge', agent: 'agents', memory: 'memory', portfolio: 'portfolio', guide: 'guide',
|
|
53
53
|
};
|
|
54
54
|
// What a DETAIL screen is called. It moved here with the reducer, for the same reason.
|
|
55
55
|
// The second block is the four-panel surface's `detail.kind` vocabulary (pm/app.js's DETAIL_TITLE).
|