create-pmos 0.128.0 → 0.129.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/guide/proactive-engine.md +2 -2
- package/workspace/lib/day-run.js +4 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/server.js +30 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.129.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-11T03:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-11T03:14:46Z"}
|
|
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-11T03:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-11T03:14:46Z"}
|
|
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-11T03:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-11T03:14:46Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Guide: Today and the proactive engine"
|
|
4
4
|
description: "What the Today item shows, how your goals for the day are prepared, how the morning brief is made, and how the workspace works out what is waiting for you."
|
|
5
|
-
timestamp: 2026-10-
|
|
5
|
+
timestamp: 2026-10-11
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Guide: Today and the proactive engine
|
|
@@ -92,7 +92,7 @@ A check is due for one of three reasons.
|
|
|
92
92
|
|
|
93
93
|
**Backoff.** A check that finds nothing new and resolves nothing is an empty check. Each empty check in a row doubles the wait, up to 120 minutes. A check that finds or resolves something sets the wait back to the interval. For that reason the session submits an empty merge when nothing changed, so the check counts.
|
|
94
94
|
|
|
95
|
-
No check is due when no run exists for the day, when a check is running, when checks are off, or outside the start and end times. **Check now** starts a check at once for the run of the day. It is refused when a check is running or when there is no run.
|
|
95
|
+
No check is due when no run exists for the day, when a check is running, when checks are off, or outside the start and end times. **Check now** starts a check at once for the run of the day. It is refused when a check is running or when there is no run. A new check uses the session of the last check again when that session is still open and quiet. Only the new run's line is typed into it, so the day keeps one check session.
|
|
96
96
|
|
|
97
97
|
The page shows when the last check ran, when the next is due, and a log of the last 20 checks with the reason, how many items they found and how many they resolved.
|
|
98
98
|
|
package/workspace/lib/day-run.js
CHANGED
|
@@ -284,6 +284,7 @@ function sessionEnded(run, sessionId, now) {
|
|
|
284
284
|
}
|
|
285
285
|
if (next.brief_session && next.brief_session.id === sessionId && !next.brief_session.ended_at) { next.brief_session.ended_at = iso(now); changed = true; }
|
|
286
286
|
if (next.checks.session_id === sessionId) { next.checks.session_id = null; changed = true; }
|
|
287
|
+
if (sessionId && next.checks.prev_session === sessionId) { next.checks.prev_session = null; changed = true; }
|
|
287
288
|
return { ok: true, run: next, changed };
|
|
288
289
|
}
|
|
289
290
|
|
|
@@ -376,6 +377,9 @@ function checkDone(run, { found = 0, resolved = 0 } = {}, now) {
|
|
|
376
377
|
const next = clone(run);
|
|
377
378
|
const f = Math.max(0, Number(found) || 0); const r = Math.max(0, Number(resolved) || 0);
|
|
378
379
|
next.checks.empty_streak = f === 0 && r === 0 ? next.checks.empty_streak + 1 : 0;
|
|
380
|
+
// The check's session stays open after its merge; prev_session keeps its id so the next check is typed into it
|
|
381
|
+
// instead of starting another session (workspace-session-lifecycle), the way a day task reuses its own.
|
|
382
|
+
if (next.checks.session_id) next.checks.prev_session = next.checks.session_id;
|
|
379
383
|
next.checks.session_id = null;
|
|
380
384
|
const open = [...next.checks.log].reverse().find((x) => x.found === null);
|
|
381
385
|
if (open) { open.found = f; open.resolved = r; open.done_at = iso(now); }
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.129.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pmos-workspace",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.129.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
package/workspace/server.js
CHANGED
|
@@ -2669,10 +2669,14 @@ function adoptOrphanTmuxSessions() {
|
|
|
2669
2669
|
for (const t of tmuxList().sort((a, b) => a.created - b.created)) {
|
|
2670
2670
|
if (!t.name.startsWith('pmos-') || claimed.has(t.name)) continue;
|
|
2671
2671
|
const dir = t.dir && isDir(t.dir) ? path.resolve(t.dir) : null;
|
|
2672
|
-
|
|
2673
|
-
//
|
|
2674
|
-
//
|
|
2675
|
-
|
|
2672
|
+
const start = t.start && isDir(t.start) ? path.resolve(t.start) : null;
|
|
2673
|
+
// Where it belongs: the workspace containing the directory the pane STARTED in, then the one containing where it is
|
|
2674
|
+
// now. Only when neither is registered is a directory registered — the start directory first, because that is a
|
|
2675
|
+
// folder a session was opened in, not wherever the PM last `cd`-ed (registering that made ~ or /tmp a permanent
|
|
2676
|
+
// workspace). The current directory is the last resort, kept because a pane the PM can see must be one they can
|
|
2677
|
+
// reach; the home workspace is assumed only when there is no readable directory at all.
|
|
2678
|
+
const w = workspaceForPath(start) || workspaceForPath(dir) || (start && registerWorkspace(start))
|
|
2679
|
+
|| (dir && registerWorkspace(dir)) || homeWorkspace();
|
|
2676
2680
|
if (!w) continue;
|
|
2677
2681
|
const id = nextSessionId();
|
|
2678
2682
|
// The agent is the pane's own start command, not a guess: tmux spawned it directly (see
|
|
@@ -2680,7 +2684,7 @@ function adoptOrphanTmuxSessions() {
|
|
|
2680
2684
|
const agent = adoptedAgent(t.cmd);
|
|
2681
2685
|
const s = {
|
|
2682
2686
|
id, ws: w.id, root: w.root, name: `recovered ${agent}`, agent,
|
|
2683
|
-
origin: null, cwd: dir && insideRoot(w.root, dir) ? dir : w.root,
|
|
2687
|
+
origin: t.origin || null, cwd: dir && insideRoot(w.root, dir) ? dir : w.root,
|
|
2684
2688
|
state: 'working', pty: null, tmux: t.name, note: '', buf: '',
|
|
2685
2689
|
cols: 80, rows: 24, clients: new Set(), created: nowISO(), recovered: true,
|
|
2686
2690
|
};
|
|
@@ -3115,6 +3119,10 @@ function tmuxList() {
|
|
|
3115
3119
|
.map((name) => ({
|
|
3116
3120
|
name,
|
|
3117
3121
|
dir: tmuxField(name, '#{pane_current_path}'),
|
|
3122
|
+
// Where the pane STARTED (the -c of new-session): the workspace it was opened in. The current path follows every
|
|
3123
|
+
// `cd`, so a pane the PM moved to ~ or /tmp would otherwise file itself under that folder.
|
|
3124
|
+
start: tmuxField(name, '#{pane_start_path}'),
|
|
3125
|
+
origin: tmuxField(name, '#{@pmos_origin}'),
|
|
3118
3126
|
created: Number(tmuxField(name, '#{session_created}')) || 0,
|
|
3119
3127
|
cmd: tmuxField(name, '#{pane_start_command}'),
|
|
3120
3128
|
}));
|
|
@@ -3461,6 +3469,9 @@ function spawnSession({ name, agent, cwd, ws, origin, initialInput, profile, ini
|
|
|
3461
3469
|
s.buf = `\x1b[38;5;203m✗ ${why}\x1b[0m\r\n\x1b[38;5;244mFix the above, then ↻ respawn.\x1b[0m\r\n`;
|
|
3462
3470
|
persist(s); pushSessions(); pushWorkspaces(); return s;
|
|
3463
3471
|
}
|
|
3472
|
+
// The origin rides on the tmux session itself (a user option), so a pane whose record is lost is adopted with it:
|
|
3473
|
+
// a recovered automatic job stays automatic and the sweep can still end it. One cheap call, only when there is one.
|
|
3474
|
+
if (origin) tmuxCtl(['set-option', '-t', tname, '@pmos_origin', String(origin)]);
|
|
3464
3475
|
announceStateHealth(s); // the unmigrated notice comes FIRST — it explains an empty skills list
|
|
3465
3476
|
announceTree(s); // before attach, so the advisory is the first line of the pane's record
|
|
3466
3477
|
announceModel(s);
|
|
@@ -3990,9 +4001,19 @@ function startDayCheck(run, reason, now) {
|
|
|
3990
4001
|
if (run.checks.session_id) run = dayRunLib.sessionEnded(run, run.checks.session_id, now).run;
|
|
3991
4002
|
const sk = daySkillPrompt(run, 'day-check');
|
|
3992
4003
|
if (sk.error) return { ok: false, error: sk.error };
|
|
3993
|
-
const
|
|
3994
|
-
|
|
3995
|
-
|
|
4004
|
+
const line = `This run: /day-check ${reason} — the last check was at ${run.checks.last || 'none yet'}; read what changed since ${run.checks.last || run.started_at}.`;
|
|
4005
|
+
// The last check's session is used again while it is open and quiet: it has the skill in its context, so only this
|
|
4006
|
+
// run's line is typed into it. Before, every check started a new session and the old one sat idle until the sweep
|
|
4007
|
+
// ended it, about one more process per interval (workspace-session-lifecycle).
|
|
4008
|
+
const prev = run.checks.prev_session && sessions.get(run.checks.prev_session);
|
|
4009
|
+
let s = null;
|
|
4010
|
+
if (prev && prev.pty && prev.state !== 'exited' && harness.deriveState(prev.state, prev.lastOutputAt, now).state !== 'working') {
|
|
4011
|
+
try { firstInput.deliver(prev, line + '\r'); s = prev; } catch { s = null; }
|
|
4012
|
+
}
|
|
4013
|
+
if (!s) {
|
|
4014
|
+
s = spawnSession({ name: 'Daytime check', agent: sk.task.agent || 'claude', ws: sk.w ? sk.w.id : run.ws, origin: 'day-check', initialInput: `${sk.prompt}\n\n${line}\r`,
|
|
4015
|
+
modelAsk: { scheduleTier: null, skillTier: sk.task.tier || null, skill: sk.task.skill } });
|
|
4016
|
+
}
|
|
3996
4017
|
if (s.state === 'exited') return { ok: false, error: s.note || 'the check session could not start' };
|
|
3997
4018
|
const c = dayRunLib.checkStarted(run, s.id, reason, now);
|
|
3998
4019
|
return c.ok ? { ok: true, run: c.run, session: s.id } : c;
|
|
@@ -9013,7 +9034,7 @@ if (require.main === module) {
|
|
|
9013
9034
|
});
|
|
9014
9035
|
});
|
|
9015
9036
|
}
|
|
9016
|
-
module.exports = { server, online: { ONLINE, applyOnline, tunnel, publicHost }, spawnSession, removeSession, sweepAutomaticSessions, endSupersededRuns, renameSession, sessions, libraryTree, readTextDoc,
|
|
9037
|
+
module.exports = { server, online: { ONLINE, applyOnline, tunnel, publicHost }, spawnSession, removeSession, sweepAutomaticSessions, endSupersededRuns, adoptOrphanTmuxSessions, renameSession, sessions, libraryTree, readTextDoc,
|
|
9017
9038
|
registerWorkspace, forgetWorkspace, workspaces, library, initiativeIndex, fsList,
|
|
9018
9039
|
// R1 — exported so the suite can assert the CARRY-THROUGH: branchInfo separates an ordinary
|
|
9019
9040
|
// non-repo folder from a real git fault, and the whole legibility defect was that distinction
|