ralph-lisa-loop 0.3.8 → 0.3.10
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/README.md +21 -18
- package/dist/cli.js +16 -8
- package/dist/commands.d.ts +7 -0
- package/dist/commands.js +71 -16
- package/dist/policy.d.ts +6 -3
- package/dist/policy.js +7 -14
- package/dist/state.d.ts +13 -0
- package/dist/state.js +74 -12
- package/package.json +2 -1
- package/templates/roles/lisa.md +55 -18
- package/templates/roles/ralph.md +37 -18
- package/dist/test/cli.test.d.ts +0 -1
- package/dist/test/cli.test.js +0 -659
- package/dist/test/policy.test.d.ts +0 -1
- package/dist/test/policy.test.js +0 -130
- package/dist/test/state.test.d.ts +0 -1
- package/dist/test/state.test.js +0 -82
- package/dist/test/watcher.test.d.ts +0 -12
- package/dist/test/watcher.test.js +0 -620
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ Ralph writes → Lisa reviews → Consensus → Next step
|
|
|
36
36
|
### 1. Install
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
npm i -g ralph-lisa
|
|
39
|
+
npm i -g ralph-lisa-loop
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
### 2. Initialize Project
|
|
@@ -61,22 +61,16 @@ ralph-lisa auto "implement login feature"
|
|
|
61
61
|
**Terminal 1 - Ralph (Claude Code)**:
|
|
62
62
|
```bash
|
|
63
63
|
ralph-lisa whose-turn # Check turn
|
|
64
|
-
# ... do work ...
|
|
65
|
-
ralph-lisa submit-ralph
|
|
66
|
-
|
|
67
|
-
1. Create login form component
|
|
68
|
-
2. Add validation
|
|
69
|
-
3. Connect to API"
|
|
64
|
+
# ... do work, write submission to .dual-agent/submit.md ...
|
|
65
|
+
ralph-lisa submit-ralph --file .dual-agent/submit.md
|
|
70
66
|
```
|
|
71
67
|
|
|
72
68
|
**Terminal 2 - Lisa (Codex)**:
|
|
73
69
|
```bash
|
|
74
70
|
ralph-lisa whose-turn # Check turn
|
|
75
71
|
ralph-lisa read work.md # Read Ralph's work
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
- Clear structure
|
|
79
|
-
- Good separation of concerns"
|
|
72
|
+
# ... write review to .dual-agent/submit.md ...
|
|
73
|
+
ralph-lisa submit-lisa --file .dual-agent/submit.md
|
|
80
74
|
```
|
|
81
75
|
|
|
82
76
|
## Features
|
|
@@ -126,7 +120,7 @@ export RL_POLICY_MODE=warn
|
|
|
126
120
|
# Enable block mode (rejects non-compliant submissions)
|
|
127
121
|
export RL_POLICY_MODE=block
|
|
128
122
|
|
|
129
|
-
# Disable
|
|
123
|
+
# Disable
|
|
130
124
|
export RL_POLICY_MODE=off
|
|
131
125
|
```
|
|
132
126
|
|
|
@@ -182,17 +176,27 @@ ralph-lisa auto --full-auto "task" # Auto mode without permission prompts
|
|
|
182
176
|
|
|
183
177
|
# Turn control
|
|
184
178
|
ralph-lisa whose-turn # Check whose turn
|
|
185
|
-
ralph-lisa
|
|
186
|
-
ralph-lisa submit-
|
|
179
|
+
ralph-lisa check-turn # Alias for whose-turn
|
|
180
|
+
ralph-lisa submit-ralph --file f.md # Ralph submits (recommended)
|
|
181
|
+
ralph-lisa submit-lisa --file f.md # Lisa submits (recommended)
|
|
182
|
+
ralph-lisa submit-ralph --stdin # Submit via stdin pipe
|
|
183
|
+
ralph-lisa submit-lisa --stdin # Lisa submit via stdin pipe
|
|
184
|
+
ralph-lisa submit-ralph "[TAG] ..." # Inline (deprecated)
|
|
187
185
|
|
|
188
186
|
# Information
|
|
189
187
|
ralph-lisa status # Current status
|
|
190
188
|
ralph-lisa read work.md # Ralph's latest
|
|
191
189
|
ralph-lisa read review.md # Lisa's latest
|
|
190
|
+
ralph-lisa read-review # Alias for read review.md
|
|
191
|
+
ralph-lisa read review --round N # Read review from round N
|
|
192
192
|
ralph-lisa history # Full history
|
|
193
|
+
ralph-lisa recap # Context recovery summary
|
|
194
|
+
ralph-lisa logs # List transcript logs
|
|
195
|
+
ralph-lisa logs cat [name] # View a specific log
|
|
193
196
|
|
|
194
197
|
# Flow control
|
|
195
|
-
ralph-lisa step "phase-name" # Enter new phase
|
|
198
|
+
ralph-lisa step "phase-name" # Enter new phase (requires consensus)
|
|
199
|
+
ralph-lisa step --force "phase-name" # Enter new phase (skip consensus check)
|
|
196
200
|
ralph-lisa update-task "new direction" # Update task direction mid-session
|
|
197
201
|
ralph-lisa archive [name] # Archive session
|
|
198
202
|
ralph-lisa clean # Clean session
|
|
@@ -247,7 +251,7 @@ For auto mode:
|
|
|
247
251
|
|
|
248
252
|
| Variable | Default | Description |
|
|
249
253
|
|----------|---------|-------------|
|
|
250
|
-
| `RL_POLICY_MODE` | `
|
|
254
|
+
| `RL_POLICY_MODE` | `warn` | Policy check mode: `off`, `warn`, `block` |
|
|
251
255
|
| `RL_CHECKPOINT_ROUNDS` | `0` (disabled) | Pause for human review every N rounds |
|
|
252
256
|
| `RL_LOG_MAX_MB` | `5` | Pane log truncation threshold in MB (min 1) |
|
|
253
257
|
|
|
@@ -257,8 +261,7 @@ Part of the [TigerHill](https://github.com/Click-Intelligence-LLC/TigerHill) pro
|
|
|
257
261
|
|
|
258
262
|
## See Also
|
|
259
263
|
|
|
260
|
-
- [CONCEPT.md](CONCEPT.md) - Why dual-agent collaboration works
|
|
261
|
-
- [UPGRADE_PLAN_V3.md](UPGRADE_PLAN_V3.md) - V3 design document
|
|
264
|
+
- [CONCEPT.md](../CONCEPT.md) - Why dual-agent collaboration works
|
|
262
265
|
|
|
263
266
|
## License
|
|
264
267
|
|
package/dist/cli.js
CHANGED
|
@@ -21,6 +21,7 @@ switch (cmd) {
|
|
|
21
21
|
(0, commands_js_1.cmdUninit)();
|
|
22
22
|
break;
|
|
23
23
|
case "whose-turn":
|
|
24
|
+
case "check-turn":
|
|
24
25
|
(0, commands_js_1.cmdWhoseTurn)();
|
|
25
26
|
break;
|
|
26
27
|
case "submit-ralph":
|
|
@@ -35,10 +36,14 @@ switch (cmd) {
|
|
|
35
36
|
case "read":
|
|
36
37
|
(0, commands_js_1.cmdRead)(rest);
|
|
37
38
|
break;
|
|
39
|
+
case "read-review":
|
|
40
|
+
(0, commands_js_1.cmdRead)(["review.md", ...rest]);
|
|
41
|
+
break;
|
|
38
42
|
case "recap":
|
|
39
43
|
(0, commands_js_1.cmdRecap)();
|
|
40
44
|
break;
|
|
41
45
|
case "step":
|
|
46
|
+
case "next-step":
|
|
42
47
|
(0, commands_js_1.cmdStep)(rest);
|
|
43
48
|
break;
|
|
44
49
|
case "history":
|
|
@@ -96,11 +101,13 @@ function showHelp() {
|
|
|
96
101
|
console.log(' ralph-lisa auto --full-auto "task" Auto mode without permission prompts');
|
|
97
102
|
console.log("");
|
|
98
103
|
console.log("Turn Control:");
|
|
99
|
-
console.log(" ralph-lisa
|
|
100
|
-
console.log(
|
|
101
|
-
console.log(" ralph-lisa submit-
|
|
102
|
-
console.log(
|
|
103
|
-
console.log(" ralph-lisa submit-lisa --
|
|
104
|
+
console.log(" ralph-lisa check-turn Check whose turn (alias: whose-turn)");
|
|
105
|
+
console.log(" ralph-lisa submit-ralph --file <f> Ralph submits from file (recommended)");
|
|
106
|
+
console.log(" ralph-lisa submit-lisa --file <f> Lisa submits from file (recommended)");
|
|
107
|
+
console.log(" ralph-lisa submit-ralph --stdin Ralph submits from stdin");
|
|
108
|
+
console.log(" ralph-lisa submit-lisa --stdin Lisa submits from stdin");
|
|
109
|
+
console.log(' ralph-lisa submit-ralph "[TAG]..." Ralph submits inline (deprecated)');
|
|
110
|
+
console.log(' ralph-lisa submit-lisa "[TAG]..." Lisa submits inline (deprecated)');
|
|
104
111
|
console.log("");
|
|
105
112
|
console.log("Tags:");
|
|
106
113
|
console.log(" Ralph: [PLAN] [RESEARCH] [CODE] [FIX] [CHALLENGE] [DISCUSS] [QUESTION] [CONSENSUS]");
|
|
@@ -108,12 +115,13 @@ function showHelp() {
|
|
|
108
115
|
console.log("");
|
|
109
116
|
console.log("Information:");
|
|
110
117
|
console.log(" ralph-lisa status Show current status");
|
|
111
|
-
console.log(" ralph-lisa read
|
|
118
|
+
console.log(" ralph-lisa read-review Read Lisa's review (alias: read review.md)");
|
|
119
|
+
console.log(" ralph-lisa read <file> Read work.md/review.md/etc");
|
|
112
120
|
console.log(" ralph-lisa recap Context recovery summary");
|
|
113
121
|
console.log(" ralph-lisa history Show full history");
|
|
114
122
|
console.log("");
|
|
115
123
|
console.log("Flow Control:");
|
|
116
|
-
console.log(' ralph-lisa step "name"
|
|
124
|
+
console.log(' ralph-lisa next-step "name" Enter new step (alias: step)');
|
|
117
125
|
console.log(' ralph-lisa update-task "desc" Update task direction');
|
|
118
126
|
console.log(" ralph-lisa archive [name] Archive session");
|
|
119
127
|
console.log(" ralph-lisa clean Clean session");
|
|
@@ -141,6 +149,6 @@ function showVersion() {
|
|
|
141
149
|
console.log(`ralph-lisa-loop v${pkg.version}`);
|
|
142
150
|
}
|
|
143
151
|
catch {
|
|
144
|
-
console.log("ralph-lisa-loop v0.3.
|
|
152
|
+
console.log("ralph-lisa-loop v0.3.10");
|
|
145
153
|
}
|
|
146
154
|
}
|
package/dist/commands.d.ts
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
* CLI commands for Ralph-Lisa Loop.
|
|
3
3
|
* Direct port of io.sh logic to Node/TS.
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Generate a project-specific tmux session name to avoid conflicts
|
|
7
|
+
* when running multiple projects simultaneously.
|
|
8
|
+
* Format: rll-{sanitized-dirname}-{short-hash}
|
|
9
|
+
* tmux session names cannot contain '.' or ':'.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateSessionName(projectDir: string): string;
|
|
5
12
|
export declare function cmdInit(args: string[]): void;
|
|
6
13
|
export declare function cmdWhoseTurn(): void;
|
|
7
14
|
export declare function cmdSubmitRalph(args: string[]): void;
|
package/dist/commands.js
CHANGED
|
@@ -37,6 +37,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
37
37
|
};
|
|
38
38
|
})();
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.generateSessionName = generateSessionName;
|
|
40
41
|
exports.cmdInit = cmdInit;
|
|
41
42
|
exports.cmdWhoseTurn = cmdWhoseTurn;
|
|
42
43
|
exports.cmdSubmitRalph = cmdSubmitRalph;
|
|
@@ -58,12 +59,26 @@ exports.cmdLogs = cmdLogs;
|
|
|
58
59
|
exports.cmdDoctor = cmdDoctor;
|
|
59
60
|
const fs = __importStar(require("node:fs"));
|
|
60
61
|
const path = __importStar(require("node:path"));
|
|
62
|
+
const crypto = __importStar(require("node:crypto"));
|
|
61
63
|
const node_child_process_1 = require("node:child_process");
|
|
62
64
|
const state_js_1 = require("./state.js");
|
|
63
65
|
const policy_js_1 = require("./policy.js");
|
|
64
66
|
function line(ch = "=", len = 40) {
|
|
65
67
|
return ch.repeat(len);
|
|
66
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Generate a project-specific tmux session name to avoid conflicts
|
|
71
|
+
* when running multiple projects simultaneously.
|
|
72
|
+
* Format: rll-{sanitized-dirname}-{short-hash}
|
|
73
|
+
* tmux session names cannot contain '.' or ':'.
|
|
74
|
+
*/
|
|
75
|
+
function generateSessionName(projectDir) {
|
|
76
|
+
const dirName = path.basename(projectDir);
|
|
77
|
+
const hash = crypto.createHash("md5").update(projectDir).digest("hex").slice(0, 6);
|
|
78
|
+
// Sanitize: keep alphanumeric and hyphens only, truncate to 20 chars
|
|
79
|
+
const sanitized = dirName.replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 20);
|
|
80
|
+
return `rll-${sanitized || "project"}-${hash}`;
|
|
81
|
+
}
|
|
67
82
|
/**
|
|
68
83
|
* Resolve submission content from args, --file, or --stdin.
|
|
69
84
|
* Returns content and whether it came from an external source (file/stdin).
|
|
@@ -92,7 +107,8 @@ function resolveContent(args) {
|
|
|
92
107
|
process.exit(1);
|
|
93
108
|
}
|
|
94
109
|
}
|
|
95
|
-
|
|
110
|
+
// Replace literal \n sequences with real newlines (IMP-3: inline format fix)
|
|
111
|
+
return { content: args.join(" ").replace(/\\n/g, "\n"), external: false };
|
|
96
112
|
}
|
|
97
113
|
/**
|
|
98
114
|
* Get list of changed files via git diff.
|
|
@@ -119,7 +135,8 @@ function cmdInit(args) {
|
|
|
119
135
|
console.error('Usage: ralph-lisa init "task description"');
|
|
120
136
|
process.exit(1);
|
|
121
137
|
}
|
|
122
|
-
|
|
138
|
+
// Always create in CWD — init is a creation command, not a discovery command
|
|
139
|
+
const dir = (0, state_js_1.stateDir)(process.cwd());
|
|
123
140
|
if (fs.existsSync(dir)) {
|
|
124
141
|
console.log("Warning: Existing session will be overwritten");
|
|
125
142
|
}
|
|
@@ -141,7 +158,7 @@ function cmdInit(args) {
|
|
|
141
158
|
console.log(`Task: ${task}`);
|
|
142
159
|
console.log("Turn: ralph");
|
|
143
160
|
console.log("");
|
|
144
|
-
console.log(
|
|
161
|
+
console.log("Ralph should start with: ralph-lisa submit-ralph --file .dual-agent/submit.md");
|
|
145
162
|
console.log(line());
|
|
146
163
|
}
|
|
147
164
|
// ─── whose-turn ──────────────────────────────────
|
|
@@ -154,9 +171,9 @@ function cmdSubmitRalph(args) {
|
|
|
154
171
|
(0, state_js_1.checkSession)();
|
|
155
172
|
const { content, external } = resolveContent(args);
|
|
156
173
|
if (!content) {
|
|
157
|
-
console.error(
|
|
158
|
-
console.error(' ralph-lisa submit-ralph --file <path>');
|
|
174
|
+
console.error("Usage: ralph-lisa submit-ralph --file <path> (recommended)");
|
|
159
175
|
console.error(" echo content | ralph-lisa submit-ralph --stdin");
|
|
176
|
+
console.error(' ralph-lisa submit-ralph "[TAG] ..." (deprecated, shell escaping issues)');
|
|
160
177
|
console.error("");
|
|
161
178
|
console.error("Valid tags: PLAN, RESEARCH, CODE, FIX, CHALLENGE, DISCUSS, QUESTION, CONSENSUS");
|
|
162
179
|
process.exit(1);
|
|
@@ -175,8 +192,15 @@ function cmdSubmitRalph(args) {
|
|
|
175
192
|
console.error("Valid tags: PLAN, RESEARCH, CODE, FIX, CHALLENGE, DISCUSS, QUESTION, CONSENSUS");
|
|
176
193
|
process.exit(1);
|
|
177
194
|
}
|
|
178
|
-
// Policy check
|
|
179
|
-
|
|
195
|
+
// Policy check (IMP-4: clear status/warning separation)
|
|
196
|
+
const { proceed, violations } = (0, policy_js_1.runPolicyCheck)("ralph", tag, content);
|
|
197
|
+
if (!proceed) {
|
|
198
|
+
console.error(line());
|
|
199
|
+
console.error("Submission BLOCKED by policy:");
|
|
200
|
+
for (const v of violations) {
|
|
201
|
+
console.error(` - ${v.message}`);
|
|
202
|
+
}
|
|
203
|
+
console.error(line());
|
|
180
204
|
process.exit(1);
|
|
181
205
|
}
|
|
182
206
|
const round = (0, state_js_1.getRound)();
|
|
@@ -207,7 +231,18 @@ function cmdSubmitRalph(args) {
|
|
|
207
231
|
(0, state_js_1.updateLastAction)("Ralph", content);
|
|
208
232
|
(0, state_js_1.setTurn)("lisa");
|
|
209
233
|
console.log(line());
|
|
210
|
-
|
|
234
|
+
if (violations.length > 0) {
|
|
235
|
+
console.log(`Submitted OK (with warnings): [${tag}] ${summary}`);
|
|
236
|
+
console.log("");
|
|
237
|
+
console.log("Policy warnings:");
|
|
238
|
+
for (const v of violations) {
|
|
239
|
+
console.log(` - ${v.message}`);
|
|
240
|
+
}
|
|
241
|
+
console.log("");
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
console.log(`Submitted: [${tag}] ${summary}`);
|
|
245
|
+
}
|
|
211
246
|
console.log("Turn passed to: Lisa");
|
|
212
247
|
console.log(line());
|
|
213
248
|
console.log("");
|
|
@@ -218,9 +253,9 @@ function cmdSubmitLisa(args) {
|
|
|
218
253
|
(0, state_js_1.checkSession)();
|
|
219
254
|
const { content, external } = resolveContent(args);
|
|
220
255
|
if (!content) {
|
|
221
|
-
console.error(
|
|
222
|
-
console.error(' ralph-lisa submit-lisa --file <path>');
|
|
256
|
+
console.error("Usage: ralph-lisa submit-lisa --file <path> (recommended)");
|
|
223
257
|
console.error(" echo content | ralph-lisa submit-lisa --stdin");
|
|
258
|
+
console.error(' ralph-lisa submit-lisa "[TAG] ..." (deprecated, shell escaping issues)');
|
|
224
259
|
console.error("");
|
|
225
260
|
console.error("Valid tags: PASS, NEEDS_WORK, CHALLENGE, DISCUSS, QUESTION, CONSENSUS");
|
|
226
261
|
process.exit(1);
|
|
@@ -239,8 +274,15 @@ function cmdSubmitLisa(args) {
|
|
|
239
274
|
console.error("Valid tags: PASS, NEEDS_WORK, CHALLENGE, DISCUSS, QUESTION, CONSENSUS");
|
|
240
275
|
process.exit(1);
|
|
241
276
|
}
|
|
242
|
-
// Policy check
|
|
243
|
-
|
|
277
|
+
// Policy check (IMP-4: clear status/warning separation)
|
|
278
|
+
const { proceed, violations } = (0, policy_js_1.runPolicyCheck)("lisa", tag, content);
|
|
279
|
+
if (!proceed) {
|
|
280
|
+
console.error(line());
|
|
281
|
+
console.error("Submission BLOCKED by policy:");
|
|
282
|
+
for (const v of violations) {
|
|
283
|
+
console.error(` - ${v.message}`);
|
|
284
|
+
}
|
|
285
|
+
console.error(line());
|
|
244
286
|
process.exit(1);
|
|
245
287
|
}
|
|
246
288
|
const round = (0, state_js_1.getRound)();
|
|
@@ -277,7 +319,18 @@ function cmdSubmitLisa(args) {
|
|
|
277
319
|
const nextRound = (parseInt(round, 10) || 0) + 1;
|
|
278
320
|
(0, state_js_1.setRound)(nextRound);
|
|
279
321
|
console.log(line());
|
|
280
|
-
|
|
322
|
+
if (violations.length > 0) {
|
|
323
|
+
console.log(`Submitted OK (with warnings): [${tag}] ${summary}`);
|
|
324
|
+
console.log("");
|
|
325
|
+
console.log("Policy warnings:");
|
|
326
|
+
for (const v of violations) {
|
|
327
|
+
console.log(` - ${v.message}`);
|
|
328
|
+
}
|
|
329
|
+
console.log("");
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
console.log(`Submitted: [${tag}] ${summary}`);
|
|
333
|
+
}
|
|
281
334
|
console.log("Turn passed to: Ralph");
|
|
282
335
|
console.log(`Round: ${round} -> ${nextRound}`);
|
|
283
336
|
console.log(line());
|
|
@@ -507,7 +560,9 @@ function cmdHistory() {
|
|
|
507
560
|
function cmdArchive(args) {
|
|
508
561
|
(0, state_js_1.checkSession)();
|
|
509
562
|
const name = args[0] || new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
510
|
-
|
|
563
|
+
// Place archive next to .dual-agent/ (in project root, not CWD)
|
|
564
|
+
const root = (0, state_js_1.findProjectRoot)() || process.cwd();
|
|
565
|
+
const archiveDir = path.join(root, state_js_1.ARCHIVE_DIR);
|
|
511
566
|
const dest = path.join(archiveDir, name);
|
|
512
567
|
fs.mkdirSync(dest, { recursive: true });
|
|
513
568
|
fs.cpSync((0, state_js_1.stateDir)(), dest, { recursive: true });
|
|
@@ -938,7 +993,7 @@ end tell'`, { stdio: "pipe" });
|
|
|
938
993
|
try {
|
|
939
994
|
execSync("which tmux", { stdio: "pipe" });
|
|
940
995
|
console.log("Launching with tmux...");
|
|
941
|
-
const sessionName =
|
|
996
|
+
const sessionName = generateSessionName(projectDir);
|
|
942
997
|
execSync(`tmux kill-session -t "${sessionName}" 2>/dev/null || true`);
|
|
943
998
|
execSync(`tmux new-session -d -s "${sessionName}" -n "Ralph" "bash -c '${ralphCmd}; exec bash'"`);
|
|
944
999
|
execSync(`tmux split-window -h -t "${sessionName}" "bash -c '${lisaCmd}; exec bash'"`);
|
|
@@ -1040,7 +1095,7 @@ function cmdAuto(args) {
|
|
|
1040
1095
|
cmdInit(task.split(" "));
|
|
1041
1096
|
console.log("");
|
|
1042
1097
|
}
|
|
1043
|
-
const sessionName =
|
|
1098
|
+
const sessionName = generateSessionName(projectDir);
|
|
1044
1099
|
const dir = (0, state_js_1.stateDir)(projectDir);
|
|
1045
1100
|
fs.mkdirSync(dir, { recursive: true });
|
|
1046
1101
|
// Archive pane logs from previous runs (for transcript preservation)
|
package/dist/policy.d.ts
CHANGED
|
@@ -22,7 +22,10 @@ export declare function checkRalph(tag: string, content: string): PolicyViolatio
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function checkLisa(tag: string, content: string): PolicyViolation[];
|
|
24
24
|
/**
|
|
25
|
-
* Run policy checks
|
|
26
|
-
* Returns
|
|
25
|
+
* Run policy checks based on mode.
|
|
26
|
+
* Returns { proceed, violations } so callers can format output clearly (IMP-4).
|
|
27
27
|
*/
|
|
28
|
-
export declare function runPolicyCheck(role: "ralph" | "lisa", tag: string, content: string):
|
|
28
|
+
export declare function runPolicyCheck(role: "ralph" | "lisa", tag: string, content: string): {
|
|
29
|
+
proceed: boolean;
|
|
30
|
+
violations: PolicyViolation[];
|
|
31
|
+
};
|
package/dist/policy.js
CHANGED
|
@@ -87,26 +87,19 @@ function checkLisa(tag, content) {
|
|
|
87
87
|
return violations;
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
|
-
* Run policy checks
|
|
91
|
-
* Returns
|
|
90
|
+
* Run policy checks based on mode.
|
|
91
|
+
* Returns { proceed, violations } so callers can format output clearly (IMP-4).
|
|
92
92
|
*/
|
|
93
93
|
function runPolicyCheck(role, tag, content) {
|
|
94
94
|
const mode = getPolicyMode();
|
|
95
95
|
if (mode === "off")
|
|
96
|
-
return true;
|
|
96
|
+
return { proceed: true, violations: [] };
|
|
97
97
|
const violations = role === "ralph" ? checkRalph(tag, content) : checkLisa(tag, content);
|
|
98
98
|
if (violations.length === 0)
|
|
99
|
-
return true;
|
|
100
|
-
console.error("");
|
|
101
|
-
console.error("⚠️ Policy warnings:");
|
|
102
|
-
for (const v of violations) {
|
|
103
|
-
console.error(` - ${v.message}`);
|
|
104
|
-
}
|
|
105
|
-
console.error("");
|
|
99
|
+
return { proceed: true, violations: [] };
|
|
106
100
|
if (mode === "block") {
|
|
107
|
-
|
|
108
|
-
return false;
|
|
101
|
+
return { proceed: false, violations };
|
|
109
102
|
}
|
|
110
|
-
// warn mode:
|
|
111
|
-
return true;
|
|
103
|
+
// warn mode: proceed but pass violations to caller
|
|
104
|
+
return { proceed: true, violations };
|
|
112
105
|
}
|
package/dist/state.d.ts
CHANGED
|
@@ -5,7 +5,20 @@
|
|
|
5
5
|
export declare const STATE_DIR = ".dual-agent";
|
|
6
6
|
export declare const ARCHIVE_DIR = ".dual-agent-archive";
|
|
7
7
|
export declare const VALID_TAGS = "PLAN|RESEARCH|CODE|FIX|PASS|NEEDS_WORK|CHALLENGE|DISCUSS|QUESTION|CONSENSUS";
|
|
8
|
+
export declare function findProjectRoot(startDir?: string): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Reset the cached project root. Used in tests.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resetProjectRootCache(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Get the .dual-agent/ state directory path.
|
|
15
|
+
* When projectDir is explicitly given, uses that path directly.
|
|
16
|
+
* When omitted, searches upward from CWD to find .dual-agent/ (like git).
|
|
17
|
+
*/
|
|
8
18
|
export declare function stateDir(projectDir?: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Check that a session exists. Searches upward from CWD when no explicit dir given.
|
|
21
|
+
*/
|
|
9
22
|
export declare function checkSession(projectDir?: string): void;
|
|
10
23
|
export declare function readFile(filePath: string): string;
|
|
11
24
|
export declare function writeFile(filePath: string, content: string): void;
|
package/dist/state.js
CHANGED
|
@@ -38,6 +38,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
})();
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
exports.VALID_TAGS = exports.ARCHIVE_DIR = exports.STATE_DIR = void 0;
|
|
41
|
+
exports.findProjectRoot = findProjectRoot;
|
|
42
|
+
exports.resetProjectRootCache = resetProjectRootCache;
|
|
41
43
|
exports.stateDir = stateDir;
|
|
42
44
|
exports.checkSession = checkSession;
|
|
43
45
|
exports.readFile = readFile;
|
|
@@ -61,11 +63,71 @@ exports.STATE_DIR = ".dual-agent";
|
|
|
61
63
|
exports.ARCHIVE_DIR = ".dual-agent-archive";
|
|
62
64
|
exports.VALID_TAGS = "PLAN|RESEARCH|CODE|FIX|PASS|NEEDS_WORK|CHALLENGE|DISCUSS|QUESTION|CONSENSUS";
|
|
63
65
|
const TAG_RE = new RegExp(`^\\[(${exports.VALID_TAGS})\\]`);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Walk up from startDir to find the nearest directory containing .dual-agent/.
|
|
68
|
+
* Similar to how git finds .git/ from any subdirectory.
|
|
69
|
+
* Result is cached per process invocation for efficiency.
|
|
70
|
+
*/
|
|
71
|
+
/**
|
|
72
|
+
* Cache keyed by resolved startDir to avoid returning wrong root
|
|
73
|
+
* when called with different directories in the same process.
|
|
74
|
+
*/
|
|
75
|
+
let _cachedStartDir;
|
|
76
|
+
let _cachedProjectRoot;
|
|
77
|
+
function findProjectRoot(startDir = process.cwd()) {
|
|
78
|
+
const resolved = path.resolve(startDir);
|
|
79
|
+
// Cache hit: same startDir as last call
|
|
80
|
+
if (_cachedStartDir === resolved && _cachedProjectRoot !== undefined) {
|
|
81
|
+
if (_cachedProjectRoot === null)
|
|
82
|
+
return null;
|
|
83
|
+
// Validate cached root still exists
|
|
84
|
+
if (fs.existsSync(path.join(_cachedProjectRoot, exports.STATE_DIR))) {
|
|
85
|
+
return _cachedProjectRoot;
|
|
86
|
+
}
|
|
87
|
+
// Invalidate stale cache
|
|
88
|
+
_cachedStartDir = undefined;
|
|
89
|
+
_cachedProjectRoot = undefined;
|
|
90
|
+
}
|
|
91
|
+
let dir = resolved;
|
|
92
|
+
while (true) {
|
|
93
|
+
if (fs.existsSync(path.join(dir, exports.STATE_DIR))) {
|
|
94
|
+
_cachedStartDir = resolved;
|
|
95
|
+
_cachedProjectRoot = dir;
|
|
96
|
+
return dir;
|
|
97
|
+
}
|
|
98
|
+
const parent = path.dirname(dir);
|
|
99
|
+
if (parent === dir)
|
|
100
|
+
break; // reached filesystem root
|
|
101
|
+
dir = parent;
|
|
102
|
+
}
|
|
103
|
+
_cachedStartDir = resolved;
|
|
104
|
+
_cachedProjectRoot = null;
|
|
105
|
+
return null;
|
|
66
106
|
}
|
|
67
|
-
|
|
68
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Reset the cached project root. Used in tests.
|
|
109
|
+
*/
|
|
110
|
+
function resetProjectRootCache() {
|
|
111
|
+
_cachedStartDir = undefined;
|
|
112
|
+
_cachedProjectRoot = undefined;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get the .dual-agent/ state directory path.
|
|
116
|
+
* When projectDir is explicitly given, uses that path directly.
|
|
117
|
+
* When omitted, searches upward from CWD to find .dual-agent/ (like git).
|
|
118
|
+
*/
|
|
119
|
+
function stateDir(projectDir) {
|
|
120
|
+
if (projectDir !== undefined) {
|
|
121
|
+
return path.join(projectDir, exports.STATE_DIR);
|
|
122
|
+
}
|
|
123
|
+
const root = findProjectRoot();
|
|
124
|
+
return path.join(root || process.cwd(), exports.STATE_DIR);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Check that a session exists. Searches upward from CWD when no explicit dir given.
|
|
128
|
+
*/
|
|
129
|
+
function checkSession(projectDir) {
|
|
130
|
+
const dir = projectDir !== undefined ? stateDir(projectDir) : stateDir();
|
|
69
131
|
if (!fs.existsSync(dir)) {
|
|
70
132
|
console.error('Error: Session not initialized. Run: ralph-lisa init "task description"');
|
|
71
133
|
process.exit(1);
|
|
@@ -87,22 +149,22 @@ function appendFile(filePath, content) {
|
|
|
87
149
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
88
150
|
fs.appendFileSync(filePath, content, "utf-8");
|
|
89
151
|
}
|
|
90
|
-
function getTurn(projectDir
|
|
152
|
+
function getTurn(projectDir) {
|
|
91
153
|
return readFile(path.join(stateDir(projectDir), "turn.txt")) || "ralph";
|
|
92
154
|
}
|
|
93
|
-
function setTurn(turn, projectDir
|
|
155
|
+
function setTurn(turn, projectDir) {
|
|
94
156
|
writeFile(path.join(stateDir(projectDir), "turn.txt"), turn);
|
|
95
157
|
}
|
|
96
|
-
function getRound(projectDir
|
|
158
|
+
function getRound(projectDir) {
|
|
97
159
|
return readFile(path.join(stateDir(projectDir), "round.txt")) || "?";
|
|
98
160
|
}
|
|
99
|
-
function setRound(round, projectDir
|
|
161
|
+
function setRound(round, projectDir) {
|
|
100
162
|
writeFile(path.join(stateDir(projectDir), "round.txt"), String(round));
|
|
101
163
|
}
|
|
102
|
-
function getStep(projectDir
|
|
164
|
+
function getStep(projectDir) {
|
|
103
165
|
return readFile(path.join(stateDir(projectDir), "step.txt")) || "?";
|
|
104
166
|
}
|
|
105
|
-
function setStep(step, projectDir
|
|
167
|
+
function setStep(step, projectDir) {
|
|
106
168
|
writeFile(path.join(stateDir(projectDir), "step.txt"), step);
|
|
107
169
|
}
|
|
108
170
|
function extractTag(content) {
|
|
@@ -124,7 +186,7 @@ function timeShort() {
|
|
|
124
186
|
const pad = (n) => String(n).padStart(2, "0");
|
|
125
187
|
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
126
188
|
}
|
|
127
|
-
function appendHistory(role, content, projectDir
|
|
189
|
+
function appendHistory(role, content, projectDir) {
|
|
128
190
|
const tag = extractTag(content);
|
|
129
191
|
const summary = extractSummary(content);
|
|
130
192
|
const round = getRound(projectDir);
|
|
@@ -142,7 +204,7 @@ ${content}
|
|
|
142
204
|
`;
|
|
143
205
|
appendFile(path.join(stateDir(projectDir), "history.md"), entry);
|
|
144
206
|
}
|
|
145
|
-
function updateLastAction(role, content, projectDir
|
|
207
|
+
function updateLastAction(role, content, projectDir) {
|
|
146
208
|
const tag = extractTag(content);
|
|
147
209
|
const summary = extractSummary(content);
|
|
148
210
|
const ts = timeShort();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ralph-lisa-loop",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"description": "Turn-based dual-agent collaboration: Ralph codes, Lisa reviews, consensus required.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ralph-lisa": "dist/cli.js"
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist/",
|
|
16
|
+
"!dist/test/",
|
|
16
17
|
"templates/"
|
|
17
18
|
],
|
|
18
19
|
"keywords": [
|