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.
- package/README.md +234 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +142 -0
- package/dist/commands.d.ts +22 -0
- package/dist/commands.js +1812 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +21 -0
- package/dist/policy.d.ts +28 -0
- package/dist/policy.js +112 -0
- package/dist/state.d.ts +24 -0
- package/dist/state.js +150 -0
- package/dist/test/cli.test.d.ts +1 -0
- package/dist/test/cli.test.js +594 -0
- package/dist/test/policy.test.d.ts +1 -0
- package/dist/test/policy.test.js +130 -0
- package/dist/test/state.test.d.ts +1 -0
- package/dist/test/state.test.js +82 -0
- package/dist/test/watcher.test.d.ts +12 -0
- package/dist/test/watcher.test.js +247 -0
- package/package.json +44 -0
- package/templates/claude-commands/check-turn.md +18 -0
- package/templates/claude-commands/next-step.md +23 -0
- package/templates/claude-commands/read-review.md +11 -0
- package/templates/claude-commands/submit-work.md +39 -0
- package/templates/claude-commands/view-status.md +18 -0
- package/templates/codex-skills/check-turn.md +16 -0
- package/templates/codex-skills/read-work.md +9 -0
- package/templates/codex-skills/submit-review.md +36 -0
- package/templates/codex-skills/view-status.md +16 -0
- package/templates/ralph-prompt.md +59 -0
- package/templates/roles/lisa.md +115 -0
- package/templates/roles/ralph.md +117 -0
- package/templates/skill.json +27 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ralph-lisa-loop - public API
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
__exportStar(require("./state.js"), exports);
|
|
21
|
+
__exportStar(require("./policy.js"), exports);
|
package/dist/policy.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy layer for Ralph-Lisa Loop.
|
|
3
|
+
* Checks submissions for required content (warn or block mode).
|
|
4
|
+
*
|
|
5
|
+
* Modes (RL_POLICY_MODE env):
|
|
6
|
+
* warn - print warnings, don't block (default)
|
|
7
|
+
* block - print warnings AND exit(1) if violations found
|
|
8
|
+
* off - no checks
|
|
9
|
+
*/
|
|
10
|
+
export type PolicyMode = "off" | "warn" | "block";
|
|
11
|
+
export interface PolicyViolation {
|
|
12
|
+
rule: string;
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function getPolicyMode(): PolicyMode;
|
|
16
|
+
/**
|
|
17
|
+
* Check Ralph's submission for policy violations.
|
|
18
|
+
*/
|
|
19
|
+
export declare function checkRalph(tag: string, content: string): PolicyViolation[];
|
|
20
|
+
/**
|
|
21
|
+
* Check Lisa's submission for policy violations.
|
|
22
|
+
*/
|
|
23
|
+
export declare function checkLisa(tag: string, content: string): PolicyViolation[];
|
|
24
|
+
/**
|
|
25
|
+
* Run policy checks and handle output/exit based on mode.
|
|
26
|
+
* Returns true if submission should proceed, false if blocked.
|
|
27
|
+
*/
|
|
28
|
+
export declare function runPolicyCheck(role: "ralph" | "lisa", tag: string, content: string): boolean;
|
package/dist/policy.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Policy layer for Ralph-Lisa Loop.
|
|
4
|
+
* Checks submissions for required content (warn or block mode).
|
|
5
|
+
*
|
|
6
|
+
* Modes (RL_POLICY_MODE env):
|
|
7
|
+
* warn - print warnings, don't block (default)
|
|
8
|
+
* block - print warnings AND exit(1) if violations found
|
|
9
|
+
* off - no checks
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.getPolicyMode = getPolicyMode;
|
|
13
|
+
exports.checkRalph = checkRalph;
|
|
14
|
+
exports.checkLisa = checkLisa;
|
|
15
|
+
exports.runPolicyCheck = runPolicyCheck;
|
|
16
|
+
function getPolicyMode() {
|
|
17
|
+
const mode = process.env.RL_POLICY_MODE || "warn";
|
|
18
|
+
if (mode === "warn" || mode === "block" || mode === "off")
|
|
19
|
+
return mode;
|
|
20
|
+
return "warn";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check Ralph's submission for policy violations.
|
|
24
|
+
*/
|
|
25
|
+
function checkRalph(tag, content) {
|
|
26
|
+
const violations = [];
|
|
27
|
+
// [CODE] or [FIX] must include Test Results and file:line references
|
|
28
|
+
if (tag === "CODE" || tag === "FIX") {
|
|
29
|
+
if (!content.includes("Test Results") &&
|
|
30
|
+
!content.includes("test results") &&
|
|
31
|
+
!content.includes("Test results")) {
|
|
32
|
+
violations.push({
|
|
33
|
+
rule: "test-results",
|
|
34
|
+
message: `[${tag}] submission missing "Test Results" section.`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (!/\w+\.\w+:\d+/.test(content)) {
|
|
38
|
+
violations.push({
|
|
39
|
+
rule: "file-line-ref",
|
|
40
|
+
message: `[${tag}] submission must include at least one file:line reference (e.g., commands.ts:42).`,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// [RESEARCH] must have substance
|
|
45
|
+
if (tag === "RESEARCH") {
|
|
46
|
+
const fieldGroups = [
|
|
47
|
+
["reference", "reference implementation"],
|
|
48
|
+
["key type", "key types"],
|
|
49
|
+
["data format", "data structure"],
|
|
50
|
+
["verification", "verified"],
|
|
51
|
+
];
|
|
52
|
+
const lc = content.toLowerCase();
|
|
53
|
+
const matchedFields = fieldGroups.filter((variants) => variants.some((v) => lc.includes(v.toLowerCase()))).length;
|
|
54
|
+
const hasSubstantialContent = content.split("\n").length > 3;
|
|
55
|
+
if (matchedFields < 2 && !hasSubstantialContent) {
|
|
56
|
+
violations.push({
|
|
57
|
+
rule: "research-content",
|
|
58
|
+
message: "[RESEARCH] submission needs at least 2 fields (reference/key types/data structure/verification) or equivalent summary with evidence.",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return violations;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check Lisa's submission for policy violations.
|
|
66
|
+
*/
|
|
67
|
+
function checkLisa(tag, content) {
|
|
68
|
+
const violations = [];
|
|
69
|
+
// [PASS] or [NEEDS_WORK] must include at least 1 reason and file:line references
|
|
70
|
+
if (tag === "PASS" || tag === "NEEDS_WORK") {
|
|
71
|
+
// Content after the first line (tag+summary) should have substance
|
|
72
|
+
const lines = content.split("\n");
|
|
73
|
+
const bodyLines = lines.slice(1).filter((l) => l.trim().length > 0);
|
|
74
|
+
if (bodyLines.length === 0) {
|
|
75
|
+
violations.push({
|
|
76
|
+
rule: "reason-required",
|
|
77
|
+
message: `[${tag}] submission must include at least 1 reason.`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (!/\w+\.\w+:\d+/.test(content)) {
|
|
81
|
+
violations.push({
|
|
82
|
+
rule: "file-line-ref",
|
|
83
|
+
message: `[${tag}] submission must include at least one file:line reference (e.g., commands.ts:42).`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return violations;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Run policy checks and handle output/exit based on mode.
|
|
91
|
+
* Returns true if submission should proceed, false if blocked.
|
|
92
|
+
*/
|
|
93
|
+
function runPolicyCheck(role, tag, content) {
|
|
94
|
+
const mode = getPolicyMode();
|
|
95
|
+
if (mode === "off")
|
|
96
|
+
return true;
|
|
97
|
+
const violations = role === "ralph" ? checkRalph(tag, content) : checkLisa(tag, content);
|
|
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("");
|
|
106
|
+
if (mode === "block") {
|
|
107
|
+
console.error("Policy mode is 'block'. Submission rejected.");
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
// warn mode: print but continue
|
|
111
|
+
return true;
|
|
112
|
+
}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State management for Ralph-Lisa Loop.
|
|
3
|
+
* Manages .dual-agent/ directory and all state files.
|
|
4
|
+
*/
|
|
5
|
+
export declare const STATE_DIR = ".dual-agent";
|
|
6
|
+
export declare const ARCHIVE_DIR = ".dual-agent-archive";
|
|
7
|
+
export declare const VALID_TAGS = "PLAN|RESEARCH|CODE|FIX|PASS|NEEDS_WORK|CHALLENGE|DISCUSS|QUESTION|CONSENSUS";
|
|
8
|
+
export declare function stateDir(projectDir?: string): string;
|
|
9
|
+
export declare function checkSession(projectDir?: string): void;
|
|
10
|
+
export declare function readFile(filePath: string): string;
|
|
11
|
+
export declare function writeFile(filePath: string, content: string): void;
|
|
12
|
+
export declare function appendFile(filePath: string, content: string): void;
|
|
13
|
+
export declare function getTurn(projectDir?: string): string;
|
|
14
|
+
export declare function setTurn(turn: string, projectDir?: string): void;
|
|
15
|
+
export declare function getRound(projectDir?: string): string;
|
|
16
|
+
export declare function setRound(round: number, projectDir?: string): void;
|
|
17
|
+
export declare function getStep(projectDir?: string): string;
|
|
18
|
+
export declare function setStep(step: string, projectDir?: string): void;
|
|
19
|
+
export declare function extractTag(content: string): string;
|
|
20
|
+
export declare function extractSummary(content: string): string;
|
|
21
|
+
export declare function timestamp(): string;
|
|
22
|
+
export declare function timeShort(): string;
|
|
23
|
+
export declare function appendHistory(role: string, content: string, projectDir?: string): void;
|
|
24
|
+
export declare function updateLastAction(role: string, content: string, projectDir?: string): void;
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* State management for Ralph-Lisa Loop.
|
|
4
|
+
* Manages .dual-agent/ directory and all state files.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.VALID_TAGS = exports.ARCHIVE_DIR = exports.STATE_DIR = void 0;
|
|
41
|
+
exports.stateDir = stateDir;
|
|
42
|
+
exports.checkSession = checkSession;
|
|
43
|
+
exports.readFile = readFile;
|
|
44
|
+
exports.writeFile = writeFile;
|
|
45
|
+
exports.appendFile = appendFile;
|
|
46
|
+
exports.getTurn = getTurn;
|
|
47
|
+
exports.setTurn = setTurn;
|
|
48
|
+
exports.getRound = getRound;
|
|
49
|
+
exports.setRound = setRound;
|
|
50
|
+
exports.getStep = getStep;
|
|
51
|
+
exports.setStep = setStep;
|
|
52
|
+
exports.extractTag = extractTag;
|
|
53
|
+
exports.extractSummary = extractSummary;
|
|
54
|
+
exports.timestamp = timestamp;
|
|
55
|
+
exports.timeShort = timeShort;
|
|
56
|
+
exports.appendHistory = appendHistory;
|
|
57
|
+
exports.updateLastAction = updateLastAction;
|
|
58
|
+
const fs = __importStar(require("node:fs"));
|
|
59
|
+
const path = __importStar(require("node:path"));
|
|
60
|
+
exports.STATE_DIR = ".dual-agent";
|
|
61
|
+
exports.ARCHIVE_DIR = ".dual-agent-archive";
|
|
62
|
+
exports.VALID_TAGS = "PLAN|RESEARCH|CODE|FIX|PASS|NEEDS_WORK|CHALLENGE|DISCUSS|QUESTION|CONSENSUS";
|
|
63
|
+
const TAG_RE = new RegExp(`^\\[(${exports.VALID_TAGS})\\]`);
|
|
64
|
+
function stateDir(projectDir = process.cwd()) {
|
|
65
|
+
return path.join(projectDir, exports.STATE_DIR);
|
|
66
|
+
}
|
|
67
|
+
function checkSession(projectDir = process.cwd()) {
|
|
68
|
+
const dir = stateDir(projectDir);
|
|
69
|
+
if (!fs.existsSync(dir)) {
|
|
70
|
+
console.error('Error: Session not initialized. Run: ralph-lisa init "task description"');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function readFile(filePath) {
|
|
75
|
+
try {
|
|
76
|
+
return fs.readFileSync(filePath, "utf-8").trim();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return "";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function writeFile(filePath, content) {
|
|
83
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
84
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
85
|
+
}
|
|
86
|
+
function appendFile(filePath, content) {
|
|
87
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
88
|
+
fs.appendFileSync(filePath, content, "utf-8");
|
|
89
|
+
}
|
|
90
|
+
function getTurn(projectDir = process.cwd()) {
|
|
91
|
+
return readFile(path.join(stateDir(projectDir), "turn.txt")) || "ralph";
|
|
92
|
+
}
|
|
93
|
+
function setTurn(turn, projectDir = process.cwd()) {
|
|
94
|
+
writeFile(path.join(stateDir(projectDir), "turn.txt"), turn);
|
|
95
|
+
}
|
|
96
|
+
function getRound(projectDir = process.cwd()) {
|
|
97
|
+
return readFile(path.join(stateDir(projectDir), "round.txt")) || "?";
|
|
98
|
+
}
|
|
99
|
+
function setRound(round, projectDir = process.cwd()) {
|
|
100
|
+
writeFile(path.join(stateDir(projectDir), "round.txt"), String(round));
|
|
101
|
+
}
|
|
102
|
+
function getStep(projectDir = process.cwd()) {
|
|
103
|
+
return readFile(path.join(stateDir(projectDir), "step.txt")) || "?";
|
|
104
|
+
}
|
|
105
|
+
function setStep(step, projectDir = process.cwd()) {
|
|
106
|
+
writeFile(path.join(stateDir(projectDir), "step.txt"), step);
|
|
107
|
+
}
|
|
108
|
+
function extractTag(content) {
|
|
109
|
+
const firstLine = content.split("\n")[0] || "";
|
|
110
|
+
const match = firstLine.match(TAG_RE);
|
|
111
|
+
return match ? match[1] : "";
|
|
112
|
+
}
|
|
113
|
+
function extractSummary(content) {
|
|
114
|
+
const firstLine = content.split("\n")[0] || "";
|
|
115
|
+
return firstLine.replace(TAG_RE, "").trim();
|
|
116
|
+
}
|
|
117
|
+
function timestamp() {
|
|
118
|
+
const d = new Date();
|
|
119
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
120
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
121
|
+
}
|
|
122
|
+
function timeShort() {
|
|
123
|
+
const d = new Date();
|
|
124
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
125
|
+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
126
|
+
}
|
|
127
|
+
function appendHistory(role, content, projectDir = process.cwd()) {
|
|
128
|
+
const tag = extractTag(content);
|
|
129
|
+
const summary = extractSummary(content);
|
|
130
|
+
const round = getRound(projectDir);
|
|
131
|
+
const step = getStep(projectDir);
|
|
132
|
+
const ts = timestamp();
|
|
133
|
+
const entry = `
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## [${role}] [${tag}] Round ${round} | Step: ${step}
|
|
137
|
+
**Time**: ${ts}
|
|
138
|
+
**Summary**: ${summary}
|
|
139
|
+
|
|
140
|
+
${content}
|
|
141
|
+
|
|
142
|
+
`;
|
|
143
|
+
appendFile(path.join(stateDir(projectDir), "history.md"), entry);
|
|
144
|
+
}
|
|
145
|
+
function updateLastAction(role, content, projectDir = process.cwd()) {
|
|
146
|
+
const tag = extractTag(content);
|
|
147
|
+
const summary = extractSummary(content);
|
|
148
|
+
const ts = timeShort();
|
|
149
|
+
writeFile(path.join(stateDir(projectDir), "last_action.txt"), `[${tag}] ${summary} (by ${role}, ${ts})`);
|
|
150
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|