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.
Files changed (43) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +182 -0
  3. package/bin/ralphy-spec.js +4 -0
  4. package/dist/commands/init.d.ts +3 -0
  5. package/dist/commands/init.d.ts.map +1 -0
  6. package/dist/commands/init.js +63 -0
  7. package/dist/commands/init.js.map +1 -0
  8. package/dist/commands/update.d.ts +3 -0
  9. package/dist/commands/update.d.ts.map +1 -0
  10. package/dist/commands/update.js +59 -0
  11. package/dist/commands/update.js.map +1 -0
  12. package/dist/commands/validate.d.ts +3 -0
  13. package/dist/commands/validate.d.ts.map +1 -0
  14. package/dist/commands/validate.js +44 -0
  15. package/dist/commands/validate.js.map +1 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +24 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/types.d.ts +12 -0
  21. package/dist/types.d.ts.map +1 -0
  22. package/dist/types.js +3 -0
  23. package/dist/types.js.map +1 -0
  24. package/dist/utils/detector.d.ts +3 -0
  25. package/dist/utils/detector.d.ts.map +1 -0
  26. package/dist/utils/detector.js +28 -0
  27. package/dist/utils/detector.js.map +1 -0
  28. package/dist/utils/installer.d.ts +6 -0
  29. package/dist/utils/installer.d.ts.map +1 -0
  30. package/dist/utils/installer.js +93 -0
  31. package/dist/utils/installer.js.map +1 -0
  32. package/dist/utils/paths.d.ts +3 -0
  33. package/dist/utils/paths.d.ts.map +1 -0
  34. package/dist/utils/paths.js +16 -0
  35. package/dist/utils/paths.js.map +1 -0
  36. package/dist/utils/validator.d.ts +3 -0
  37. package/dist/utils/validator.d.ts.map +1 -0
  38. package/dist/utils/validator.js +54 -0
  39. package/dist/utils/validator.js.map +1 -0
  40. package/package.json +64 -0
  41. package/scripts/clean.mjs +14 -0
  42. package/scripts/copy-templates.mjs +29 -0
  43. 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,4 @@
1
+ #!/usr/bin/env node
2
+
3
+ /* eslint-disable @typescript-eslint/no-var-requires */
4
+ require("../dist/index.js");
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerInitCommand(program: Command): void;
3
+ //# sourceMappingURL=init.d.ts.map
@@ -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,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerUpdateCommand(program: Command): void;
3
+ //# sourceMappingURL=update.d.ts.map
@@ -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,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerValidateCommand(program: Command): void;
3
+ //# sourceMappingURL=validate.d.ts.map
@@ -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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import type { ToolId } from "../types";
2
+ export declare function detectExistingTools(projectDir: string): Promise<ToolId[]>;
3
+ //# sourceMappingURL=detector.d.ts.map
@@ -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,3 @@
1
+ export declare function getDistTemplatesDir(): string;
2
+ export declare function resolveProjectDir(dirArg?: string): string;
3
+ //# sourceMappingURL=paths.d.ts.map
@@ -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,3 @@
1
+ import type { ToolId, ValidationIssue } from "../types";
2
+ export declare function validateProject(projectDir: string, tools: ToolId[]): Promise<ValidationIssue[]>;
3
+ //# sourceMappingURL=validator.d.ts.map
@@ -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"