felenova-backlog 0.1.0

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.
Files changed (147) hide show
  1. package/README.md +58 -0
  2. package/bin/backlog.js +2 -0
  3. package/dist/_core/criteria.d.ts +13 -0
  4. package/dist/_core/criteria.js +44 -0
  5. package/dist/_core/criteria.js.map +1 -0
  6. package/dist/_core/errors.d.ts +53 -0
  7. package/dist/_core/errors.js +91 -0
  8. package/dist/_core/errors.js.map +1 -0
  9. package/dist/_core/index.d.ts +24 -0
  10. package/dist/_core/index.js +27 -0
  11. package/dist/_core/index.js.map +1 -0
  12. package/dist/_core/loop.d.ts +73 -0
  13. package/dist/_core/loop.js +46 -0
  14. package/dist/_core/loop.js.map +1 -0
  15. package/dist/_core/markdown/items.d.ts +35 -0
  16. package/dist/_core/markdown/items.js +256 -0
  17. package/dist/_core/markdown/items.js.map +1 -0
  18. package/dist/_core/markdown/matrix.d.ts +20 -0
  19. package/dist/_core/markdown/matrix.js +147 -0
  20. package/dist/_core/markdown/matrix.js.map +1 -0
  21. package/dist/_core/markdown/track.d.ts +71 -0
  22. package/dist/_core/markdown/track.js +79 -0
  23. package/dist/_core/markdown/track.js.map +1 -0
  24. package/dist/_core/matrix-status.d.ts +9 -0
  25. package/dist/_core/matrix-status.js +31 -0
  26. package/dist/_core/matrix-status.js.map +1 -0
  27. package/dist/_core/matrix.d.ts +46 -0
  28. package/dist/_core/matrix.js +0 -0
  29. package/dist/_core/matrix.js.map +1 -0
  30. package/dist/_core/permissions.d.ts +14 -0
  31. package/dist/_core/permissions.js +87 -0
  32. package/dist/_core/permissions.js.map +1 -0
  33. package/dist/_core/promote.d.ts +20 -0
  34. package/dist/_core/promote.js +47 -0
  35. package/dist/_core/promote.js.map +1 -0
  36. package/dist/_core/ready.d.ts +8 -0
  37. package/dist/_core/ready.js +22 -0
  38. package/dist/_core/ready.js.map +1 -0
  39. package/dist/_core/review.d.ts +49 -0
  40. package/dist/_core/review.js +137 -0
  41. package/dist/_core/review.js.map +1 -0
  42. package/dist/_core/schema.d.ts +636 -0
  43. package/dist/_core/schema.js +304 -0
  44. package/dist/_core/schema.js.map +1 -0
  45. package/dist/_core/ship.d.ts +23 -0
  46. package/dist/_core/ship.js +31 -0
  47. package/dist/_core/ship.js.map +1 -0
  48. package/dist/_core/spec-diff.d.ts +19 -0
  49. package/dist/_core/spec-diff.js +25 -0
  50. package/dist/_core/spec-diff.js.map +1 -0
  51. package/dist/_core/status.d.ts +7 -0
  52. package/dist/_core/status.js +25 -0
  53. package/dist/_core/status.js.map +1 -0
  54. package/dist/_core/util/deep-equal.d.ts +8 -0
  55. package/dist/_core/util/deep-equal.js +31 -0
  56. package/dist/_core/util/deep-equal.js.map +1 -0
  57. package/dist/api.d.ts +46 -0
  58. package/dist/api.js +104 -0
  59. package/dist/api.js.map +1 -0
  60. package/dist/cache.d.ts +53 -0
  61. package/dist/cache.js +114 -0
  62. package/dist/cache.js.map +1 -0
  63. package/dist/commands/common.d.ts +59 -0
  64. package/dist/commands/common.js +94 -0
  65. package/dist/commands/common.js.map +1 -0
  66. package/dist/commands/export.d.ts +20 -0
  67. package/dist/commands/export.js +56 -0
  68. package/dist/commands/export.js.map +1 -0
  69. package/dist/commands/matrix.d.ts +16 -0
  70. package/dist/commands/matrix.js +48 -0
  71. package/dist/commands/matrix.js.map +1 -0
  72. package/dist/commands/next.d.ts +14 -0
  73. package/dist/commands/next.js +30 -0
  74. package/dist/commands/next.js.map +1 -0
  75. package/dist/commands/promote.d.ts +15 -0
  76. package/dist/commands/promote.js +172 -0
  77. package/dist/commands/promote.js.map +1 -0
  78. package/dist/commands/report.d.ts +18 -0
  79. package/dist/commands/report.js +45 -0
  80. package/dist/commands/report.js.map +1 -0
  81. package/dist/commands/ship.d.ts +15 -0
  82. package/dist/commands/ship.js +231 -0
  83. package/dist/commands/ship.js.map +1 -0
  84. package/dist/commands/status.d.ts +13 -0
  85. package/dist/commands/status.js +141 -0
  86. package/dist/commands/status.js.map +1 -0
  87. package/dist/commands/sync.d.ts +17 -0
  88. package/dist/commands/sync.js +90 -0
  89. package/dist/commands/sync.js.map +1 -0
  90. package/dist/conductor/registry.d.ts +45 -0
  91. package/dist/conductor/registry.js +164 -0
  92. package/dist/conductor/registry.js.map +1 -0
  93. package/dist/config.d.ts +47 -0
  94. package/dist/config.js +137 -0
  95. package/dist/config.js.map +1 -0
  96. package/dist/doctor.d.ts +28 -0
  97. package/dist/doctor.js +377 -0
  98. package/dist/doctor.js.map +1 -0
  99. package/dist/errors.d.ts +23 -0
  100. package/dist/errors.js +114 -0
  101. package/dist/errors.js.map +1 -0
  102. package/dist/export.d.ts +1 -0
  103. package/dist/export.js +9 -0
  104. package/dist/export.js.map +1 -0
  105. package/dist/import.d.ts +3 -0
  106. package/dist/import.js +89 -0
  107. package/dist/import.js.map +1 -0
  108. package/dist/index.d.ts +1 -0
  109. package/dist/index.js +183 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/init/auth.d.ts +85 -0
  112. package/dist/init/auth.js +194 -0
  113. package/dist/init/auth.js.map +1 -0
  114. package/dist/init/claudemd.d.ts +16 -0
  115. package/dist/init/claudemd.js +62 -0
  116. package/dist/init/claudemd.js.map +1 -0
  117. package/dist/init/index.d.ts +47 -0
  118. package/dist/init/index.js +159 -0
  119. package/dist/init/index.js.map +1 -0
  120. package/dist/init/mcp.d.ts +18 -0
  121. package/dist/init/mcp.js +97 -0
  122. package/dist/init/mcp.js.map +1 -0
  123. package/dist/init/plugin.d.ts +60 -0
  124. package/dist/init/plugin.js +120 -0
  125. package/dist/init/plugin.js.map +1 -0
  126. package/dist/init/scaffold.d.ts +19 -0
  127. package/dist/init/scaffold.js +46 -0
  128. package/dist/init/scaffold.js.map +1 -0
  129. package/dist/init/templates.d.ts +48 -0
  130. package/dist/init/templates.js +147 -0
  131. package/dist/init/templates.js.map +1 -0
  132. package/dist/plugin/.claude-plugin/plugin.json +11 -0
  133. package/dist/plugin/README.md +117 -0
  134. package/dist/plugin/commands/next.md +28 -0
  135. package/dist/plugin/commands/promote.md +30 -0
  136. package/dist/plugin/commands/report.md +26 -0
  137. package/dist/plugin/commands/ship.md +30 -0
  138. package/dist/plugin/skills/backlog-management/SKILL.md +63 -0
  139. package/dist/plugin/skills/backlog-query/SKILL.md +57 -0
  140. package/dist/plugin/skills/feature-kickoff/SKILL.md +112 -0
  141. package/dist/snapshot.d.ts +24 -0
  142. package/dist/snapshot.js +2 -0
  143. package/dist/snapshot.js.map +1 -0
  144. package/dist/token.d.ts +29 -0
  145. package/dist/token.js +99 -0
  146. package/dist/token.js.map +1 -0
  147. package/package.json +52 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The `backlog-execution` payload: three skills, four commands, and where each one goes in a
3
+ * repo.
4
+ *
5
+ * **The layout is verified, not guessed** (Claude Code 2.1.269, `claude plugin validate` and
6
+ * an `init`-message probe):
7
+ *
8
+ * - `packages/plugin/` is a real plugin — `.claude-plugin/plugin.json` at its root, `skills/
9
+ * <name>/SKILL.md`, flat `commands/<name>.md`. `claude plugin validate --strict` passes and
10
+ * `claude --plugin-dir … plugin details backlog-execution` inventories all seven components.
11
+ * That is the marketplace / `--plugin-dir` route.
12
+ * - A plugin directory dropped *inside a project* is **not** auto-loaded: neither
13
+ * `.claude/plugins/<name>/` nor `.claude/skills/<name>/` (the user-level `~/.claude/skills`
14
+ * is the only skills-dir that auto-loads). So `init` cannot install a plugin directory and
15
+ * have it work.
16
+ * - What does work in a repo, and is what PRODUCT-SPEC §7.1 asks for, is the project surface:
17
+ * `.claude/skills/<name>/SKILL.md` loads as the skill `<name>`, and
18
+ * `.claude/commands/backlog/<name>.md` loads as the slash command **`/backlog:next`** — the
19
+ * exact names §7.2 documents. The plugin's own prefix comes from `plugin.json`'s `name`, so
20
+ * via the plugin route the same commands read `/backlog-execution:next`.
21
+ *
22
+ * So the payload's bytes are written once, under `packages/plugin/`, and installed into the two
23
+ * project directories below. The `.mcp.json` server entry is deliberately **not** in the
24
+ * payload: `init/mcp.ts` owns that file and `init/templates.ts` owns the key and the entry
25
+ * shape, and a second copy here would be a second thing to keep in step.
26
+ */
27
+ /** The plugin's name — `plugin.json`'s `name`, and the namespace on the plugin route. */
28
+ export declare const PLUGIN_NAME = "backlog-execution";
29
+ /** Project-level skills: `.claude/skills/<name>/SKILL.md` loads as the skill `<name>`. */
30
+ export declare const PLUGIN_SKILLS_TARGET_DIR: string;
31
+ /** Project-level commands: `.claude/commands/backlog/next.md` is `/backlog:next`. */
32
+ export declare const PLUGIN_COMMANDS_TARGET_DIR: string;
33
+ export interface PayloadFile {
34
+ /** Path inside `packages/plugin/`, POSIX-separated as the plugin format writes it. */
35
+ source: string;
36
+ /** Path inside the repo, platform-separated. */
37
+ target: string;
38
+ }
39
+ /** Every file `init` installs, in write order. */
40
+ export declare const PLUGIN_PAYLOAD: readonly PayloadFile[];
41
+ export declare function payloadRoot(): string;
42
+ /** One payload file's bytes, exactly as packaged. */
43
+ export declare function readPayloadFile(source: string): string;
44
+ export interface PluginResult {
45
+ installed: string[];
46
+ kept: string[];
47
+ /** Present, different, and left alone — the `--force-plugin` list. */
48
+ differing: string[];
49
+ replaced: string[];
50
+ lines: string[];
51
+ }
52
+ /**
53
+ * Create-if-missing, file by file, with the same manners as the conductor scaffold: a file
54
+ * that is already byte-identical is left alone, and a file the user has edited is **kept** and
55
+ * reported rather than clobbered. `--force-plugin` is the only way the packaged copy wins.
56
+ */
57
+ export declare function installPlugin(opts: {
58
+ cwd: string;
59
+ force?: boolean;
60
+ }): PluginResult;
@@ -0,0 +1,120 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ /**
5
+ * The `backlog-execution` payload: three skills, four commands, and where each one goes in a
6
+ * repo.
7
+ *
8
+ * **The layout is verified, not guessed** (Claude Code 2.1.269, `claude plugin validate` and
9
+ * an `init`-message probe):
10
+ *
11
+ * - `packages/plugin/` is a real plugin — `.claude-plugin/plugin.json` at its root, `skills/
12
+ * <name>/SKILL.md`, flat `commands/<name>.md`. `claude plugin validate --strict` passes and
13
+ * `claude --plugin-dir … plugin details backlog-execution` inventories all seven components.
14
+ * That is the marketplace / `--plugin-dir` route.
15
+ * - A plugin directory dropped *inside a project* is **not** auto-loaded: neither
16
+ * `.claude/plugins/<name>/` nor `.claude/skills/<name>/` (the user-level `~/.claude/skills`
17
+ * is the only skills-dir that auto-loads). So `init` cannot install a plugin directory and
18
+ * have it work.
19
+ * - What does work in a repo, and is what PRODUCT-SPEC §7.1 asks for, is the project surface:
20
+ * `.claude/skills/<name>/SKILL.md` loads as the skill `<name>`, and
21
+ * `.claude/commands/backlog/<name>.md` loads as the slash command **`/backlog:next`** — the
22
+ * exact names §7.2 documents. The plugin's own prefix comes from `plugin.json`'s `name`, so
23
+ * via the plugin route the same commands read `/backlog-execution:next`.
24
+ *
25
+ * So the payload's bytes are written once, under `packages/plugin/`, and installed into the two
26
+ * project directories below. The `.mcp.json` server entry is deliberately **not** in the
27
+ * payload: `init/mcp.ts` owns that file and `init/templates.ts` owns the key and the entry
28
+ * shape, and a second copy here would be a second thing to keep in step.
29
+ */
30
+ /** The plugin's name — `plugin.json`'s `name`, and the namespace on the plugin route. */
31
+ export const PLUGIN_NAME = 'backlog-execution';
32
+ /** Project-level skills: `.claude/skills/<name>/SKILL.md` loads as the skill `<name>`. */
33
+ export const PLUGIN_SKILLS_TARGET_DIR = join('.claude', 'skills');
34
+ /** Project-level commands: `.claude/commands/backlog/next.md` is `/backlog:next`. */
35
+ export const PLUGIN_COMMANDS_TARGET_DIR = join('.claude', 'commands', 'backlog');
36
+ const SKILLS = ['backlog-query', 'backlog-management', 'feature-kickoff'];
37
+ const COMMANDS = ['next', 'promote', 'report', 'ship'];
38
+ /** Every file `init` installs, in write order. */
39
+ export const PLUGIN_PAYLOAD = [
40
+ ...SKILLS.map((name) => ({
41
+ source: `skills/${name}/SKILL.md`,
42
+ target: join(PLUGIN_SKILLS_TARGET_DIR, name, 'SKILL.md'),
43
+ })),
44
+ ...COMMANDS.map((name) => ({
45
+ source: `commands/${name}.md`,
46
+ target: join(PLUGIN_COMMANDS_TARGET_DIR, `${name}.md`),
47
+ })),
48
+ ];
49
+ /**
50
+ * Where the payload sits at runtime. `dist/plugin/` in a built or packed CLI (the build copies
51
+ * it there so `files: ["dist"]` carries it), and `packages/plugin/` in the source tree, which
52
+ * is what the tests read. Both are tried; the first that has a skill in it wins.
53
+ */
54
+ const PAYLOAD_CANDIDATES = ['../plugin/', '../../../plugin/'];
55
+ const PROBE = 'skills/backlog-query/SKILL.md';
56
+ let cachedRoot = null;
57
+ export function payloadRoot() {
58
+ if (cachedRoot !== null)
59
+ return cachedRoot;
60
+ const tried = [];
61
+ for (const candidate of PAYLOAD_CANDIDATES) {
62
+ const dir = fileURLToPath(new URL(candidate, import.meta.url));
63
+ tried.push(dir);
64
+ if (existsSync(join(dir, PROBE))) {
65
+ cachedRoot = dir;
66
+ return dir;
67
+ }
68
+ }
69
+ throw new Error(`the ${PLUGIN_NAME} payload is missing from this install — looked in ${tried.join(', ')}`);
70
+ }
71
+ /** One payload file's bytes, exactly as packaged. */
72
+ export function readPayloadFile(source) {
73
+ return readFileSync(join(payloadRoot(), source), 'utf8');
74
+ }
75
+ /**
76
+ * Create-if-missing, file by file, with the same manners as the conductor scaffold: a file
77
+ * that is already byte-identical is left alone, and a file the user has edited is **kept** and
78
+ * reported rather than clobbered. `--force-plugin` is the only way the packaged copy wins.
79
+ */
80
+ export function installPlugin(opts) {
81
+ const installed = [];
82
+ const kept = [];
83
+ const differing = [];
84
+ const replaced = [];
85
+ const lines = [];
86
+ for (const file of PLUGIN_PAYLOAD) {
87
+ const body = readPayloadFile(file.source);
88
+ const abs = join(opts.cwd, file.target);
89
+ let current = null;
90
+ try {
91
+ current = readFileSync(abs, 'utf8');
92
+ }
93
+ catch {
94
+ current = null;
95
+ }
96
+ if (current === body) {
97
+ kept.push(file.target);
98
+ lines.push(`${file.target}: unchanged`);
99
+ continue;
100
+ }
101
+ if (current !== null && !opts.force) {
102
+ differing.push(file.target);
103
+ lines.push(`${file.target}: kept — differs from the packaged copy (--force-plugin to replace)`);
104
+ continue;
105
+ }
106
+ mkdirSync(dirname(abs), { recursive: true });
107
+ writeFileSync(abs, body, 'utf8');
108
+ if (current === null) {
109
+ installed.push(file.target);
110
+ lines.push(`${file.target}: added`);
111
+ }
112
+ else {
113
+ replaced.push(file.target);
114
+ lines.push(`${file.target}: replaced (--force-plugin)`);
115
+ }
116
+ }
117
+ lines.push(`plugin ${PLUGIN_NAME}: ${installed.length + replaced.length} added, ${kept.length} kept, ${differing.length} differing`);
118
+ return { installed, kept, differing, replaced, lines };
119
+ }
120
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/init/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Create-if-missing, file by file. Two conductor conventions are in the wild during the
3
+ * migration, so a repo that already has `conductor/` is merged into and never rewritten:
4
+ * an existing file is left byte-identical, whatever it says.
5
+ */
6
+ export interface ScaffoldResult {
7
+ added: string[];
8
+ kept: string[];
9
+ lines: string[];
10
+ }
11
+ export declare function scaffoldConductor(opts: {
12
+ cwd: string;
13
+ conductorDir: string;
14
+ }): ScaffoldResult;
15
+ /**
16
+ * Appends the entries under one `# backlog` comment. Never rewrites, reorders or removes a
17
+ * line, and never creates the file: a repo with no `.gitignore` has made a choice.
18
+ */
19
+ export declare function updateGitignore(cwd: string): string;
@@ -0,0 +1,46 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { CONDUCTOR_TEMPLATES, GITIGNORE_ENTRIES, GITIGNORE_HEADER } from './templates.js';
4
+ export function scaffoldConductor(opts) {
5
+ const added = [];
6
+ const kept = [];
7
+ const lines = [];
8
+ for (const template of CONDUCTOR_TEMPLATES) {
9
+ const rel = `${opts.conductorDir}/${template.path}`;
10
+ const abs = join(opts.cwd, opts.conductorDir, template.path);
11
+ if (existsSync(abs)) {
12
+ kept.push(rel);
13
+ lines.push(`${rel}: kept`);
14
+ continue;
15
+ }
16
+ mkdirSync(dirname(abs), { recursive: true });
17
+ writeFileSync(abs, template.body, 'utf8');
18
+ added.push(rel);
19
+ lines.push(`${rel}: added`);
20
+ }
21
+ lines.push(`${opts.conductorDir}/: ${added.length} added, ${kept.length} kept`);
22
+ return { added, kept, lines };
23
+ }
24
+ /**
25
+ * Appends the entries under one `# backlog` comment. Never rewrites, reorders or removes a
26
+ * line, and never creates the file: a repo with no `.gitignore` has made a choice.
27
+ */
28
+ export function updateGitignore(cwd) {
29
+ const path = join(cwd, '.gitignore');
30
+ let text;
31
+ try {
32
+ text = readFileSync(path, 'utf8');
33
+ }
34
+ catch {
35
+ return `.gitignore: absent — skipped; add ${GITIGNORE_ENTRIES.join(', ')} if you create one`;
36
+ }
37
+ const present = new Set(text.split('\n').map((l) => l.trim()));
38
+ const missing = GITIGNORE_ENTRIES.filter((entry) => !present.has(entry));
39
+ if (missing.length === 0)
40
+ return '.gitignore: unchanged — every entry is already there';
41
+ const prefix = text === '' || text.endsWith('\n') ? text : `${text}\n`;
42
+ const block = `\n${GITIGNORE_HEADER}\n${missing.join('\n')}\n`;
43
+ writeFileSync(path, `${prefix}${block}`, 'utf8');
44
+ return `.gitignore: added ${missing.join(', ')}`;
45
+ }
46
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/init/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAc1F,MAAM,UAAU,iBAAiB,CAAC,IAA2C;IAC3E,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,QAAQ,IAAI,mBAAmB,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,YAAY,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC7D,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,QAAQ,CAAC,CAAC;YAC3B,SAAS;QACX,CAAC;QACD,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,aAAa,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,YAAY,MAAM,KAAK,CAAC,MAAM,WAAW,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC;IAChF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACrC,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,qCAAqC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC;IAC/F,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IACzE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,sDAAsD,CAAC;IAExF,MAAM,MAAM,GAAG,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC;IACvE,MAAM,KAAK,GAAG,KAAK,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IAC/D,aAAa,CAAC,IAAI,EAAE,GAAG,MAAM,GAAG,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;IACjD,OAAO,qBAAqB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACnD,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Everything `init` can write, as data. Templates are TypeScript string constants rather
3
+ * than files under `templates/` so `tsc` alone produces a complete `dist` — a data
4
+ * directory would need a copy step in the build and would be missing from the published
5
+ * tarball the first time someone forgot it.
6
+ *
7
+ * Nothing here is dated or randomised: a template's bytes are a pure function of the
8
+ * project, so a second `init` can compare and leave the file alone.
9
+ */
10
+ export declare const DEFAULT_URL = "https://felenova-backlog.fly.dev";
11
+ export declare const DEFAULT_CONDUCTOR_DIR = "conductor";
12
+ /** The `.mcp.json` server key (`.mcp.json.example`, and `doctor`'s `mcp_entry` check). */
13
+ export declare const MCP_SERVER_KEY = "backlog";
14
+ /** The literal the file carries. A token value never reaches `.mcp.json`. */
15
+ export declare const TOKEN_PLACEHOLDER = "${BACKLOG_TOKEN}";
16
+ export interface McpServerEntry {
17
+ type: 'http';
18
+ url: string;
19
+ headers: {
20
+ Authorization: string;
21
+ };
22
+ }
23
+ /** Exactly `.mcp.json.example`'s shape, with the service URL substituted. */
24
+ export declare function mcpServerEntry(url: string): McpServerEntry;
25
+ export interface ConductorTemplate {
26
+ /** Path relative to the conductor directory. */
27
+ path: string;
28
+ body: string;
29
+ }
30
+ /**
31
+ * The scaffold, in write order. `knowledge/errors.json` is in the set because the shipped
32
+ * conductor layout has it and the Evaluate-Loop appends to it; it is one empty array.
33
+ */
34
+ export declare const CONDUCTOR_TEMPLATES: readonly ConductorTemplate[];
35
+ export declare const START_MARKER = "<!-- backlog:start -->";
36
+ export declare const END_MARKER = "<!-- backlog:end -->";
37
+ /** An older, hand-written section. `init` recognises it and keeps its hands off. */
38
+ export declare const LEGACY_HEADING = "## Backlog \u2192 Conductor";
39
+ export interface ClaudeSectionContext {
40
+ url: string;
41
+ project: string;
42
+ conductorDir: string;
43
+ }
44
+ /** The marker-delimited block, markers included, ending in a newline. */
45
+ export declare function claudeSection(ctx: ClaudeSectionContext): string;
46
+ /** Kept out of git by `init`: the MCP file, the offline cache, and the import snapshot. */
47
+ export declare const GITIGNORE_ENTRIES: readonly [".mcp.json", ".backlog/", "backlog-snapshot.json"];
48
+ export declare const GITIGNORE_HEADER = "# backlog";
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Everything `init` can write, as data. Templates are TypeScript string constants rather
3
+ * than files under `templates/` so `tsc` alone produces a complete `dist` — a data
4
+ * directory would need a copy step in the build and would be missing from the published
5
+ * tarball the first time someone forgot it.
6
+ *
7
+ * Nothing here is dated or randomised: a template's bytes are a pure function of the
8
+ * project, so a second `init` can compare and leave the file alone.
9
+ */
10
+ export const DEFAULT_URL = 'https://felenova-backlog.fly.dev';
11
+ export const DEFAULT_CONDUCTOR_DIR = 'conductor';
12
+ /** The `.mcp.json` server key (`.mcp.json.example`, and `doctor`'s `mcp_entry` check). */
13
+ export const MCP_SERVER_KEY = 'backlog';
14
+ /** The literal the file carries. A token value never reaches `.mcp.json`. */
15
+ export const TOKEN_PLACEHOLDER = '${BACKLOG_TOKEN}';
16
+ /** Exactly `.mcp.json.example`'s shape, with the service URL substituted. */
17
+ export function mcpServerEntry(url) {
18
+ return {
19
+ type: 'http',
20
+ url: `${url.replace(/\/+$/, '')}/mcp`,
21
+ headers: { Authorization: `Bearer ${TOKEN_PLACEHOLDER}` },
22
+ };
23
+ }
24
+ const WORKFLOW = `# Conductor Workflow — Evaluate-Loop v3
25
+
26
+ Every track goes through one loop. The register (backlog service) owns *what* and *why*;
27
+ this directory owns *how* and *now*.
28
+
29
+ ## Loop steps
30
+
31
+ \`\`\`
32
+ PLAN → EVALUATE_PLAN → EXECUTE → EVALUATE_EXECUTION → COMPLETE
33
+ ↑ | |
34
+ | FAIL → back FAIL → FIX → re-evaluate
35
+ └─────────────────────────────────────┘
36
+ \`\`\`
37
+
38
+ Statuses: \`IN_PROGRESS\`, \`PASS\`, \`FAIL\`, \`COMPLETE\`. Report each transition with
39
+ \`backlog report <track> <step> <status>\`; the first \`EXECUTE\` flips the track's promoted
40
+ items to \`in-progress\`.
41
+
42
+ ## The four backlog touch-points
43
+
44
+ | When | Command | What it does |
45
+ |---|---|---|
46
+ | before PLAN | \`backlog next\` | lists items whose criteria are locked and ready |
47
+ | at PLAN | \`backlog promote <ID…>\` | writes \`tracks/<slug>/{spec.md,plan.md,metadata.json}\` |
48
+ | during the loop | \`backlog report\`, \`backlog sync\` | records progress; re-renders \`spec.md\` when criteria move |
49
+ | at COMPLETE | \`backlog ship <slug>\` | writes \`shipped_commit\` and \`production_date\` back |
50
+
51
+ ## Rules
52
+
53
+ - Nothing is promoted until its criteria are **locked** in the register.
54
+ - Locked criteria are immutable: suggest a change, never edit the copy in \`spec.md\`.
55
+ - A track's \`spec.md\` is generated. Hand-edit \`plan.md\`, not \`spec.md\`.
56
+ - Two human checkpoints stay human: approving a plan, and approving a ship.
57
+ `;
58
+ const INDEX = `# Project Status
59
+
60
+ ## Current Focus
61
+
62
+ _Nothing yet. \`backlog next\` shows what is ready; \`backlog promote <ID…>\` starts a track._
63
+
64
+ ## Recent Completions
65
+
66
+ | Track | Date | Notes |
67
+ |---|---|---|
68
+
69
+ ## System Health
70
+
71
+ - conductor scaffolded by \`backlog init\`; run \`backlog doctor\` if anything looks wrong
72
+ `;
73
+ const TRACKS = `# Conductor Track Registry
74
+
75
+ ## Active Tracks
76
+
77
+ | Track ID | Name | Type | Priority | Status | Depends On |
78
+ |---|---|---|---|---|---|
79
+
80
+ ## Next (promote only when the backlog items are \`locked\`)
81
+
82
+ | Track ID | Backlog IDs | Name |
83
+ |---|---|---|
84
+ `;
85
+ const DECISION_LOG = `# Decision Log
86
+
87
+ Decisions that changed the shape of the system, newest last. Product decisions (what, why,
88
+ on what conditions) belong in the register, not here — this file is for the *how*.
89
+
90
+ <!-- One entry per decision, in this shape:
91
+
92
+ ### DECISION-001: <one-line title>
93
+ - **Date**: YYYY-MM-DD · **Track**: <track_id>
94
+ - **Decision**: what was decided, in the present tense.
95
+ - **Alternatives**: what was rejected, and why.
96
+ -->
97
+ `;
98
+ const PATTERNS = `# Patterns
99
+
100
+ Durable lessons, one \`##\` section per pattern, each naming the track that produced it.
101
+ A pattern earns a place here when it would change how the next track is built.
102
+ `;
103
+ const ERRORS_JSON = `[]
104
+ `;
105
+ /**
106
+ * The scaffold, in write order. `knowledge/errors.json` is in the set because the shipped
107
+ * conductor layout has it and the Evaluate-Loop appends to it; it is one empty array.
108
+ */
109
+ export const CONDUCTOR_TEMPLATES = [
110
+ { path: 'index.md', body: INDEX },
111
+ { path: 'tracks.md', body: TRACKS },
112
+ { path: 'decision-log.md', body: DECISION_LOG },
113
+ { path: 'workflow.md', body: WORKFLOW },
114
+ { path: 'knowledge/patterns.md', body: PATTERNS },
115
+ { path: 'knowledge/errors.json', body: ERRORS_JSON },
116
+ { path: 'tracks/.gitkeep', body: '' },
117
+ ];
118
+ export const START_MARKER = '<!-- backlog:start -->';
119
+ export const END_MARKER = '<!-- backlog:end -->';
120
+ /** An older, hand-written section. `init` recognises it and keeps its hands off. */
121
+ export const LEGACY_HEADING = '## Backlog → Conductor';
122
+ /** The marker-delimited block, markers included, ending in a newline. */
123
+ export function claudeSection(ctx) {
124
+ return `${START_MARKER}
125
+ ${LEGACY_HEADING}
126
+
127
+ The product register for this repo is the backlog service at ${ctx.url}, project
128
+ **\`${ctx.project}\`** — it owns what / why / on what conditions. \`${ctx.conductorDir}/\` owns how / now.
129
+ The only coupling is the MCP tool surface.
130
+
131
+ - Nothing reaches a track until the item's criteria are **locked** in the register.
132
+ - \`backlog next\` lists what is ready; \`backlog promote <ID…>\` writes
133
+ \`${ctx.conductorDir}/tracks/<slug>/{spec.md,plan.md,metadata.json}\` from the register's payload.
134
+ - \`backlog report <track> <step> <status>\` records Evaluate-Loop progress; \`backlog sync <track>\`
135
+ re-renders \`spec.md\` when criteria move; \`backlog ship <track>\` writes \`shipped_commit\` and
136
+ \`production_date\` back.
137
+ - **Locked means immutable.** Suggest a change against the item; never edit the copy in \`spec.md\`.
138
+ - The PAT lives in \`$BACKLOG_TOKEN\` or \`~/.config/backlog/token\` (mode 600). \`.mcp.json\`
139
+ references \`${TOKEN_PLACEHOLDER}\` and never a token value.
140
+ - \`backlog doctor\` is the first thing to run when any of this looks wrong.
141
+ ${END_MARKER}
142
+ `;
143
+ }
144
+ /** Kept out of git by `init`: the MCP file, the offline cache, and the import snapshot. */
145
+ export const GITIGNORE_ENTRIES = ['.mcp.json', '.backlog/', 'backlog-snapshot.json'];
146
+ export const GITIGNORE_HEADER = '# backlog';
147
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/init/templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,kCAAkC,CAAC;AAC9D,MAAM,CAAC,MAAM,qBAAqB,GAAG,WAAW,CAAC;AAEjD,0FAA0F;AAC1F,MAAM,CAAC,MAAM,cAAc,GAAG,SAAS,CAAC;AAExC,6EAA6E;AAC7E,MAAM,CAAC,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAQpD,6EAA6E;AAC7E,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,GAAG,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM;QACrC,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,iBAAiB,EAAE,EAAE;KAC1D,CAAC;AACJ,CAAC;AAQD,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiChB,CAAC;AAEF,MAAM,KAAK,GAAG;;;;;;;;;;;;;;CAcb,CAAC;AAEF,MAAM,MAAM,GAAG;;;;;;;;;;;CAWd,CAAC;AAEF,MAAM,YAAY,GAAG;;;;;;;;;;;;CAYpB,CAAC;AAEF,MAAM,QAAQ,GAAG;;;;CAIhB,CAAC;AAEF,MAAM,WAAW,GAAG;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAiC;IAC/D,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE;IACjC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE;IACnC,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,YAAY,EAAE;IAC/C,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE;IACvC,EAAE,IAAI,EAAE,uBAAuB,EAAE,IAAI,EAAE,QAAQ,EAAE;IACjD,EAAE,IAAI,EAAE,uBAAuB,EAAE,IAAI,EAAE,WAAW,EAAE;IACpD,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,EAAE;CACtC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,wBAAwB,CAAC;AACrD,MAAM,CAAC,MAAM,UAAU,GAAG,sBAAsB,CAAC;AAEjD,oFAAoF;AACpF,MAAM,CAAC,MAAM,cAAc,GAAG,wBAAwB,CAAC;AAQvD,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,GAAyB;IACrD,OAAO,GAAG,YAAY;EACtB,cAAc;;+DAE+C,GAAG,CAAC,GAAG;MAChE,GAAG,CAAC,OAAO,qDAAqD,GAAG,CAAC,YAAY;;;;;MAKhF,GAAG,CAAC,YAAY;;;;;;iBAML,iBAAiB;;EAEhC,UAAU;CACX,CAAC;AACF,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,uBAAuB,CAAU,CAAC;AAC9F,MAAM,CAAC,MAAM,gBAAgB,GAAG,WAAW,CAAC"}
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
+ "name": "backlog-execution",
4
+ "version": "0.1.0",
5
+ "description": "Talk to the Felenova backlog register from a repo: read what is ready, file and review drafts, and run a feature from promotion to ship without ever hand-editing docs/backlog/.",
6
+ "author": {
7
+ "name": "Felenova"
8
+ },
9
+ "homepage": "https://felenova-backlog.fly.dev",
10
+ "keywords": ["backlog", "product-register", "conductor", "qa-matrix"]
11
+ }
@@ -0,0 +1,117 @@
1
+ # @felenova/backlog-plugin
2
+
3
+ The `backlog-execution` Claude Code plugin: the repo-side agent surface of the backlog
4
+ register (PRODUCT-SPEC §7). `npx felenova-backlog init` installs this payload into a repo;
5
+ the same directory is a valid standalone plugin, so it also works over `--plugin-dir` or from
6
+ a marketplace.
7
+
8
+ ```
9
+ packages/plugin/
10
+ ├── .claude-plugin/plugin.json # the manifest — name `backlog-execution`
11
+ ├── skills/
12
+ │ ├── backlog-query/SKILL.md # read: status, next, get_item — the natural-language door
13
+ │ ├── backlog-management/SKILL.md # write: file, edit drafts, review, comment, suggest
14
+ │ └── feature-kickoff/SKILL.md # the loop: promote → plan → matrix init → report → ship
15
+ └── commands/
16
+ ├── next.md promote.md
17
+ └── report.md ship.md # thin wrappers; each shells one CLI subcommand
18
+ ```
19
+
20
+ ## What it is, and the two routes in
21
+
22
+ The payload is **markdown only** — three skills and four commands, no code, no credential. It
23
+ teaches an agent the register's vocabulary: which tool or CLI subcommand does what, the role each
24
+ one needs, and the refusal codes it can hit (every role word and every code in these files is
25
+ checked against `packages/core`'s `ROLES` and the CLI's `ERROR_CODES` by
26
+ `packages/cli/src/init/plugin.test.ts`, so a fabricated one fails the build).
27
+
28
+ 1. **Via `init`** — `npx felenova-backlog init` copies these files into the repo at
29
+ `.claude/skills/<name>/SKILL.md` and `.claude/commands/backlog/<name>.md`, create-if-missing.
30
+ An existing file that differs is kept and named (`--force-plugin` replaces it).
31
+ 2. **As a standalone plugin** — `claude --plugin-dir packages/plugin`, or a marketplace install.
32
+ Same bytes, different command prefix (see the table below).
33
+
34
+ ### The skills and commands, and the role each needs
35
+
36
+ | Surface | What it is for | Role required |
37
+ |---|---|---|
38
+ | skill **backlog-query** | the natural-language door: status, what is ready, read an item | Viewer+ |
39
+ | skill **backlog-management** | file and co-author drafts, review criteria, comment, suggest a change to locked criteria | Engineer or QA+ to write, Product+ to lock, review or resolve |
40
+ | skill **feature-kickoff** | the loop: promote → plan → matrix init → report → ship, with both human checkpoints | per step — promote Engineer+, matrix init QA/Engineer+, report Engineer+, ship Product+ |
41
+ | `/backlog:next` | what is ready to work on | Viewer+ |
42
+ | `/backlog:promote` | locked items → a conductor track | Engineer+ (QA and Viewer refused) |
43
+ | `/backlog:report` | one Evaluate-Loop step/status | Engineer+ |
44
+ | `/backlog:ship` | close a track, write back commit + date | Product+ |
45
+
46
+ Each command shells exactly one CLI subcommand, so there is one implementation of every rule and
47
+ the role is enforced by the service, not by the prose.
48
+
49
+ ### No token ever goes in a file here
50
+
51
+ Nothing in this package contains, or may contain, a credential. The MCP entry references the
52
+ literal `${BACKLOG_TOKEN}`; the PAT lives in the environment or at `~/.config/backlog/token`
53
+ (mode 600), outside any repo. `packages/cli/src/init/plugin.test.ts` greps every payload file for
54
+ the three token shapes, before and after installation, and `backlog doctor`'s `no_literal_token`
55
+ check scans the git-tracked tree as the backstop.
56
+
57
+ PRODUCT-SPEC §7.1 listed `/backlog:status` among the commands; this payload ships
58
+ `/backlog:report` and `/backlog:ship` in its place, per the Task 5 brief. `backlog status` is
59
+ still one CLI command away, and **backlog-query** is the skill that answers status questions in
60
+ words.
61
+
62
+ ## The verified layout (Claude Code 2.1.269)
63
+
64
+ Two routes, and they name the commands differently — both were checked, not assumed:
65
+
66
+ | Route | How it loads | Command names |
67
+ |---|---|---|
68
+ | **plugin** (`claude --plugin-dir packages/plugin`, or a marketplace install) | `.claude-plugin/plugin.json` + `skills/` + flat `commands/` | `/backlog-execution:next`, `/backlog-execution:backlog-query`, … — the prefix is `plugin.json`'s `name` |
69
+ | **repo install** (what `backlog init` does) | `.claude/skills/<name>/SKILL.md` and `.claude/commands/backlog/<name>.md` | `/backlog:next`, `/backlog:promote`, `/backlog:report`, `/backlog:ship`, and the three skills by name — the names §7.2 documents |
70
+
71
+ `init` uses the second route because a plugin directory placed inside a project is **not**
72
+ auto-loaded — neither `.claude/plugins/<name>/` nor a project `.claude/skills/<name>/` with a
73
+ manifest in it. Only `~/.claude/skills/<name>/` auto-loads as a plugin, and that is the user's
74
+ home, not a repo. Verification: `claude plugin validate packages/plugin --strict` passes, and a
75
+ headless session's `init` message lists the slash commands each layout produces.
76
+
77
+ ## The MCP server entry is not here
78
+
79
+ There is deliberately no `.mcp.json` in this payload. The `backlog` server key, its
80
+ `${BACKLOG_TOKEN}` header and the entry shape live in
81
+ `packages/cli/src/init/templates.ts` (`MCP_SERVER_KEY`, `mcpServerEntry`), and
82
+ `packages/cli/src/init/mcp.ts` merges that one entry into the repo's own `.mcp.json` without
83
+ touching anybody else's server. A copy here would be a second thing to keep in step — and a
84
+ second place a token could be pasted.
85
+
86
+ ## Connect by hand
87
+
88
+ Point any MCP client at `https://felenova-backlog.fly.dev/mcp` (Streamable HTTP, POST only).
89
+ Copy the repo-root template and fill in a token from the environment — never commit the real
90
+ file (`.mcp.json` is gitignored):
91
+
92
+ ```bash
93
+ cp .mcp.json.example .mcp.json
94
+ ```
95
+
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "backlog": {
100
+ "type": "http",
101
+ "url": "https://felenova-backlog.fly.dev/mcp",
102
+ "headers": {
103
+ "Authorization": "Bearer ${BACKLOG_TOKEN}"
104
+ }
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ Mint the `${BACKLOG_TOKEN}` yourself: sign in to the web UI, go to `/me`, and use "Mint a
111
+ token" (which posts to `POST /v1/tokens`). The value is shown once — save it wherever your MCP
112
+ client reads `${BACKLOG_TOKEN}` from, or at `~/.config/backlog/token` (mode 600). A session
113
+ cookie is not accepted on `/mcp`; only a PAT works there. **No file in this package may ever
114
+ contain a token value**; `packages/cli/src/init/plugin.test.ts` greps for that.
115
+
116
+ `X-Backlog-Client` / `X-Backlog-Actor` are REST-only claims. On `/mcp` the transport stamps
117
+ `via=mcp` / `actor_kind=agent` itself, so those two headers have no effect over MCP.
@@ -0,0 +1,28 @@
1
+ ---
2
+ description: What is ready to work on — locked, unblocked, unpromoted items, ranked by priority.
3
+ argument-hint: "[--json]"
4
+ allowed-tools: Bash(backlog next:*)
5
+ ---
6
+
7
+ # /backlog:next
8
+
9
+ **Role required: Viewer or above.** Reading the register is allowed for every role
10
+ (PRODUCT-SPEC §3.1); nothing here can be refused for being too small a role.
11
+
12
+ Run exactly this, from the repo root, and show the output:
13
+
14
+ ```bash
15
+ backlog next $ARGUMENTS
16
+ ```
17
+
18
+ It calls `next_ready` for the project in `backlog.config.json`. One implementation: the CLI.
19
+ Do not reimplement it with an MCP call, and do not fall back to reading `docs/backlog/**`.
20
+
21
+ ## Refusals
22
+
23
+ `no_session` (no PAT reached the service — run `backlog doctor`), `token_unknown` /
24
+ `token_revoked` / `token_expired` (mint a new PAT in the web UI `/me`, then `backlog init`),
25
+ `no_member`, `not_found` (no role on the configured project), `rate_limited` (120/min),
26
+ `unreachable` (the service is down — `backlog status` prints the cached view).
27
+
28
+ The CLI prints one human sentence per refusal. Relay it; never print a token.
@@ -0,0 +1,30 @@
1
+ ---
2
+ description: Promote locked backlog items into a conductor track and write spec.md, plan.md and metadata.json.
3
+ argument-hint: "<ID…> [--slug <slug>] [--type feature|fix|chore]"
4
+ allowed-tools: Bash(backlog promote:*)
5
+ ---
6
+
7
+ # /backlog:promote
8
+
9
+ **Role required: Engineer, Product or Owner.** QA and Viewer are refused (PRODUCT-SPEC §3.1,
10
+ "Promote locked item → track").
11
+
12
+ Run exactly this, from the repo root, and show the output:
13
+
14
+ ```bash
15
+ backlog promote $ARGUMENTS
16
+ ```
17
+
18
+ It calls `promote_items`, then writes `conductor/tracks/<slug>/{spec.md,plan.md,metadata.json}`
19
+ from the payload the service returned and updates `conductor/tracks.md`. `spec.md` is
20
+ generated from the register's locked criteria — never hand-edit it.
21
+
22
+ Nothing is promoted until every named item's criteria are **locked**. If they are not, stop
23
+ and ask a Product role to lock them.
24
+
25
+ ## Refusals
26
+
27
+ `not_locked` (an item is not locked — the whole call is refused, nothing is written),
28
+ `forbidden` (the role is short — say so and stop), `conflict` (the slug is taken, or an item
29
+ is already promoted), `not_found` (unknown ref, or no role on the project),
30
+ `invalid_request` (bad arguments), `unreachable`.