@graypark/loophaus 2.0.2 → 2.1.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/bin/uninstall.mjs
CHANGED
|
@@ -175,8 +175,14 @@ export async function uninstall({
|
|
|
175
175
|
log("-", "Plugin directory not found");
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
// 3. Remove skill directories
|
|
178
|
+
// 3. Remove skill directories (both legacy ralph-* and new loop-* names)
|
|
179
179
|
const skillNames = [
|
|
180
|
+
// New skill names
|
|
181
|
+
"loop",
|
|
182
|
+
"loop-stop",
|
|
183
|
+
"loop-plan",
|
|
184
|
+
"loop-pulse",
|
|
185
|
+
// Legacy skill names
|
|
180
186
|
"ralph-loop",
|
|
181
187
|
"cancel-ralph",
|
|
182
188
|
"ralph-interview",
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// platforms/codex-cli/installer.mjs
|
|
2
|
-
import { readFile, writeFile, mkdir, cp, access } from "node:fs/promises";
|
|
2
|
+
import { readFile, writeFile, mkdir, cp, access, rm } from "node:fs/promises";
|
|
3
3
|
import { join, resolve, dirname } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import {
|
|
@@ -20,6 +20,103 @@ export async function detect() {
|
|
|
20
20
|
return fileExists(getCodexHome());
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// Legacy ralph-* skill names to clean up
|
|
24
|
+
const LEGACY_SKILLS = [
|
|
25
|
+
"ralph-loop",
|
|
26
|
+
"cancel-ralph",
|
|
27
|
+
"ralph-interview",
|
|
28
|
+
"ralph-orchestrator",
|
|
29
|
+
"ralph-claude-interview",
|
|
30
|
+
"ralph-claude-loop",
|
|
31
|
+
"ralph-claude-cancel",
|
|
32
|
+
"ralph-claude-orchestrator",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// New skill definitions for Codex CLI
|
|
36
|
+
const CODEX_SKILLS = {
|
|
37
|
+
loop: {
|
|
38
|
+
content: `---
|
|
39
|
+
name: loop
|
|
40
|
+
description: "Start iterative dev loop"
|
|
41
|
+
argument-hint: "PROMPT [--max-iterations N] [--completion-promise TEXT]"
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
# /loop — Start Iterative Dev Loop
|
|
45
|
+
|
|
46
|
+
Parse the user's arguments:
|
|
47
|
+
1. Extract \`--max-iterations N\` (default: 20)
|
|
48
|
+
2. Extract \`--completion-promise TEXT\` (default: "TADA")
|
|
49
|
+
3. Everything else is the prompt
|
|
50
|
+
|
|
51
|
+
Create \`.loophaus/state.json\`:
|
|
52
|
+
\`\`\`json
|
|
53
|
+
{
|
|
54
|
+
"active": true,
|
|
55
|
+
"prompt": "<user's prompt>",
|
|
56
|
+
"completionPromise": "<promise text>",
|
|
57
|
+
"maxIterations": 20,
|
|
58
|
+
"currentIteration": 0,
|
|
59
|
+
"sessionId": ""
|
|
60
|
+
}
|
|
61
|
+
\`\`\`
|
|
62
|
+
|
|
63
|
+
Then begin working on the task. The stop hook intercepts exit and feeds the SAME PROMPT back.
|
|
64
|
+
|
|
65
|
+
CRITICAL: If a completion promise is set, ONLY output \`<promise>TEXT</promise>\` when genuinely complete.
|
|
66
|
+
`,
|
|
67
|
+
},
|
|
68
|
+
"loop-stop": {
|
|
69
|
+
content: `---
|
|
70
|
+
name: loop-stop
|
|
71
|
+
description: "Stop active loop"
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
# /loop-stop
|
|
75
|
+
|
|
76
|
+
1. Check \`.loophaus/state.json\` exists
|
|
77
|
+
- Also check legacy \`.codex/ralph-loop.state.json\`
|
|
78
|
+
|
|
79
|
+
2. If not found: "No active loop."
|
|
80
|
+
|
|
81
|
+
3. If found: read \`currentIteration\`, set \`active: false\`, report "Stopped loop at iteration N."
|
|
82
|
+
`,
|
|
83
|
+
},
|
|
84
|
+
"loop-plan": {
|
|
85
|
+
content: `---
|
|
86
|
+
name: loop-plan
|
|
87
|
+
description: "Plan and start loop via interactive interview"
|
|
88
|
+
argument-hint: "TASK_DESCRIPTION"
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
# /loop-plan — Interactive Planning & Loop
|
|
92
|
+
|
|
93
|
+
## Phase 1: Discovery Interview
|
|
94
|
+
Ask 3-5 focused questions about the task to understand scope, acceptance criteria, constraints.
|
|
95
|
+
|
|
96
|
+
## Phase 2: PRD Generation
|
|
97
|
+
Generate \`prd.json\` with right-sized user stories.
|
|
98
|
+
|
|
99
|
+
## Phase 3: Loop Activation
|
|
100
|
+
Create \`.loophaus/state.json\` and start working on US-001 immediately.
|
|
101
|
+
|
|
102
|
+
Use \`<promise>TASK COMPLETE</promise>\` ONLY when ALL stories pass.
|
|
103
|
+
`,
|
|
104
|
+
},
|
|
105
|
+
"loop-pulse": {
|
|
106
|
+
content: `---
|
|
107
|
+
name: loop-pulse
|
|
108
|
+
description: "Check loop status"
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
# /loop-pulse
|
|
112
|
+
|
|
113
|
+
1. Read \`.loophaus/state.json\` (or legacy paths)
|
|
114
|
+
2. If active, show iteration, promise, progress
|
|
115
|
+
3. If \`prd.json\` exists, show story progress
|
|
116
|
+
`,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
23
120
|
export async function install({ dryRun = false, force = false, local = false } = {}) {
|
|
24
121
|
const pluginDir = local
|
|
25
122
|
? join(process.cwd(), ".codex", "plugins", "loophaus")
|
|
@@ -46,8 +143,20 @@ export async function install({ dryRun = false, force = false, local = false } =
|
|
|
46
143
|
}
|
|
47
144
|
}
|
|
48
145
|
|
|
49
|
-
// Step 1:
|
|
50
|
-
console.log("[1/
|
|
146
|
+
// Step 1: Clean up legacy ralph-* skills
|
|
147
|
+
console.log("[1/4] Cleaning up legacy skills...");
|
|
148
|
+
for (const name of LEGACY_SKILLS) {
|
|
149
|
+
const legacyDir = join(skillsDir, name);
|
|
150
|
+
if (await fileExists(legacyDir)) {
|
|
151
|
+
console.log(` > Remove legacy skill: ${name}`);
|
|
152
|
+
if (!dryRun) {
|
|
153
|
+
await rm(legacyDir, { recursive: true, force: true });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Step 2: Copy files
|
|
159
|
+
console.log("[2/4] Copying plugin files...");
|
|
51
160
|
for (const dir of ["hooks", "codex/commands", "lib", "core", "store"]) {
|
|
52
161
|
const src = join(PROJECT_ROOT, dir);
|
|
53
162
|
if (!(await fileExists(src))) continue;
|
|
@@ -61,8 +170,8 @@ export async function install({ dryRun = false, force = false, local = false } =
|
|
|
61
170
|
}
|
|
62
171
|
if (!dryRun) await cp(join(PROJECT_ROOT, "package.json"), join(pluginDir, "package.json"));
|
|
63
172
|
|
|
64
|
-
// Step
|
|
65
|
-
console.log("[
|
|
173
|
+
// Step 3: Hooks
|
|
174
|
+
console.log("[3/4] Configuring Stop hook...");
|
|
66
175
|
const stopCmd = `node "${join(pluginDir, "hooks", "stop-hook.mjs")}"`;
|
|
67
176
|
let existing = { hooks: {} };
|
|
68
177
|
if (await fileExists(hooksJsonPath)) {
|
|
@@ -79,42 +188,18 @@ export async function install({ dryRun = false, force = false, local = false } =
|
|
|
79
188
|
await writeFile(hooksJsonPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
80
189
|
}
|
|
81
190
|
|
|
82
|
-
// Step
|
|
83
|
-
console.log("[
|
|
84
|
-
for (const name of
|
|
191
|
+
// Step 4: Install new skills (loop, loop-stop, loop-plan, loop-pulse)
|
|
192
|
+
console.log("[4/4] Installing skills...");
|
|
193
|
+
for (const [name, skill] of Object.entries(CODEX_SKILLS)) {
|
|
85
194
|
const skillDir = join(skillsDir, name);
|
|
86
|
-
const src = name === "ralph-loop" ? "codex/commands/ralph-loop.md" : "codex/commands/cancel-ralph.md";
|
|
87
195
|
console.log(` > Install skill: ${name}`);
|
|
88
196
|
if (!dryRun) {
|
|
89
197
|
await mkdir(skillDir, { recursive: true });
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
content.replaceAll("${RALPH_CODEX_ROOT}", pluginDir).replaceAll("${LOOPHAUS_ROOT}", pluginDir),
|
|
96
|
-
"utf-8",
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const standaloneSkills = [
|
|
103
|
-
"ralph-interview",
|
|
104
|
-
"ralph-orchestrator",
|
|
105
|
-
"ralph-claude-interview",
|
|
106
|
-
"ralph-claude-loop",
|
|
107
|
-
"ralph-claude-cancel",
|
|
108
|
-
"ralph-claude-orchestrator",
|
|
109
|
-
];
|
|
110
|
-
for (const sk of standaloneSkills) {
|
|
111
|
-
const srcDir = join(PROJECT_ROOT, "skills", sk);
|
|
112
|
-
if (await fileExists(srcDir)) {
|
|
113
|
-
console.log(` > Install skill: ${sk}`);
|
|
114
|
-
if (!dryRun) {
|
|
115
|
-
await mkdir(join(skillsDir, sk), { recursive: true });
|
|
116
|
-
await cp(srcDir, join(skillsDir, sk), { recursive: true });
|
|
117
|
-
}
|
|
198
|
+
await writeFile(
|
|
199
|
+
join(skillDir, "SKILL.md"),
|
|
200
|
+
skill.content.replaceAll("${RALPH_CODEX_ROOT}", pluginDir).replaceAll("${LOOPHAUS_ROOT}", pluginDir),
|
|
201
|
+
"utf-8",
|
|
202
|
+
);
|
|
118
203
|
}
|
|
119
204
|
}
|
|
120
205
|
|
|
@@ -15,6 +15,28 @@ async function fileExists(p) {
|
|
|
15
15
|
try { await access(p); return true; } catch { return false; }
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Convert Claude Code frontmatter to Kiro steering manual mode format.
|
|
20
|
+
* Strips Claude-specific fields (allowed-tools, argument-hint, hide-from-slash-command-tool)
|
|
21
|
+
* and ensures `inclusion: manual` is set for Kiro CLI slash command support.
|
|
22
|
+
*/
|
|
23
|
+
function convertToKiroFrontmatter(content) {
|
|
24
|
+
// Match the YAML frontmatter block
|
|
25
|
+
const fmRegex = /^---\n([\s\S]*?)\n---\n/;
|
|
26
|
+
const match = content.match(fmRegex);
|
|
27
|
+
|
|
28
|
+
if (!match) {
|
|
29
|
+
// No frontmatter found — add Kiro frontmatter
|
|
30
|
+
return `---\ninclusion: manual\n---\n\n${content}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Extract the body after frontmatter
|
|
34
|
+
const body = content.slice(match[0].length);
|
|
35
|
+
|
|
36
|
+
// Build new Kiro frontmatter with inclusion: manual
|
|
37
|
+
return `---\ninclusion: manual\n---\n\n${body}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
18
40
|
export async function detect() {
|
|
19
41
|
return fileExists(getKiroHome());
|
|
20
42
|
}
|
|
@@ -30,7 +52,7 @@ export async function install({ dryRun = false, force = false } = {}) {
|
|
|
30
52
|
console.log("");
|
|
31
53
|
|
|
32
54
|
// Step 1: Create agent config with stop hook
|
|
33
|
-
console.log("[1/
|
|
55
|
+
console.log("[1/3] Configuring agent with stop hook...");
|
|
34
56
|
const agentConfig = {
|
|
35
57
|
name: "loophaus",
|
|
36
58
|
description: "loophaus — iterative dev loop agent",
|
|
@@ -55,8 +77,22 @@ export async function install({ dryRun = false, force = false } = {}) {
|
|
|
55
77
|
await writeFile(agentPath, JSON.stringify(agentConfig, null, 2), "utf-8");
|
|
56
78
|
}
|
|
57
79
|
|
|
58
|
-
// Step 2:
|
|
59
|
-
console.log("[2/
|
|
80
|
+
// Step 2: Clean up legacy ralph-* steering files
|
|
81
|
+
console.log("[2/3] Cleaning up legacy steering files...");
|
|
82
|
+
const legacySteering = [
|
|
83
|
+
"ralph-loop.md",
|
|
84
|
+
"cancel-ralph.md",
|
|
85
|
+
];
|
|
86
|
+
for (const name of legacySteering) {
|
|
87
|
+
const legacyPath = join(steeringDir, name);
|
|
88
|
+
if (await fileExists(legacyPath)) {
|
|
89
|
+
console.log(` > Remove legacy steering: ${name}`);
|
|
90
|
+
if (!dryRun) await rm(legacyPath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Step 3: Copy steering files with Kiro frontmatter conversion
|
|
95
|
+
console.log("[3/3] Installing steering files...");
|
|
60
96
|
const commands = [
|
|
61
97
|
{ src: "commands/loop.md", dest: "loop.md" },
|
|
62
98
|
{ src: "commands/loop-plan.md", dest: "loop-plan.md" },
|
|
@@ -67,10 +103,12 @@ export async function install({ dryRun = false, force = false } = {}) {
|
|
|
67
103
|
const srcPath = join(PROJECT_ROOT, src);
|
|
68
104
|
if (await fileExists(srcPath)) {
|
|
69
105
|
const destPath = join(steeringDir, dest);
|
|
70
|
-
console.log(` > Copy ${src} -> ${destPath}`);
|
|
106
|
+
console.log(` > Copy ${src} -> ${destPath} (Kiro frontmatter)`);
|
|
71
107
|
if (!dryRun) {
|
|
72
108
|
await mkdir(steeringDir, { recursive: true });
|
|
73
|
-
await
|
|
109
|
+
const content = await readFile(srcPath, "utf-8");
|
|
110
|
+
const kiroContent = convertToKiroFrontmatter(content);
|
|
111
|
+
await writeFile(destPath, kiroContent, "utf-8");
|
|
74
112
|
}
|
|
75
113
|
}
|
|
76
114
|
}
|
|
@@ -96,6 +134,9 @@ export async function uninstall({ dryRun = false } = {}) {
|
|
|
96
134
|
join(kiroHome, "steering", "loop-plan.md"),
|
|
97
135
|
join(kiroHome, "steering", "loop-stop.md"),
|
|
98
136
|
join(kiroHome, "steering", "loop-pulse.md"),
|
|
137
|
+
// Legacy steering files
|
|
138
|
+
join(kiroHome, "steering", "ralph-loop.md"),
|
|
139
|
+
join(kiroHome, "steering", "cancel-ralph.md"),
|
|
99
140
|
];
|
|
100
141
|
|
|
101
142
|
console.log("");
|