@neriros/ralphy 3.2.0 → 3.3.1
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 +24 -3
- package/dist/mcp/index.js +69 -2
- package/dist/shell/index.js +1065 -300
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -187,6 +187,7 @@ Example `ralphy.config.json`:
|
|
|
187
187
|
"mentionHandle": "@ralphy",
|
|
188
188
|
"codeReviewTrigger": true,
|
|
189
189
|
"codeReviewStaleHours": 24,
|
|
190
|
+
"syncTasksToComment": true,
|
|
190
191
|
"syncTasksToDescription": false,
|
|
191
192
|
"indicators": {
|
|
192
193
|
"getTodo": { "filter": [{ "type": "status", "value": "Todo" }] },
|
|
@@ -231,9 +232,29 @@ Set `linear.codeReviewTrigger: true` (or pass `--code-review`) to watch open, un
|
|
|
231
232
|
|
|
232
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.
|
|
233
234
|
|
|
234
|
-
#### Sync tasks into Linear
|
|
235
|
-
|
|
236
|
-
|
|
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.
|
|
237
258
|
|
|
238
259
|
#### Conflict re-fix
|
|
239
260
|
|
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));
|