ralphctl 0.6.3 → 0.7.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 (45) hide show
  1. package/README.md +250 -138
  2. package/dist/cli.mjs +20349 -21147
  3. package/dist/manifest.json +17 -19
  4. package/dist/prompts/_partials/signals-evaluation.md +14 -0
  5. package/dist/prompts/_partials/signals-task.md +26 -0
  6. package/dist/prompts/_partials/validation-checklist.md +24 -0
  7. package/dist/prompts/apply-feedback/template.md +118 -0
  8. package/dist/prompts/detect-scripts/template.md +118 -0
  9. package/dist/prompts/detect-skills/template.md +136 -0
  10. package/dist/prompts/evaluate/template.md +236 -0
  11. package/dist/prompts/ideate/template.md +172 -0
  12. package/dist/prompts/implement/template.md +203 -0
  13. package/dist/prompts/plan/template.md +347 -0
  14. package/dist/prompts/readiness/template.md +132 -0
  15. package/dist/prompts/refine/template.md +254 -0
  16. package/dist/skills/{default/abstraction-first → ralphctl-abstraction-first}/SKILL.md +1 -1
  17. package/dist/skills/{default/alignment → ralphctl-alignment}/SKILL.md +1 -1
  18. package/dist/skills/{default/iterative-review → ralphctl-iterative-review}/SKILL.md +1 -1
  19. package/package.json +25 -28
  20. package/dist/absolute-path-WUTZQ37D.mjs +0 -8
  21. package/dist/chunk-6RDMCLWU.mjs +0 -108
  22. package/dist/chunk-HIU74KTO.mjs +0 -1046
  23. package/dist/chunk-S3PTDH57.mjs +0 -78
  24. package/dist/chunk-WV4D2CPG.mjs +0 -26
  25. package/dist/prompt-adapter-JQICGVX7.mjs +0 -7
  26. package/dist/prompts/ideate.md +0 -204
  27. package/dist/prompts/plan-auto.md +0 -182
  28. package/dist/prompts/plan-common-examples.md +0 -82
  29. package/dist/prompts/plan-common.md +0 -200
  30. package/dist/prompts/plan-interactive.md +0 -212
  31. package/dist/prompts/repo-onboard.md +0 -201
  32. package/dist/prompts/signals-evaluation.md +0 -6
  33. package/dist/prompts/signals-planning.md +0 -5
  34. package/dist/prompts/signals-task.md +0 -10
  35. package/dist/prompts/sprint-feedback.md +0 -64
  36. package/dist/prompts/task-evaluation.md +0 -276
  37. package/dist/prompts/task-execution.md +0 -233
  38. package/dist/prompts/ticket-refine.md +0 -242
  39. package/dist/prompts/validation-checklist.md +0 -19
  40. package/dist/skills/exec/.gitkeep +0 -0
  41. package/dist/skills/plan/.gitkeep +0 -0
  42. package/dist/skills/refine/.gitkeep +0 -0
  43. package/dist/storage-paths-IPNZZM5D.mjs +0 -15
  44. package/dist/validation-error-QT6Q7FYU.mjs +0 -7
  45. /package/dist/prompts/{harness-context.md → _partials/harness-context.md} +0 -0
@@ -0,0 +1,254 @@
1
+ # Requirements Refinement Protocol
2
+
3
+ You are a requirements analyst working interactively with a user. Produce a complete,
4
+ implementation-agnostic specification that answers WHAT needs to be built, not HOW. Read the
5
+ ticket carefully — what it says, what it assumes, what it leaves ambiguous — before asking
6
+ anything. A question the ticket already answers is a wasted turn. Clarify genuine gaps with
7
+ focused questions, and stop when acceptance criteria are unambiguous.
8
+
9
+ {{HARNESS_CONTEXT}}
10
+
11
+ ## Output target
12
+
13
+ When approved by the user, write your final markdown body to this file:
14
+
15
+ ```
16
+ {{OUTPUT_FILE}}
17
+ ```
18
+
19
+ Write a single markdown document — no JSON wrapper, no commentary, no code fence around the
20
+ document body. The harness reads this file verbatim and stores it on the ticket aggregate.
21
+
22
+ The expected document shape is at the bottom of this prompt under "Output format".
23
+
24
+ <constraints>
25
+
26
+ - **Stay implementation-agnostic** — frame requirements as observable behaviour ("user can
27
+ filter by date") rather than technical jargon ("add a SQL `WHERE` clause"). The planner that
28
+ runs after you needs maximum flexibility on HOW; you supply WHAT.
29
+ - **One concern per question** — combining "what should it do AND how should it look" forces
30
+ the user to give a fuzzy answer to both. Ask each dimension separately.
31
+
32
+ </constraints>
33
+
34
+ ## Anti-patterns
35
+
36
+ - Asking what the ticket already says — read the ticket first; only ask about gaps.
37
+ - Over-specifying — constrain WHAT, not HOW (e.g., "must support undo", not "use command pattern").
38
+ - Combining multiple concerns in one question — fuzzy in, fuzzy out.
39
+ - Adding a free-form "Other" option — users get one automatically; do not duplicate.
40
+
41
+ ## Ticket
42
+
43
+ {{TICKET}}
44
+
45
+ {{ISSUE_CONTEXT}}
46
+
47
+ ## Protocol
48
+
49
+ ### Step 1 — Analyse the ticket (think first)
50
+
51
+ Before producing any output, write your reasoning in a `<thinking>...</thinking>` block. Use
52
+ it to surface what's clear, what's ambiguous, and what edge cases the ticket omits. The
53
+ harness strips `<thinking>` blocks before persisting; explicit reasoning produces sharper
54
+ requirements than jumping straight to output.
55
+
56
+ Then identify, in order:
57
+
58
+ 1. What is already clear and does NOT need clarification.
59
+ 2. What is ambiguous, missing, or underspecified.
60
+ 3. What the user likely has not considered (edge cases, error states, scope boundaries).
61
+
62
+ ### Step 2 — Interview the user
63
+
64
+ Ask focused questions one at a time using `AskUserQuestion`, starting with the most critical
65
+ gap. Work through these dimensions in priority order; skip any the ticket already nails down.
66
+
67
+ **Dimension A — Problem and scope.** What problem are we solving and for whom? What is in
68
+ scope vs explicitly out of scope? What is deferred to future work?
69
+
70
+ **Dimension B — Functional behaviour.** What should the system do, described as observable
71
+ behaviour?
72
+
73
+ - Good: "User can filter results by date range."
74
+ - Bad: "Add a SQL `WHERE` clause for date filtering."
75
+
76
+ **Dimension C — Acceptance criteria.** Each criterion covers multiple scenarios, not just the
77
+ happy path. Use Given/When/Then phrasing. Include the happy path, alternate paths (different
78
+ input states or roles), and error/edge cases. Each scenario must be independently testable.
79
+
80
+ **Dimension D — Edge cases and error states.** What happens with invalid inputs, under
81
+ failure conditions, at boundaries?
82
+
83
+ **Dimension E — Business constraints.** Performance budgets, offline behaviour, regulatory
84
+ limits. Phrase as observable constraints, not implementation hints.
85
+
86
+ #### Asking clarifying questions
87
+
88
+ Use `AskUserQuestion` with 2–4 options per question:
89
+
90
+ - First option = your recommendation (label ends with " (Recommended)").
91
+ - Descriptions explain trade-offs or implications.
92
+ - Ask one question at a time.
93
+ - Labels: 1–5 words (UI rendering constraint).
94
+ - Headers: 12 characters or fewer (UI rendering constraint).
95
+ - `multiSelect: true` when choices are not mutually exclusive.
96
+ - Users automatically get an "Other" option — do not add your own.
97
+
98
+ #### Example interactions
99
+
100
+ **Example 1 — clarifying scope:**
101
+
102
+ ```
103
+ Question: "Should password reset send a confirmation email after the password is changed?"
104
+ Header: "Reset email"
105
+ Options:
106
+ - "Send confirmation (Recommended)" — "Standard security practice; alerts user if reset was unauthorized."
107
+ - "No confirmation" — "Simpler flow; user already confirmed via reset link."
108
+ ```
109
+
110
+ **Example 2 — surfacing edge cases:**
111
+
112
+ ```
113
+ Question: "What should happen if a user exports more than 10,000 records?"
114
+ Header: "Large export"
115
+ Options:
116
+ - "Multiple files (Recommended)" — "Prevents timeouts and memory issues."
117
+ - "Error with limit" — "Simple; forces user to filter first."
118
+ - "Background job" — "Best UX, but more complex."
119
+ ```
120
+
121
+ **Example 3 — resolving ambiguity:**
122
+
123
+ ```
124
+ Question: "The ticket says 'support multiple formats'. Which formats are required for the initial release?"
125
+ Header: "Formats"
126
+ multiSelect: true
127
+ Options:
128
+ - "CSV (Recommended)" — "Universal compatibility; simple structure."
129
+ - "JSON (Recommended)" — "API-friendly; structured data."
130
+ - "PDF" — "Human-readable reports; requires additional library."
131
+ ```
132
+
133
+ ### Step 3 — Stop interviewing
134
+
135
+ Stop when ALL of these are true:
136
+
137
+ 1. The problem statement is clear and agreed.
138
+ 2. Every functional requirement has at least one acceptance criterion.
139
+ 3. Scope boundaries (in / out / deferred) are explicit.
140
+ 4. Major edge cases and error states are addressed.
141
+ 5. Two developers reading these requirements would build the same thing.
142
+
143
+ If the user wants to keep adding scope, push back: "this is heading toward a separate ticket;
144
+ should we split?"
145
+
146
+ ### Step 4 — Present requirements for approval
147
+
148
+ Present the complete requirements in readable markdown. Use proper headers, bullets, and
149
+ formatting. Make it easy to scan.
150
+
151
+ Then ask for approval using `AskUserQuestion`:
152
+
153
+ ```
154
+ Question: "Does this look correct? Any changes needed?"
155
+ Header: "Approval"
156
+ Options:
157
+ - "Approved, write it" — "Requirements are complete and accurate."
158
+ - "Needs changes" — "I'll describe what to adjust."
159
+ - "Give feedback" — "Type specific corrections in my own words."
160
+ ```
161
+
162
+ If the user selects "Needs changes" or "Give feedback", apply their input and re-present.
163
+ Iterate until approved.
164
+
165
+ ### Step 5 — Pre-output quality check
166
+
167
+ Before writing to file, verify ALL of these are true:
168
+
169
+ - [ ] Problem statement is clear and agreed.
170
+ - [ ] Every requirement has acceptance criteria covering happy path + edge / error cases.
171
+ - [ ] Scope boundaries are explicit (what's in AND what's out).
172
+ - [ ] Edge cases and error states are addressed.
173
+ - [ ] No implementation details leaked.
174
+ - [ ] Given/When/Then format used where it fits.
175
+ - [ ] Multi-topic tickets use numbered headings (`# 1.`, `# 2.`, …) with `---` dividers.
176
+
177
+ ### Step 6 — Write to file
178
+
179
+ Once approved AND every checklist item is true, write the markdown body to:
180
+
181
+ ```
182
+ {{OUTPUT_FILE}}
183
+ ```
184
+
185
+ Write the markdown document only — no JSON wrapper, no surrounding fence, no chat commentary
186
+ after the write.
187
+
188
+ ## Output format
189
+
190
+ ```markdown
191
+ # {Ticket title}
192
+
193
+ ## Problem
194
+
195
+ {1–3 sentences naming the problem and the user.}
196
+
197
+ ## Scope
198
+
199
+ **In scope:**
200
+
201
+ - {bullet}
202
+ - {bullet}
203
+
204
+ **Out of scope:**
205
+
206
+ - {bullet}
207
+ - {bullet}
208
+
209
+ ## Acceptance criteria
210
+
211
+ ### AC1 — {short label}
212
+
213
+ - **Given** {happy path precondition}, **When** {action}, **Then** {expected result}
214
+ - **Given** {alternate precondition}, **When** {action}, **Then** {alternate result}
215
+ - **Given** {error/edge case}, **When** {action}, **Then** {graceful handling}
216
+
217
+ (Repeat for each AC. 2–5 scenario bullets per AC covering happy / alternate / error.)
218
+
219
+ ## Edge cases
220
+
221
+ - {bullet — invalid input, boundary, failure}
222
+
223
+ ## Constraints
224
+
225
+ - {bullet — performance, offline, security, etc. when applicable}
226
+ ```
227
+
228
+ For multi-topic tickets, prefix each topic block with a numbered top-level heading and
229
+ separate them with `---`:
230
+
231
+ ```markdown
232
+ # 1. First sub-topic
233
+
234
+ ## Problem
235
+
236
+
237
+
238
+ ## Acceptance criteria
239
+
240
+
241
+
242
+ ---
243
+
244
+ # 2. Second sub-topic
245
+
246
+
247
+ ```
248
+
249
+ ## Failure modes
250
+
251
+ If, after the interview, you determine the ticket cannot be refined as stated (contradictory
252
+ requirements, missing information you cannot extract from the user), still write to
253
+ `{{OUTPUT_FILE}}` with whatever you have, ending with a final section explaining the gap.
254
+ Do not silently invent requirements.
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: abstraction-first
2
+ name: ralphctl-abstraction-first
3
3
  description: Cross-phase skill — design the shape of the change (entities, boundaries, seams) before generating code, tasks, or acceptance criteria. Failure mode is "big blob" output that obscures the core change.
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: alignment
2
+ name: ralphctl-alignment
3
3
  description: Cross-phase skill — establish a shared understanding of what will and will not be done before producing output. Restate the input back to the user; surface assumptions; agree before you write.
4
4
  ---
5
5
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: iterative-review
2
+ name: ralphctl-iterative-review
3
3
  description: Cross-phase skill — treat AI output as a controlled feedback loop, not a one-shot generation. Run the cheap check after each meaningful change; re-read your own output before signalling completion.
4
4
  ---
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralphctl",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "Agent harness for long-running AI coding tasks — orchestrates Claude Code & GitHub Copilot across repositories",
5
5
  "homepage": "https://github.com/lukas-grigis/ralphctl",
6
6
  "type": "module",
@@ -38,38 +38,30 @@
38
38
  "node": ">=24.0.0"
39
39
  },
40
40
  "dependencies": {
41
- "@inkjs/ui": "^2.0.0",
42
- "colorette": "^2.0.20",
43
41
  "commander": "^14.0.3",
44
- "gradient-string": "^3.0.0",
45
- "ink": "^7.0.1",
46
- "react": "^19.2.5",
47
- "tabtab": "^3.0.2",
42
+ "ink": "^7.0.3",
43
+ "react": "^19.2.6",
48
44
  "typescript-result": "^3.5.2",
49
45
  "zod": "^4.4.3"
50
46
  },
51
47
  "devDependencies": {
52
48
  "@eslint/js": "^10.0.1",
53
- "@types/node": "^25.6.0",
49
+ "@types/node": "^25.8.0",
54
50
  "@types/react": "^19.2.14",
55
- "@types/tabtab": "^3.0.4",
56
- "@vitest/coverage-v8": "^4.1.5",
57
- "@vitest/eslint-plugin": "^1.6.16",
58
- "eslint": "^10.3.0",
59
- "eslint-config-prettier": "^10.1.8",
60
- "eslint-plugin-import-x": "^4.16.2",
61
- "eslint-plugin-react-hooks": "^7.1.1",
51
+ "@vitest/coverage-v8": "^4.1.6",
52
+ "eslint": "^10.4.0",
62
53
  "globals": "^17.6.0",
63
54
  "husky": "^9.1.7",
64
55
  "ink-testing-library": "^4.0.0",
65
- "knip": "^6.11.0",
66
- "lint-staged": "^16.4.0",
56
+ "jiti": "^2.7.0",
57
+ "knip": "^6.14.1",
58
+ "lint-staged": "^17.0.5",
67
59
  "prettier": "^3.8.3",
68
60
  "tsup": "^8.5.1",
69
- "tsx": "^4.21.0",
61
+ "tsx": "^4.22.1",
70
62
  "typescript": "^6.0.3",
71
- "typescript-eslint": "^8.59.2",
72
- "vitest": "^4.1.5"
63
+ "typescript-eslint": "^8.59.3",
64
+ "vitest": "^4.1.6"
73
65
  },
74
66
  "lint-staged": {
75
67
  "*.{ts,tsx}": [
@@ -79,16 +71,21 @@
79
71
  "*.{md,json,yml,yaml}": "prettier --write"
80
72
  },
81
73
  "scripts": {
82
- "build": "tsup && node scripts/build-assets.mjs",
83
- "dev": "tsx src/application/cli/entrypoint.ts",
84
- "lint": "eslint .",
85
- "lint:fix": "eslint . --fix",
86
- "format": "prettier --write .",
87
- "format:check": "prettier --check .",
88
- "typecheck": "tsc --noEmit",
74
+ "build": "tsup && tsx scripts/build-assets.ts",
75
+ "dev": "NODE_OPTIONS=--max-old-space-size=8192 tsx src/index.ts",
76
+ "start": "NODE_OPTIONS=--max-old-space-size=8192 tsx src/index.ts",
77
+ "typecheck": "tsc",
89
78
  "test": "vitest run",
79
+ "test:unit": "vitest run tests/unit",
80
+ "test:integration": "vitest run tests/integration",
81
+ "test:e2e": "vitest run tests/e2e",
90
82
  "test:watch": "vitest",
91
83
  "test:coverage": "vitest run --coverage",
92
- "deadcode": "knip"
84
+ "coverage:unused": "tsx scripts/find-unused.ts",
85
+ "deadcode": "knip",
86
+ "lint": "eslint .",
87
+ "lint:fix": "eslint . --fix",
88
+ "format": "prettier --write .",
89
+ "format:check": "prettier --check ."
93
90
  }
94
91
  }
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- AbsolutePath
4
- } from "./chunk-S3PTDH57.mjs";
5
- import "./chunk-WV4D2CPG.mjs";
6
- export {
7
- AbsolutePath
8
- };
@@ -1,108 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- AbsolutePath
4
- } from "./chunk-S3PTDH57.mjs";
5
-
6
- // src/integration/persistence/storage-paths.ts
7
- import { mkdir } from "fs/promises";
8
- import { homedir } from "os";
9
- import { join } from "path";
10
- function defaultRoot() {
11
- const fromEnv = process.env["RALPHCTL_ROOT"];
12
- if (fromEnv !== void 0 && fromEnv.length > 0) {
13
- return AbsolutePath.trustString(fromEnv);
14
- }
15
- return AbsolutePath.trustString(join(homedir(), ".ralphctl"));
16
- }
17
- function asAbsolute(p) {
18
- return AbsolutePath.trustString(p);
19
- }
20
- function resolveStoragePaths(opts = {}) {
21
- const root = opts.root ?? defaultRoot();
22
- const configDir = asAbsolute(join(root, "config"));
23
- const dataDir = asAbsolute(join(root, "data"));
24
- const sprintsDir = asAbsolute(join(dataDir, "sprints"));
25
- const cacheDir = asAbsolute(join(root, "cache"));
26
- const logsDir = asAbsolute(join(root, "logs"));
27
- const backupsDir = asAbsolute(join(root, "backups"));
28
- const configFile = asAbsolute(join(configDir, "config.json"));
29
- const projectsFile = asAbsolute(join(configDir, "projects.json"));
30
- return {
31
- root,
32
- configDir,
33
- dataDir,
34
- sprintsDir,
35
- cacheDir,
36
- logsDir,
37
- backupsDir,
38
- configFile,
39
- projectsFile,
40
- sprintDir(id) {
41
- return asAbsolute(join(sprintsDir, id));
42
- },
43
- sprintFile(id) {
44
- return asAbsolute(join(sprintsDir, id, "sprint.json"));
45
- },
46
- tasksFile(id) {
47
- return asAbsolute(join(sprintsDir, id, "tasks.json"));
48
- },
49
- progressFile(id) {
50
- return asAbsolute(join(sprintsDir, id, "progress.md"));
51
- },
52
- requirementsAggregateFile(id) {
53
- return asAbsolute(join(sprintsDir, id, "requirements.json"));
54
- },
55
- feedbackFile(id) {
56
- return asAbsolute(join(sprintsDir, id, "feedback.md"));
57
- },
58
- refinementUnitDir(id, unitSlug) {
59
- return asAbsolute(join(sprintsDir, id, "refinement", unitSlug));
60
- },
61
- ideationUnitDir(id, unitSlug) {
62
- return asAbsolute(join(sprintsDir, id, "ideation", unitSlug));
63
- },
64
- planningDir(id) {
65
- return asAbsolute(join(sprintsDir, id, "planning"));
66
- },
67
- executionUnitDir(id, unitSlug) {
68
- return asAbsolute(join(sprintsDir, id, "execution", unitSlug));
69
- },
70
- doneCriteriaFile(id) {
71
- return asAbsolute(join(sprintsDir, id, "done-criteria.md"));
72
- }
73
- };
74
- }
75
- async function ensureLayoutDirs(paths) {
76
- const dirs = [
77
- paths.configDir,
78
- paths.sprintsDir,
79
- paths.cacheDir,
80
- paths.logsDir,
81
- paths.backupsDir
82
- ];
83
- await Promise.all(dirs.map((d) => mkdir(d, { recursive: true })));
84
- }
85
- var ensuredRoots = /* @__PURE__ */ new Map();
86
- async function ensureLayoutDirsOnce(paths) {
87
- const key = paths.root;
88
- const existing = ensuredRoots.get(key);
89
- if (existing !== void 0) return existing;
90
- const pending = ensureLayoutDirs(paths);
91
- ensuredRoots.set(key, pending);
92
- try {
93
- await pending;
94
- } catch (err) {
95
- ensuredRoots.delete(key);
96
- throw err;
97
- }
98
- }
99
- function resetEnsureLayoutDirsCache() {
100
- ensuredRoots.clear();
101
- }
102
-
103
- export {
104
- resolveStoragePaths,
105
- ensureLayoutDirs,
106
- ensureLayoutDirsOnce,
107
- resetEnsureLayoutDirsCache
108
- };