scrumrun 4.0.0 → 4.1.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/CHANGELOG.md +26 -0
- package/README.md +45 -1
- package/SPEC.md +31 -0
- package/bin/scrumrun.js +89 -11
- package/docs/COMMANDS.md +12 -2
- package/docs/ERROR-CODES.md +4 -0
- package/docs/SCHEMA.md +4 -0
- package/docs/SEMANTIC-MEMORY.md +28 -0
- package/lib/actions/index.js +81 -0
- package/lib/commands/manifest.js +3 -2
- package/lib/commands/repair.js +24 -3
- package/lib/errors.js +4 -0
- package/lib/git/context.js +30 -0
- package/lib/guardrails/changeset.js +45 -0
- package/lib/guardrails/evaluate.js +175 -0
- package/lib/memory/compaction.js +289 -0
- package/lib/memory/index.js +62 -2
- package/lib/migrate/ops.js +92 -0
- package/lib/migrate/run.js +108 -0
- package/lib/runtime/context.js +3 -1
- package/lib/runtime/policy-engine.js +13 -1
- package/lib/runtime/watcher.js +185 -0
- package/lib/v2/conformance.js +27 -2
- package/lib/v2/runs-jsonl.js +134 -0
- package/lib/v2/task-schema.js +133 -0
- package/package.json +1 -1
- package/scripts/generate-contract-docs.js +4 -0
- package/templates/project/.scrumrun/config.md +9 -0
- package/templates/shared/hooks/pre-commit +16 -0
- package/templates/shared/view.html +281 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ All notable changes follow Semantic Versioning.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 4.1.1 - 2026-09-22
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`scrumrun update --repair-legacy`.** New flag runs `repair --apply` after refreshing project guidance, so a single `scrumrun update --project --repair-legacy` upgrades the CLI, refreshes packaged Markdown, and normalizes the local `.scrumrun/` tree in one shot. The repair pass covers status aliases, `feature:` free-text kebab labels, guardrail scopes, missing frontmatter, orphaned Runs/Tasks, and legacy Sprint projections — never touches secrets (they always require human review).
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **Broader status aliases in `scrumrun repair`.** `TASK_STATUS_ALIAS` now maps `pending`, `planned`, `skipped`, `canceled`, `wontdo`/`won't do`, `wip`, `doing`, `in-progress`, `review`, `reviewing`, `finished`, `closed` alongside the existing `done`/`todo`/`complete`/`in_progress`/`executing`/`active`. `RUN_STATUS_ALIAS` gains `in-progress`, `wip`, `finished`, `closed`, `canceled`/`cancelled`, `skipped`, `running`. `doctor --strict` remains strict for fresh installs; the aliases only take effect when an owner explicitly runs `repair --apply` (or `update --repair-legacy`).
|
|
16
|
+
- **Guardrail scope parser tolerates parentheses and `+` separators.** `Scope: frontend + backend` and `Scope: frontend (vue templates, docs)` now parse to the intended token(s) instead of splitting on commas inside parens and producing bogus scope names. Unknown scopes still fail conformance in fresh installs — `repair` rewrites them to `all` when normalizing legacy projects.
|
|
17
|
+
|
|
18
|
+
## 4.1.0 - 2026-09-22
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **Declarative migration runner (`lib/migrate/run.js`).** Migrations become plain Markdown files under `migrations/*.md` with fenced ````yaml steps`, ````yaml verify`, and optional ````yaml rollback` blocks. Whitelisted ops: `create_backup`, `rename_path`, `bump_schema`, `move_frontmatter_field`, `assert_hash`. Runner supports `{ dryRun: true }` (no writes) and returns `{ status, journal }` so rollback can operate on exactly what was applied. Path safety enforced: absolute paths and paths escaping the project root are rejected. See `migrations/README.md` for the format contract. Rewrite of the existing v1→v2 imperative migration into this format is delegated to the release cycle (large diff, coordinated release).
|
|
23
|
+
- **Declarative actions API (`lib/actions/`).** New `executeAction(name, projectRoot, payload)` entry point exposes 7 pure functions (`plan.task.add`, `plan.feature.add`, `plan.sprint.add`, `plan.task.amend`, `knowledge.fact.add`, `knowledge.decision.add`, `knowledge.insight.propose`) that delegate to the existing orchestrator/memory services, sharing validation and atomicity. New CLI command `scrumrun action <name> [--payload <json>|--file <path>|--stdin]` and `scrumrun action --list`. Enables hooks, the static view (TASK-026), and future clients to share one write layer without shelling into subcommands. Existing `plan/knowledge/rules/review/config` subcommands are unchanged; rip-out is a release-cycle task.
|
|
24
|
+
- **Append-only per-Task Run ledger (`runs/TASK-NNN.jsonl`).** New `lib/v2/runs-jsonl.js` records each execution event on one line with SHA-256 chain (`prev`/`hash`) for tamper detection. Supersedes the one-file-per-attempt Markdown layout for new work; historic `RUN-NNN.md` files remain readable. Cross-process append safety via `TASK-NNN.jsonl.lock` (exclusive `open wx` + jittered backoff). Design captured in `DEC-010`. Reader/writer wiring for `scrumrun plan run *` and migration of legacy Markdown Runs will land alongside the declarative migration runner (see task TASK-030).
|
|
25
|
+
- **Static HTML view at `.scrumrun/view.html`.** Zero-dependency single-file dashboard (vanilla JS, inline CSS, self-contained) that reads canonical Markdown via relative `fetch()` and renders a status kanban (Tasks), a recent-Runs list, active Guardrails, and Decisions. `scrumrun init` copies it into every project; `scrumrun update --project` refreshes it. Open with `open .scrumrun/view.html` on macOS, `xdg-open .scrumrun/view.html` on Linux, or `start .scrumrun\view.html` on Windows. If your browser blocks `file://` fetches, serve locally: `cd .scrumrun && python3 -m http.server 8080`.
|
|
26
|
+
- **Task Markdown schema validator (opt-in).** New pure library `lib/v2/task-schema.js` validates that a Task carries the sections and evidence its lifecycle requires: `## Request` + `## Done when` for opt-in Tasks, `## Completion` on `status: completed` (or a linked Run whose body contains `## Technical Summary`), and — inside a git repo — a `branch` field in the frontmatter when the Task is `running`/`in_progress`/`validating`/`learning`. Errors: `SR-E-452` (missing section), `SR-E-453` (completed without summary), `SR-E-454` (running without branch, warning-only). Legacy Tasks without `task_schema: 1` in frontmatter are silently skipped for full backward compatibility. Wired into `scrumrun review artifact --run` and `scrumrun doctor --strict`; both accept `--strict` to promote schema warnings to blocking errors.
|
|
27
|
+
- **`lib/git/context.js`.** Pure shell-out helper that returns `{ isRepo, branch, headSha }` for any project directory. Never throws — returns a non-repo shape when git is missing or the cwd is outside a working tree. Cross-platform: resolves `git` from `PATH` (Git for Windows, macOS, Linux). Consumed by the task-schema validator and available to any future feature (e.g. review/verify diff scoping).
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **Legacy `sc-*` skill directories are removed on install/update.** Older versions of ScrumRun published one skill per compatibility alias (`sc-sprint`, `sc-decisions`, `sc-vault`, …). Every listed skill costs context in every client conversation, so install/update now sweeps those directories from the target skills folder alongside the pre-existing `ai-scrum` cleanup, while leaving unrelated user skills untouched. The canonical skill remains `scrumrun`; the `scrumrun sc-*` command aliases are unchanged.
|
|
32
|
+
|
|
7
33
|
## 4.0.0 - 2026-08-31
|
|
8
34
|
|
|
9
35
|
### Changed
|
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:** `4.
|
|
7
|
+
**Package:** `4.1.1` · **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
|
|
|
@@ -63,6 +63,32 @@ scrumrun <noun> <subject> <action> [args]
|
|
|
63
63
|
|
|
64
64
|
The five nouns are `plan`, `knowledge`, `rules`, `review`, and `config`. `/sc` is an optional AI-client shortcut; `scrumrun sc ...` remains a compatibility alias for existing integrations.
|
|
65
65
|
|
|
66
|
+
## Compatibility
|
|
67
|
+
|
|
68
|
+
ScrumRun is designed to be **portable across AI clients and operating systems**. The runtime is plain Markdown + a small Node.js CLI; nothing is tied to a specific vendor.
|
|
69
|
+
|
|
70
|
+
### Operating systems
|
|
71
|
+
|
|
72
|
+
| OS | Status | Notes |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| macOS 12+ | Supported | Primary development platform. |
|
|
75
|
+
| Linux (glibc-based distros) | Supported | Tested on Ubuntu/Debian/Fedora; `fs.watch` uses inotify. |
|
|
76
|
+
| Windows 10/11 | Supported | Requires Git for Windows for the optional pre-commit hook; `.scrumrun/` paths are POSIX-normalized internally. |
|
|
77
|
+
| WSL2 | Supported | Behaves as Linux. |
|
|
78
|
+
|
|
79
|
+
Requirement everywhere: **Node.js ≥ 22.13.0** and (optionally) `git` on `PATH`.
|
|
80
|
+
|
|
81
|
+
### AI clients
|
|
82
|
+
|
|
83
|
+
| Client | Integration | Notes |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| Claude Code | `scrumrun install claude` | Installs the `scrumrun` skill under `~/.claude/skills/`. |
|
|
86
|
+
| OpenCode | `scrumrun install opencode` | Installs skill under `~/.config/opencode/skills/`. |
|
|
87
|
+
| Codex | `scrumrun install codex` | Installs skill under `~/.codex/skills/`. |
|
|
88
|
+
| Cursor / Windsurf / Zed / any Markdown-capable agent | Manual | Point the agent at `.scrumrun/` and `CORE.md`; no CLI adapter required. |
|
|
89
|
+
|
|
90
|
+
The method itself (Markdown tree + guardrails + task/run model) is client-agnostic: any agent that can read files can operate a ScrumRun project.
|
|
91
|
+
|
|
66
92
|
## Daily flow
|
|
67
93
|
|
|
68
94
|
Natural language is the normal entry point:
|
|
@@ -90,6 +116,8 @@ EXECUTING → VALIDATING → LEARNING → COMPLETED | FAILED | BLOCKED
|
|
|
90
116
|
|
|
91
117
|
Every Task carries a short `## Done when` delivery contract. After approval, the agent works directly in code and Task Markdown until that contract is delivered: it keeps the full discover → implement → verify → fix → verify loop running. A report is allowed only when you ask for it and never ends execution. The normal close is a concise `## Completion`; `## Follow-ups` may only contain work outside the agreed contract. No CLI transition is required.
|
|
92
118
|
|
|
119
|
+
**Opt-in Task-schema validator.** Adding `task_schema: 1` to a Task's frontmatter turns on the structural checks in `lib/v2/task-schema.js`: `## Request` + `## Done when` are required, `## Completion` (or an associated Run's `## Technical Summary`) is required once the Task is `completed`, and inside a git repo the Task must record its `branch` while executing. `scrumrun doctor --strict` and `scrumrun review artifact --run --strict` promote these to blocking errors; without `--strict` they are warnings. Legacy Tasks without `task_schema` are unaffected.
|
|
120
|
+
|
|
93
121
|
Guardrails still apply. An agent stops only for an explicit active Guardrail, a secret/security risk, destructive work without approval, or an unmet required delivery criterion. Tests, reviews, and environments are gates only when the owner, `Done when`, or a Guardrail explicitly requires them. Optional missing E2E coverage is a follow-up/risk, not a failed Task.
|
|
94
122
|
|
|
95
123
|
Use the CLI at the edges, where its safety is valuable:
|
|
@@ -151,6 +179,22 @@ The fast graph/search layer is `.scrumrun/.cache/semantic-index.sqlite`. It is i
|
|
|
151
179
|
|
|
152
180
|
`map.md` is shown only when its source fingerprint matches the current semantic index. A fresh placeholder or stale map is rejected with an explicit rebuild instruction instead of being presented as project truth.
|
|
153
181
|
|
|
182
|
+
For local projects that benefit from always-fresh projections, opt into the lightweight watcher in `.scrumrun/config.md` and start it once:
|
|
183
|
+
|
|
184
|
+
```yaml
|
|
185
|
+
watcher.enabled: true
|
|
186
|
+
watcher.debounce_ms: 250
|
|
187
|
+
watcher.poll_interval_ms: 1500
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
scrumrun config watch --start
|
|
192
|
+
scrumrun config watch --status
|
|
193
|
+
scrumrun config watch --stop
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The watcher uses native `fs.watch` where recursive watching is available and polling otherwise. It is never authority or a work gate: a later on-demand rebuild always remains correct if it stops. The daemon records its PID in `.scrumrun/.cache/watcher.pid`, ignores the vault, `.cache/contexts/`, and its own generated outputs, coalesces rapid writes, and writes only `state.md`, `map.md`, and `.cache/`.
|
|
197
|
+
|
|
154
198
|
## Project briefing
|
|
155
199
|
|
|
156
200
|
`state.md` is the **briefing**: a bounded summary an agent reads first, before touching anything else.
|
package/SPEC.md
CHANGED
|
@@ -234,6 +234,37 @@ Secret-like content detection is canonical-policy-level and applies to every art
|
|
|
234
234
|
|
|
235
235
|
The executable Policy Engine may infer enforcement for migrated prose, but fresh v2 policy declares it explicitly. Unknown enforcement, duplicate ids, inactive-only policy, configuration that disables approval, and unsafe read-only paths fail conformance. Configuration can tune presentation and workflow preferences; it cannot retire, bypass, or weaken active Guardrails.
|
|
236
236
|
|
|
237
|
+
### 6.1 Declarative Guardrail enforcement
|
|
238
|
+
|
|
239
|
+
A Guardrail may add one optional fenced `yaml enforcement` block. It is parsed without an LLM and evaluated as a pure function over a ChangeSet (`paths`, `diff`, and optional `symbols`); ordinary prose-only Guardrails remain valid. The supported YAML subset is intentionally limited to mappings and string lists so every installed client evaluates the same rule without a network dependency.
|
|
240
|
+
|
|
241
|
+
````markdown
|
|
242
|
+
## GR-010 - No debug logs in production code
|
|
243
|
+
|
|
244
|
+
Status: active
|
|
245
|
+
Enforcement: manual
|
|
246
|
+
Scope: validation, commit
|
|
247
|
+
Rule: Production changes must not add debug logging.
|
|
248
|
+
|
|
249
|
+
```yaml enforcement
|
|
250
|
+
match:
|
|
251
|
+
paths:
|
|
252
|
+
- "src/**/*.js"
|
|
253
|
+
diff:
|
|
254
|
+
- "/\\bconsole\\.log\\s*\\(/"
|
|
255
|
+
symbols:
|
|
256
|
+
- "debug*"
|
|
257
|
+
on_violation: block
|
|
258
|
+
severity: high
|
|
259
|
+
evidence:
|
|
260
|
+
- "Remove debug output or justify it in the reviewed policy."
|
|
261
|
+
```
|
|
262
|
+
````
|
|
263
|
+
|
|
264
|
+
`match.paths` accepts globs (`src/**`), extensions (`.ts` or `ext:.ts`), and regexes (`regex:^src/` or `/^src\\//`). `match.diff` accepts regexes (recommended) or literal fragments; `match.symbols` uses the same glob/regex form. At least one matcher is required. `on_violation` is `block` or `warn`; `severity` is `low`, `medium`, `high`, or `critical`; `evidence` is an optional scalar or string list surfaced with a match. Invalid blocks are a conformance error with `SR-E-153`.
|
|
265
|
+
|
|
266
|
+
The declared action is authoritative. `config.md` may promote a warning to a block through `Guardrail On Violation: block`; it may never downgrade an explicit block. `scrumrun review artifact --run` evaluates the worktree, `--staged` evaluates the index, and `doctor --strict` consumes the same evaluator. The optional offline pre-commit template is `templates/shared/hooks/pre-commit`.
|
|
267
|
+
|
|
237
268
|
## 7. Semantic memory and code intelligence
|
|
238
269
|
|
|
239
270
|
Memory records include subject, source, evidence, validity window, confidence where useful, review trigger, and last-verified commit. Insight types may include placement rationale, design constraint, known trade-off, failure history, usage warning, compatibility reason, business rule, performance reason, security reason, and testing note.
|
package/bin/scrumrun.js
CHANGED
|
@@ -35,11 +35,13 @@ const { addPlanArtifact, amendPlanArtifact, approveRequest, finalizeRun, nextBac
|
|
|
35
35
|
const { authorizeMutation, recordMutation, satisfyGuardrail } = require(path.join(root, "lib", "runtime", "mutation-gateway"));
|
|
36
36
|
const { recordArtifactReview } = require(path.join(root, "lib", "runtime", "review-service"));
|
|
37
37
|
const { createMemory, listMemory, showMemory, transitionMemory } = require(path.join(root, "lib", "memory", "service"));
|
|
38
|
+
const { applyCompaction, proposedClusters, rollbackCompaction } = require(path.join(root, "lib", "memory", "compaction"));
|
|
38
39
|
const { indexPath, indexStatus, mapStatus, queryIndex, rebuildIndex, writeMap } = require(path.join(root, "lib", "memory", "index"));
|
|
39
40
|
const { auditProject } = require(path.join(root, "lib", "v2", "conformance"));
|
|
40
41
|
const { recoverPendingTransactions, previewPendingRecovery } = require(path.join(root, "lib", "v2", "transaction"));
|
|
41
42
|
const { containsSecret } = require(path.join(root, "lib", "security", "secrets"));
|
|
42
43
|
const { sealPolicyIntegrity } = require(path.join(root, "lib", "runtime", "policy-integrity"));
|
|
44
|
+
const { readStatus: watcherStatus, startWatcher, stopWatcher } = require(path.join(root, "lib", "runtime", "watcher"));
|
|
43
45
|
|
|
44
46
|
const COMMANDS = ["sc"];
|
|
45
47
|
const COMPATIBILITY_COMMANDS = Object.keys(COMMAND_ALIASES);
|
|
@@ -59,7 +61,7 @@ Usage:
|
|
|
59
61
|
scrumrun <noun> <subject> <action> [args]
|
|
60
62
|
scrumrun sc <noun> <subject> <action> [args] # compatibility alias
|
|
61
63
|
scrumrun install [all|codex|opencode|claude] [--force]
|
|
62
|
-
scrumrun update [all|codex|opencode|claude] [--project] [--seal-policy] [--migrate] [--verbose]
|
|
64
|
+
scrumrun update [all|codex|opencode|claude] [--project] [--seal-policy] [--migrate] [--repair-legacy] [--verbose]
|
|
63
65
|
scrumrun init [--local|--shared] [--lean] [--no-agent-hint] [--force]
|
|
64
66
|
scrumrun status
|
|
65
67
|
scrumrun core [--path|--prompt]
|
|
@@ -68,6 +70,7 @@ Usage:
|
|
|
68
70
|
scrumrun migrate --to 2 --apply
|
|
69
71
|
scrumrun migrate --to 2 --rollback
|
|
70
72
|
scrumrun doctor [all|codex|opencode|claude] [--strict] [--recover]
|
|
73
|
+
scrumrun config watch --start|--stop|--status # optional generated-projection daemon; never a gate
|
|
71
74
|
scrumrun repair [--recover-orphan-tasks] [--apply]
|
|
72
75
|
scrumrun uninstall [--force]
|
|
73
76
|
|
|
@@ -219,11 +222,16 @@ function cleanupLegacy(commandsDir, skillsDir) {
|
|
|
219
222
|
}
|
|
220
223
|
}
|
|
221
224
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
fs.
|
|
225
|
-
|
|
226
|
-
|
|
225
|
+
if (fs.existsSync(skillsDir)) {
|
|
226
|
+
const legacySkillNames = new Set(["ai-scrum", ...COMPATIBILITY_COMMANDS]);
|
|
227
|
+
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
228
|
+
if (!entry.isDirectory()) continue;
|
|
229
|
+
if (!legacySkillNames.has(entry.name)) continue;
|
|
230
|
+
const target = path.join(skillsDir, entry.name);
|
|
231
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
232
|
+
installSummary.cleaned += 1;
|
|
233
|
+
installLog(` rm legacy ${target}`);
|
|
234
|
+
}
|
|
227
235
|
}
|
|
228
236
|
}
|
|
229
237
|
|
|
@@ -384,10 +392,13 @@ function refreshProjectGuidance(cwd = process.cwd()) {
|
|
|
384
392
|
const marker = path.join(cwd, ".scrumrun", "method.json");
|
|
385
393
|
const sealed = writeFile(marker, sealPolicyIntegrity(path.join(cwd, ".scrumrun")), { backup: true });
|
|
386
394
|
results.push({ status: sealed.changed ? "updated" : "skipped", dest: marker, backup: sealed.backup });
|
|
395
|
+
const viewFile = path.join(cwd, ".scrumrun", "view.html");
|
|
396
|
+
const viewResult = writeFile(viewFile, fs.readFileSync(path.join(templates, "shared", "view.html"), "utf8"), { backup: true });
|
|
397
|
+
results.push({ status: viewResult.changed ? "updated" : "skipped", dest: viewFile, backup: viewResult.backup });
|
|
387
398
|
return results;
|
|
388
399
|
}
|
|
389
400
|
|
|
390
|
-
function updateInstallation(target, { migrate = false, project = false, sealPolicy = false, verbose = false } = {}) {
|
|
401
|
+
function updateInstallation(target, { migrate = false, project = false, sealPolicy = false, verbose = false, repairLegacy = false } = {}) {
|
|
391
402
|
installVerbose = verbose;
|
|
392
403
|
installSummary.cleaned = 0;
|
|
393
404
|
installSummary.written = 0;
|
|
@@ -403,6 +414,18 @@ function updateInstallation(target, { migrate = false, project = false, sealPoli
|
|
|
403
414
|
const sealed = writeFile(marker, sealPolicyIntegrity(scrumDir, { includeGuardrails: true }), { backup: true });
|
|
404
415
|
projectResults.push({ status: sealed.changed ? "updated" : "skipped", dest: marker, backup: sealed.backup });
|
|
405
416
|
}
|
|
417
|
+
let repairSummary = "";
|
|
418
|
+
if (repairLegacy && v2Project()) {
|
|
419
|
+
try {
|
|
420
|
+
const { repair } = require(path.join(root, "lib", "commands", "repair"));
|
|
421
|
+
const scrumDir = path.join(process.cwd(), ".scrumrun");
|
|
422
|
+
const result = repair(scrumDir, { apply: true, recoverOrphanTasks: true });
|
|
423
|
+
const applied = (result.plan && result.plan.entries && result.plan.entries.length) || 0;
|
|
424
|
+
repairSummary = ` Legacy repair: ${applied} entry(ies) normalized.`;
|
|
425
|
+
} catch (error) {
|
|
426
|
+
repairSummary = ` Legacy repair skipped: ${error.message}.`;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
406
429
|
if (migrate && v2Project()) {
|
|
407
430
|
try {
|
|
408
431
|
refreshState(path.join(process.cwd(), ".scrumrun"));
|
|
@@ -414,7 +437,7 @@ function updateInstallation(target, { migrate = false, project = false, sealPoli
|
|
|
414
437
|
if (!verbose) {
|
|
415
438
|
const targetSummary = installSummary.targets.join(", ") || "no clients";
|
|
416
439
|
const projectSummary = projectResults.length ? ` Project guidance: ${projectResults.filter((item) => item.status === "updated").length} file(s) refreshed.` : "";
|
|
417
|
-
console.log(`Updated ${targetSummary} — ${installSummary.written} files written, ${installSummary.cleaned} legacy removed.${projectSummary} Run with --verbose to see file paths.`);
|
|
440
|
+
console.log(`Updated ${targetSummary} — ${installSummary.written} files written, ${installSummary.cleaned} legacy removed.${projectSummary}${repairSummary} Run with --verbose to see file paths.`);
|
|
418
441
|
}
|
|
419
442
|
return migration;
|
|
420
443
|
}
|
|
@@ -1372,6 +1395,25 @@ function printMemoryArtifact(artifact) {
|
|
|
1372
1395
|
function runV2Memory(subject, args) {
|
|
1373
1396
|
const kind = subject === "fact" ? "knowledge" : subject;
|
|
1374
1397
|
const action = args[0];
|
|
1398
|
+
if (kind === "dossier" && action === "--compact") {
|
|
1399
|
+
if (args.includes("--dry-run")) {
|
|
1400
|
+
const preview = proposedClusters(process.cwd());
|
|
1401
|
+
console.log(JSON.stringify({ mode: "dry-run", ...preview }, null, 2));
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
if (args.includes("--apply")) {
|
|
1405
|
+
const result = applyCompaction(process.cwd(), { approved: args.includes("--approve") });
|
|
1406
|
+
console.log(JSON.stringify({ mode: "apply", ...result }, null, 2));
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
if (args.includes("--rollback")) {
|
|
1410
|
+
const dossier = args[args.indexOf("--rollback") + 1];
|
|
1411
|
+
if (!dossier || dossier.startsWith("--")) throw new Error("--rollback requires a DOS-NNN id.");
|
|
1412
|
+
console.log(JSON.stringify(rollbackCompaction(process.cwd(), dossier), null, 2));
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
throw new Error("Usage: scrumrun knowledge dossier --compact --dry-run|--apply --approve|--rollback DOS-NNN");
|
|
1416
|
+
}
|
|
1375
1417
|
const createAction = kind === "insight" ? "--propose" : "--add";
|
|
1376
1418
|
if (action === createAction) {
|
|
1377
1419
|
const artifact = createMemory(process.cwd(), kind, memoryOptions(args));
|
|
@@ -1691,7 +1733,7 @@ function executeRootRoute(route) {
|
|
|
1691
1733
|
if (noun === "knowledge" && subject === "vault") return runVault(routeArgs);
|
|
1692
1734
|
if (noun === "knowledge" && subject === "context") return runContext(routeArgs);
|
|
1693
1735
|
if (noun === "review" && subject === "artifact" && routeArgs[0] === "--run") {
|
|
1694
|
-
const audit = auditProject(process.cwd());
|
|
1736
|
+
const audit = auditProject(process.cwd(), { staged: routeArgs.includes("--staged"), strict: routeArgs.includes("--strict") });
|
|
1695
1737
|
console.log(JSON.stringify(audit, null, 2));
|
|
1696
1738
|
if (!audit.passed) process.exitCode = 1;
|
|
1697
1739
|
return;
|
|
@@ -1715,6 +1757,15 @@ function executeRootRoute(route) {
|
|
|
1715
1757
|
const target = ["all", "codex", "opencode", "claude"].includes(routeArgs[0]) ? routeArgs[0] : "all";
|
|
1716
1758
|
return doctor(target, { strict: routeArgs.includes("--strict"), recover: routeArgs.includes("--recover"), dryRun: routeArgs.includes("--dry-run") });
|
|
1717
1759
|
}
|
|
1760
|
+
if (noun === "config" && subject === "watch") {
|
|
1761
|
+
let result;
|
|
1762
|
+
if (routeArgs[0] === "--start") result = startWatcher(process.cwd());
|
|
1763
|
+
else if (routeArgs[0] === "--stop") result = stopWatcher(process.cwd());
|
|
1764
|
+
else if (routeArgs[0] === "--status") result = watcherStatus(process.cwd());
|
|
1765
|
+
else throw new Error("Usage: scrumrun config watch --start|--stop|--status");
|
|
1766
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1718
1769
|
if (noun === "config" && subject === "update") {
|
|
1719
1770
|
const target = ["all", "codex", "opencode", "claude"].includes(routeArgs[0]) ? routeArgs[0] : "all";
|
|
1720
1771
|
return updateInstallation(target, {
|
|
@@ -2350,6 +2401,7 @@ function initProject({ force, mode, agentHint, lean }) {
|
|
|
2350
2401
|
|
|
2351
2402
|
results.push(...copyDir(path.join(projectTemplate, ".scrumrun"), path.join(cwd, ".scrumrun"), { force, vars }));
|
|
2352
2403
|
results.push(copyFile(path.join(root, "CORE.md"), path.join(cwd, ".scrumrun", "core.md"), { force, vars }));
|
|
2404
|
+
results.push(copyFile(path.join(templates, "shared", "view.html"), path.join(cwd, ".scrumrun", "view.html"), { force }));
|
|
2353
2405
|
if (force || !markerExisted) {
|
|
2354
2406
|
const sealed = writeFile(marker, sealPolicyIntegrity(path.join(cwd, ".scrumrun"), { includeGuardrails: true }), { backup: false });
|
|
2355
2407
|
results.push({ status: sealed.changed ? "written" : "skipped", dest: marker, backup: sealed.backup });
|
|
@@ -2632,7 +2684,7 @@ function doctor(target = "all", { compatibility = false, strict = false, recover
|
|
|
2632
2684
|
ok = false;
|
|
2633
2685
|
console.log(`miss ScrumRun project audit: ${scrumDir}`);
|
|
2634
2686
|
} else {
|
|
2635
|
-
const audit = auditProject(process.cwd());
|
|
2687
|
+
const audit = auditProject(process.cwd(), { strict: true });
|
|
2636
2688
|
const blocking = audit.findings.filter((item) => ["critical", "high"].includes(item.severity));
|
|
2637
2689
|
ok = ok && audit.passed && blocking.length === 0;
|
|
2638
2690
|
console.log(`${audit.passed && blocking.length === 0 ? "ok " : "fail"} ScrumRun project audit: ${audit.findings.length} finding(s)`);
|
|
@@ -2656,12 +2708,38 @@ if (!command || command === "--help" || command === "-h") {
|
|
|
2656
2708
|
console.log(`ScrumRun ${version}`);
|
|
2657
2709
|
} else if (command === "install" || command === "update") {
|
|
2658
2710
|
const target = ["all", "codex", "opencode", "claude"].includes(args[1]) ? args[1] : "all";
|
|
2659
|
-
if (command === "update") updateInstallation(target, { migrate: args.includes("--migrate"), project: args.includes("--project"), sealPolicy: args.includes("--seal-policy"), verbose: args.includes("--verbose") });
|
|
2711
|
+
if (command === "update") updateInstallation(target, { migrate: args.includes("--migrate"), project: args.includes("--project"), sealPolicy: args.includes("--seal-policy"), verbose: args.includes("--verbose"), repairLegacy: args.includes("--repair-legacy") });
|
|
2660
2712
|
else install(target, true, { compatibility: false });
|
|
2661
2713
|
} else if (command === "sc") {
|
|
2662
2714
|
runRoot(args.slice(1));
|
|
2663
2715
|
} else if (["plan", "knowledge", "rules", "review", "config"].includes(command)) {
|
|
2664
2716
|
runRoot(args);
|
|
2717
|
+
} else if (command === "action") {
|
|
2718
|
+
const { executeAction, listActions } = require(path.join(root, "lib", "actions"));
|
|
2719
|
+
try {
|
|
2720
|
+
const name = args[1];
|
|
2721
|
+
if (!name || name === "--list") {
|
|
2722
|
+
console.log("Available actions:");
|
|
2723
|
+
for (const item of listActions()) console.log(` ${item.name.padEnd(30)} ${item.describe}`);
|
|
2724
|
+
process.exit(0);
|
|
2725
|
+
}
|
|
2726
|
+
const rest = args.slice(2);
|
|
2727
|
+
let payload = {};
|
|
2728
|
+
const payloadFlag = rest.indexOf("--payload");
|
|
2729
|
+
const fileFlag = rest.indexOf("--file");
|
|
2730
|
+
if (payloadFlag !== -1) {
|
|
2731
|
+
payload = JSON.parse(rest[payloadFlag + 1] || "{}");
|
|
2732
|
+
} else if (fileFlag !== -1) {
|
|
2733
|
+
payload = JSON.parse(fs.readFileSync(rest[fileFlag + 1], "utf8"));
|
|
2734
|
+
} else if (rest.includes("--stdin")) {
|
|
2735
|
+
payload = JSON.parse(fs.readFileSync(0, "utf8"));
|
|
2736
|
+
}
|
|
2737
|
+
const result = executeAction(name, process.cwd(), payload);
|
|
2738
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2739
|
+
} catch (error) {
|
|
2740
|
+
console.error(`action failed: ${error.message}`);
|
|
2741
|
+
process.exitCode = 1;
|
|
2742
|
+
}
|
|
2665
2743
|
} else if (COMMAND_ALIASES[command]) {
|
|
2666
2744
|
runCompatibilityAlias(command, args.slice(1));
|
|
2667
2745
|
} else if (command === "init") {
|
package/docs/COMMANDS.md
CHANGED
|
@@ -69,13 +69,20 @@ Creation options include `--title`, `--content`, repeated `--evidence`, repeated
|
|
|
69
69
|
scrumrun rules guardrail --add|--list|--show|--retire
|
|
70
70
|
scrumrun rules reviewer --add|--list|--show|--run
|
|
71
71
|
scrumrun review code --run
|
|
72
|
-
scrumrun review artifact --run
|
|
72
|
+
scrumrun review artifact --run [--staged]
|
|
73
73
|
scrumrun review artifact --record --task TASK-NNN [--run RUN-NNN] [--title "..."] [--evidence "..."]
|
|
74
74
|
scrumrun review migration --run
|
|
75
75
|
scrumrun review release --run
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
`review artifact --run` is read-only and returns a machine-readable
|
|
78
|
+
`review artifact --run` is read-only and returns a machine-readable project audit, including declarative Guardrail matches against the current Git worktree. `--staged` evaluates the Git index, for the optional pre-commit hook. `--record` reruns that audit and persists its exact pass/fail result as a canonical `REV-NNN`; supplied evidence is additive and cannot turn a failed audit into a pass. Other review routes require repository reasoning and remain read-only unless fixes receive separate approval.
|
|
79
|
+
|
|
80
|
+
To install the optional offline hook locally:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
cp node_modules/scrumrun/templates/shared/hooks/pre-commit .git/hooks/pre-commit
|
|
84
|
+
chmod +x .git/hooks/pre-commit
|
|
85
|
+
```
|
|
79
86
|
|
|
80
87
|
## Config and lifecycle
|
|
81
88
|
|
|
@@ -85,6 +92,7 @@ scrumrun config init --local|--shared|--lean|--no-agent-hint|--force
|
|
|
85
92
|
scrumrun config update [all|codex|opencode|claude] [--project] [--seal-policy] [--migrate]
|
|
86
93
|
scrumrun config migrate --to 2 --dry-run|--apply|--rollback
|
|
87
94
|
scrumrun config doctor [all|codex|opencode|claude] [--strict] [--recover]
|
|
95
|
+
scrumrun config watch --start|--stop|--status
|
|
88
96
|
scrumrun config uninstall --force
|
|
89
97
|
scrumrun config help <topic>
|
|
90
98
|
```
|
|
@@ -94,3 +102,5 @@ Top-level CLI aliases (`init`, `update`, `migrate`, `doctor`, `uninstall`, `stat
|
|
|
94
102
|
Run transitions accept typed evidence through `--command`, `--test`, `--file`, `--review`, `--decision`, `--insight`, `--risk`, or generic `--evidence kind:value`. `doctor --recover` is an explicit write that resolves only safe pending kernel transactions; doctor without it remains read-only.
|
|
95
103
|
|
|
96
104
|
Run `scrumrun commands` for grammar rendered directly from the current manifest.
|
|
105
|
+
|
|
106
|
+
`watch` is an optional convenience, never a gate or authority. Set `watcher.enabled: true` in `.scrumrun/config.md`, then use `--start`. It records its liveness PID at `.scrumrun/.cache/watcher.pid`; `--stop` shuts that daemon down. Native recursive `fs.watch` is used where supported; otherwise the daemon falls back to portable polling. It ignores `vault.local.md`, `.cache/contexts/`, its own generated outputs, and writes only generated `state.md`, `map.md`, and `.cache/semantic-index.sqlite`.
|
package/docs/ERROR-CODES.md
CHANGED
|
@@ -77,6 +77,7 @@ SR-E-102 Run transition rejected: missing validation or learning evidence.
|
|
|
77
77
|
| `SR-E-150` | Guardrail check blocked the operation. | Read the reported GR-NNN, satisfy or retire it explicitly; guardrails never bypass silently. |
|
|
78
78
|
| `SR-E-151` | Guardrail obligation is still pending. | Resolve each pending guardrail via `scrumrun plan run --satisfy-guardrail` before completing the Run. |
|
|
79
79
|
| `SR-E-152` | Guardrail declaration is malformed. | Every active guardrail requires Status, Enforcement, Scope, and Rule fields; check `.scrumrun/guardrails.md`. |
|
|
80
|
+
| `SR-E-153` | Declarative Guardrail YAML is malformed. | Use the documented yaml enforcement block with match, on_violation, severity, and optional evidence. |
|
|
80
81
|
|
|
81
82
|
### Edit permits (Mutation Gateway)
|
|
82
83
|
|
|
@@ -121,6 +122,9 @@ SR-E-102 Run transition rejected: missing validation or learning evidence.
|
|
|
121
122
|
|---|---|---|
|
|
122
123
|
| `SR-E-450` | Conformance check failed. | The reported invariant identifies the exact violation; the message includes the file and expected shape. |
|
|
123
124
|
| `SR-E-451` | Installed client asset is stale. | Re-run `scrumrun update` for the specific client. `doctor --strict` shows which files diverge. |
|
|
125
|
+
| `SR-E-452` | Task Markdown is missing a required section. | Add the reported section (e.g. `## Request`, `## Done when`) to the Task file. The validator lives in `lib/v2/task-schema.js`. |
|
|
126
|
+
| `SR-E-453` | Completed Task has no `## Completion` or associated Run `## Technical Summary`. | Append a short `## Completion` bullet list to the Task, or complete the Run with `plan run --complete --summary "..."`. |
|
|
127
|
+
| `SR-E-454` | Task is running inside a git repository but no `git.branch` was captured. | Warning-only. Reconcile with `scrumrun repair --apply`, or add `git: { branch: <name>, base_sha: <sha> }` to the Task frontmatter. |
|
|
124
128
|
|
|
125
129
|
### Configuration and installation
|
|
126
130
|
|
package/docs/SCHEMA.md
CHANGED
|
@@ -86,6 +86,10 @@ A native ledger begins with `created → executing`; an evidenced migration `sna
|
|
|
86
86
|
| insight | `candidate` → `confirmed`, `invalidated`<br>`confirmed` → `stale`, `deprecated`, `invalidated`<br>`stale` → `confirmed`, `deprecated`, `invalidated`<br>`deprecated` → `confirmed`<br>`invalidated` → terminal |
|
|
87
87
|
| dossier | `active` → `stale`, `deprecated`, `archived`<br>`stale` → `active`, `deprecated`, `archived`<br>`deprecated` → `archived`<br>`archived` → terminal |
|
|
88
88
|
|
|
89
|
+
## Declarative Guardrail enforcement
|
|
90
|
+
|
|
91
|
+
Project Guardrails may include an optional fenced `yaml enforcement` block. Its restricted, dependency-free YAML schema is defined normatively in `SPEC.md §6.1`: `match.paths[]`, `match.diff[]`, `match.symbols[]`, `on_violation` (`block` or `warn`), `severity`, and optional `evidence`. The pure evaluator consumes only that normalized rule data and a supplied ChangeSet; it has no network or LLM dependency. Prose-only Guardrails remain valid.
|
|
92
|
+
|
|
89
93
|
## Projections
|
|
90
94
|
|
|
91
95
|
`state.md`, `map.md`, context packages, and `.cache/` are disposable. They may summarize or index canonical artifacts, but they cannot introduce status, policy, relations, decisions, or knowledge.
|
package/docs/SEMANTIC-MEMORY.md
CHANGED
|
@@ -66,3 +66,31 @@ scrumrun knowledge context --clear
|
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
SQLite is ignored and disposable. The derived index records its search backend: FTS5/BM25 is selected when the current Node.js SQLite build supports it; otherwise ScrumRun uses deterministic parameterized token matching over the same artifact, code, and relation tables. Queries default to 10 records/40 relations and hard-cap at 100/100. Match type, truth state, warnings, relation counts, and evidence are returned so recommendations remain explainable.
|
|
69
|
+
|
|
70
|
+
## Deterministic Dossier compaction
|
|
71
|
+
|
|
72
|
+
Compaction is an opt-in maintenance operation for a project with many related,
|
|
73
|
+
already reviewed memory records. It never calls an LLM and never runs in the
|
|
74
|
+
background:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
scrumrun knowledge dossier --compact --dry-run
|
|
78
|
+
scrumrun knowledge dossier --compact --apply --approve
|
|
79
|
+
scrumrun knowledge dossier --compact --rollback DOS-001
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Only approved Knowledge and confirmed Insights without `superseded_by` are
|
|
83
|
+
eligible. Each record is represented by its subject, declared relations
|
|
84
|
+
(including `used_by`), and explicit `#tags` or `## Tags`/`## Topics` bullets.
|
|
85
|
+
Two records are connected when the Jaccard overlap of those features is at
|
|
86
|
+
least `memory.compaction.threshold` (default `0.6`). Connected components with
|
|
87
|
+
at least `memory.compaction.min_members` records (default `3`) become proposed
|
|
88
|
+
clusters. IDs, feature sorting, links, and labels are all deterministic.
|
|
89
|
+
|
|
90
|
+
Dry-run is read-only and reports member IDs, pair evidence, score and effective
|
|
91
|
+
configuration. Apply additionally requires the explicit `--approve` flag: it
|
|
92
|
+
creates a Dossier with original artifact IDs as evidence and adds only
|
|
93
|
+
`superseded_by: DOS-NNN` to the source records. Sources are never deleted. The
|
|
94
|
+
Dossier holds a reversible snapshot; rollback restores byte-exact sources only
|
|
95
|
+
when they have not changed since compaction, then archives the Dossier. This
|
|
96
|
+
refusal protects later owner edits rather than overwriting them.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { addPlanArtifact, amendPlanArtifact } = require("../runtime/orchestrator");
|
|
4
|
+
const { createMemory } = require("../memory/service");
|
|
5
|
+
|
|
6
|
+
const REGISTRY = Object.freeze({
|
|
7
|
+
"plan.task.add": {
|
|
8
|
+
describe: "Create a Task (optionally as backlog or fix).",
|
|
9
|
+
handler: (projectRoot, payload) => {
|
|
10
|
+
requireString(payload, "title");
|
|
11
|
+
return addPlanArtifact(projectRoot, "task", payload.title, {
|
|
12
|
+
type: payload.type || null,
|
|
13
|
+
status: payload.status || null
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"plan.feature.add": {
|
|
18
|
+
describe: "Create a Feature.",
|
|
19
|
+
handler: (projectRoot, payload) => {
|
|
20
|
+
requireString(payload, "title");
|
|
21
|
+
return addPlanArtifact(projectRoot, "feature", payload.title, {});
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"plan.sprint.add": {
|
|
25
|
+
describe: "Create a Sprint.",
|
|
26
|
+
handler: (projectRoot, payload) => {
|
|
27
|
+
requireString(payload, "title");
|
|
28
|
+
return addPlanArtifact(projectRoot, "sprint", payload.title, {});
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"plan.task.amend": {
|
|
32
|
+
describe: "Amend an existing Task's title/sections/type/feature/sprint.",
|
|
33
|
+
handler: (projectRoot, payload) => {
|
|
34
|
+
requireString(payload, "id");
|
|
35
|
+
return amendPlanArtifact(projectRoot, "task", payload.id, payload);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"knowledge.fact.add": {
|
|
39
|
+
describe: "Create a Knowledge (K-NNN) candidate.",
|
|
40
|
+
handler: (projectRoot, payload) => {
|
|
41
|
+
requireString(payload, "title");
|
|
42
|
+
return createMemory(projectRoot, "knowledge", payload);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"knowledge.decision.add": {
|
|
46
|
+
describe: "Create a Decision (DEC-NNN) candidate.",
|
|
47
|
+
handler: (projectRoot, payload) => {
|
|
48
|
+
requireString(payload, "title");
|
|
49
|
+
return createMemory(projectRoot, "decision", payload);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"knowledge.insight.propose": {
|
|
53
|
+
describe: "Propose an Insight (INS-NNN) candidate.",
|
|
54
|
+
handler: (projectRoot, payload) => {
|
|
55
|
+
requireString(payload, "title");
|
|
56
|
+
return createMemory(projectRoot, "insight", payload);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
function requireString(payload, field) {
|
|
62
|
+
if (!payload || typeof payload[field] !== "string" || !payload[field].trim()) {
|
|
63
|
+
throw new Error(`payload.${field} must be a non-empty string`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function listActions() {
|
|
68
|
+
return Object.entries(REGISTRY).map(([name, { describe }]) => ({ name, describe }));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function executeAction(name, projectRoot, payload) {
|
|
72
|
+
const entry = REGISTRY[name];
|
|
73
|
+
if (!entry) {
|
|
74
|
+
const known = Object.keys(REGISTRY).sort().join(", ");
|
|
75
|
+
throw new Error(`Unknown action "${name}". Known actions: ${known}`);
|
|
76
|
+
}
|
|
77
|
+
const normalizedPayload = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
78
|
+
return entry.handler(projectRoot, normalizedPayload);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { REGISTRY, executeAction, listActions };
|
package/lib/commands/manifest.js
CHANGED
|
@@ -36,7 +36,7 @@ const nouns = Object.freeze({
|
|
|
36
36
|
fact: ["--add [--title] [--content] [--evidence] [--relation] [--subject] [--source] [--source-id] [--valid-from] [--valid-until] [--review-trigger]", "--list [--include-inactive]", "--show", "--approve [--evidence] [--note]", "--reject [--note]", "--deprecate [--note]", "--invalidate [--note]"],
|
|
37
37
|
decision: ["--add [--title] [--content] [--evidence] [--relation] [--subject] [--source] [--source-id] [--valid-from] [--valid-until] [--review-trigger]", "--list [--include-inactive]", "--show", "--resolve [--evidence] [--note]", "--deprecate [--note]", "--invalidate [--note]"],
|
|
38
38
|
insight: ["--propose [--title] [--content] [--evidence] [--relation] [--subject] [--source] [--source-id] [--confidence] [--valid-from] [--valid-until] [--review-trigger]", "--list [--include-inactive]", "--show", "--confirm [--evidence] [--note]", "--stale [--note]", "--reject [--note]", "--deprecate [--note]", "--invalidate [--note]"],
|
|
39
|
-
dossier: ["--add [--title] [--content] [--evidence] [--relation] [--subject] [--source] [--source-id] [--valid-from] [--valid-until] [--review-trigger]", "--list [--include-inactive]", "--show", "--refresh [--evidence] [--note]", "--stale [--note]", "--deprecate [--note]", "--archive [--note]"],
|
|
39
|
+
dossier: ["--add [--title] [--content] [--evidence] [--relation] [--subject] [--source] [--source-id] [--valid-from] [--valid-until] [--review-trigger]", "--list [--include-inactive]", "--show", "--refresh [--evidence] [--note]", "--stale [--note]", "--deprecate [--note]", "--archive [--note]", "--compact --dry-run", "--compact --apply --approve", "--compact --rollback <DOS-NNN>"],
|
|
40
40
|
context: ["--build", "--update", "--show", "--clear"],
|
|
41
41
|
map: ["--build", "--show"],
|
|
42
42
|
errors: ["--show"],
|
|
@@ -55,7 +55,7 @@ const nouns = Object.freeze({
|
|
|
55
55
|
description: "run scoped evidence-based quality gates",
|
|
56
56
|
subjects: {
|
|
57
57
|
code: ["--run"],
|
|
58
|
-
artifact: ["--run", "--record --task <TASK-NNN> [--run <RUN-NNN>] [--title] [--evidence]"],
|
|
58
|
+
artifact: ["--run [--staged]", "--record --task <TASK-NNN> [--run <RUN-NNN>] [--title] [--evidence]"],
|
|
59
59
|
migration: ["--run"],
|
|
60
60
|
release: ["--run"]
|
|
61
61
|
}
|
|
@@ -68,6 +68,7 @@ const nouns = Object.freeze({
|
|
|
68
68
|
update: ["all [--project] [--seal-policy] [--migrate]", "codex [--project] [--seal-policy] [--migrate]", "opencode [--project] [--seal-policy] [--migrate]", "claude [--project] [--seal-policy] [--migrate]"],
|
|
69
69
|
migrate: ["--to 2 --dry-run", "--to 2 --apply", "--to 2 --rollback"],
|
|
70
70
|
doctor: ["all [--strict] [--recover] [--dry-run]", "codex [--strict] [--recover] [--dry-run]", "opencode [--strict] [--recover] [--dry-run]", "claude [--strict] [--recover] [--dry-run]"],
|
|
71
|
+
watch: ["--start", "--stop", "--status"],
|
|
71
72
|
uninstall: ["--force"],
|
|
72
73
|
help: ["<topic>"]
|
|
73
74
|
}
|
package/lib/commands/repair.js
CHANGED
|
@@ -51,17 +51,38 @@ const VALID_RUN_STATUS = new Set(["executing", "validating", "learning", "partia
|
|
|
51
51
|
const TASK_STATUS_ALIAS = {
|
|
52
52
|
done: "completed",
|
|
53
53
|
todo: "backlog",
|
|
54
|
+
pending: "backlog",
|
|
55
|
+
planned: "backlog",
|
|
54
56
|
complete: "completed",
|
|
57
|
+
finished: "completed",
|
|
58
|
+
closed: "completed",
|
|
59
|
+
skipped: "cancelled",
|
|
60
|
+
canceled: "cancelled",
|
|
61
|
+
wontdo: "cancelled",
|
|
62
|
+
"won't do": "cancelled",
|
|
55
63
|
in_progress: "running",
|
|
56
|
-
|
|
57
|
-
|
|
64
|
+
"in-progress": "running",
|
|
65
|
+
wip: "running",
|
|
66
|
+
doing: "running",
|
|
67
|
+
executing: "running",
|
|
68
|
+
active: "running",
|
|
69
|
+
review: "validating",
|
|
70
|
+
reviewing: "validating"
|
|
58
71
|
};
|
|
59
72
|
|
|
60
73
|
const RUN_STATUS_ALIAS = {
|
|
61
74
|
complete: "completed",
|
|
62
75
|
in_progress: "executing",
|
|
76
|
+
"in-progress": "executing",
|
|
77
|
+
running: "executing",
|
|
78
|
+
wip: "executing",
|
|
63
79
|
done: "completed",
|
|
64
|
-
|
|
80
|
+
finished: "completed",
|
|
81
|
+
closed: "completed",
|
|
82
|
+
partial: "failed",
|
|
83
|
+
canceled: "blocked",
|
|
84
|
+
cancelled: "blocked",
|
|
85
|
+
skipped: "blocked"
|
|
65
86
|
};
|
|
66
87
|
|
|
67
88
|
// When syncing Task.status from latest Run.status, translate Run vocabulary
|
package/lib/errors.js
CHANGED
|
@@ -40,6 +40,7 @@ const CATALOG = Object.freeze({
|
|
|
40
40
|
"SR-E-150": { summary: "Guardrail check blocked the operation.", remediation: "Read the reported GR-NNN, satisfy or retire it explicitly; guardrails never bypass silently." },
|
|
41
41
|
"SR-E-151": { summary: "Guardrail obligation is still pending.", remediation: "Resolve each `pending guardrail` via `scrumrun plan run --satisfy-guardrail` before completing the Run." },
|
|
42
42
|
"SR-E-152": { summary: "Guardrail declaration is malformed.", remediation: "Every active guardrail requires Status, Enforcement, Scope, and Rule fields; check .scrumrun/guardrails.md." },
|
|
43
|
+
"SR-E-153": { summary: "Declarative Guardrail YAML is malformed.", remediation: "Use the documented yaml enforcement block with match, on_violation, severity, and optional evidence." },
|
|
43
44
|
|
|
44
45
|
// Edit permits (Mutation Gateway)
|
|
45
46
|
"SR-E-200": { summary: "No edit permit for this path.", remediation: "Request one with `scrumrun plan run --authorize-mutation RUN-NNN --path <path>` before editing canonical or source files." },
|
|
@@ -66,6 +67,9 @@ const CATALOG = Object.freeze({
|
|
|
66
67
|
// Conformance / doctor
|
|
67
68
|
"SR-E-450": { summary: "Conformance check failed.", remediation: "The reported invariant identifies the exact violation; the message includes the file and expected shape." },
|
|
68
69
|
"SR-E-451": { summary: "Installed client asset is stale.", remediation: "Re-run `scrumrun update` for the specific client. `doctor --strict` shows which files diverge." },
|
|
70
|
+
"SR-E-452": { summary: "Task Markdown is missing a required section.", remediation: "Add the reported section (e.g. `## Request`, `## Done when`) to the Task file. The validator lives in `lib/v2/task-schema.js`." },
|
|
71
|
+
"SR-E-453": { summary: "Completed Task has no `## Completion` or associated Run `## Technical Summary`.", remediation: "Append a short `## Completion` bullet list to the Task, or complete the Run with `plan run --complete --summary \"...\"`." },
|
|
72
|
+
"SR-E-454": { summary: "Task is running inside a git repository but no `git.branch` was captured.", remediation: "Warning-only. Reconcile with `scrumrun repair --apply`, or add `git: { branch: <name>, base_sha: <sha> }` to the Task frontmatter." },
|
|
69
73
|
|
|
70
74
|
// Configuration / install
|
|
71
75
|
"SR-E-500": { summary: "ScrumRun project not initialized.", remediation: "Run `scrumrun init` in the repository root." },
|