@neriros/ralphy 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -1
- package/dist/mcp/index.js +69 -2
- package/dist/shell/index.js +1001 -171
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -187,6 +187,8 @@ Example `ralphy.config.json`:
|
|
|
187
187
|
"mentionHandle": "@ralphy",
|
|
188
188
|
"codeReviewTrigger": true,
|
|
189
189
|
"codeReviewStaleHours": 24,
|
|
190
|
+
"syncTasksToComment": true,
|
|
191
|
+
"syncTasksToDescription": false,
|
|
190
192
|
"indicators": {
|
|
191
193
|
"getTodo": { "filter": [{ "type": "status", "value": "Todo" }] },
|
|
192
194
|
"getInProgress": {
|
|
@@ -219,7 +221,7 @@ When a Linear issue is in a done state and a reviewer adds the `getReview` marke
|
|
|
219
221
|
|
|
220
222
|
#### `@ralphy` mention trigger
|
|
221
223
|
|
|
222
|
-
Set `linear.mentionTrigger: true` to scan
|
|
224
|
+
Set `linear.mentionTrigger: true` to scan Linear issue comments on every non-cancelled issue (Todo, In Progress, Backlog, Triage, Done) _and_ on the linked GitHub PR for a configurable handle (`linear.mentionHandle`, default `@ralphy`). Each unprocessed mention queues the issue as a review run, with the mention text used **verbatim** as the prepended task. Idempotency: a mention is processed when its `createdAt` is older than Ralph's latest `🔁 picked up` Linear comment, so the same comment never re-fires. Requires `gh` for the GitHub side.
|
|
223
225
|
|
|
224
226
|
#### Code-review iteration
|
|
225
227
|
|
|
@@ -230,6 +232,30 @@ Set `linear.codeReviewTrigger: true` (or pass `--code-review`) to watch open, un
|
|
|
230
232
|
|
|
231
233
|
The loop exits; the next poll re-checks the PR. The cycle continues until the PR is **approved** or **merged**. If the reviewer is silent for more than `linear.codeReviewStaleHours` (default `24`, `0` disables) while Ralph is the last actor, one `@`-mention ping comment is posted on the GitHub PR.
|
|
232
234
|
|
|
235
|
+
#### Sync tasks into a Linear comment
|
|
236
|
+
|
|
237
|
+
`linear.syncTasksToComment` (default `true`) mirrors the active change's
|
|
238
|
+
`tasks.md` into a dedicated Linear **comment** instead of the issue
|
|
239
|
+
description. The same comment is updated in place across iterations so
|
|
240
|
+
the timeline stays clean. When `ralph_append_steering` is invoked the
|
|
241
|
+
existing tasks comment is deleted and re-posted so it always lands at
|
|
242
|
+
the bottom of the timeline, after the new steering comment.
|
|
243
|
+
|
|
244
|
+
The first time planning completes (every `- [ ]` under `## Planning` in
|
|
245
|
+
`tasks.md` becomes `- [x]`), Ralph posts a one-shot "📋 Plan" comment
|
|
246
|
+
summarizing `proposal.md` (`## Why` + `## What Changes`) and the first
|
|
247
|
+
paragraph of `design.md`.
|
|
248
|
+
|
|
249
|
+
##### Legacy: sync into the issue description
|
|
250
|
+
|
|
251
|
+
Set `linear.syncTasksToDescription: true` to mirror `tasks.md` into the
|
|
252
|
+
linked Linear issue description body instead (the pre-RLF-62 behavior).
|
|
253
|
+
Ralph writes a checklist between sentinel HTML comments
|
|
254
|
+
(`<!-- ralphy:tasks:start -->` / `<!-- ralphy:tasks:end -->`); content
|
|
255
|
+
outside the markers is preserved verbatim. When both
|
|
256
|
+
`syncTasksToComment` and `syncTasksToDescription` are true,
|
|
257
|
+
comment-sync wins and a one-time warning is logged.
|
|
258
|
+
|
|
233
259
|
#### Conflict re-fix
|
|
234
260
|
|
|
235
261
|
Done issues whose PR `gh pr view --json mergeable` reports as `CONFLICTING` get `setConflicted` applied and a conflict-fix task prepended. The scanner is resilient to:
|
package/dist/mcp/index.js
CHANGED
|
@@ -24057,7 +24057,12 @@ var StateSchema = exports_external.object({
|
|
|
24057
24057
|
createPr: exports_external.boolean().default(false),
|
|
24058
24058
|
usage: UsageSchema.default({}),
|
|
24059
24059
|
history: exports_external.array(HistoryEntrySchema).default([]),
|
|
24060
|
-
metadata: exports_external.object({ branch: exports_external.string().optional() }).default({})
|
|
24060
|
+
metadata: exports_external.object({ branch: exports_external.string().optional() }).default({}),
|
|
24061
|
+
linearComments: exports_external.object({
|
|
24062
|
+
planCommentId: exports_external.string().nullable().default(null),
|
|
24063
|
+
tasksCommentId: exports_external.string().nullable().default(null),
|
|
24064
|
+
planPostedAt: exports_external.string().nullable().default(null)
|
|
24065
|
+
}).default({ planCommentId: null, tasksCommentId: null, planPostedAt: null })
|
|
24061
24066
|
});
|
|
24062
24067
|
var PhaseFrontmatterSchema = exports_external.object({
|
|
24063
24068
|
name: exports_external.string(),
|
|
@@ -24132,7 +24137,7 @@ function buildInitialState(options) {
|
|
|
24132
24137
|
function safeTool(server, name, config2, callback) {
|
|
24133
24138
|
server.registerTool(name, config2, callback);
|
|
24134
24139
|
}
|
|
24135
|
-
function registerTools(server, changesDir, changeStore, taskFilesDir = changesDir) {
|
|
24140
|
+
function registerTools(server, changesDir, changeStore, taskFilesDir = changesDir, hooks = {}) {
|
|
24136
24141
|
safeTool(server, "ralph_list_changes", {
|
|
24137
24142
|
description: "List all active OpenSpec changes with their status",
|
|
24138
24143
|
inputSchema: {
|
|
@@ -24320,6 +24325,14 @@ function registerTools(server, changesDir, changeStore, taskFilesDir = changesDi
|
|
|
24320
24325
|
};
|
|
24321
24326
|
}
|
|
24322
24327
|
await changeStore.appendSteering(name, message);
|
|
24328
|
+
if (hooks.onSteeringAppended) {
|
|
24329
|
+
try {
|
|
24330
|
+
await hooks.onSteeringAppended(name, message);
|
|
24331
|
+
} catch (err) {
|
|
24332
|
+
process.stderr.write(`! onSteeringAppended hook failed for ${name}: ${err instanceof Error ? err.message : String(err)}
|
|
24333
|
+
`);
|
|
24334
|
+
}
|
|
24335
|
+
}
|
|
24323
24336
|
return {
|
|
24324
24337
|
content: [{ type: "text", text: `Steering appended to change '${name}'` }]
|
|
24325
24338
|
};
|
|
@@ -24982,6 +24995,50 @@ function runOpenspec(args, options = {}) {
|
|
|
24982
24995
|
stderr: proc.stderr ? decoder.decode(proc.stderr) : ""
|
|
24983
24996
|
};
|
|
24984
24997
|
}
|
|
24998
|
+
function appendSteeringTaskToTasksMd(existing, taskLine) {
|
|
24999
|
+
const SECTION = "## Steering";
|
|
25000
|
+
const trimmed = existing.replace(/\s+$/, "");
|
|
25001
|
+
if (trimmed.length === 0) {
|
|
25002
|
+
return `${SECTION}
|
|
25003
|
+
|
|
25004
|
+
${taskLine}
|
|
25005
|
+
`;
|
|
25006
|
+
}
|
|
25007
|
+
const lines = trimmed.split(/\r?\n/);
|
|
25008
|
+
let sectionStart = -1;
|
|
25009
|
+
for (let i = 0;i < lines.length; i += 1) {
|
|
25010
|
+
if (/^##\s+Steering\s*$/i.test(lines[i])) {
|
|
25011
|
+
sectionStart = i;
|
|
25012
|
+
break;
|
|
25013
|
+
}
|
|
25014
|
+
}
|
|
25015
|
+
if (sectionStart === -1) {
|
|
25016
|
+
return `${trimmed}
|
|
25017
|
+
|
|
25018
|
+
${SECTION}
|
|
25019
|
+
|
|
25020
|
+
${taskLine}
|
|
25021
|
+
`;
|
|
25022
|
+
}
|
|
25023
|
+
let sectionEnd = lines.length;
|
|
25024
|
+
for (let i = sectionStart + 1;i < lines.length; i += 1) {
|
|
25025
|
+
if (/^##\s+/.test(lines[i])) {
|
|
25026
|
+
sectionEnd = i;
|
|
25027
|
+
break;
|
|
25028
|
+
}
|
|
25029
|
+
}
|
|
25030
|
+
let insertAt = sectionEnd;
|
|
25031
|
+
while (insertAt - 1 > sectionStart && (lines[insertAt - 1] ?? "").trim() === "") {
|
|
25032
|
+
insertAt -= 1;
|
|
25033
|
+
}
|
|
25034
|
+
const before = lines.slice(0, insertAt);
|
|
25035
|
+
const after = lines.slice(insertAt);
|
|
25036
|
+
const out = [...before, taskLine, ...after.length ? [""] : [], ...after].join(`
|
|
25037
|
+
`);
|
|
25038
|
+
return out.endsWith(`
|
|
25039
|
+
`) ? out : `${out}
|
|
25040
|
+
`;
|
|
25041
|
+
}
|
|
24985
25042
|
|
|
24986
25043
|
class OpenSpecChangeStore {
|
|
24987
25044
|
async createChange(name, description) {
|
|
@@ -25038,6 +25095,16 @@ ${existing.trimStart()}` : `${message}
|
|
|
25038
25095
|
`;
|
|
25039
25096
|
await mkdir(dirname3(path), { recursive: true });
|
|
25040
25097
|
await Bun.write(path, updated);
|
|
25098
|
+
const firstLine = message.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0) ?? message.trim();
|
|
25099
|
+
if (firstLine.length === 0)
|
|
25100
|
+
return;
|
|
25101
|
+
const tasksPath = join4("openspec", "changes", name, "tasks.md");
|
|
25102
|
+
const tasksFile = Bun.file(tasksPath);
|
|
25103
|
+
const existingTasks = await tasksFile.exists() ? await tasksFile.text() : "";
|
|
25104
|
+
const taskLine = `- [ ] Address steering: ${firstLine}`;
|
|
25105
|
+
const next = appendSteeringTaskToTasksMd(existingTasks, taskLine);
|
|
25106
|
+
await mkdir(dirname3(tasksPath), { recursive: true });
|
|
25107
|
+
await Bun.write(tasksPath, next);
|
|
25041
25108
|
}
|
|
25042
25109
|
async readSection(name, artifact, heading) {
|
|
25043
25110
|
const file = Bun.file(join4("openspec", "changes", name, artifact));
|