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 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.3` · **Method target:** `2.0.0` · **Runtime:** Node.js `>=22.13.0` · **License:** MIT
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 = `scrumrun sc plan intake --approve ${plan.approvalToken}`;
244
- const truncated = command.length > width - 8 ? `scrumrun sc plan intake --approve ${plan.approvalToken.slice(0, 20)}…` : command;
245
- for (const boxRow of commandBox(width - 6, truncated)) {
246
- lines.push(boxLine(width, ` ${boxRow}`));
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 [encoded, checksum, extra] = String(token || "").split(".");
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scrumrun",
3
- "version": "2.7.3",
3
+ "version": "2.7.5",
4
4
  "description": "Evidence-driven Agile runtime and semantic project memory for AI coding agents.",
5
5
  "bin": {
6
6
  "scrumrun": "bin/scrumrun.js",