@ai-dev-methodologies/rlp-desk 0.11.1 → 0.12.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 (44) hide show
  1. package/docs/rlp-desk/artifact-schema.md +99 -0
  2. package/docs/rlp-desk/ci-setup.md +100 -0
  3. package/docs/rlp-desk/e2e-scenarios.md +102 -0
  4. package/docs/rlp-desk/plans/rlp-desk-tmux-flywheel-routing.md +730 -0
  5. package/install.sh +93 -20
  6. package/package.json +8 -2
  7. package/scripts/build-node-manifest.js +52 -0
  8. package/scripts/postinstall.js +162 -8
  9. package/src/commands/rlp-desk.md +48 -25
  10. package/src/governance.md +55 -6
  11. package/src/node/MANIFEST.txt +15 -0
  12. package/src/node/cli/command-builder.mjs +25 -5
  13. package/src/node/constants.mjs +19 -0
  14. package/src/node/polling/signal-poller.mjs +119 -3
  15. package/src/node/runner/campaign-main-loop.mjs +470 -41
  16. package/src/node/runner/leader-registry.mjs +100 -0
  17. package/src/node/runner/prompt-dismisser.mjs +200 -0
  18. package/src/node/shared/fs.mjs +38 -0
  19. package/src/node/util/debug-log.mjs +56 -0
  20. package/src/node/util/shell-quote.mjs +12 -0
  21. package/docs/superpowers/plans/2026-04-24-gpt-5-5-default.md +0 -517
  22. package/docs/superpowers/specs/2026-04-24-gpt-5-5-default.md +0 -107
  23. /package/docs/{TODO-verification-next.md → rlp-desk/TODO-verification-next.md} +0 -0
  24. /package/docs/{architecture.md → rlp-desk/architecture.md} +0 -0
  25. /package/docs/{blueprints → rlp-desk/blueprints}/blueprint-flywheel-enhancement.md +0 -0
  26. /package/docs/{blueprints → rlp-desk/blueprints}/blueprint-pivot-step.md +0 -0
  27. /package/docs/{blueprints → rlp-desk/blueprints}/plan-flywheel-enhancement.md +0 -0
  28. /package/docs/{blueprints → rlp-desk/blueprints}/sv-architecture-rethink.md +0 -0
  29. /package/docs/{getting-started.md → rlp-desk/getting-started.md} +0 -0
  30. /package/docs/{internal → rlp-desk/internal}/verification-policy-gap-analysis.md +0 -0
  31. /package/docs/{internal → rlp-desk/internal}/verification-strategy-research.md +0 -0
  32. /package/docs/{multi-mission-orchestration.md → rlp-desk/multi-mission-orchestration.md} +0 -0
  33. /package/docs/{plans → rlp-desk/plans}/cozy-gliding-trinket.md +0 -0
  34. /package/docs/{plans → rlp-desk/plans}/frolicking-churning-honey.md +0 -0
  35. /package/docs/{plans → rlp-desk/plans}/keen-sauteeing-snowflake.md +0 -0
  36. /package/docs/{plans → rlp-desk/plans}/mutable-booping-corbato.md +0 -0
  37. /package/docs/{plans → rlp-desk/plans}/rlp-desk-0.11-handoff-7fixes.md +0 -0
  38. /package/docs/{plans → rlp-desk/plans}/rlp-desk-0.11.1-tmux-pane-disappearance.md +0 -0
  39. /package/docs/{plans → rlp-desk/plans}/rlp-desk-elegant-papert-agent-a8cd695ffca2a3ad8.md +0 -0
  40. /package/docs/{plans → rlp-desk/plans}/rlp-desk-elegant-papert.md +0 -0
  41. /package/docs/{plans → rlp-desk/plans}/toasty-whistling-diffie-agent-a6814625642e956da.md +0 -0
  42. /package/docs/{plans → rlp-desk/plans}/toasty-whistling-diffie.md +0 -0
  43. /package/docs/{plans → rlp-desk/plans}/validated-snacking-crayon.md +0 -0
  44. /package/docs/{protocol-reference.md → rlp-desk/protocol-reference.md} +0 -0
@@ -1,3 +1,6 @@
1
+ import { shellQuote } from '../util/shell-quote.mjs';
2
+ import { OPUS_1M_BETA, isOpusModel } from '../constants.mjs';
3
+
1
4
  const CLAUDE_BIN = 'claude';
2
5
  const CODEX_BIN = 'codex';
3
6
  const CLAUDE_MODELS = new Set(['haiku', 'sonnet', 'opus']);
@@ -11,19 +14,36 @@ function assertTuiMode(mode, builderName) {
11
14
  export function buildClaudeCmd(mode, model, options = {}) {
12
15
  assertTuiMode(mode, 'buildClaudeCmd');
13
16
 
14
- const parts = [
15
- 'DISABLE_OMC=1',
17
+ // v5.7 §4.9: auto-enable 1M-token context for Opus models. Long campaigns
18
+ // no longer silently truncate at 200K. Header is benign for non-Opus calls
19
+ // but we omit it there to keep the cmdline tidy.
20
+ const parts = ['DISABLE_OMC=1'];
21
+ if (isOpusModel(model)) {
22
+ parts.push(`ANTHROPIC_BETA=${shellQuote(OPUS_1M_BETA)}`);
23
+ }
24
+ parts.push(
16
25
  CLAUDE_BIN,
17
26
  '--model',
18
- model,
27
+ shellQuote(model),
19
28
  '--mcp-config',
20
29
  '\'{"mcpServers":{}}\'',
21
30
  '--strict-mcp-config',
22
31
  '--dangerously-skip-permissions',
23
- ];
32
+ );
33
+
34
+ // v5.7 §4.11.a: explicit --add-dir whitelist. With --dangerously-skip-permissions
35
+ // alone, claude CLI still surfaces TUI prompts for cwd-adjacent paths in some
36
+ // versions. Add the home rlp-desk tree (where Leader writes registry.jsonl
37
+ // and reads governance docs) plus the campaign cwd, so Worker has full
38
+ // authorized access without prompts.
39
+ if (options.addDirs && Array.isArray(options.addDirs)) {
40
+ for (const dir of options.addDirs) {
41
+ if (dir) parts.push('--add-dir', shellQuote(dir));
42
+ }
43
+ }
24
44
 
25
45
  if (options.effort !== undefined && options.effort !== '') {
26
- parts.push('--effort', options.effort);
46
+ parts.push('--effort', shellQuote(options.effort));
27
47
  }
28
48
 
29
49
  return parts.join(' ');
@@ -0,0 +1,19 @@
1
+ // Shared runtime constants. Single-source for cross-module values.
2
+
3
+ // Anthropic Claude API beta header that activates the 1M-token context window
4
+ // for Opus models. Auto-prepended to every claude CLI invocation that uses
5
+ // --model opus so long campaigns no longer silently truncate at 200K.
6
+ //
7
+ // Docs: https://docs.anthropic.com/en/docs/build-with-claude/context-windows
8
+ // (search "1M context") — header rotates with each beta phase.
9
+ export const OPUS_1M_BETA = 'context-1m-2025-08-07';
10
+
11
+ // Model id that triggers Opus 1M auto-enable. Plain string match against the
12
+ // --model value (post-shellQuote stripping). Bracketed form
13
+ // 'claude-opus-4-7[1m]' is also Opus and benefits from this; pattern match
14
+ // covers both.
15
+ export function isOpusModel(model) {
16
+ if (!model) return false;
17
+ const m = String(model).toLowerCase();
18
+ return m === 'opus' || m.startsWith('claude-opus-');
19
+ }
@@ -3,6 +3,8 @@ import { execFile } from 'node:child_process';
3
3
  import { promisify } from 'node:util';
4
4
  import { setTimeout as delay } from 'node:timers/promises';
5
5
 
6
+ import { autoDismissPrompts } from '../runner/prompt-dismisser.mjs';
7
+
6
8
  const execFileAsync = promisify(execFile);
7
9
  const SHELL_COMMANDS = new Set(['', 'zsh', 'bash', 'sh']);
8
10
 
@@ -13,6 +15,35 @@ export class TimeoutError extends Error {
13
15
  }
14
16
  }
15
17
 
18
+ // v5.7 §4.17 (Node parity): default-No prompt detected while polling. Caller
19
+ // must write a BLOCKED `infra_failure` sentinel and abort — never auto-Enter,
20
+ // never wait silently for the human.
21
+ export class PromptBlockedError extends Error {
22
+ constructor(message, info = {}) {
23
+ super(message);
24
+ this.name = 'PromptBlockedError';
25
+ this.paneId = info.paneId;
26
+ this.category = info.category ?? 'infra_failure';
27
+ this.reason = info.reason ?? message;
28
+ }
29
+ }
30
+
31
+ // v5.7 §4.22 (E2E real-claude-CLI finding): Worker process exited (back to
32
+ // shell prompt) but no signal/done-claim file was written. fresh-context +
33
+ // file-based architecture is broken — Leader has no way to know what Worker
34
+ // did. zsh runner has `handle_worker_exit_claude` for this; Node leader did
35
+ // not. Throw a specific error so the campaign loop can write BLOCKED with a
36
+ // descriptive reason instead of silent iter-timeout.
37
+ export class WorkerExitedError extends Error {
38
+ constructor(message, info = {}) {
39
+ super(message);
40
+ this.name = 'WorkerExitedError';
41
+ this.paneId = info.paneId;
42
+ this.category = info.category ?? 'infra_failure';
43
+ this.reason = info.reason ?? message;
44
+ }
45
+ }
46
+
16
47
  async function defaultReadFile(filePath) {
17
48
  return fs.readFile(filePath, 'utf8');
18
49
  }
@@ -29,6 +60,28 @@ async function defaultGetPaneCommand(paneId) {
29
60
  return stdout.trim();
30
61
  }
31
62
 
63
+ async function defaultCapturePane(paneId) {
64
+ // v5.7 §4.21 (E2E real-claude-CLI finding): claude v2.x trust prompt is
65
+ // ~30+ lines tall when the pane wraps narrowly. -S -10 missed the question
66
+ // header ("Quick safety check / Is this a project you trust?") so PROMPT_RE
67
+ // never matched and the unknown-prompt fast-fail BLOCKed instead of
68
+ // auto-dismissing. -50 covers the full prompt with margin for typical
69
+ // pane heights.
70
+ const { stdout } = await execFileAsync('tmux', [
71
+ 'capture-pane',
72
+ '-t',
73
+ paneId,
74
+ '-p',
75
+ '-S',
76
+ '-50',
77
+ ]);
78
+ return stdout;
79
+ }
80
+
81
+ async function defaultSendKeys(paneId, key) {
82
+ await execFileAsync('tmux', ['send-keys', '-t', paneId, key]);
83
+ }
84
+
32
85
  function isMissingFileError(error) {
33
86
  return error?.code === 'ENOENT';
34
87
  }
@@ -71,11 +124,41 @@ export async function pollForSignal(
71
124
  timeoutMs = 5000,
72
125
  readFile = defaultReadFile,
73
126
  getPaneCommand = defaultGetPaneCommand,
127
+ capturePane = defaultCapturePane,
128
+ sendKeys = defaultSendKeys,
129
+ log = () => {},
74
130
  } = {},
75
131
  ) {
76
132
  const deadline = Date.now() + timeoutMs;
133
+ let pendingBlock = null;
134
+ // v5.7 §4.22: track whether the worker process was ever observed running.
135
+ // Used to detect "worker started, did some work, then exited without
136
+ // writing signal/done-claim" — fresh-context architecture violation.
137
+ let seenWorkerRunning = false;
77
138
 
78
139
  while (!deadlineExceeded(deadline)) {
140
+ // v5.7 §4.13.b: auto-dismiss mid-execution permission prompts before
141
+ // checking the signal file. Without this, Worker hangs on TUI prompts
142
+ // even with --dangerously-skip-permissions (Bug 4).
143
+ // v5.7 §4.17 (Node parity): default-No prompts must NOT be auto-Entered;
144
+ // they raise a PromptBlockedError so the caller writes BLOCKED and aborts.
145
+ if (paneId) {
146
+ await autoDismissPrompts(paneId, {
147
+ capturePane,
148
+ sendKeys,
149
+ log,
150
+ onDefaultNoBlock: (info) => {
151
+ pendingBlock = info;
152
+ },
153
+ }).catch(() => {});
154
+ if (pendingBlock) {
155
+ throw new PromptBlockedError(
156
+ `Default-No prompt on pane ${pendingBlock.paneId}: ${pendingBlock.reason}`,
157
+ pendingBlock,
158
+ );
159
+ }
160
+ }
161
+
79
162
  try {
80
163
  const rawContent = await readFile(signalFile);
81
164
  const parsed = JSON.parse(rawContent);
@@ -89,9 +172,42 @@ export async function pollForSignal(
89
172
  }
90
173
 
91
174
  return parsed;
92
- } catch (error) {
93
- if (!isMissingFileError(error) && !isJsonParseError(error)) {
94
- throw error;
175
+ } catch (signalError) {
176
+ if (!isMissingFileError(signalError) && !isJsonParseError(signalError)) {
177
+ throw signalError;
178
+ }
179
+ // Signal file missing OR partial JSON. v5.7 §4.22: parity with zsh
180
+ // `handle_worker_exit_claude` — if Worker pane process is back to
181
+ // shell, the worker exited without writing artifacts. Stop polling
182
+ // immediately and surface a WorkerExitedError so the campaign loop
183
+ // can write BLOCKED with reason `worker_exited_without_artifacts`.
184
+ //
185
+ // IMPORTANT: only run the pane-exit check on ENOENT (signal file
186
+ // entirely missing). A SyntaxError means the file EXISTS but the
187
+ // Worker is mid-write (atomic-rename race) — checking pane state
188
+ // here would race against the imminent successful read. Skip the
189
+ // check; next iteration's read will succeed.
190
+ if (paneId && isMissingFileError(signalError)) {
191
+ try {
192
+ const currentCommand = await getPaneCommand(paneId);
193
+ if (SHELL_COMMANDS.has(currentCommand)) {
194
+ if (seenWorkerRunning) {
195
+ throw new WorkerExitedError(
196
+ `Worker pane ${paneId} exited (now '${currentCommand || 'shell'}') without writing signal at ${signalFile} — fresh-context contract violated`,
197
+ {
198
+ paneId,
199
+ category: 'infra_failure',
200
+ reason: 'worker_exited_without_artifacts',
201
+ },
202
+ );
203
+ }
204
+ } else if (currentCommand) {
205
+ seenWorkerRunning = true;
206
+ }
207
+ } catch (commandError) {
208
+ if (commandError instanceof WorkerExitedError) throw commandError;
209
+ // Other tmux lookup errors: don't end the loop early.
210
+ }
95
211
  }
96
212
  }
97
213