ralph-lisa-loop 0.3.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,130 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const node_test_1 = require("node:test");
37
+ const assert = __importStar(require("node:assert"));
38
+ const policy_js_1 = require("../policy.js");
39
+ (0, node_test_1.describe)("checkRalph", () => {
40
+ (0, node_test_1.it)("warns when CODE missing Test Results and file:line", () => {
41
+ const violations = (0, policy_js_1.checkRalph)("CODE", "[CODE] Done\n\nImplemented it.");
42
+ assert.strictEqual(violations.length, 2);
43
+ assert.ok(violations.some((v) => v.rule === "test-results"));
44
+ assert.ok(violations.some((v) => v.rule === "file-line-ref"));
45
+ });
46
+ (0, node_test_1.it)("passes when CODE includes Test Results and file:line", () => {
47
+ const violations = (0, policy_js_1.checkRalph)("CODE", "[CODE] Done\n\nchanges in commands.ts:42\n\nTest Results\n- Passed");
48
+ assert.strictEqual(violations.length, 0);
49
+ });
50
+ (0, node_test_1.it)("warns when CODE has Test Results but no file:line", () => {
51
+ const violations = (0, policy_js_1.checkRalph)("CODE", "[CODE] Done\n\nTest Results\n- Passed");
52
+ assert.strictEqual(violations.length, 1);
53
+ assert.strictEqual(violations[0].rule, "file-line-ref");
54
+ });
55
+ (0, node_test_1.it)("warns when CODE has file:line but no Test Results", () => {
56
+ const violations = (0, policy_js_1.checkRalph)("CODE", "[CODE] Done\n\nChanged commands.ts:42");
57
+ assert.strictEqual(violations.length, 1);
58
+ assert.strictEqual(violations[0].rule, "test-results");
59
+ });
60
+ (0, node_test_1.it)("warns when FIX missing Test Results", () => {
61
+ const violations = (0, policy_js_1.checkRalph)("FIX", "[FIX] Fixed\n\nChanged code.");
62
+ assert.ok(violations.some((v) => v.rule === "test-results"));
63
+ });
64
+ (0, node_test_1.it)("no warnings for PLAN", () => {
65
+ const violations = (0, policy_js_1.checkRalph)("PLAN", "[PLAN] Plan\n\nDetails");
66
+ assert.strictEqual(violations.length, 0);
67
+ });
68
+ (0, node_test_1.it)("warns when RESEARCH has no substance", () => {
69
+ const violations = (0, policy_js_1.checkRalph)("RESEARCH", "[RESEARCH] Done");
70
+ assert.strictEqual(violations.length, 1);
71
+ assert.strictEqual(violations[0].rule, "research-content");
72
+ });
73
+ (0, node_test_1.it)("passes RESEARCH with 2+ English fields", () => {
74
+ const violations = (0, policy_js_1.checkRalph)("RESEARCH", "[RESEARCH] Done\n\nReference implementation: file.ts\nKey types: MyType\nVerification: tested");
75
+ assert.strictEqual(violations.length, 0);
76
+ });
77
+ (0, node_test_1.it)("passes RESEARCH with exactly 2 fields", () => {
78
+ const violations = (0, policy_js_1.checkRalph)("RESEARCH", "[RESEARCH] Done\n\nReference: file.ts\nVerification: curl tested");
79
+ assert.strictEqual(violations.length, 0);
80
+ });
81
+ (0, node_test_1.it)("passes RESEARCH with data format + verification fields", () => {
82
+ const violations = (0, policy_js_1.checkRalph)("RESEARCH", "[RESEARCH] Done\n\nData structure: { id: string }\nVerified: works");
83
+ assert.strictEqual(violations.length, 0);
84
+ });
85
+ (0, node_test_1.it)("warns RESEARCH with only 1 field", () => {
86
+ const violations = (0, policy_js_1.checkRalph)("RESEARCH", "[RESEARCH] Done\n\nReference: file.ts");
87
+ assert.strictEqual(violations.length, 1);
88
+ });
89
+ (0, node_test_1.it)("passes RESEARCH with substantial content (>3 lines) even without fields", () => {
90
+ const violations = (0, policy_js_1.checkRalph)("RESEARCH", "[RESEARCH] API analysis\n\nLine 1\nLine 2\nLine 3\nLine 4");
91
+ assert.strictEqual(violations.length, 0);
92
+ });
93
+ (0, node_test_1.it)("file:line not required for PLAN or RESEARCH", () => {
94
+ const planV = (0, policy_js_1.checkRalph)("PLAN", "[PLAN] Plan\n\nNo file refs here");
95
+ assert.strictEqual(planV.length, 0);
96
+ });
97
+ });
98
+ (0, node_test_1.describe)("checkLisa", () => {
99
+ (0, node_test_1.it)("warns when PASS has no reason and no file:line", () => {
100
+ const violations = (0, policy_js_1.checkLisa)("PASS", "[PASS] Looks good");
101
+ assert.strictEqual(violations.length, 2);
102
+ assert.ok(violations.some((v) => v.rule === "reason-required"));
103
+ assert.ok(violations.some((v) => v.rule === "file-line-ref"));
104
+ });
105
+ (0, node_test_1.it)("passes when PASS has reason and file:line", () => {
106
+ const violations = (0, policy_js_1.checkLisa)("PASS", "[PASS] Looks good\n\n- Clean code at commands.ts:42\n- Tests pass");
107
+ assert.strictEqual(violations.length, 0);
108
+ });
109
+ (0, node_test_1.it)("warns when PASS has reason but no file:line", () => {
110
+ const violations = (0, policy_js_1.checkLisa)("PASS", "[PASS] Looks good\n\n- Clean code\n- Tests pass");
111
+ assert.strictEqual(violations.length, 1);
112
+ assert.strictEqual(violations[0].rule, "file-line-ref");
113
+ });
114
+ (0, node_test_1.it)("warns when NEEDS_WORK has no reason", () => {
115
+ const violations = (0, policy_js_1.checkLisa)("NEEDS_WORK", "[NEEDS_WORK] Fix it");
116
+ assert.ok(violations.some((v) => v.rule === "reason-required"));
117
+ });
118
+ (0, node_test_1.it)("passes NEEDS_WORK with reason and file:line", () => {
119
+ const violations = (0, policy_js_1.checkLisa)("NEEDS_WORK", "[NEEDS_WORK] Fix it\n\n- Bug at policy.ts:30");
120
+ assert.strictEqual(violations.length, 0);
121
+ });
122
+ (0, node_test_1.it)("no warnings for DISCUSS", () => {
123
+ const violations = (0, policy_js_1.checkLisa)("DISCUSS", "[DISCUSS] About this");
124
+ assert.strictEqual(violations.length, 0);
125
+ });
126
+ (0, node_test_1.it)("no file:line required for CONSENSUS", () => {
127
+ const violations = (0, policy_js_1.checkLisa)("CONSENSUS", "[CONSENSUS] Agreed");
128
+ assert.strictEqual(violations.length, 0);
129
+ });
130
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const node_test_1 = require("node:test");
37
+ const assert = __importStar(require("node:assert"));
38
+ const state_js_1 = require("../state.js");
39
+ (0, node_test_1.describe)("extractTag", () => {
40
+ (0, node_test_1.it)("extracts known tags", () => {
41
+ assert.strictEqual((0, state_js_1.extractTag)("[PLAN] My plan"), "PLAN");
42
+ assert.strictEqual((0, state_js_1.extractTag)("[CODE] Implementation done"), "CODE");
43
+ assert.strictEqual((0, state_js_1.extractTag)("[RESEARCH] Results"), "RESEARCH");
44
+ assert.strictEqual((0, state_js_1.extractTag)("[CHALLENGE] I disagree"), "CHALLENGE");
45
+ assert.strictEqual((0, state_js_1.extractTag)("[PASS] Looks good"), "PASS");
46
+ assert.strictEqual((0, state_js_1.extractTag)("[NEEDS_WORK] Fix this"), "NEEDS_WORK");
47
+ assert.strictEqual((0, state_js_1.extractTag)("[DISCUSS] Let's talk"), "DISCUSS");
48
+ assert.strictEqual((0, state_js_1.extractTag)("[QUESTION] What about?"), "QUESTION");
49
+ assert.strictEqual((0, state_js_1.extractTag)("[CONSENSUS] Agreed"), "CONSENSUS");
50
+ assert.strictEqual((0, state_js_1.extractTag)("[FIX] Fixed the bug"), "FIX");
51
+ });
52
+ (0, node_test_1.it)("returns empty for invalid tags", () => {
53
+ assert.strictEqual((0, state_js_1.extractTag)("No tag here"), "");
54
+ assert.strictEqual((0, state_js_1.extractTag)("[INVALID] Nope"), "");
55
+ assert.strictEqual((0, state_js_1.extractTag)(""), "");
56
+ });
57
+ });
58
+ (0, node_test_1.describe)("extractSummary", () => {
59
+ (0, node_test_1.it)("extracts summary after tag", () => {
60
+ assert.strictEqual((0, state_js_1.extractSummary)("[PLAN] My plan"), "My plan");
61
+ assert.strictEqual((0, state_js_1.extractSummary)("[CODE] Implementation done"), "Implementation done");
62
+ });
63
+ (0, node_test_1.it)("returns full line if no tag", () => {
64
+ assert.strictEqual((0, state_js_1.extractSummary)("No tag here"), "No tag here");
65
+ });
66
+ });
67
+ (0, node_test_1.describe)("VALID_TAGS", () => {
68
+ (0, node_test_1.it)("contains all expected tags", () => {
69
+ const tags = state_js_1.VALID_TAGS.split("|");
70
+ assert.ok(tags.includes("PLAN"));
71
+ assert.ok(tags.includes("RESEARCH"));
72
+ assert.ok(tags.includes("CODE"));
73
+ assert.ok(tags.includes("FIX"));
74
+ assert.ok(tags.includes("PASS"));
75
+ assert.ok(tags.includes("NEEDS_WORK"));
76
+ assert.ok(tags.includes("CHALLENGE"));
77
+ assert.ok(tags.includes("DISCUSS"));
78
+ assert.ok(tags.includes("QUESTION"));
79
+ assert.ok(tags.includes("CONSENSUS"));
80
+ assert.strictEqual(tags.length, 10);
81
+ });
82
+ });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Watcher state machine tests.
3
+ *
4
+ * The watcher is a bash script generated at runtime, so we can't unit-test it
5
+ * directly. Instead we test the state machine logic by simulating the key
6
+ * decision points in TypeScript:
7
+ *
8
+ * 1. LAST_TURN only updates on successful trigger (ack semantics)
9
+ * 2. Failure backoff: 10 → degraded (30s), 30 → ALERT
10
+ * 3. Interactive prompt pause/resume with dual-condition gate
11
+ */
12
+ export {};
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ /**
3
+ * Watcher state machine tests.
4
+ *
5
+ * The watcher is a bash script generated at runtime, so we can't unit-test it
6
+ * directly. Instead we test the state machine logic by simulating the key
7
+ * decision points in TypeScript:
8
+ *
9
+ * 1. LAST_TURN only updates on successful trigger (ack semantics)
10
+ * 2. Failure backoff: 10 → degraded (30s), 30 → ALERT
11
+ * 3. Interactive prompt pause/resume with dual-condition gate
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ const node_test_1 = require("node:test");
48
+ const assert = __importStar(require("node:assert"));
49
+ function newState() {
50
+ return {
51
+ seenTurn: "",
52
+ ackedTurn: "",
53
+ failCount: 0,
54
+ panePromptHits: 0,
55
+ panePaused: false,
56
+ panePauseSize: 0,
57
+ };
58
+ }
59
+ /**
60
+ * Simulate check_and_trigger logic (matches bash watcher v2).
61
+ * Two-variable approach: seenTurn (observed) vs ackedTurn (delivered).
62
+ * triggerResult: true = trigger succeeded, false = failed.
63
+ * Returns the action taken.
64
+ */
65
+ function checkAndTrigger(state, currentTurn, triggerResult) {
66
+ // Detect new turn change
67
+ if (currentTurn && currentTurn !== state.seenTurn) {
68
+ state.seenTurn = currentTurn;
69
+ state.failCount = 0;
70
+ }
71
+ // Need to deliver? (seen but not acked)
72
+ if (state.seenTurn && state.seenTurn !== state.ackedTurn) {
73
+ let mode = "retry";
74
+ if (state.failCount >= 30) {
75
+ mode = "alert";
76
+ }
77
+ else if (state.failCount >= 10) {
78
+ mode = "degraded";
79
+ }
80
+ if (triggerResult) {
81
+ state.ackedTurn = state.seenTurn;
82
+ state.failCount = 0;
83
+ return "ack";
84
+ }
85
+ else {
86
+ state.failCount++;
87
+ return mode;
88
+ }
89
+ }
90
+ return "noop";
91
+ }
92
+ /**
93
+ * Simulate interactive prompt pause/resume logic.
94
+ * Returns whether send_go should proceed.
95
+ */
96
+ function handleInteractivePrompt(state, promptDetected, outputChanged, currentLogSize) {
97
+ if (state.panePaused) {
98
+ // Resume requires BOTH: output changed AND prompt gone
99
+ if (outputChanged && currentLogSize !== state.panePauseSize && !promptDetected) {
100
+ state.panePaused = false;
101
+ state.panePromptHits = 0;
102
+ return true; // resumed, proceed
103
+ }
104
+ return false; // still paused
105
+ }
106
+ if (promptDetected) {
107
+ state.panePromptHits++;
108
+ if (state.panePromptHits >= 3) {
109
+ state.panePaused = true;
110
+ state.panePauseSize = currentLogSize;
111
+ }
112
+ return false; // don't send
113
+ }
114
+ state.panePromptHits = 0;
115
+ return true; // proceed
116
+ }
117
+ // ─── Tests ───────────────────────────────────────
118
+ (0, node_test_1.describe)("Watcher: ack semantics (seenTurn vs ackedTurn)", () => {
119
+ (0, node_test_1.it)("updates ackedTurn only on successful trigger", () => {
120
+ const s = newState();
121
+ const action = checkAndTrigger(s, "ralph", true);
122
+ assert.strictEqual(action, "ack");
123
+ assert.strictEqual(s.ackedTurn, "ralph");
124
+ assert.strictEqual(s.seenTurn, "ralph");
125
+ });
126
+ (0, node_test_1.it)("does NOT update ackedTurn on failed trigger", () => {
127
+ const s = newState();
128
+ const action = checkAndTrigger(s, "ralph", false);
129
+ assert.strictEqual(action, "retry");
130
+ assert.strictEqual(s.seenTurn, "ralph"); // seen
131
+ assert.strictEqual(s.ackedTurn, ""); // NOT acked
132
+ assert.strictEqual(s.failCount, 1);
133
+ });
134
+ (0, node_test_1.it)("retries on next cycle after failure (same turn)", () => {
135
+ const s = newState();
136
+ checkAndTrigger(s, "ralph", false); // fail
137
+ assert.strictEqual(s.ackedTurn, "");
138
+ assert.strictEqual(s.failCount, 1);
139
+ // Same turn, seen != acked, so it retries
140
+ const action = checkAndTrigger(s, "ralph", true);
141
+ assert.strictEqual(action, "ack");
142
+ assert.strictEqual(s.ackedTurn, "ralph");
143
+ assert.strictEqual(s.failCount, 0);
144
+ });
145
+ (0, node_test_1.it)("resets failCount on new turn change", () => {
146
+ const s = newState();
147
+ s.seenTurn = "ralph";
148
+ s.ackedTurn = "ralph";
149
+ s.failCount = 5;
150
+ const action = checkAndTrigger(s, "lisa", true);
151
+ assert.strictEqual(action, "ack");
152
+ assert.strictEqual(s.failCount, 0);
153
+ assert.strictEqual(s.ackedTurn, "lisa");
154
+ });
155
+ (0, node_test_1.it)("noop when turn unchanged and already acked", () => {
156
+ const s = newState();
157
+ s.seenTurn = "ralph";
158
+ s.ackedTurn = "ralph";
159
+ const action = checkAndTrigger(s, "ralph", true);
160
+ assert.strictEqual(action, "noop");
161
+ });
162
+ });
163
+ (0, node_test_1.describe)("Watcher: failure backoff", () => {
164
+ (0, node_test_1.it)("enters degraded mode after 10 failures", () => {
165
+ const s = newState();
166
+ // 10 consecutive failures
167
+ for (let i = 0; i < 10; i++) {
168
+ checkAndTrigger(s, "ralph", false);
169
+ }
170
+ assert.strictEqual(s.failCount, 10);
171
+ // Next failure should be degraded
172
+ const action = checkAndTrigger(s, "ralph", false);
173
+ assert.strictEqual(action, "degraded");
174
+ assert.strictEqual(s.failCount, 11);
175
+ });
176
+ (0, node_test_1.it)("enters alert mode after 30 failures", () => {
177
+ const s = newState();
178
+ for (let i = 0; i < 30; i++) {
179
+ checkAndTrigger(s, "ralph", false);
180
+ }
181
+ assert.strictEqual(s.failCount, 30);
182
+ const action = checkAndTrigger(s, "ralph", false);
183
+ assert.strictEqual(action, "alert");
184
+ assert.strictEqual(s.failCount, 31);
185
+ });
186
+ (0, node_test_1.it)("recovers from degraded on success", () => {
187
+ const s = newState();
188
+ for (let i = 0; i < 15; i++) {
189
+ checkAndTrigger(s, "ralph", false);
190
+ }
191
+ assert.ok(s.failCount >= 10);
192
+ const action = checkAndTrigger(s, "ralph", true);
193
+ assert.strictEqual(action, "ack");
194
+ assert.strictEqual(s.failCount, 0);
195
+ assert.strictEqual(s.ackedTurn, "ralph");
196
+ });
197
+ });
198
+ (0, node_test_1.describe)("Watcher: interactive prompt pause/resume", () => {
199
+ (0, node_test_1.it)("pauses after 3 consecutive prompt detections", () => {
200
+ const s = newState();
201
+ handleInteractivePrompt(s, true, false, 100);
202
+ assert.strictEqual(s.panePromptHits, 1);
203
+ assert.strictEqual(s.panePaused, false);
204
+ handleInteractivePrompt(s, true, false, 100);
205
+ assert.strictEqual(s.panePromptHits, 2);
206
+ handleInteractivePrompt(s, true, false, 100);
207
+ assert.strictEqual(s.panePromptHits, 3);
208
+ assert.strictEqual(s.panePaused, true);
209
+ assert.strictEqual(s.panePauseSize, 100);
210
+ });
211
+ (0, node_test_1.it)("does not resume with only output change (prompt still present)", () => {
212
+ const s = newState();
213
+ s.panePaused = true;
214
+ s.panePauseSize = 100;
215
+ // Output changed but prompt still there
216
+ const proceed = handleInteractivePrompt(s, true, true, 200);
217
+ assert.strictEqual(proceed, false);
218
+ assert.strictEqual(s.panePaused, true);
219
+ });
220
+ (0, node_test_1.it)("does not resume with only prompt gone (no output change)", () => {
221
+ const s = newState();
222
+ s.panePaused = true;
223
+ s.panePauseSize = 100;
224
+ // Prompt gone but output unchanged
225
+ const proceed = handleInteractivePrompt(s, false, false, 100);
226
+ assert.strictEqual(proceed, false);
227
+ assert.strictEqual(s.panePaused, true);
228
+ });
229
+ (0, node_test_1.it)("resumes when BOTH output changed AND prompt gone", () => {
230
+ const s = newState();
231
+ s.panePaused = true;
232
+ s.panePauseSize = 100;
233
+ const proceed = handleInteractivePrompt(s, false, true, 200);
234
+ assert.strictEqual(proceed, true);
235
+ assert.strictEqual(s.panePaused, false);
236
+ assert.strictEqual(s.panePromptHits, 0);
237
+ });
238
+ (0, node_test_1.it)("resets prompt hits when no prompt detected", () => {
239
+ const s = newState();
240
+ handleInteractivePrompt(s, true, false, 100);
241
+ handleInteractivePrompt(s, true, false, 100);
242
+ assert.strictEqual(s.panePromptHits, 2);
243
+ // No prompt this time
244
+ handleInteractivePrompt(s, false, false, 100);
245
+ assert.strictEqual(s.panePromptHits, 0);
246
+ });
247
+ });
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "ralph-lisa-loop",
3
+ "version": "0.3.0",
4
+ "description": "Turn-based dual-agent collaboration: Ralph codes, Lisa reviews, consensus required.",
5
+ "bin": {
6
+ "ralph-lisa": "dist/cli.js"
7
+ },
8
+ "main": "./dist/index.js",
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "test": "node --test dist/test/*.js",
12
+ "prepack": "npm run build"
13
+ },
14
+ "files": [
15
+ "dist/",
16
+ "templates/"
17
+ ],
18
+ "keywords": [
19
+ "dual-agent",
20
+ "collaboration",
21
+ "code-review",
22
+ "claude",
23
+ "codex",
24
+ "ai-agents"
25
+ ],
26
+ "author": "YW1975",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/YW1975/Ralph-Lisa-Loop.git",
31
+ "directory": "cli"
32
+ },
33
+ "homepage": "https://github.com/YW1975/Ralph-Lisa-Loop#readme",
34
+ "bugs": {
35
+ "url": "https://github.com/YW1975/Ralph-Lisa-Loop/issues"
36
+ },
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "devDependencies": {
41
+ "typescript": "^5.4.0",
42
+ "@types/node": "^20.0.0"
43
+ }
44
+ }
@@ -0,0 +1,18 @@
1
+ ---
2
+ description: Check whose turn it is in Ralph-Lisa collaboration
3
+ ---
4
+
5
+ # Check Turn
6
+
7
+ Check whose turn it is before taking any action.
8
+
9
+ ```!
10
+ ralph-lisa whose-turn
11
+ ```
12
+
13
+ ## Rules
14
+
15
+ - If output is `ralph`: You can proceed with your work
16
+ - If output is `lisa`: STOP immediately and wait for Lisa's response
17
+
18
+ **NEVER skip this check before working.**
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: Move to next implementation step
3
+ argument-hint: "step name"
4
+ ---
5
+
6
+ # Next Step
7
+
8
+ Enter a new implementation step after consensus is reached.
9
+
10
+ ```!
11
+ ralph-lisa step "$ARGUMENTS"
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ Only use this after:
17
+ 1. Lisa gave PASS
18
+ 2. Both parties confirmed consensus
19
+
20
+ Example:
21
+ ```
22
+ /next-step implement login form
23
+ ```
@@ -0,0 +1,11 @@
1
+ ---
2
+ description: Read Lisa's latest review
3
+ ---
4
+
5
+ # Read Lisa's Review
6
+
7
+ View Lisa's latest feedback.
8
+
9
+ ```!
10
+ ralph-lisa read review.md
11
+ ```
@@ -0,0 +1,39 @@
1
+ ---
2
+ description: Submit your work to Lisa for review (Ralph only)
3
+ argument-hint: "[TAG] summary and content"
4
+ ---
5
+
6
+ # Submit Work to Lisa
7
+
8
+ Submit your work and pass the turn to Lisa.
9
+
10
+ ```!
11
+ ralph-lisa submit-ralph "$ARGUMENTS"
12
+ ```
13
+
14
+ ## Required Format
15
+
16
+ Your content MUST start with a tag and one-line summary:
17
+
18
+ ```
19
+ [TAG] One line summary
20
+
21
+ Detailed content here...
22
+ ```
23
+
24
+ ## Valid Tags for Ralph
25
+
26
+ | Tag | When to Use |
27
+ |-----|-------------|
28
+ | `[PLAN]` | Submitting a plan |
29
+ | `[RESEARCH]` | Submitting research results (before coding, when involving reference implementations/protocols/APIs) |
30
+ | `[CODE]` | Submitting code implementation (must include Test Results) |
31
+ | `[FIX]` | Submitting fixes based on feedback (must include Test Results) |
32
+ | `[CHALLENGE]` | Disagreeing with Lisa's suggestion, providing counter-argument |
33
+ | `[DISCUSS]` | General discussion or clarification |
34
+ | `[QUESTION]` | Asking for clarification |
35
+ | `[CONSENSUS]` | Confirming agreement |
36
+
37
+ ## After Submission
38
+
39
+ The turn automatically passes to Lisa. You must STOP and wait.
@@ -0,0 +1,18 @@
1
+ ---
2
+ description: View current collaboration status
3
+ ---
4
+
5
+ # View Status
6
+
7
+ See current task, turn, round, and last action.
8
+
9
+ ```!
10
+ ralph-lisa status
11
+ ```
12
+
13
+ ## Output Shows
14
+
15
+ - Current task
16
+ - Whose turn it is
17
+ - Current round and step
18
+ - Last action summary
@@ -0,0 +1,16 @@
1
+ # Check Turn
2
+
3
+ Check whose turn it is before taking any action.
4
+
5
+ ## Command
6
+
7
+ ```bash
8
+ ./mini-skill/io.sh whose-turn
9
+ ```
10
+
11
+ ## Rules
12
+
13
+ - If output is `lisa`: You can proceed with your review
14
+ - If output is `ralph`: STOP immediately and wait for Ralph's submission
15
+
16
+ **NEVER skip this check before working.**
@@ -0,0 +1,9 @@
1
+ # Read Ralph's Work
2
+
3
+ View Ralph's latest submission.
4
+
5
+ ## Command
6
+
7
+ ```bash
8
+ ./mini-skill/io.sh read work.md
9
+ ```