scrumrun 2.7.3 → 2.7.5
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/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/bin/scrumrun.js +21 -0
- package/lib/commands/pretty-intake.js +15 -17
- package/lib/runtime/orchestrator.js +4 -0
- package/lib/runtime/request-engine.js +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes follow Semantic Versioning.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 2.7.5 - 2026-08-22
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Project-root discovery.** The `sc` CLI now walks up from the current directory to find the nearest `.scrumrun/method.json`, so commands run from any subdirectory (e.g. `app/front`) operate on the project root instead of failing with a cryptic "Mutation Gateway requires a valid method.json" error. `sc config init` and `sc-init` still initialize in the current directory.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **Missing-project error.** `sc plan task --add` now reports a clear "ScrumRun project not found" message (with `scrumrun init` / run-from-root guidance) instead of leaking an internal ENOENT, and no longer leaves a stray `.scrumrun/.cache` lock directory behind when the command is run outside a project.
|
|
16
|
+
|
|
17
|
+
## 2.7.4 - 2026-08-22
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **Approval token truncated in interactive intake.** The pretty terminal renderer was shrinking the approval command to the first 20 characters of the token, so a copy-pasted `sc plan intake --approve …` failed with "Approval token is malformed or was modified." The full token now renders wrapped across lines (copyable, no ellipsis), and `decodeApproval` strips surrounding/interstitial whitespace so pasted tokens with line breaks or spaces still validate.
|
|
22
|
+
|
|
7
23
|
## 2.7.3 - 2026-08-22
|
|
8
24
|
|
|
9
25
|
### Added
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
ScrumRun gives an agent a small command surface and a precise project memory: what should be done, how each attempt happened, which decisions constrain the code, and why the architecture exists in its current form.
|
|
6
6
|
|
|
7
|
-
**Package:** `2.7.
|
|
7
|
+
**Package:** `2.7.5` · **Method target:** `2.0.0` · **Runtime:** Node.js `>=22.13.0` · **License:** MIT
|
|
8
8
|
|
|
9
9
|
**New here?** Read the [Quickstart](docs/QUICKSTART.md) — first Run in under 10 minutes, no `SPEC.md` reading required. Full docs map in [`docs/INDEX.md`](docs/INDEX.md).
|
|
10
10
|
|
package/bin/scrumrun.js
CHANGED
|
@@ -1198,6 +1198,25 @@ function v2Project(cwd = process.cwd()) {
|
|
|
1198
1198
|
}
|
|
1199
1199
|
}
|
|
1200
1200
|
|
|
1201
|
+
function findProjectRoot(startDir = process.cwd()) {
|
|
1202
|
+
let dir = path.resolve(startDir);
|
|
1203
|
+
while (true) {
|
|
1204
|
+
const marker = path.join(dir, ".scrumrun", "method.json");
|
|
1205
|
+
if (fs.existsSync(marker) && !fs.lstatSync(marker).isSymbolicLink() && fs.lstatSync(marker).isFile()) {
|
|
1206
|
+
return dir;
|
|
1207
|
+
}
|
|
1208
|
+
const parent = path.dirname(dir);
|
|
1209
|
+
if (parent === dir) return null;
|
|
1210
|
+
dir = parent;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function chdirToProjectRoot() {
|
|
1215
|
+
const root = findProjectRoot();
|
|
1216
|
+
if (root && root !== process.cwd()) process.chdir(root);
|
|
1217
|
+
return root;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1201
1220
|
function optionValues(args, flag) {
|
|
1202
1221
|
const values = [];
|
|
1203
1222
|
for (let index = 0; index < args.length; index++) {
|
|
@@ -1644,6 +1663,7 @@ function runRoot(parts) {
|
|
|
1644
1663
|
process.exitCode = 1;
|
|
1645
1664
|
return;
|
|
1646
1665
|
}
|
|
1666
|
+
if (!(route.noun === "config" && route.subject === "init")) chdirToProjectRoot();
|
|
1647
1667
|
return executeRootRoute(route);
|
|
1648
1668
|
}
|
|
1649
1669
|
|
|
@@ -1652,6 +1672,7 @@ function runCompatibilityAlias(alias, parts) {
|
|
|
1652
1672
|
console.warn(`Deprecated: ${alias} now executes /sc ${route.noun} ${route.subject}.`);
|
|
1653
1673
|
if (route.note) console.warn(route.note);
|
|
1654
1674
|
if (alias === "sc-backlog") return runBacklog(parts);
|
|
1675
|
+
if (alias !== "sc-init") chdirToProjectRoot();
|
|
1655
1676
|
return executeRootRoute({ type: "route", noun: route.noun, subject: route.subject, args: route.args });
|
|
1656
1677
|
}
|
|
1657
1678
|
|
|
@@ -89,6 +89,15 @@ function wrap(text, width) {
|
|
|
89
89
|
return lines.length ? lines : [""];
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
function hardWrap(text, width) {
|
|
93
|
+
const source = String(text || "");
|
|
94
|
+
const lines = [];
|
|
95
|
+
for (let index = 0; index < source.length; index += width) {
|
|
96
|
+
lines.push(source.slice(index, index + width));
|
|
97
|
+
}
|
|
98
|
+
return lines.length ? lines : [""];
|
|
99
|
+
}
|
|
100
|
+
|
|
92
101
|
function boxTop(width, title) {
|
|
93
102
|
const label = ` ${title} `;
|
|
94
103
|
const remaining = width - visibleLength(label) - 4;
|
|
@@ -154,16 +163,6 @@ function fieldLine(label, value, width, valueColor = FG.white, labelColor = FG.g
|
|
|
154
163
|
return rendered;
|
|
155
164
|
}
|
|
156
165
|
|
|
157
|
-
function commandBox(interiorWidth, command) {
|
|
158
|
-
const inside = ` $ ${command} `;
|
|
159
|
-
const boxWidth = Math.min(interiorWidth, visibleLength(inside) + 4);
|
|
160
|
-
const top = paint(DIM_ACID_FG, `┌${"─".repeat(boxWidth - 2)}┐`);
|
|
161
|
-
const bottom = paint(DIM_ACID_FG, `└${"─".repeat(boxWidth - 2)}┘`);
|
|
162
|
-
const content = padEnd(inside, boxWidth - 2);
|
|
163
|
-
const middle = `${paint(DIM_ACID_FG, "│")}${paint(ACID_FG, content)}${paint(DIM_ACID_FG, "│")}`;
|
|
164
|
-
return [top, middle, bottom];
|
|
165
|
-
}
|
|
166
|
-
|
|
167
166
|
function pipelineDetail(stage, plan) {
|
|
168
167
|
if (stage === "POLICY") {
|
|
169
168
|
return `${plan.policy.checked.length} checked · ${plan.policy.deferred.length} deferred`;
|
|
@@ -240,16 +239,15 @@ function renderIntake(plan) {
|
|
|
240
239
|
}
|
|
241
240
|
}
|
|
242
241
|
} else if (plan.approvalToken) {
|
|
243
|
-
const command =
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
242
|
+
const command = "scrumrun sc plan intake --approve";
|
|
243
|
+
const tokenWidth = Math.max(16, width - 8);
|
|
244
|
+
lines.push(boxLine(width, ` ${paint(FG.gray, "APPROVE · copy the command below to create Task + Run")}`));
|
|
245
|
+
lines.push(boxLine(width, ` ${paint(ACID_FG, "$")} ${paint(FG.white, command)}`));
|
|
246
|
+
for (const chunk of hardWrap(plan.approvalToken, tokenWidth)) {
|
|
247
|
+
lines.push(boxLine(width, ` ${paint(DIM_ACID_FG, chunk)}`));
|
|
247
248
|
}
|
|
248
249
|
lines.push(boxBlank(width));
|
|
249
250
|
lines.push(boxLine(width, ` ${paint(FG.gray, "[ awaiting owner approval ]")}`));
|
|
250
|
-
if (command.length !== truncated.length) {
|
|
251
|
-
lines.push(boxLine(width, ` ${paint(FG.gray, "(full token above is truncated for display; copy from --json if needed)")}`));
|
|
252
|
-
}
|
|
253
251
|
}
|
|
254
252
|
lines.push(boxBlank(width));
|
|
255
253
|
lines.push(boxBottom(width));
|
|
@@ -521,6 +521,10 @@ function addPlanArtifactUnlocked(projectRoot, kind, label, { type = null, status
|
|
|
521
521
|
|
|
522
522
|
function addPlanArtifact(projectRoot, kind, label, options = {}) {
|
|
523
523
|
const scrumDir = path.join(projectRoot, ".scrumrun");
|
|
524
|
+
const methodFile = path.join(scrumDir, "method.json");
|
|
525
|
+
if (!fs.existsSync(methodFile) || !fs.lstatSync(methodFile).isFile()) {
|
|
526
|
+
throw new Error(`ScrumRun project not found in ${projectRoot}. Run \`scrumrun init\` there, or run this command from inside the project.`);
|
|
527
|
+
}
|
|
524
528
|
return withArtifactLock(scrumDir, "create", () => addPlanArtifactUnlocked(projectRoot, kind, label, options));
|
|
525
529
|
}
|
|
526
530
|
|
|
@@ -82,7 +82,8 @@ function encodeApproval(plan) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
function decodeApproval(token) {
|
|
85
|
-
const
|
|
85
|
+
const cleaned = String(token || "").replace(/\s+/g, "");
|
|
86
|
+
const [encoded, checksum, extra] = cleaned.split(".");
|
|
86
87
|
if (!encoded || !checksum || extra || sha256(`scrumrun-v2-approval\0${encoded}`) !== checksum) {
|
|
87
88
|
throw new Error("Approval token is malformed or was modified.");
|
|
88
89
|
}
|