super-backlog 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 super-backlog contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # super-backlog
2
+
3
+ [![CI](https://github.com/adam-s-k-i/super-backlog/actions/workflows/ci.yml/badge.svg)](https://github.com/adam-s-k-i/super-backlog/actions/workflows/ci.yml)
4
+ [![Docs](https://img.shields.io/badge/docs-adam--s--k--i.github.io%2Fsuper--backlog-blue)](https://adam-s-k-i.github.io/super-backlog/)
5
+
6
+ ![Super Backlog](docs/assets/super-backlog-logo.jpg)
7
+
8
+ One command to equip any project with [Backlog.md](https://github.com/MrLesk/Backlog.md) + [Superpowers](https://github.com/obra/superpowers), plus a Project Dashboard.
9
+
10
+ ## What & why
11
+
12
+ Superpowers and Backlog.md are strong on their own, but nothing wires them together: Superpowers defines *how* agents should work (brainstorming → plans → TDD → review), Backlog.md defines *what* is tracked (markdown tasks, Kanban board, browser UI). Connecting them today means hand-copying glue from project to project — workflow blocks in `AGENTS.md`, bridge skills like `spec-to-backlog`, npm scripts, plugin config. super-backlog is the glue orchestrator that installs and maintains all of it in one step, with a guaranteed-clean exit path (`sbl uninstall`).
13
+
14
+ ## Requirements
15
+
16
+ - Node >= 20
17
+ - A package manager (npm, pnpm, or bun) for dependency installation
18
+ - Works with **OpenCode** and **Claude Code** (both configured by default)
19
+
20
+ ## Quickstart
21
+
22
+ ```bash
23
+ npx super-backlog init # install everything into the current project
24
+ npm run board # open the Backlog.md kanban board
25
+ sbl dashboard --serve # live Project Dashboard on http://localhost:6428
26
+ # (or: npx super-backlog dashboard --serve)
27
+ ```
28
+
29
+ On Windows PowerShell, `npx super-backlog init` may fail with an execution-policy error before the CLI starts. Run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once in PowerShell, or run `sbl doctor` for the exact policy and fix.
30
+
31
+ `init` is idempotent — safe to re-run any time; re-running with a newer kit version is the upgrade path for all injected files.
32
+
33
+ ## What gets installed
34
+
35
+ | Target | Content | Ownership model |
36
+ |---|---|---|
37
+ | `package.json` devDependencies | `backlog.md@latest` + `super-backlog@latest` | merged |
38
+ | `backlog/` | created by `backlog init --defaults` (upstream owns it) | delegated |
39
+ | `opencode.json` | `plugin[] += superpowers@git+https://github.com/obra/superpowers.git` | merged; other keys untouched |
40
+ | `.claude/` | Superpowers via official marketplace — init prints the exact command to paste (`/plugin install superpowers@claude-plugins-official`); file-based skills work immediately | instructed/delegated |
41
+ | `AGENTS.md` | `<!-- SUPER-BACKLOG:x.y.z START -->` … `<!-- SUPER-BACKLOG END -->` workflow block | marker-scoped |
42
+ | `CLAUDE.md` | one-line pointer to the AGENTS.md block | marker-scoped |
43
+ | `.opencode/skill/<skill>/SKILL.md` (3 glue skills) | skill templates | fingerprint header line |
44
+ | `.claude/skills/<skill>/SKILL.md` | same templates | fingerprint header line |
45
+ | `package.json` scripts | `tasks` → `backlog task list`, `board` → `backlog board`, `browser` → `backlog browser`, `dashboard` → `super-backlog dashboard` (never overwrite existing values) | merged, add-only-if-absent |
46
+ | `dashboard.html` | generated Project Dashboard | regenerated wholesale |
47
+ | `.git/hooks/post-commit` | dashboard freshness block — regenerates `dashboard.html` after commits that touch `backlog/` (default; opt out with `--no-refresh-hook`) | appended marker block |
48
+ | `.git/hooks/pre-commit` | integrity guard hook — only with `--guard` (opt-in) | appended marker block |
49
+
50
+ Commands: `sbl init` · `sbl doctor` · `sbl uninstall [--with-backlog]` · `sbl update` · `sbl dashboard [--serve] [--port <n>] [--no-open] [--out <file>]`. See `sbl help` for every flag.
51
+
52
+ ## Project Dashboard
53
+
54
+ `sbl dashboard` generates a single self-contained `dashboard.html`: a dark, HTS-style cockpit rendered from your Backlog data in seven sections — Board & Quick Actions, Status (donut), Milestones, Tasks (sortable/filterable table with a click-in detail panel per task), Feature Cycle (pipeline stepper), Activity (30-day sparkline), and Decisions & Docs. A layered dependency graph maps task `depends-on` relations (cycle- and dangling-ref-tolerant): hover highlights edges, click opens the task. Glossary tooltips explain domain terms inline; extend or override them project-wide via `backlog/docs/glossary.md` (`## Term` heading plus the text below it). No CDNs, no external fonts — works offline, diffs cleanly in git, hostable anywhere. Use `--serve` for live mode: it watches `backlog/`, regenerates on change, and serves on port 6428 by default.
55
+
56
+ ### Keeping it fresh
57
+
58
+ By default `init` installs a `post-commit` hook block that regenerates `dashboard.html` whenever the commit touched `backlog/`. The block is marker-delimited, so it composes with the guard hook and foreign hook content, `uninstall` removes exactly that block, and `update` refreshes it. It never blocks a commit: failures print a one-line stderr note and the exit status stays 0. Opt out with `--no-refresh-hook`.
59
+
60
+ ![Project Dashboard](docs/assets/dashboard.png)
61
+
62
+ ## Uninstall guarantee
63
+
64
+ super-backlog uninstall removes only provably owned artifacts and keeps your Backlog task data unless you pass --with-backlog. Every removal decision is reported line by line as removed / kept / skipped; files whose ownership cannot be proven are left untouched.
65
+
66
+ ## Harness support
67
+
68
+ - **OpenCode** — native: `opencode.json` plugin entry plus file-based skills under `.opencode/skill/` (spec-to-backlog, backlog-status-report, task-review-gate).
69
+ - **Claude Code** — file-based skills under `.claude/skills/` always work immediately; the marketplace plugin cannot be installed from a script, so init prints the exact command to paste (`/plugin install superpowers@claude-plugins-official`) and exits with a warning (exit code 4).
70
+
71
+ Details and matrix: [docs/guide/harness-support.md](docs/guide/harness-support.md).
72
+
73
+ ## Troubleshooting
74
+
75
+ Windows OpenCode fallback, exit codes, and environment seams: [docs/guide/troubleshooting.md](docs/guide/troubleshooting.md). Architecture deep-dive: [docs/guide/architecture.md](docs/guide/architecture.md). Guard hook details: [docs/guide/guard.md](docs/guide/guard.md).
76
+
77
+ ## Development
78
+
79
+ ```bash
80
+ npm ci # install dependencies
81
+ npm test # build + vitest suite
82
+ npm run lint # markdownlint + cspell over all Markdown
83
+ ```
84
+
85
+ ## License
86
+
87
+ [MIT](LICENSE)
package/dist/cli.js ADDED
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ // src/cli.ts
3
+ import { parseArgs } from 'node:util';
4
+ import process from 'node:process';
5
+ import { runDashboard } from './commands/dashboard.js';
6
+ import { runDoctor } from './commands/doctor.js';
7
+ import { runInit } from './commands/init.js';
8
+ import { runUninstall } from './commands/uninstall.js';
9
+ import { runUpdate } from './commands/update.js';
10
+ import { assertNode20, KIT_VERSION } from './lib/version.js';
11
+ const HELP = `super-backlog (sbl) - equip any project with Backlog.md + Superpowers
12
+
13
+ Usage: sbl <command> [options]
14
+
15
+ Commands:
16
+ init Install the kit into the current project
17
+ uninstall Remove kit-managed files (project data kept unless --with-backlog)
18
+ update Refresh kit-managed files and report upstream versions
19
+ dashboard Generate the single-file project dashboard (--serve for live mode)
20
+ doctor Check the environment (node, PowerShell policy, backlog CLI)
21
+
22
+ init options:
23
+ --pm <auto|npm|pnpm|bun|skip> Package manager to use (default: auto)
24
+ --harness <opencode|claude> Target harness; repeatable or comma-separated (default: both)
25
+ --guard Install the integrity pre-commit hook (opt-in)
26
+ --no-dashboard Skip generating the project dashboard
27
+ --no-refresh-hook Skip the post-commit dashboard freshness hook
28
+ --dry-run Show what would be done without writing anything
29
+
30
+ uninstall options:
31
+ --with-backlog Also permanently delete the backlog/ data directory
32
+
33
+ update options:
34
+ (none) Refreshes injected files, skills, hook; prints upstream versions
35
+
36
+ dashboard options:
37
+ --serve Live mode: watch backlog/ and regenerate on changes
38
+ --port <n> Port for --serve (default: 6428)
39
+ --no-open With --serve: do not open the browser automatically
40
+ --out <file> Output file name or path (default: dashboard.html)
41
+
42
+ doctor options:
43
+ (none) Prints one [ok]/[warn]/[skip] line per check; exit 4 on any warn
44
+
45
+ Global options:
46
+ --version Print the super-backlog version and exit
47
+
48
+ Exit codes:
49
+ 0 ok | 1 usage/detection failure | 2 ownership or merge refusal
50
+ 3 upstream command failure | 4 success with warnings`;
51
+ async function main(argv) {
52
+ const [command, ...rest] = argv;
53
+ if (command === '--version' || command === '-v') {
54
+ console.log(KIT_VERSION);
55
+ return 0;
56
+ }
57
+ if (command === undefined || command === 'help' || command === '--help' || command === '-h') {
58
+ console.log(HELP);
59
+ return 0;
60
+ }
61
+ switch (command) {
62
+ case 'init': {
63
+ const parsed = parseArgs({
64
+ args: rest,
65
+ allowPositionals: true,
66
+ options: {
67
+ pm: { type: 'string' },
68
+ harness: { type: 'string', multiple: true },
69
+ guard: { type: 'boolean' },
70
+ 'no-dashboard': { type: 'boolean' },
71
+ 'no-refresh-hook': { type: 'boolean' },
72
+ 'dry-run': { type: 'boolean' },
73
+ },
74
+ });
75
+ return await runInit(process.cwd(), {
76
+ values: parsed.values,
77
+ positionals: parsed.positionals,
78
+ });
79
+ }
80
+ case 'uninstall': {
81
+ const parsed = parseArgs({
82
+ args: rest,
83
+ allowPositionals: true,
84
+ options: { 'with-backlog': { type: 'boolean' } },
85
+ });
86
+ return runUninstall(process.cwd(), {
87
+ values: parsed.values,
88
+ positionals: parsed.positionals,
89
+ });
90
+ }
91
+ case 'update': {
92
+ const parsed = parseArgs({ args: rest, allowPositionals: true, options: {} });
93
+ return await runUpdate(process.cwd(), {
94
+ values: parsed.values,
95
+ positionals: parsed.positionals,
96
+ });
97
+ }
98
+ case 'dashboard': {
99
+ const parsed = parseArgs({
100
+ args: rest,
101
+ allowPositionals: true,
102
+ options: {
103
+ serve: { type: 'boolean' },
104
+ port: { type: 'string' },
105
+ 'no-open': { type: 'boolean' },
106
+ out: { type: 'string' },
107
+ },
108
+ });
109
+ return await runDashboard(process.cwd(), {
110
+ values: parsed.values,
111
+ positionals: parsed.positionals,
112
+ });
113
+ }
114
+ case 'doctor':
115
+ return runDoctor(process.cwd());
116
+ default:
117
+ console.error(`Unknown command "${command}".\n`);
118
+ console.error(HELP);
119
+ return 1;
120
+ }
121
+ }
122
+ assertNode20();
123
+ main(process.argv.slice(2))
124
+ .then((code) => {
125
+ process.exitCode = code;
126
+ })
127
+ .catch((err) => {
128
+ console.error(err instanceof Error ? err.message : String(err));
129
+ process.exitCode = 1;
130
+ });
@@ -0,0 +1,69 @@
1
+ // src/commands/dashboard.ts
2
+ import { isAbsolute, join, resolve } from 'node:path';
3
+ import { collectDashboardData } from '../dashboard/data.js';
4
+ import { renderDashboard } from '../dashboard/render.js';
5
+ import { DASHBOARD_PORT, startServeServer } from '../dashboard/server.js';
6
+ import { atomicWrite } from '../lib/atomic.js';
7
+ import { KIT_VERSION } from '../lib/version.js';
8
+ async function regenerateInto(outPath, cwd) {
9
+ const data = collectDashboardData(cwd, { kitVersion: KIT_VERSION });
10
+ atomicWrite(outPath, renderDashboard(data));
11
+ }
12
+ /**
13
+ * Generate dashboard.html for the project; with `{ serve: true }`, start the
14
+ * live-reload server afterwards (the listening socket keeps the process up).
15
+ * Called by `sbl init` with serve disabled.
16
+ */
17
+ export async function generateDashboard(cwd, o) {
18
+ const outPath = join(cwd, 'dashboard.html');
19
+ await regenerateInto(outPath, cwd);
20
+ if (o.serve) {
21
+ // Dynamic import keeps init resilient if the serve module is unavailable.
22
+ const specifier = '../dashboard/server.js';
23
+ const mod = (await import(specifier));
24
+ if (typeof mod.startServeServer !== 'function') {
25
+ throw new Error('serve module does not export startServeServer');
26
+ }
27
+ await mod.startServeServer(cwd, {
28
+ port: DASHBOARD_PORT,
29
+ regenerate: () => regenerateInto(outPath, cwd),
30
+ openBrowser: true,
31
+ });
32
+ }
33
+ return outPath;
34
+ }
35
+ /** CLI entry for `sbl dashboard [--serve] [--port N] [--no-open] [--out FILE]`. */
36
+ export async function runDashboard(cwd, args) {
37
+ const values = args.values;
38
+ let port = DASHBOARD_PORT;
39
+ if (values['port'] !== undefined) {
40
+ const parsed = Number(values['port']);
41
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
42
+ console.error(`error: invalid --port "${String(values['port'])}" (expected 0-65535)`);
43
+ return 1;
44
+ }
45
+ port = parsed;
46
+ }
47
+ const serve = values['serve'] === true;
48
+ const noOpen = values['no-open'] === true;
49
+ const outFile = values['out'] === undefined ? 'dashboard.html' : String(values['out']);
50
+ const outPath = isAbsolute(outFile) ? outFile : resolve(cwd, outFile);
51
+ try {
52
+ await regenerateInto(outPath, cwd);
53
+ console.log(`dashboard written: ${outPath}`);
54
+ if (serve) {
55
+ console.log(`serving dashboard at http://127.0.0.1:${port}/ (press Ctrl+C to stop)`);
56
+ await startServeServer(cwd, {
57
+ port,
58
+ file: outPath,
59
+ regenerate: () => regenerateInto(outPath, cwd),
60
+ openBrowser: !noOpen,
61
+ });
62
+ }
63
+ return 0;
64
+ }
65
+ catch (err) {
66
+ console.error(`error: dashboard generation failed (${err instanceof Error ? err.message : String(err)})`);
67
+ return 1;
68
+ }
69
+ }
@@ -0,0 +1,65 @@
1
+ // src/commands/doctor.ts
2
+ import process from 'node:process';
3
+ import { getEffectiveExecutionPolicy, isBlockingExecutionPolicy, } from '../lib/powershell.js';
4
+ import { resolveBacklogBin } from '../lib/run.js';
5
+ const MARK = { ok: '[ok] ', warn: '[warn]', skip: '[skip]' };
6
+ export function runDoctor(cwd, deps = {}) {
7
+ const platform = deps.platform ?? process.platform;
8
+ const nodeVersion = deps.nodeVersion ?? process.versions.node;
9
+ const resolveBacklog = deps.resolveBacklog ?? resolveBacklogBin;
10
+ const log = deps.log ?? ((line) => console.log(line));
11
+ let okCount = 0;
12
+ let warnCount = 0;
13
+ let skipCount = 0;
14
+ const emit = (status, line, extra = []) => {
15
+ if (status === 'ok')
16
+ okCount += 1;
17
+ else if (status === 'warn')
18
+ warnCount += 1;
19
+ else
20
+ skipCount += 1;
21
+ log(`${MARK[status]} ${line}`);
22
+ for (const extraLine of extra)
23
+ log(` ${extraLine}`);
24
+ };
25
+ // check 1: node >= 20
26
+ const major = Number(nodeVersion.split('.')[0]);
27
+ if (Number.isNaN(major)) {
28
+ emit('warn', `node version unreadable ("${nodeVersion}") - super-backlog requires Node >= 20`);
29
+ }
30
+ else if (major >= 20) {
31
+ emit('ok', `node v${nodeVersion} (>= 20)`);
32
+ }
33
+ else {
34
+ emit('warn', `node v${nodeVersion} is too old - super-backlog requires Node >= 20`, [
35
+ 'fix: upgrade Node.js (https://nodejs.org)',
36
+ ]);
37
+ }
38
+ // check 2: PowerShell execution policy (win32 only, fakeable via SBL_FAKE_POLICY)
39
+ const policy = getEffectiveExecutionPolicy({ platform, executor: deps.executor });
40
+ if (policy === null) {
41
+ emit('skip', 'PowerShell execution policy check (not Windows or undetectable)');
42
+ }
43
+ else if (isBlockingExecutionPolicy(policy)) {
44
+ emit('warn', `PowerShell execution policy "${policy}" blocks .ps1 shims - direct npm/npx/sbl calls in PowerShell will fail`, [
45
+ 'fix: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned',
46
+ 'alt: call the .cmd shims explicitly (npx.cmd, sbl.cmd) or run from cmd.exe',
47
+ 'note: "npm run ..." scripts are unaffected (they execute via cmd.exe)',
48
+ ]);
49
+ }
50
+ else {
51
+ emit('ok', `PowerShell execution policy: ${policy}`);
52
+ }
53
+ // check 3: backlog binary resolvability
54
+ const backlogBin = resolveBacklog(cwd);
55
+ if (backlogBin !== null) {
56
+ emit('ok', `backlog CLI resolvable at ${backlogBin}`);
57
+ }
58
+ else {
59
+ emit('warn', 'backlog CLI not found - run sbl init or npm install first', [
60
+ 'fix: npx.cmd super-backlog init',
61
+ ]);
62
+ }
63
+ log(`doctor summary: ${okCount} ok, ${warnCount} warn, ${skipCount} skip`);
64
+ return warnCount > 0 ? 4 : 0;
65
+ }
@@ -0,0 +1,138 @@
1
+ // src/commands/init.ts
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { basename, join, resolve } from 'node:path';
4
+ import { detectPackageManager } from '../lib/pm.js';
5
+ import { getEffectiveExecutionPolicy, isBlockingExecutionPolicy, policyWarningLines, } from '../lib/powershell.js';
6
+ import { KIT_VERSION } from '../lib/version.js';
7
+ import { executeActions, InvalidJsonError, RefusalError, UpstreamError } from '../init/execute.js';
8
+ import { planInit } from '../init/planner.js';
9
+ const HARNESS_VALUES = ['opencode', 'claude'];
10
+ const PM_VALUES = ['auto', 'npm', 'pnpm', 'bun', 'skip'];
11
+ function isHarness(value) {
12
+ return HARNESS_VALUES.includes(value);
13
+ }
14
+ function describeAction(action) {
15
+ switch (action.kind) {
16
+ case 'upstream-install':
17
+ return `upstream-install via ${action.pm}`;
18
+ case 'merge-json':
19
+ return `merge-json ${action.path} (${action.transform})`;
20
+ case 'inject-agents-block':
21
+ return 'inject-agents-block AGENTS.md';
22
+ case 'write-claude-pointer':
23
+ return 'write-claude-pointer CLAUDE.md';
24
+ case 'copy-skills':
25
+ return 'copy-skills (.opencode/skill + .claude/skills)';
26
+ case 'install-guard-hook':
27
+ return 'install-guard-hook .git/hooks/pre-commit';
28
+ case 'install-refresh-hook':
29
+ return 'install-refresh-hook .git/hooks/post-commit';
30
+ case 'generate-dashboard':
31
+ return 'generate-dashboard';
32
+ case 'write':
33
+ return `write ${action.path}`;
34
+ }
35
+ }
36
+ function maybePrintPolicyWarning() {
37
+ const policy = getEffectiveExecutionPolicy();
38
+ if (isBlockingExecutionPolicy(policy)) {
39
+ for (const line of policyWarningLines(policy)) {
40
+ console.log(line);
41
+ }
42
+ }
43
+ }
44
+ export async function runInit(cwd, args) {
45
+ const harnesses = [];
46
+ const rawHarnesses = args.values.harness;
47
+ const listed = Array.isArray(rawHarnesses)
48
+ ? rawHarnesses
49
+ : rawHarnesses === undefined
50
+ ? []
51
+ : [rawHarnesses];
52
+ for (const entry of listed) {
53
+ for (const part of String(entry).split(',')) {
54
+ const name = part.trim();
55
+ if (name === '')
56
+ continue;
57
+ if (!isHarness(name)) {
58
+ console.error(`Invalid --harness "${name}" (expected: ${HARNESS_VALUES.join(', ')})`);
59
+ return 1;
60
+ }
61
+ if (!harnesses.includes(name))
62
+ harnesses.push(name);
63
+ }
64
+ }
65
+ if (harnesses.length === 0)
66
+ harnesses.push('opencode', 'claude');
67
+ const rawPm = args.values.pm === undefined ? 'auto' : String(args.values.pm);
68
+ if (!PM_VALUES.includes(rawPm)) {
69
+ console.error(`Invalid --pm "${rawPm}" (expected: ${PM_VALUES.join(', ')})`);
70
+ return 1;
71
+ }
72
+ const pm = rawPm;
73
+ const guard = args.values.guard === true; // opt-in per spec D8
74
+ const dashboard = args.values['no-dashboard'] !== true;
75
+ const refreshHook = args.values['no-refresh-hook'] !== true; // default on, opt-out flag
76
+ const dryRun = args.values['dry-run'] === true;
77
+ const projectName = args.positionals[0] ?? basename(resolve(cwd));
78
+ let opencodeConfig;
79
+ const opencodePath = join(cwd, 'opencode.json');
80
+ if (existsSync(opencodePath)) {
81
+ try {
82
+ opencodeConfig = JSON.parse(readFileSync(opencodePath, 'utf8'));
83
+ }
84
+ catch {
85
+ console.error('error: opencode.json is not valid JSON - fix it manually, then re-run');
86
+ return 1; // detection failure, consistent with uninstall
87
+ }
88
+ }
89
+ const state = {
90
+ cwd,
91
+ detectedPm: detectPackageManager(cwd),
92
+ hasBacklogConfig: existsSync(join(cwd, 'backlog', 'config.yml')),
93
+ agentsExists: existsSync(join(cwd, 'AGENTS.md')),
94
+ claudeMdExists: existsSync(join(cwd, 'CLAUDE.md')),
95
+ opencodeConfig,
96
+ pkgExists: existsSync(join(cwd, 'package.json')),
97
+ };
98
+ const opts = { projectName, harnesses, pm, guard, dashboard, refreshHook, skipInstall: false };
99
+ const plan = planInit(state, opts, KIT_VERSION);
100
+ if (dryRun) {
101
+ console.log(`dry-run for "${projectName}": ${plan.actions.length} action(s) planned, nothing written`);
102
+ for (const action of plan.actions)
103
+ console.log(` - ${describeAction(action)}`);
104
+ for (const warning of plan.warnings)
105
+ console.log(`warning: ${warning}`);
106
+ maybePrintPolicyWarning();
107
+ return plan.warnings.length > 0 ? 4 : 0;
108
+ }
109
+ try {
110
+ const result = await executeActions(cwd, plan.actions, {
111
+ version: KIT_VERSION,
112
+ projectName,
113
+ hasBacklogConfig: state.hasBacklogConfig,
114
+ });
115
+ const warnings = [...plan.warnings, ...result.warnings];
116
+ console.log(`super-backlog init complete - planned ${plan.actions.length}, applied ${result.applied}, skipped ${result.skipped}`);
117
+ for (const warning of warnings)
118
+ console.log(`warning: ${warning}`);
119
+ maybePrintPolicyWarning();
120
+ return warnings.length > 0 ? 4 : 0;
121
+ }
122
+ catch (err) {
123
+ if (err instanceof UpstreamError) {
124
+ console.error(`error: upstream command failed: ${err.message}`);
125
+ return 3;
126
+ }
127
+ if (err instanceof InvalidJsonError) {
128
+ // detection failure, not ownership refusal: exit 1 per contract
129
+ console.error(`error: ${err.message}`);
130
+ return 1;
131
+ }
132
+ if (err instanceof RefusalError) {
133
+ console.error(`error: ${err.message}`);
134
+ return 2;
135
+ }
136
+ throw err;
137
+ }
138
+ }