create-pmos 0.12.1 → 0.14.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.
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+ /* drive-plan — the autonomous drive's decision function (pmos-autonomous-drive-build, R1/R2/R10).
3
+ *
4
+ * PURE. Given a ladder (scripts/workflow-state.js `<id> --json`) and the loop's own memory, decide
5
+ * whether to dispatch the next step or stop. No I/O, no clock, no spawning — so every rule below is
6
+ * testable without a PTY, which is the point: the rules are the risky part, not the plumbing.
7
+ *
8
+ * WHY A TERMINATION RULE EXISTS AT ALL. The engine's `next` is NEVER null — measured across the
9
+ * board, 0 of 25 sampled initiatives return one. It always names something, because there is always
10
+ * a next honest action even on finished work. So `while (next) dispatch()` never exits. The counter
11
+ * cannot tell the driver when to stop, and the driver must decide for itself. That is R2, and it is
12
+ * the difference between a loop and a runaway.
13
+ *
14
+ * THE THREE STOPS (R2), in the order they are checked:
15
+ * 1. human-gate — the next action is a decision, not work. D16 contract, D12 acceptance.
16
+ * 2. no-progress — the same next action twice running. A step that did not move the ladder will
17
+ * not move it by being run again; retrying is how a loop burns a budget looking
18
+ * busy. NO PROGRESS IS A STOP, NOT A RETRY.
19
+ * 3. budget — a hard ceiling, so a case nobody predicted still terminates.
20
+ *
21
+ * The driver never writes anything from here. Deciding and writing are separate on purpose — see
22
+ * drive-guard.js, which is what makes the human-gate stop structural rather than merely intended.
23
+ */
24
+
25
+ /** Ladder steps whose next action is a HUMAN decision the driver may never take (R9/R10).
26
+ * `contract` is D16 (no build begins without PM approval); `accept` is D12 (non-delegable).
27
+ * Deliberately NOT here: `review` (an evaluator agent scores it) and `quality` (CI decides). */
28
+ const HUMAN_GATES = Object.freeze(['contract', 'accept']);
29
+
30
+ /** A disposition is the PM's (D60) and is recorded on the initiative record rather than as a
31
+ * ladder step, so it cannot appear as `next.step`. Named for the guard, which does enforce it. */
32
+ const HUMAN_RECORDS = Object.freeze(['disposition', 'acceptance', 'pre_run_contract.PM_approved']);
33
+
34
+ const DEFAULT_BUDGET = 12;
35
+
36
+ /**
37
+ * @param {object} ladder parsed `workflow-state.js <id> --json`
38
+ * @param {object} memory { steps: number, lastKey: string|null } the loop's own history
39
+ * @param {object} [opts] { budget?: number }
40
+ * @returns {{action:'dispatch'|'stop', reason:string, step?:string, why?:string, detail:string}}
41
+ */
42
+ function plan(ladder, memory, opts) {
43
+ const budget = (opts && Number.isFinite(opts.budget)) ? opts.budget : DEFAULT_BUDGET;
44
+ const mem = memory || { steps: 0, lastKey: null };
45
+
46
+ // An unreadable ladder is a stop, never a guess. The engine reports `ok:false` when it cannot
47
+ // derive; proceeding on that would be inventing state, which R1 forbids.
48
+ if (!ladder || ladder.ok === false) {
49
+ return { action: 'stop', reason: 'unreadable',
50
+ detail: 'the ladder could not be derived — the driver never invents state (R1)' };
51
+ }
52
+
53
+ // Accepted work is finished work. Checked before `next`, which still names an action even here.
54
+ if (ladder.accepted) {
55
+ return { action: 'stop', reason: 'complete',
56
+ detail: `${ladder.id} has a recorded acceptance — nothing further is owed` };
57
+ }
58
+
59
+ const next = ladder.next;
60
+ if (!next || !next.step) {
61
+ return { action: 'stop', reason: 'no-next',
62
+ detail: 'the ladder named no next step (unexpected — `next` is normally never null)' };
63
+ }
64
+
65
+ // 1. HUMAN GATE. The decision is the PM's; the driver stops and names what is owed.
66
+ if (HUMAN_GATES.includes(next.step)) {
67
+ return { action: 'stop', reason: 'human-gate', step: next.step, why: next.why,
68
+ detail: `${next.step} is the PM's decision — ${next.why || 'no reason given'}` };
69
+ }
70
+
71
+ // 2. NO PROGRESS. Same step + same reason as last iteration means the previous dispatch changed
72
+ // nothing the engine can see. Running it again is not a retry, it is the same failure twice.
73
+ const key = `${next.step}::${next.why || ''}`;
74
+ if (mem.lastKey && mem.lastKey === key) {
75
+ return { action: 'stop', reason: 'no-progress', step: next.step, why: next.why,
76
+ detail: `'${next.step}' is unchanged after a dispatch — stopping rather than retrying` };
77
+ }
78
+
79
+ // 3. BUDGET. Checked last so a genuine human-gate or no-progress stop is reported as itself
80
+ // rather than masked by an exhausted counter.
81
+ if (mem.steps >= budget) {
82
+ return { action: 'stop', reason: 'budget', step: next.step,
83
+ detail: `step budget of ${budget} exhausted — refusing to continue silently (R13)` };
84
+ }
85
+
86
+ return { action: 'dispatch', reason: 'next-action', step: next.step, why: next.why,
87
+ detail: next.action || `advance '${next.step}'` };
88
+ }
89
+
90
+ /** Fold a decision into the loop's memory. Pure; the caller owns the value. */
91
+ function remember(memory, decision) {
92
+ const mem = memory || { steps: 0, lastKey: null };
93
+ if (!decision || decision.action !== 'dispatch') return mem;
94
+ return { steps: mem.steps + 1, lastKey: `${decision.step}::${decision.why || ''}` };
95
+ }
96
+
97
+ module.exports = { plan, remember, HUMAN_GATES, HUMAN_RECORDS, DEFAULT_BUDGET };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "private": true,
5
5
  "description": "PMOS Terminal Workspace — local companion hosting real PTYs beside the library and gates (D62)",
6
6
  "license": "MIT",