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.
- package/README.md +58 -0
- package/bin/backlog.js +2 -0
- package/dist/_core/criteria.d.ts +13 -0
- package/dist/_core/criteria.js +44 -0
- package/dist/_core/criteria.js.map +1 -0
- package/dist/_core/errors.d.ts +53 -0
- package/dist/_core/errors.js +91 -0
- package/dist/_core/errors.js.map +1 -0
- package/dist/_core/index.d.ts +24 -0
- package/dist/_core/index.js +27 -0
- package/dist/_core/index.js.map +1 -0
- package/dist/_core/loop.d.ts +73 -0
- package/dist/_core/loop.js +46 -0
- package/dist/_core/loop.js.map +1 -0
- package/dist/_core/markdown/items.d.ts +35 -0
- package/dist/_core/markdown/items.js +256 -0
- package/dist/_core/markdown/items.js.map +1 -0
- package/dist/_core/markdown/matrix.d.ts +20 -0
- package/dist/_core/markdown/matrix.js +147 -0
- package/dist/_core/markdown/matrix.js.map +1 -0
- package/dist/_core/markdown/track.d.ts +71 -0
- package/dist/_core/markdown/track.js +79 -0
- package/dist/_core/markdown/track.js.map +1 -0
- package/dist/_core/matrix-status.d.ts +9 -0
- package/dist/_core/matrix-status.js +31 -0
- package/dist/_core/matrix-status.js.map +1 -0
- package/dist/_core/matrix.d.ts +46 -0
- package/dist/_core/matrix.js +0 -0
- package/dist/_core/matrix.js.map +1 -0
- package/dist/_core/permissions.d.ts +14 -0
- package/dist/_core/permissions.js +87 -0
- package/dist/_core/permissions.js.map +1 -0
- package/dist/_core/promote.d.ts +20 -0
- package/dist/_core/promote.js +47 -0
- package/dist/_core/promote.js.map +1 -0
- package/dist/_core/ready.d.ts +8 -0
- package/dist/_core/ready.js +22 -0
- package/dist/_core/ready.js.map +1 -0
- package/dist/_core/review.d.ts +49 -0
- package/dist/_core/review.js +137 -0
- package/dist/_core/review.js.map +1 -0
- package/dist/_core/schema.d.ts +636 -0
- package/dist/_core/schema.js +304 -0
- package/dist/_core/schema.js.map +1 -0
- package/dist/_core/ship.d.ts +23 -0
- package/dist/_core/ship.js +31 -0
- package/dist/_core/ship.js.map +1 -0
- package/dist/_core/spec-diff.d.ts +19 -0
- package/dist/_core/spec-diff.js +25 -0
- package/dist/_core/spec-diff.js.map +1 -0
- package/dist/_core/status.d.ts +7 -0
- package/dist/_core/status.js +25 -0
- package/dist/_core/status.js.map +1 -0
- package/dist/_core/util/deep-equal.d.ts +8 -0
- package/dist/_core/util/deep-equal.js +31 -0
- package/dist/_core/util/deep-equal.js.map +1 -0
- package/dist/api.d.ts +46 -0
- package/dist/api.js +104 -0
- package/dist/api.js.map +1 -0
- package/dist/cache.d.ts +53 -0
- package/dist/cache.js +114 -0
- package/dist/cache.js.map +1 -0
- package/dist/commands/common.d.ts +59 -0
- package/dist/commands/common.js +94 -0
- package/dist/commands/common.js.map +1 -0
- package/dist/commands/export.d.ts +20 -0
- package/dist/commands/export.js +56 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/matrix.d.ts +16 -0
- package/dist/commands/matrix.js +48 -0
- package/dist/commands/matrix.js.map +1 -0
- package/dist/commands/next.d.ts +14 -0
- package/dist/commands/next.js +30 -0
- package/dist/commands/next.js.map +1 -0
- package/dist/commands/promote.d.ts +15 -0
- package/dist/commands/promote.js +172 -0
- package/dist/commands/promote.js.map +1 -0
- package/dist/commands/report.d.ts +18 -0
- package/dist/commands/report.js +45 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/ship.d.ts +15 -0
- package/dist/commands/ship.js +231 -0
- package/dist/commands/ship.js.map +1 -0
- package/dist/commands/status.d.ts +13 -0
- package/dist/commands/status.js +141 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +17 -0
- package/dist/commands/sync.js +90 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/conductor/registry.d.ts +45 -0
- package/dist/conductor/registry.js +164 -0
- package/dist/conductor/registry.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.js +137 -0
- package/dist/config.js.map +1 -0
- package/dist/doctor.d.ts +28 -0
- package/dist/doctor.js +377 -0
- package/dist/doctor.js.map +1 -0
- package/dist/errors.d.ts +23 -0
- package/dist/errors.js +114 -0
- package/dist/errors.js.map +1 -0
- package/dist/export.d.ts +1 -0
- package/dist/export.js +9 -0
- package/dist/export.js.map +1 -0
- package/dist/import.d.ts +3 -0
- package/dist/import.js +89 -0
- package/dist/import.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +183 -0
- package/dist/index.js.map +1 -0
- package/dist/init/auth.d.ts +85 -0
- package/dist/init/auth.js +194 -0
- package/dist/init/auth.js.map +1 -0
- package/dist/init/claudemd.d.ts +16 -0
- package/dist/init/claudemd.js +62 -0
- package/dist/init/claudemd.js.map +1 -0
- package/dist/init/index.d.ts +47 -0
- package/dist/init/index.js +159 -0
- package/dist/init/index.js.map +1 -0
- package/dist/init/mcp.d.ts +18 -0
- package/dist/init/mcp.js +97 -0
- package/dist/init/mcp.js.map +1 -0
- package/dist/init/plugin.d.ts +60 -0
- package/dist/init/plugin.js +120 -0
- package/dist/init/plugin.js.map +1 -0
- package/dist/init/scaffold.d.ts +19 -0
- package/dist/init/scaffold.js +46 -0
- package/dist/init/scaffold.js.map +1 -0
- package/dist/init/templates.d.ts +48 -0
- package/dist/init/templates.js +147 -0
- package/dist/init/templates.js.map +1 -0
- package/dist/plugin/.claude-plugin/plugin.json +11 -0
- package/dist/plugin/README.md +117 -0
- package/dist/plugin/commands/next.md +28 -0
- package/dist/plugin/commands/promote.md +30 -0
- package/dist/plugin/commands/report.md +26 -0
- package/dist/plugin/commands/ship.md +30 -0
- package/dist/plugin/skills/backlog-management/SKILL.md +63 -0
- package/dist/plugin/skills/backlog-query/SKILL.md +57 -0
- package/dist/plugin/skills/feature-kickoff/SKILL.md +112 -0
- package/dist/snapshot.d.ts +24 -0
- package/dist/snapshot.js +2 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/token.d.ts +29 -0
- package/dist/token.js +99 -0
- package/dist/token.js.map +1 -0
- 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":"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"}
|
|
@@ -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`.
|