ralphy-spec 0.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/LICENSE +28 -0
- package/README.md +182 -0
- package/bin/ralphy-spec.js +4 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +63 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/update.d.ts +3 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +59 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/validate.d.ts +3 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +44 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/detector.d.ts +3 -0
- package/dist/utils/detector.d.ts.map +1 -0
- package/dist/utils/detector.js +28 -0
- package/dist/utils/detector.js.map +1 -0
- package/dist/utils/installer.d.ts +6 -0
- package/dist/utils/installer.d.ts.map +1 -0
- package/dist/utils/installer.js +93 -0
- package/dist/utils/installer.js.map +1 -0
- package/dist/utils/paths.d.ts +3 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +16 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/validator.d.ts +3 -0
- package/dist/utils/validator.d.ts.map +1 -0
- package/dist/utils/validator.js +54 -0
- package/dist/utils/validator.js.map +1 -0
- package/package.json +64 -0
- package/scripts/clean.mjs +14 -0
- package/scripts/copy-templates.mjs +29 -0
- package/scripts/install.sh +22 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Wenqing Yu
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# ralphy-spec
|
|
2
|
+
|
|
3
|
+
One-command setup for **Ralph loop + OpenSpec** workflows across:
|
|
4
|
+
- Cursor
|
|
5
|
+
- OpenCode
|
|
6
|
+
- Claude Code
|
|
7
|
+
|
|
8
|
+
**Website:** [https://ralphy-spec.org](https://ralphy-spec.org)
|
|
9
|
+
|
|
10
|
+
## What is Ralphy-Spec?
|
|
11
|
+
|
|
12
|
+
Ralphy-Spec combines two powerful AI development methodologies:
|
|
13
|
+
|
|
14
|
+
### The Ralph Wiggum Loop
|
|
15
|
+
|
|
16
|
+
The Ralph methodology (coined by [Geoffrey Huntley](https://ghuntley.com/ralph)) is a development approach where an AI agent receives the **same prompt repeatedly** until it completes a task. Each iteration, the AI sees its previous work in files and git history, creating a self-correction feedback loop.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
while true; do
|
|
20
|
+
ai_agent "Build feature X. Output <promise>DONE</promise> when complete."
|
|
21
|
+
# AI sees previous work, fixes mistakes, continues progress
|
|
22
|
+
done
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### OpenSpec (Spec-Driven Development)
|
|
26
|
+
|
|
27
|
+
[OpenSpec](https://github.com/Fission-AI/OpenSpec) brings structure to AI coding by requiring specs before code:
|
|
28
|
+
- `openspec/specs/` - Source of truth specifications
|
|
29
|
+
- `openspec/changes/` - Proposed changes with acceptance criteria
|
|
30
|
+
- Archive workflow to merge completed changes back
|
|
31
|
+
|
|
32
|
+
### Why Combine Them?
|
|
33
|
+
|
|
34
|
+
| Problem | Solution |
|
|
35
|
+
|---------|----------|
|
|
36
|
+
| Vague requirements in chat history | OpenSpec locks intent in structured specs |
|
|
37
|
+
| AI stops mid-task or makes mistakes | Ralph loop retries until completion |
|
|
38
|
+
| No way to verify correctness | Acceptance criteria + tests validate output |
|
|
39
|
+
| Tool-specific setup is tedious | One command sets up Cursor, OpenCode, Claude Code |
|
|
40
|
+
|
|
41
|
+
## What it installs into your project
|
|
42
|
+
|
|
43
|
+
- `openspec/` scaffold:
|
|
44
|
+
- `openspec/specs/` (source of truth)
|
|
45
|
+
- `openspec/changes/` (active changes)
|
|
46
|
+
- `openspec/archive/` (completed changes)
|
|
47
|
+
- `openspec/project.md` (project context)
|
|
48
|
+
- Tool integrations:
|
|
49
|
+
- Cursor: `.cursor/prompts/ralphy-*.md`
|
|
50
|
+
- Claude Code: `.claude/commands/ralphy-*.md`
|
|
51
|
+
- OpenCode: `AGENTS.md`
|
|
52
|
+
- Ralph loop state/config:
|
|
53
|
+
- `.ralphy/config.json`
|
|
54
|
+
- `.ralphy/ralph-loop.state.json`
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
### npm (global)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install -g ralphy-spec
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### npx (no install)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx ralphy-spec init
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### curl install script
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
curl -fsSL https://raw.githubusercontent.com/anthropics/ralphy-openspec/main/scripts/install.sh | sh
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
### Initialize in a project
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cd your-project
|
|
82
|
+
ralphy-spec init
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Non-interactive tool selection:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
ralphy-spec init --tools cursor,claude-code,opencode
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Overwrite existing files:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
ralphy-spec init --force
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Validate setup
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
ralphy-spec validate
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Update templates
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
ralphy-spec update --force
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Workflow (PRD -> Ship)
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
PRD/Requirements --> OpenSpec (specs + tasks + acceptance criteria)
|
|
113
|
+
|
|
|
114
|
+
v
|
|
115
|
+
Ralph Loop (iterate until tests pass)
|
|
116
|
+
|
|
|
117
|
+
v
|
|
118
|
+
Archive (merge back to source specs)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 1) Plan: PRD -> OpenSpec change
|
|
122
|
+
|
|
123
|
+
Use your AI tool with `/ralphy:plan` (Cursor / Claude Code), or ask OpenCode to follow `AGENTS.md`.
|
|
124
|
+
|
|
125
|
+
Expected output in your repo:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
openspec/changes/<change-name>/
|
|
129
|
+
proposal.md
|
|
130
|
+
tasks.md
|
|
131
|
+
specs/
|
|
132
|
+
<domain>/
|
|
133
|
+
spec.md
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 2) Implement: iterate until done
|
|
137
|
+
|
|
138
|
+
Use `/ralphy:implement` to implement tasks and add tests.
|
|
139
|
+
|
|
140
|
+
If you run this via a Ralph loop runner, the agent should only output:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
<promise>TASK_COMPLETE</promise>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
when **all tasks are done and tests pass**.
|
|
147
|
+
|
|
148
|
+
### 3) Validate: tests prove acceptance criteria
|
|
149
|
+
|
|
150
|
+
Use `/ralphy:validate` to run tests and map passing tests back to OpenSpec scenarios.
|
|
151
|
+
|
|
152
|
+
### 4) Archive: merge change back into specs
|
|
153
|
+
|
|
154
|
+
Use `/ralphy:archive` and (if available) the OpenSpec CLI:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
openspec archive <change-name> --yes
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Credits and Appreciation
|
|
161
|
+
|
|
162
|
+
Ralphy-Spec stands on the shoulders of giants:
|
|
163
|
+
|
|
164
|
+
- **Ralph Wiggum Methodology** - Conceived by [Geoffrey Huntley](https://ghuntley.com/ralph). The insight that AI agents can self-correct through iteration changed how we think about AI-assisted development.
|
|
165
|
+
|
|
166
|
+
- **[opencode-ralph-wiggum](https://github.com/Th0rgal/opencode-ralph-wiggum)** by [@Th0rgal](https://github.com/Th0rgal) - A clean CLI implementation of the Ralph loop for OpenCode that inspired our integration approach.
|
|
167
|
+
|
|
168
|
+
- **[OpenSpec](https://github.com/Fission-AI/OpenSpec)** by [Fission-AI](https://github.com/Fission-AI) - The spec-driven development framework that brings structure and predictability to AI coding. Their `specs/` + `changes/` model is elegant and practical.
|
|
169
|
+
|
|
170
|
+
We are grateful to these projects and their maintainers for pioneering these approaches.
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npm install
|
|
176
|
+
npm run build
|
|
177
|
+
node bin/ralphy-spec.js --help
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
BSD-3-Clause
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuCzC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA+B1D"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerInitCommand = registerInitCommand;
|
|
7
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
|
+
const detector_1 = require("../utils/detector");
|
|
9
|
+
const installer_1 = require("../utils/installer");
|
|
10
|
+
const paths_1 = require("../utils/paths");
|
|
11
|
+
function parseToolsArg(arg) {
|
|
12
|
+
if (!arg)
|
|
13
|
+
return undefined;
|
|
14
|
+
const parts = arg
|
|
15
|
+
.split(",")
|
|
16
|
+
.map((s) => s.trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
const allowed = ["cursor", "claude-code", "opencode"];
|
|
19
|
+
const tools = [];
|
|
20
|
+
for (const p of parts) {
|
|
21
|
+
if (allowed.includes(p))
|
|
22
|
+
tools.push(p);
|
|
23
|
+
}
|
|
24
|
+
return tools.length ? tools : undefined;
|
|
25
|
+
}
|
|
26
|
+
async function promptForTools(defaultTools) {
|
|
27
|
+
const { tools } = await inquirer_1.default.prompt([
|
|
28
|
+
{
|
|
29
|
+
type: "checkbox",
|
|
30
|
+
name: "tools",
|
|
31
|
+
message: "Which AI tools do you want to configure?",
|
|
32
|
+
choices: [
|
|
33
|
+
{ name: "Cursor", value: "cursor" },
|
|
34
|
+
{ name: "Claude Code", value: "claude-code" },
|
|
35
|
+
{ name: "OpenCode", value: "opencode" },
|
|
36
|
+
],
|
|
37
|
+
default: defaultTools,
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
return tools;
|
|
41
|
+
}
|
|
42
|
+
function registerInitCommand(program) {
|
|
43
|
+
program
|
|
44
|
+
.command("init")
|
|
45
|
+
.description("Initialize Ralph + OpenSpec workflow files in a project")
|
|
46
|
+
.option("--dir <path>", "Target project directory (default: current directory)")
|
|
47
|
+
.option("--tools <list>", "Comma-separated list: cursor,claude-code,opencode")
|
|
48
|
+
.option("--force", "Overwrite existing files", false)
|
|
49
|
+
.action(async (opts) => {
|
|
50
|
+
const options = {
|
|
51
|
+
dir: (0, paths_1.resolveProjectDir)(opts.dir),
|
|
52
|
+
tools: parseToolsArg(opts.tools),
|
|
53
|
+
force: opts.force,
|
|
54
|
+
};
|
|
55
|
+
const detected = await (0, detector_1.detectExistingTools)(options.dir);
|
|
56
|
+
const defaultTools = options.tools ?? (detected.length ? detected : ["cursor", "claude-code", "opencode"]);
|
|
57
|
+
const tools = options.tools ?? (await promptForTools(defaultTools));
|
|
58
|
+
await (0, installer_1.ensureOpenSpecScaffold)(options.dir);
|
|
59
|
+
await (0, installer_1.installToolTemplates)(options.dir, tools, { force: options.force });
|
|
60
|
+
process.stdout.write(`Initialized Ralph-OpenSpec in ${options.dir}\nConfigured tools: ${tools.join(", ")}\n`);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuCzC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAqB5D"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerUpdateCommand = registerUpdateCommand;
|
|
7
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
|
+
const detector_1 = require("../utils/detector");
|
|
9
|
+
const installer_1 = require("../utils/installer");
|
|
10
|
+
const paths_1 = require("../utils/paths");
|
|
11
|
+
function parseToolsArg(arg) {
|
|
12
|
+
if (!arg)
|
|
13
|
+
return undefined;
|
|
14
|
+
const parts = arg
|
|
15
|
+
.split(",")
|
|
16
|
+
.map((s) => s.trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
const allowed = ["cursor", "claude-code", "opencode"];
|
|
19
|
+
const tools = [];
|
|
20
|
+
for (const p of parts) {
|
|
21
|
+
if (allowed.includes(p))
|
|
22
|
+
tools.push(p);
|
|
23
|
+
}
|
|
24
|
+
return tools.length ? tools : undefined;
|
|
25
|
+
}
|
|
26
|
+
async function promptForTools(defaultTools) {
|
|
27
|
+
const { tools } = await inquirer_1.default.prompt([
|
|
28
|
+
{
|
|
29
|
+
type: "checkbox",
|
|
30
|
+
name: "tools",
|
|
31
|
+
message: "Which AI tools do you want to update templates for?",
|
|
32
|
+
choices: [
|
|
33
|
+
{ name: "Cursor", value: "cursor" },
|
|
34
|
+
{ name: "Claude Code", value: "claude-code" },
|
|
35
|
+
{ name: "OpenCode", value: "opencode" },
|
|
36
|
+
],
|
|
37
|
+
default: defaultTools,
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
return tools;
|
|
41
|
+
}
|
|
42
|
+
function registerUpdateCommand(program) {
|
|
43
|
+
program
|
|
44
|
+
.command("update")
|
|
45
|
+
.description("Update Ralph-OpenSpec templates in a project")
|
|
46
|
+
.option("--dir <path>", "Target project directory (default: current directory)")
|
|
47
|
+
.option("--tools <list>", "Comma-separated list: cursor,claude-code,opencode")
|
|
48
|
+
.option("--force", "Overwrite existing files", false)
|
|
49
|
+
.action(async (opts) => {
|
|
50
|
+
const dir = (0, paths_1.resolveProjectDir)(opts.dir);
|
|
51
|
+
const parsed = parseToolsArg(opts.tools);
|
|
52
|
+
const detected = await (0, detector_1.detectExistingTools)(dir);
|
|
53
|
+
const defaultTools = parsed ?? (detected.length ? detected : ["cursor", "claude-code", "opencode"]);
|
|
54
|
+
const tools = parsed ?? (await promptForTools(defaultTools));
|
|
55
|
+
await (0, installer_1.installToolTemplates)(dir, tools, { force: opts.force });
|
|
56
|
+
process.stdout.write(`Updated templates in ${dir}\nUpdated tools: ${tools.join(", ")}\n`);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.js","sourceRoot":"","sources":["../../src/commands/update.ts"],"names":[],"mappings":";;;;;AAuCA,sDAqBC;AA3DD,wDAAgC;AAEhC,gDAAwD;AACxD,kDAA0D;AAC1D,0CAAmD;AAEnD,SAAS,aAAa,CAAC,GAAY;IACjC,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,KAAK,GAAG,GAAG;SACd,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IAEnB,MAAM,OAAO,GAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,UAAU,CAAC,CAAC;IAChE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAK,OAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,CAAW,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1C,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,YAAsB;IAClD,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,kBAAQ,CAAC,MAAM,CAAsB;QAC3D;YACE,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,qDAAqD;YAC9D,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAyB,EAAE;gBACpD,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,aAA8B,EAAE;gBAC9D,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAA2B,EAAE;aACzD;YACD,OAAO,EAAE,YAAY;SACtB;KACF,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAgB,qBAAqB,CAAC,OAAgB;IACpD,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,8CAA8C,CAAC;SAC3D,MAAM,CAAC,cAAc,EAAE,uDAAuD,CAAC;SAC/E,MAAM,CAAC,gBAAgB,EAAE,mDAAmD,CAAC;SAC7E,MAAM,CAAC,SAAS,EAAE,0BAA0B,EAAE,KAAK,CAAC;SACpD,MAAM,CAAC,KAAK,EAAE,IAAsD,EAAE,EAAE;QACvE,MAAM,GAAG,GAAG,IAAA,yBAAiB,EAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,QAAQ,GAAG,MAAM,IAAA,8BAAmB,EAAC,GAAG,CAAC,CAAC;QAChD,MAAM,YAAY,GAChB,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,QAAQ,EAAE,aAAa,EAAE,UAAU,CAAc,CAAC,CAAC;QAE/F,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC;QAC7D,MAAM,IAAA,gCAAoB,EAAC,GAAG,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAE9D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,wBAAwB,GAAG,oBAAoB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CACpE,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBzC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6B9D"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerValidateCommand = registerValidateCommand;
|
|
4
|
+
const detector_1 = require("../utils/detector");
|
|
5
|
+
const paths_1 = require("../utils/paths");
|
|
6
|
+
const validator_1 = require("../utils/validator");
|
|
7
|
+
function parseToolsArg(arg) {
|
|
8
|
+
if (!arg)
|
|
9
|
+
return undefined;
|
|
10
|
+
const parts = arg
|
|
11
|
+
.split(",")
|
|
12
|
+
.map((s) => s.trim())
|
|
13
|
+
.filter(Boolean);
|
|
14
|
+
const allowed = ["cursor", "claude-code", "opencode"];
|
|
15
|
+
const tools = [];
|
|
16
|
+
for (const p of parts) {
|
|
17
|
+
if (allowed.includes(p))
|
|
18
|
+
tools.push(p);
|
|
19
|
+
}
|
|
20
|
+
return tools.length ? tools : undefined;
|
|
21
|
+
}
|
|
22
|
+
function registerValidateCommand(program) {
|
|
23
|
+
program
|
|
24
|
+
.command("validate")
|
|
25
|
+
.description("Validate that Ralph-OpenSpec setup is complete")
|
|
26
|
+
.option("--dir <path>", "Target project directory (default: current directory)")
|
|
27
|
+
.option("--tools <list>", "Comma-separated list: cursor,claude-code,opencode (default: detect)")
|
|
28
|
+
.action(async (opts) => {
|
|
29
|
+
const dir = (0, paths_1.resolveProjectDir)(opts.dir);
|
|
30
|
+
const tools = parseToolsArg(opts.tools) ?? (await (0, detector_1.detectExistingTools)(dir));
|
|
31
|
+
const issues = await (0, validator_1.validateProject)(dir, tools);
|
|
32
|
+
if (!issues.length) {
|
|
33
|
+
process.stdout.write("OK: Ralph-OpenSpec setup looks good.\n");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
for (const issue of issues) {
|
|
37
|
+
const prefix = issue.level === "error" ? "ERROR" : "WARN";
|
|
38
|
+
process.stdout.write(`${prefix}: ${issue.message}${issue.path ? ` (${issue.path})` : ""}\n`);
|
|
39
|
+
}
|
|
40
|
+
const hasErrors = issues.some((i) => i.level === "error");
|
|
41
|
+
process.exitCode = hasErrors ? 1 : 0;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":";;AAqBA,0DA6BC;AAhDD,gDAAwD;AACxD,0CAAmD;AACnD,kDAAqD;AAErD,SAAS,aAAa,CAAC,GAAY;IACjC,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,KAAK,GAAG,GAAG;SACd,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IAEnB,MAAM,OAAO,GAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,UAAU,CAAC,CAAC;IAChE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAK,OAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,CAAW,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1C,CAAC;AAED,SAAgB,uBAAuB,CAAC,OAAgB;IACtD,OAAO;SACJ,OAAO,CAAC,UAAU,CAAC;SACnB,WAAW,CAAC,gDAAgD,CAAC;SAC7D,MAAM,CAAC,cAAc,EAAE,uDAAuD,CAAC;SAC/E,MAAM,CACL,gBAAgB,EAChB,qEAAqE,CACtE;SACA,MAAM,CAAC,KAAK,EAAE,IAAsC,EAAE,EAAE;QACvD,MAAM,GAAG,GAAG,IAAA,yBAAiB,EAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,IAAA,8BAAmB,EAAC,GAAG,CAAC,CAAC,CAAC;QAC5E,MAAM,MAAM,GAAG,MAAM,IAAA,2BAAe,EAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAEjD,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;YAC/D,OAAO;QACT,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;YAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,MAAM,KAAK,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CACvE,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC;QAC1D,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACP,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const commander_1 = require("commander");
|
|
4
|
+
const init_1 = require("./commands/init");
|
|
5
|
+
const validate_1 = require("./commands/validate");
|
|
6
|
+
const update_1 = require("./commands/update");
|
|
7
|
+
function buildProgram() {
|
|
8
|
+
const program = new commander_1.Command();
|
|
9
|
+
program
|
|
10
|
+
.name("ralphy-spec")
|
|
11
|
+
.description("One-command setup for Ralph loop + OpenSpec workflows across Cursor, OpenCode, and Claude Code.")
|
|
12
|
+
.version("0.1.0");
|
|
13
|
+
(0, init_1.registerInitCommand)(program);
|
|
14
|
+
(0, validate_1.registerValidateCommand)(program);
|
|
15
|
+
(0, update_1.registerUpdateCommand)(program);
|
|
16
|
+
return program;
|
|
17
|
+
}
|
|
18
|
+
async function main() {
|
|
19
|
+
const program = buildProgram();
|
|
20
|
+
await program.parseAsync(process.argv);
|
|
21
|
+
}
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
23
|
+
main();
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,yCAAoC;AACpC,0CAAsD;AACtD,kDAA8D;AAC9D,8CAA0D;AAE1D,SAAS,YAAY;IACnB,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,aAAa,CAAC;SACnB,WAAW,CACV,iGAAiG,CAClG;SACA,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpB,IAAA,0BAAmB,EAAC,OAAO,CAAC,CAAC;IAC7B,IAAA,kCAAuB,EAAC,OAAO,CAAC,CAAC;IACjC,IAAA,8BAAqB,EAAC,OAAO,CAAC,CAAC;IAE/B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,YAAY,EAAE,CAAC;IAC/B,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,mEAAmE;AACnE,IAAI,EAAE,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ToolId = "cursor" | "claude-code" | "opencode";
|
|
2
|
+
export type InitOptions = {
|
|
3
|
+
dir: string;
|
|
4
|
+
tools?: ToolId[];
|
|
5
|
+
force: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type ValidationIssue = {
|
|
8
|
+
level: "error" | "warning";
|
|
9
|
+
message: string;
|
|
10
|
+
path?: string;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,QAAQ,GAAG,aAAa,GAAG,UAAU,CAAC;AAE3D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/utils/detector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAWvC,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ/E"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.detectExistingTools = detectExistingTools;
|
|
7
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
async function exists(p) {
|
|
10
|
+
try {
|
|
11
|
+
await promises_1.default.access(p);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function detectExistingTools(projectDir) {
|
|
19
|
+
const found = new Set();
|
|
20
|
+
if (await exists(node_path_1.default.join(projectDir, ".cursor")))
|
|
21
|
+
found.add("cursor");
|
|
22
|
+
if (await exists(node_path_1.default.join(projectDir, ".claude")))
|
|
23
|
+
found.add("claude-code");
|
|
24
|
+
if (await exists(node_path_1.default.join(projectDir, "AGENTS.md")))
|
|
25
|
+
found.add("opencode");
|
|
26
|
+
return [...found];
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/utils/detector.ts"],"names":[],"mappings":";;;;;AAaA,kDAQC;AArBD,gEAAkC;AAClC,0DAA6B;AAG7B,KAAK,UAAU,MAAM,CAAC,CAAS;IAC7B,IAAI,CAAC;QACH,MAAM,kBAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,mBAAmB,CAAC,UAAkB;IAC1D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAEhC,IAAI,MAAM,MAAM,CAAC,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxE,IAAI,MAAM,MAAM,CAAC,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC7E,IAAI,MAAM,MAAM,CAAC,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QAAE,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAE5E,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ToolId } from "../types";
|
|
2
|
+
export declare function ensureOpenSpecScaffold(projectDir: string): Promise<void>;
|
|
3
|
+
export declare function installToolTemplates(projectDir: string, tools: ToolId[], opts: {
|
|
4
|
+
force: boolean;
|
|
5
|
+
}): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=installer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../src/utils/installer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAgBvC,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB9E;AAED,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GACvB,OAAO,CAAC,IAAI,CAAC,CA+Df"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ensureOpenSpecScaffold = ensureOpenSpecScaffold;
|
|
7
|
+
exports.installToolTemplates = installToolTemplates;
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
|
+
const paths_1 = require("./paths");
|
|
12
|
+
async function ensureDir(p) {
|
|
13
|
+
await promises_1.default.mkdir(p, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
async function writeFileIfMissing(filePath, contents) {
|
|
16
|
+
try {
|
|
17
|
+
await promises_1.default.access(filePath);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
await ensureDir(node_path_1.default.dirname(filePath));
|
|
21
|
+
await promises_1.default.writeFile(filePath, contents, "utf8");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function ensureOpenSpecScaffold(projectDir) {
|
|
25
|
+
const openspecDir = node_path_1.default.join(projectDir, "openspec");
|
|
26
|
+
await ensureDir(node_path_1.default.join(openspecDir, "specs"));
|
|
27
|
+
await ensureDir(node_path_1.default.join(openspecDir, "changes"));
|
|
28
|
+
await ensureDir(node_path_1.default.join(openspecDir, "archive"));
|
|
29
|
+
await writeFileIfMissing(node_path_1.default.join(openspecDir, "project.md"), [
|
|
30
|
+
"# Project Context",
|
|
31
|
+
"",
|
|
32
|
+
"Describe your project's tech stack, conventions, and architecture here.",
|
|
33
|
+
"",
|
|
34
|
+
"## Stack",
|
|
35
|
+
"- Language:",
|
|
36
|
+
"- Framework:",
|
|
37
|
+
"- Package manager:",
|
|
38
|
+
"",
|
|
39
|
+
"## Conventions",
|
|
40
|
+
"- Code style:",
|
|
41
|
+
"- Testing:",
|
|
42
|
+
"- CI:",
|
|
43
|
+
"",
|
|
44
|
+
].join("\n"));
|
|
45
|
+
}
|
|
46
|
+
async function installToolTemplates(projectDir, tools, opts) {
|
|
47
|
+
const templatesRoot = (0, paths_1.getDistTemplatesDir)();
|
|
48
|
+
// Cursor
|
|
49
|
+
if (tools.includes("cursor")) {
|
|
50
|
+
const src = node_path_1.default.join(templatesRoot, "cursor");
|
|
51
|
+
const dst = node_path_1.default.join(projectDir, ".cursor", "prompts");
|
|
52
|
+
await fs_extra_1.default.ensureDir(dst);
|
|
53
|
+
await fs_extra_1.default.copy(src, dst, { overwrite: opts.force, errorOnExist: false });
|
|
54
|
+
}
|
|
55
|
+
// Claude Code
|
|
56
|
+
if (tools.includes("claude-code")) {
|
|
57
|
+
const src = node_path_1.default.join(templatesRoot, "claude-code");
|
|
58
|
+
const dst = node_path_1.default.join(projectDir, ".claude", "commands");
|
|
59
|
+
await fs_extra_1.default.ensureDir(dst);
|
|
60
|
+
await fs_extra_1.default.copy(src, dst, { overwrite: opts.force, errorOnExist: false });
|
|
61
|
+
}
|
|
62
|
+
// OpenCode / AGENTS.md
|
|
63
|
+
if (tools.includes("opencode")) {
|
|
64
|
+
const srcAgents = node_path_1.default.join(templatesRoot, "opencode", "AGENTS.md");
|
|
65
|
+
const dstAgents = node_path_1.default.join(projectDir, "AGENTS.md");
|
|
66
|
+
if (opts.force) {
|
|
67
|
+
await fs_extra_1.default.copyFile(srcAgents, dstAgents);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Only write if missing
|
|
71
|
+
try {
|
|
72
|
+
await promises_1.default.access(dstAgents);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
await fs_extra_1.default.copyFile(srcAgents, dstAgents);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Ralphy config/state
|
|
80
|
+
const ralphyDir = node_path_1.default.join(projectDir, ".ralphy");
|
|
81
|
+
await ensureDir(ralphyDir);
|
|
82
|
+
await writeFileIfMissing(node_path_1.default.join(ralphyDir, "config.json"), JSON.stringify({
|
|
83
|
+
testCommand: "npm test",
|
|
84
|
+
completionPromise: "TASK_COMPLETE",
|
|
85
|
+
}, null, 2) + "\n");
|
|
86
|
+
await writeFileIfMissing(node_path_1.default.join(ralphyDir, "ralph-loop.state.json"), JSON.stringify({
|
|
87
|
+
active: false,
|
|
88
|
+
change: null,
|
|
89
|
+
task: null,
|
|
90
|
+
iteration: 0,
|
|
91
|
+
}, null, 2) + "\n");
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../../src/utils/installer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAEA,wBAAgB,mBAAmB,IAAI,MAAM,CAG5C;AAED,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getDistTemplatesDir = getDistTemplatesDir;
|
|
7
|
+
exports.resolveProjectDir = resolveProjectDir;
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
function getDistTemplatesDir() {
|
|
10
|
+
// Compiled files live in dist/**. This resolves dist/templates.
|
|
11
|
+
return node_path_1.default.resolve(__dirname, "..", "templates");
|
|
12
|
+
}
|
|
13
|
+
function resolveProjectDir(dirArg) {
|
|
14
|
+
return node_path_1.default.resolve(process.cwd(), dirArg ?? ".");
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":";;;;;AAEA,kDAGC;AAED,8CAEC;AATD,0DAA6B;AAE7B,SAAgB,mBAAmB;IACjC,gEAAgE;IAChE,OAAO,mBAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AACpD,CAAC;AAED,SAAgB,iBAAiB,CAAC,MAAe;IAC/C,OAAO,mBAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,IAAI,GAAG,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/utils/validator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAWxD,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,eAAe,EAAE,CAAC,CAyC5B"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateProject = validateProject;
|
|
7
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
async function exists(p) {
|
|
10
|
+
try {
|
|
11
|
+
await promises_1.default.access(p);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function validateProject(projectDir, tools) {
|
|
19
|
+
const issues = [];
|
|
20
|
+
const openspecDir = node_path_1.default.join(projectDir, "openspec");
|
|
21
|
+
if (!(await exists(openspecDir))) {
|
|
22
|
+
issues.push({
|
|
23
|
+
level: "error",
|
|
24
|
+
message: "Missing openspec directory. Run `ralphy-openspec init`.",
|
|
25
|
+
path: "openspec/",
|
|
26
|
+
});
|
|
27
|
+
return issues;
|
|
28
|
+
}
|
|
29
|
+
for (const p of ["openspec/specs", "openspec/changes", "openspec/project.md"]) {
|
|
30
|
+
if (!(await exists(node_path_1.default.join(projectDir, p)))) {
|
|
31
|
+
issues.push({ level: "error", message: `Missing ${p}`, path: p });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (tools.includes("cursor")) {
|
|
35
|
+
const p = ".cursor/prompts/ralphy-plan.md";
|
|
36
|
+
if (!(await exists(node_path_1.default.join(projectDir, p)))) {
|
|
37
|
+
issues.push({ level: "warning", message: `Missing ${p}`, path: p });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (tools.includes("claude-code")) {
|
|
41
|
+
const p = ".claude/commands/ralphy-plan.md";
|
|
42
|
+
if (!(await exists(node_path_1.default.join(projectDir, p)))) {
|
|
43
|
+
issues.push({ level: "warning", message: `Missing ${p}`, path: p });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (tools.includes("opencode")) {
|
|
47
|
+
const p = "AGENTS.md";
|
|
48
|
+
if (!(await exists(node_path_1.default.join(projectDir, p)))) {
|
|
49
|
+
issues.push({ level: "warning", message: `Missing ${p}`, path: p });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return issues;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/utils/validator.ts"],"names":[],"mappings":";;;;;AAaA,0CA4CC;AAzDD,gEAAkC;AAClC,0DAA6B;AAG7B,KAAK,UAAU,MAAM,CAAC,CAAS;IAC7B,IAAI,CAAC;QACH,MAAM,kBAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,eAAe,CACnC,UAAkB,EAClB,KAAe;IAEf,MAAM,MAAM,GAAsB,EAAE,CAAC;IAErC,MAAM,WAAW,GAAG,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IACtD,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC;YACV,KAAK,EAAE,OAAO;YACd,OAAO,EAAE,yDAAyD;YAClE,IAAI,EAAE,WAAW;SAClB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,kBAAkB,EAAE,qBAAqB,CAAC,EAAE,CAAC;QAC9E,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,gCAAgC,CAAC;QAC3C,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QAClC,MAAM,CAAC,GAAG,iCAAiC,CAAC;QAC5C,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,MAAM,CAAC,GAAG,WAAW,CAAC;QACtB,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ralphy-spec",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "One-command setup for Ralph loop + OpenSpec workflows across Cursor, OpenCode, and Claude Code.",
|
|
5
|
+
"license": "BSD-3-Clause",
|
|
6
|
+
"author": "Wenqing Yu",
|
|
7
|
+
"homepage": "https://ralphy-spec.org",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/anthropics/ralphy-openspec.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/anthropics/ralphy-openspec/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"openspec",
|
|
17
|
+
"ralph",
|
|
18
|
+
"ralph-wiggum",
|
|
19
|
+
"ai",
|
|
20
|
+
"cursor",
|
|
21
|
+
"claude",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"opencode",
|
|
24
|
+
"spec-driven",
|
|
25
|
+
"ai-coding",
|
|
26
|
+
"ai-assistant"
|
|
27
|
+
],
|
|
28
|
+
"bin": {
|
|
29
|
+
"ralphy-spec": "bin/ralphy-spec.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"bin/",
|
|
33
|
+
"dist/",
|
|
34
|
+
"scripts/",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20.19.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "npm run clean && tsc -p tsconfig.json && node scripts/copy-templates.mjs",
|
|
43
|
+
"clean": "node scripts/clean.mjs",
|
|
44
|
+
"dev": "node --enable-source-maps dist/index.js --help",
|
|
45
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
46
|
+
"prepublishOnly": "npm run build"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"chalk": "^5.4.1",
|
|
53
|
+
"commander": "^14.0.0",
|
|
54
|
+
"fs-extra": "^11.3.0",
|
|
55
|
+
"inquirer": "^12.6.3",
|
|
56
|
+
"ora": "^9.0.0",
|
|
57
|
+
"yaml": "^2.8.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/fs-extra": "^11.0.4",
|
|
61
|
+
"@types/node": "^22.13.0",
|
|
62
|
+
"typescript": "^5.7.3"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
async function rmIfExists(p) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.rm(p, { recursive: true, force: true });
|
|
7
|
+
} catch {
|
|
8
|
+
// ignore
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const root = path.resolve(new URL(".", import.meta.url).pathname, "..", "..");
|
|
13
|
+
await rmIfExists(path.join(root, "dist"));
|
|
14
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
async function copyDir(srcDir, dstDir) {
|
|
5
|
+
await fs.mkdir(dstDir, { recursive: true });
|
|
6
|
+
const entries = await fs.readdir(srcDir, { withFileTypes: true });
|
|
7
|
+
for (const entry of entries) {
|
|
8
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
9
|
+
const dstPath = path.join(dstDir, entry.name);
|
|
10
|
+
if (entry.isDirectory()) {
|
|
11
|
+
await copyDir(srcPath, dstPath);
|
|
12
|
+
} else if (entry.isFile()) {
|
|
13
|
+
await fs.mkdir(path.dirname(dstPath), { recursive: true });
|
|
14
|
+
await fs.copyFile(srcPath, dstPath);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const root = path.resolve(new URL(".", import.meta.url).pathname, "..", "..");
|
|
20
|
+
const srcTemplates = path.join(root, "src", "templates");
|
|
21
|
+
const dstTemplates = path.join(root, "dist", "templates");
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await fs.access(srcTemplates);
|
|
25
|
+
await copyDir(srcTemplates, dstTemplates);
|
|
26
|
+
} catch {
|
|
27
|
+
// No templates yet; ignore.
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
echo "Installing ralphy-spec via npm..."
|
|
5
|
+
|
|
6
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
7
|
+
echo "ERROR: node is not installed. Please install Node.js >= 20.19.0." >&2
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
if ! command -v npm >/dev/null 2>&1; then
|
|
12
|
+
echo "ERROR: npm is not installed. Please install npm (or a Node distribution that includes it)." >&2
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
NODE_VERSION="$(node --version | sed 's/^v//')"
|
|
17
|
+
echo "Detected node v$NODE_VERSION"
|
|
18
|
+
|
|
19
|
+
npm install -g ralphy-spec@latest
|
|
20
|
+
|
|
21
|
+
echo "Done."
|
|
22
|
+
echo "Try: ralphy-spec --help"
|