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
package/dist/config.js ADDED
@@ -0,0 +1,137 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ /** The committed, token-free project file (DECISION-026). */
4
+ export const CONFIG_FILENAME = 'backlog.config.json';
5
+ /** A refusal about the config file itself: always names the file and the offending key. */
6
+ export class ConfigError extends Error {
7
+ path;
8
+ constructor(path, message) {
9
+ super(message);
10
+ this.name = 'ConfigError';
11
+ this.path = path;
12
+ }
13
+ }
14
+ const REQUIRED_KEYS = ['url', 'project', 'conductor_dir'];
15
+ /**
16
+ * Anything that could hold a credential is a hard error, not a warning: this file is
17
+ * committed, and `no secret in a tracked file` is a hard rule. The name alone is enough —
18
+ * we never look at the value, so nothing here can echo one.
19
+ */
20
+ const CREDENTIAL_KEY = /token|secret|key|password|credential/i;
21
+ /** Walks up from `cwd` looking for `backlog.config.json`. Returns its path, or null. */
22
+ export function findConfig(cwd) {
23
+ let dir = resolve(cwd);
24
+ for (;;) {
25
+ const candidate = join(dir, CONFIG_FILENAME);
26
+ if (existsSync(candidate))
27
+ return candidate;
28
+ const parent = dirname(dir);
29
+ if (parent === dir)
30
+ return null;
31
+ dir = parent;
32
+ }
33
+ }
34
+ function validate(path, raw) {
35
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
36
+ throw new ConfigError(path, `${CONFIG_FILENAME} must contain a JSON object`);
37
+ }
38
+ const doc = raw;
39
+ for (const key of Object.keys(doc)) {
40
+ if (CREDENTIAL_KEY.test(key)) {
41
+ throw new ConfigError(path, `${CONFIG_FILENAME} has a key that could hold a credential: "${key}" — this file is committed and must never carry a token; keep the PAT in ~/.config/backlog/token or $BACKLOG_TOKEN`);
42
+ }
43
+ if (!REQUIRED_KEYS.includes(key)) {
44
+ throw new ConfigError(path, `${CONFIG_FILENAME} has an unknown key: "${key}" — only ${REQUIRED_KEYS.join(', ')} are allowed`);
45
+ }
46
+ }
47
+ for (const key of REQUIRED_KEYS) {
48
+ const value = doc[key];
49
+ if (value === undefined) {
50
+ throw new ConfigError(path, `${CONFIG_FILENAME} is missing "${key}"`);
51
+ }
52
+ if (typeof value !== 'string') {
53
+ throw new ConfigError(path, `${CONFIG_FILENAME} key "${key}" must be a string`);
54
+ }
55
+ if (value.trim() === '') {
56
+ throw new ConfigError(path, `${CONFIG_FILENAME} key "${key}" must not be empty`);
57
+ }
58
+ }
59
+ const url = doc.url.trim();
60
+ let parsed;
61
+ try {
62
+ parsed = new URL(url);
63
+ }
64
+ catch {
65
+ throw new ConfigError(path, `${CONFIG_FILENAME} key "url" is not a URL: ${url}`);
66
+ }
67
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
68
+ throw new ConfigError(path, `${CONFIG_FILENAME} key "url" must be http(s), not ${parsed.protocol}`);
69
+ }
70
+ return {
71
+ url: url.replace(/\/+$/, ''),
72
+ project: doc.project.trim(),
73
+ conductor_dir: doc.conductor_dir.trim(),
74
+ };
75
+ }
76
+ /** Reads and validates one config file. Throws `ConfigError` on anything but the exact shape. */
77
+ export function readConfig(path) {
78
+ let text;
79
+ try {
80
+ text = readFileSync(path, 'utf8');
81
+ }
82
+ catch {
83
+ throw new ConfigError(path, `cannot read ${CONFIG_FILENAME} at ${path}`);
84
+ }
85
+ let raw;
86
+ try {
87
+ raw = JSON.parse(text);
88
+ }
89
+ catch {
90
+ throw new ConfigError(path, `${CONFIG_FILENAME} at ${path} is not valid JSON`);
91
+ }
92
+ return validate(path, raw);
93
+ }
94
+ /** Walk-up + read. Null when no config exists above `cwd`; throws when one exists but is bad. */
95
+ export function loadConfig(cwd) {
96
+ const path = findConfig(cwd);
97
+ if (!path)
98
+ return null;
99
+ return { config: readConfig(path), path };
100
+ }
101
+ /** Writes the three keys, validated first so a bad object can never reach a tracked file. */
102
+ export function writeConfig(path, config) {
103
+ const clean = validate(path, config);
104
+ const body = {
105
+ url: clean.url,
106
+ project: clean.project,
107
+ conductor_dir: clean.conductor_dir,
108
+ };
109
+ writeFileSync(path, `${JSON.stringify(body, null, 2)}\n`, 'utf8');
110
+ }
111
+ /**
112
+ * Precedence for every command: `--url`/`--project` → `BACKLOG_URL`/`BACKLOG_PROJECT` →
113
+ * the config file → a refusal pointing at `backlog init`. Resolved per field, so a flag
114
+ * can name a project while the URL still comes from the committed file.
115
+ */
116
+ export function resolveTarget(flags, env, cwd) {
117
+ const loaded = loadConfig(cwd);
118
+ const project = flags.project?.trim() || env.BACKLOG_PROJECT?.trim() || loaded?.config.project;
119
+ const url = flags.url?.trim() || env.BACKLOG_URL?.trim() || loaded?.config.url;
120
+ if (!project)
121
+ throw new Error('no project configured — run: backlog init');
122
+ if (!url)
123
+ throw new Error('no service URL configured — run: backlog init');
124
+ const source = flags.project?.trim()
125
+ ? 'flag'
126
+ : env.BACKLOG_PROJECT?.trim()
127
+ ? 'env'
128
+ : 'config';
129
+ return {
130
+ url: url.replace(/\/+$/, ''),
131
+ project,
132
+ conductor_dir: loaded?.config.conductor_dir ?? 'conductor',
133
+ source,
134
+ configPath: loaded?.path ?? null,
135
+ };
136
+ }
137
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ export interface DoctorCheck {
2
+ name: string;
3
+ ok: boolean;
4
+ hint?: string;
5
+ }
6
+ export interface DoctorReport {
7
+ ok: boolean;
8
+ checks: DoctorCheck[];
9
+ }
10
+ /**
11
+ * Every file `init` installs for the plugin, relative to the repo root. The list is the
12
+ * payload's own — one source of truth, so a file added to the payload is a file the doctor
13
+ * starts checking for.
14
+ */
15
+ export declare const PLUGIN_INSTALL_TARGETS: readonly string[];
16
+ /** Local-only checks: the token store, the tracked-file scan, and the four local artifacts. */
17
+ export declare function runDoctor(env: NodeJS.ProcessEnv, opts?: {
18
+ cwd?: string;
19
+ home?: string;
20
+ }): DoctorReport;
21
+ /** Local checks plus the remote `project_visible` check. Service reachability comes from here. */
22
+ export declare function runDoctorRemote(env: NodeJS.ProcessEnv, opts?: {
23
+ cwd?: string;
24
+ home?: string;
25
+ url?: string;
26
+ project?: string;
27
+ fetchImpl?: typeof fetch;
28
+ }): Promise<DoctorReport>;
package/dist/doctor.js ADDED
@@ -0,0 +1,377 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { existsSync, readFileSync, statSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { ApiError } from './api.js';
6
+ import { CONFIG_FILENAME, ConfigError, loadConfig } from './config.js';
7
+ import { explain } from './errors.js';
8
+ import { PLUGIN_NAME, PLUGIN_PAYLOAD } from './init/plugin.js';
9
+ import { MCP_SERVER_KEY, TOKEN_PLACEHOLDER as TEMPLATE_TOKEN_PLACEHOLDER, } from './init/templates.js';
10
+ const MAX_SCAN_BYTES = 2 * 1024 * 1024;
11
+ // Patterns are inert regex source, never live-looking literals, so this file itself
12
+ // stays clean under the same scan it implements (and under gitleaks' backlog-pat rule).
13
+ const LITERAL_TOKEN_PATTERNS = [/bkl_[A-Za-z0-9]{40}/, /sb_secret_[A-Za-z0-9_-]{20,}/];
14
+ /** `~/.config/backlog/token` presence + permissions, when no BACKLOG_TOKEN is set in the env. */
15
+ function checkTokenFile(env, home) {
16
+ const path = join(home, '.config', 'backlog', 'token');
17
+ if (process.platform === 'win32') {
18
+ return {
19
+ name: 'token_file',
20
+ ok: true,
21
+ hint: `POSIX file-mode checks are skipped on ${process.platform}; token file would be ${path}`,
22
+ };
23
+ }
24
+ if (env.BACKLOG_TOKEN) {
25
+ return {
26
+ name: 'token_file',
27
+ ok: true,
28
+ hint: `BACKLOG_TOKEN is set from the environment; ${path} is not required`,
29
+ };
30
+ }
31
+ let stat;
32
+ try {
33
+ stat = statSync(path);
34
+ }
35
+ catch {
36
+ return {
37
+ name: 'token_file',
38
+ ok: false,
39
+ hint: `no BACKLOG_TOKEN and ${path} does not exist — set BACKLOG_TOKEN or write your PAT there (chmod 600)`,
40
+ };
41
+ }
42
+ const mode = stat.mode & 0o777;
43
+ if (mode !== 0o600) {
44
+ return {
45
+ name: 'token_file',
46
+ ok: false,
47
+ hint: `${path} must be mode 600 (found ${mode.toString(8)}) — run: chmod 600 ${path}`,
48
+ };
49
+ }
50
+ return { name: 'token_file', ok: true };
51
+ }
52
+ /** Scans every git-tracked file in `cwd` for a literal `bkl_…` or `sb_secret_…` pattern. */
53
+ function checkNoLiteralToken(cwd) {
54
+ let files;
55
+ try {
56
+ const out = execFileSync('git', ['-C', cwd, 'ls-files', '-z'], {
57
+ encoding: 'utf8',
58
+ stdio: ['ignore', 'pipe', 'ignore'],
59
+ });
60
+ files = out.split('\0').filter(Boolean);
61
+ }
62
+ catch {
63
+ return {
64
+ name: 'no_literal_token',
65
+ ok: true,
66
+ hint: `${cwd} is not a git repository — skipped`,
67
+ };
68
+ }
69
+ const findings = [];
70
+ for (const file of files) {
71
+ const abs = join(cwd, file);
72
+ let stat;
73
+ try {
74
+ stat = statSync(abs);
75
+ }
76
+ catch {
77
+ continue; // tracked but missing on disk (e.g. a submodule pointer)
78
+ }
79
+ if (!stat.isFile() || stat.size > MAX_SCAN_BYTES)
80
+ continue;
81
+ let content;
82
+ try {
83
+ content = readFileSync(abs, 'utf8');
84
+ }
85
+ catch {
86
+ continue; // unreadable/binary
87
+ }
88
+ const lines = content.split('\n');
89
+ for (let i = 0; i < lines.length; i++) {
90
+ if (LITERAL_TOKEN_PATTERNS.some((pattern) => pattern.test(lines[i]))) {
91
+ findings.push(`${file}:${i + 1}`);
92
+ }
93
+ }
94
+ }
95
+ if (findings.length > 0) {
96
+ return {
97
+ name: 'no_literal_token',
98
+ ok: false,
99
+ hint: `literal token pattern found in: ${findings.join(', ')}`,
100
+ };
101
+ }
102
+ return { name: 'no_literal_token', ok: true };
103
+ }
104
+ /**
105
+ * Every file `init` installs for the plugin, relative to the repo root. The list is the
106
+ * payload's own — one source of truth, so a file added to the payload is a file the doctor
107
+ * starts checking for.
108
+ */
109
+ export const PLUGIN_INSTALL_TARGETS = PLUGIN_PAYLOAD.map((f) => f.target);
110
+ /** The three files a usable `conductor/` always has (PRODUCT-SPEC §7.3). */
111
+ const CONDUCTOR_FILES = ['workflow.md', 'tracks.md', 'index.md'];
112
+ /** The literal `.mcp.json` must carry — never a token value. `templates.ts` owns it. */
113
+ const TOKEN_PLACEHOLDER = TEMPLATE_TOKEN_PLACEHOLDER;
114
+ /** One walk-up per doctor run, shared by the `config`, `conductor_dir` and remote checks. */
115
+ function resolveConfig(cwd) {
116
+ try {
117
+ const loaded = loadConfig(cwd);
118
+ if (!loaded)
119
+ return { config: null, path: null, error: null };
120
+ return { config: loaded.config, path: loaded.path, error: null };
121
+ }
122
+ catch (err) {
123
+ if (err instanceof ConfigError)
124
+ return { config: null, path: err.path, error: err.message };
125
+ return {
126
+ config: null,
127
+ path: null,
128
+ error: err instanceof Error ? err.message : String(err),
129
+ };
130
+ }
131
+ }
132
+ /** `backlog.config.json` found, valid, and what it says. */
133
+ function checkConfig(resolved) {
134
+ if (resolved.error) {
135
+ return { name: 'config', ok: false, hint: resolved.error };
136
+ }
137
+ if (!resolved.config || !resolved.path) {
138
+ return {
139
+ name: 'config',
140
+ ok: false,
141
+ hint: `no ${CONFIG_FILENAME} found here or in any parent directory — run: backlog init`,
142
+ };
143
+ }
144
+ return {
145
+ name: 'config',
146
+ ok: true,
147
+ hint: `${resolved.path}: url ${resolved.config.url}, project ${resolved.config.project}`,
148
+ };
149
+ }
150
+ /** The conductor directory: present with its scaffold, or not there yet. */
151
+ function checkConductorDir(cwd, resolved) {
152
+ const rel = resolved.config?.conductor_dir ?? 'conductor';
153
+ const dir = join(cwd, rel);
154
+ if (!existsSync(dir)) {
155
+ return {
156
+ name: 'conductor_dir',
157
+ ok: true,
158
+ hint: `${rel}/ does not exist — it will be created by init`,
159
+ };
160
+ }
161
+ const missing = CONDUCTOR_FILES.filter((f) => !existsSync(join(dir, f)));
162
+ if (missing.length > 0) {
163
+ return {
164
+ name: 'conductor_dir',
165
+ ok: false,
166
+ hint: `${rel}/ exists but is missing ${missing.join(', ')} — run: backlog init (it adds only the files that are absent)`,
167
+ };
168
+ }
169
+ return { name: 'conductor_dir', ok: true, hint: `${rel}/ has ${CONDUCTOR_FILES.join(', ')}` };
170
+ }
171
+ /** `.mcp.json` references the placeholder and carries no token value. */
172
+ function checkMcpEntry(cwd) {
173
+ const path = join(cwd, '.mcp.json');
174
+ let text;
175
+ try {
176
+ text = readFileSync(path, 'utf8');
177
+ }
178
+ catch {
179
+ return {
180
+ name: 'mcp_entry',
181
+ ok: false,
182
+ hint: `no .mcp.json in ${cwd} — run: backlog init (it writes the ${MCP_SERVER_KEY} server entry referencing ${TOKEN_PLACEHOLDER})`,
183
+ };
184
+ }
185
+ let doc;
186
+ try {
187
+ doc = JSON.parse(text);
188
+ }
189
+ catch {
190
+ return { name: 'mcp_entry', ok: false, hint: `.mcp.json is not valid JSON` };
191
+ }
192
+ const servers = doc?.mcpServers;
193
+ if (typeof servers !== 'object' || servers === null) {
194
+ return { name: 'mcp_entry', ok: false, hint: '.mcp.json has no mcpServers object' };
195
+ }
196
+ const entry = servers[MCP_SERVER_KEY];
197
+ if (entry === undefined) {
198
+ return {
199
+ name: 'mcp_entry',
200
+ ok: false,
201
+ hint: `.mcp.json has no "${MCP_SERVER_KEY}" server entry — run: backlog init`,
202
+ };
203
+ }
204
+ const serialised = JSON.stringify(entry);
205
+ if (LITERAL_TOKEN_PATTERNS.some((pattern) => pattern.test(serialised))) {
206
+ return {
207
+ name: 'mcp_entry',
208
+ ok: false,
209
+ hint: `.mcp.json's ${MCP_SERVER_KEY} entry carries a literal token — replace it with ${TOKEN_PLACEHOLDER} and revoke that PAT`,
210
+ };
211
+ }
212
+ if (!serialised.includes(TOKEN_PLACEHOLDER)) {
213
+ return {
214
+ name: 'mcp_entry',
215
+ ok: false,
216
+ hint: `.mcp.json's ${MCP_SERVER_KEY} entry does not reference ${TOKEN_PLACEHOLDER} — the token must come from the environment, never the file`,
217
+ };
218
+ }
219
+ return { name: 'mcp_entry', ok: true, hint: `.mcp.json references ${TOKEN_PLACEHOLDER}` };
220
+ }
221
+ /**
222
+ * The plugin payload, file by file. The skills live in `.claude/skills/` and the commands in
223
+ * `.claude/commands/backlog/` because that is the only layout a repo-installed payload
224
+ * actually loads from (see `init/plugin.ts` for what was verified and how).
225
+ */
226
+ function checkPluginInstalled(cwd) {
227
+ const missing = PLUGIN_INSTALL_TARGETS.filter((rel) => !existsSync(join(cwd, rel)));
228
+ if (missing.length > 0) {
229
+ return {
230
+ name: 'plugin_installed',
231
+ ok: false,
232
+ hint: `missing ${missing.join(', ')} — run: backlog init to install the ${PLUGIN_NAME} plugin`,
233
+ };
234
+ }
235
+ return {
236
+ name: 'plugin_installed',
237
+ ok: true,
238
+ hint: `${PLUGIN_NAME}: ${PLUGIN_INSTALL_TARGETS.length} skill and command files installed`,
239
+ };
240
+ }
241
+ /** Local-only checks: the token store, the tracked-file scan, and the four local artifacts. */
242
+ export function runDoctor(env, opts = {}) {
243
+ const cwd = opts.cwd ?? process.cwd();
244
+ const home = opts.home ?? homedir();
245
+ const resolved = resolveConfig(cwd);
246
+ const checks = [
247
+ {
248
+ name: 'token',
249
+ ok: Boolean(env.BACKLOG_TOKEN),
250
+ hint: 'set BACKLOG_TOKEN (PAT from the web UI; stored at ~/.config/backlog/token)',
251
+ },
252
+ checkTokenFile(env, home),
253
+ checkNoLiteralToken(cwd),
254
+ checkConfig(resolved),
255
+ checkConductorDir(cwd, resolved),
256
+ checkMcpEntry(cwd),
257
+ checkPluginInstalled(cwd),
258
+ ];
259
+ return { ok: checks.every((c) => c.ok), checks };
260
+ }
261
+ /**
262
+ * The configured project/URL come from `--project`/`--url`, then
263
+ * `BACKLOG_PROJECT`/`BACKLOG_URL`, then `backlog.config.json` (PLG-001) — the same
264
+ * precedence every command uses (`config.resolveTarget`). The flag still wins.
265
+ *
266
+ * The token stays env-only here on purpose: reading `~/.config/backlog/token` from a
267
+ * diagnostic would make `doctor` touch a live credential it does not need. `token_file`
268
+ * already reports on that file.
269
+ */
270
+ async function checkProjectVisible(env, opts) {
271
+ const project = opts.project ?? env.BACKLOG_PROJECT ?? opts.config?.project;
272
+ if (!project) {
273
+ return {
274
+ name: 'project_visible',
275
+ ok: true,
276
+ hint: 'no project configured (set BACKLOG_PROJECT or pass --project); skipping the visibility check',
277
+ };
278
+ }
279
+ const token = env.BACKLOG_TOKEN;
280
+ const url = opts.url ?? env.BACKLOG_URL ?? opts.config?.url;
281
+ if (!token && !url) {
282
+ return {
283
+ name: 'project_visible',
284
+ ok: true,
285
+ hint: 'cannot check project visibility: BACKLOG_TOKEN and BACKLOG_URL (or --url) are not set',
286
+ };
287
+ }
288
+ if (!token) {
289
+ return {
290
+ name: 'project_visible',
291
+ ok: true,
292
+ hint: 'cannot check project visibility: BACKLOG_TOKEN is not set',
293
+ };
294
+ }
295
+ if (!url) {
296
+ return {
297
+ name: 'project_visible',
298
+ ok: true,
299
+ hint: 'cannot check project visibility: BACKLOG_URL (or --url) is not set',
300
+ };
301
+ }
302
+ const fetchImpl = opts.fetchImpl ?? fetch;
303
+ let response;
304
+ try {
305
+ response = await fetchImpl(`${url}/v1/projects`, {
306
+ headers: { Authorization: `Bearer ${token}`, accept: 'application/json' },
307
+ });
308
+ }
309
+ catch (err) {
310
+ const message = err instanceof Error ? err.message : String(err);
311
+ return { name: 'project_visible', ok: false, hint: `could not reach ${url}: ${message}` };
312
+ }
313
+ if (response.status === 401 || response.status === 403) {
314
+ return {
315
+ name: 'project_visible',
316
+ ok: false,
317
+ hint: `the token was rejected by ${url} (HTTP ${response.status}) — mint a new PAT at ${url}/me`,
318
+ };
319
+ }
320
+ if (response.status !== 200) {
321
+ return {
322
+ name: 'project_visible',
323
+ ok: false,
324
+ hint: `${url} answered HTTP ${response.status} for /v1/projects`,
325
+ };
326
+ }
327
+ let body;
328
+ try {
329
+ body = await response.json();
330
+ }
331
+ catch {
332
+ return {
333
+ name: 'project_visible',
334
+ ok: false,
335
+ hint: `${url} returned a malformed response for /v1/projects`,
336
+ };
337
+ }
338
+ if (!Array.isArray(body)) {
339
+ return {
340
+ name: 'project_visible',
341
+ ok: false,
342
+ hint: `${url} returned a malformed response for /v1/projects`,
343
+ };
344
+ }
345
+ const projects = body;
346
+ const match = projects.find((p) => p.slug === project);
347
+ if (match) {
348
+ const roleClause = match.role ? ` as ${match.role}` : '';
349
+ return {
350
+ name: 'project_visible',
351
+ ok: true,
352
+ hint: `project ${project} is visible to this token${roleClause}`,
353
+ };
354
+ }
355
+ const visibleSlugs = projects.map((p) => p.slug).filter((s) => Boolean(s));
356
+ const visibleClause = visibleSlugs.length > 0 ? ` (visible: ${visibleSlugs.join(', ')})` : '';
357
+ // The same sentence every command prints for this case, so `doctor` and `status` agree:
358
+ // the service answers `not_found` for a project the caller has no role on, by design.
359
+ const sentence = explain(new ApiError(404, 'not_found', ''), { project, url });
360
+ return {
361
+ name: 'project_visible',
362
+ ok: false,
363
+ hint: `${sentence}; the token may also be pinned to another project${visibleClause}`,
364
+ };
365
+ }
366
+ /** Local checks plus the remote `project_visible` check. Service reachability comes from here. */
367
+ export async function runDoctorRemote(env, opts = {}) {
368
+ const local = runDoctor(env, opts);
369
+ const cwd = opts.cwd ?? process.cwd();
370
+ const remoteCheck = await checkProjectVisible(env, {
371
+ ...opts,
372
+ config: resolveConfig(cwd).config,
373
+ });
374
+ const checks = [...local.checks, remoteCheck];
375
+ return { ok: checks.every((c) => c.ok), checks };
376
+ }
377
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { ApiError } from './api.js';
2
+ /**
3
+ * One human sentence per refusal — never JSON, never a status code, never a stack.
4
+ * Every code `apps/service` can answer has a row here; `src/errors.test.ts` enumerates the
5
+ * shipped list, so a new service code fails the CLI build until it gets a sentence.
6
+ */
7
+ export interface ErrorContext {
8
+ /** The configured project — what makes a `not_found` a membership problem. */
9
+ project?: string | undefined;
10
+ /** The service URL, for the rows that tell the reader where to go. */
11
+ url?: string | undefined;
12
+ /** The command being run, for the rows that can name it. */
13
+ command?: string | undefined;
14
+ /** An item/track ref, when the command had one. */
15
+ ref?: string | undefined;
16
+ }
17
+ /** Every code with a row, in the order the table below reads. */
18
+ export declare const ERROR_CODES: readonly ["unreachable", "forbidden", "not_found", "locked", "not_locked", "invalid_transition", "reason_required", "invalid_request", "conflict", "superseded", "internal", "no_session", "token_required", "token_unknown", "token_revoked", "token_expired", "no_member", "rate_limited", "read_only_token", "session_required"];
19
+ export type ErrorCode = (typeof ERROR_CODES)[number];
20
+ /** The code → sentence table. Exactly one line, always. */
21
+ export declare function explain(err: ApiError, ctx?: ErrorContext): string;
22
+ /** The plan's name for `explain`. */
23
+ export declare function renderError(err: ApiError, ctx?: ErrorContext): string;