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,141 @@
1
+ import { ApiError } from '../api.js';
2
+ import { gitignoreCache, readStatusCache, writeStatusCache } from '../cache.js';
3
+ import { asArray, asRecord, jsonResult, pad, plural, renderFailure, resolveContext, text, } from './common.js';
4
+ function roleOf(projects, slug) {
5
+ for (const entry of asArray(projects)) {
6
+ const row = asRecord(entry);
7
+ if (row.slug === slug)
8
+ return typeof row.role === 'string' ? row.role : null;
9
+ }
10
+ return null;
11
+ }
12
+ /** `plugin_20260912 feature EXECUTE/IN_PROGRESS last report <iso> 2 items drift` */
13
+ function trackLine(entry) {
14
+ const detail = asRecord(entry);
15
+ const track = asRecord(detail.track);
16
+ const loop = asRecord(detail.loop);
17
+ const step = text(loop.loop_step, 'no report');
18
+ const status = text(loop.loop_status, '-');
19
+ const cells = [
20
+ pad(text(track.slug), 24),
21
+ pad(text(track.type), 10),
22
+ pad(loop.loop_step ? `${step}/${status}` : step, 24),
23
+ `last report ${text(loop.last_report_at, 'never')}`,
24
+ plural(asArray(track.backlog_ids).length, 'item'),
25
+ ];
26
+ if (track.spec_drift === true)
27
+ cells.push('drift');
28
+ return ` ${cells.join(' ')}`;
29
+ }
30
+ /** `PLG-001 P1 locked plugin_20260912 the command set` */
31
+ function itemLine(entry) {
32
+ const item = asRecord(entry);
33
+ return ` ${pad(text(item.id), 12)}${pad(text(item.priority), 4)}${pad(text(item.criteria_status), 10)}${pad(text(item.track), 22)}${text(item.title, '(untitled)')}`;
34
+ }
35
+ const STATUS_ORDER = ['in-progress', 'promoted', 'scheduled', 'backlog', 'shipped', 'rejected'];
36
+ function render(view) {
37
+ const lines = [];
38
+ lines.push(`project ${view.project} (${view.role ?? 'role unknown'}) — ${view.url}`);
39
+ const tracks = view.tracks;
40
+ if (tracks.length === 0) {
41
+ lines.push('active tracks: none');
42
+ }
43
+ else {
44
+ lines.push(`active tracks (${tracks.length}):`);
45
+ for (const track of tracks)
46
+ lines.push(trackLine(track));
47
+ }
48
+ const items = view.items;
49
+ if (items.length === 0) {
50
+ lines.push('items: none');
51
+ }
52
+ else {
53
+ lines.push('items by status:');
54
+ const groups = new Map();
55
+ for (const item of items) {
56
+ const status = text(asRecord(item).status, 'unknown');
57
+ const bucket = groups.get(status);
58
+ if (bucket)
59
+ bucket.push(item);
60
+ else
61
+ groups.set(status, [item]);
62
+ }
63
+ const names = [...groups.keys()].sort((a, b) => {
64
+ const ia = STATUS_ORDER.indexOf(a);
65
+ const ib = STATUS_ORDER.indexOf(b);
66
+ return (ia === -1 ? STATUS_ORDER.length : ia) - (ib === -1 ? STATUS_ORDER.length : ib);
67
+ });
68
+ for (const name of names) {
69
+ const group = groups.get(name) ?? [];
70
+ lines.push(` ${name} (${group.length})`);
71
+ for (const item of group)
72
+ lines.push(itemLine(item));
73
+ }
74
+ }
75
+ // An item the register says is promoted, with no active track carrying it, is the drift the
76
+ // §12 friction list warns about: work that was handed over and then dropped.
77
+ const activeSlugs = new Set(view.tracks.map((entry) => text(asRecord(asRecord(entry).track).slug, '')));
78
+ const orphans = view.items
79
+ .map((entry) => asRecord(entry))
80
+ .filter((item) => item.status === 'promoted' &&
81
+ !(typeof item.track === 'string' && activeSlugs.has(item.track)))
82
+ .map((item) => text(item.id));
83
+ if (orphans.length > 0) {
84
+ lines.push(`orphans (promoted, no active track): ${orphans.join(', ')}`);
85
+ }
86
+ return lines;
87
+ }
88
+ function fromCache(cache) {
89
+ return {
90
+ project: cache.project,
91
+ url: cache.url,
92
+ role: cache.role,
93
+ tracks: cache.tracks,
94
+ items: cache.items,
95
+ };
96
+ }
97
+ export async function runStatus(io, flags) {
98
+ let ctx;
99
+ try {
100
+ ctx = resolveContext('status', io, flags);
101
+ }
102
+ catch (err) {
103
+ return { ok: false, lines: [renderFailure(err, { command: 'status' })] };
104
+ }
105
+ const slug = encodeURIComponent(ctx.project);
106
+ try {
107
+ const projects = await ctx.client.get('/v1/projects');
108
+ const tracks = await ctx.client.get(`/v1/projects/${slug}/tracks?active_only=true`);
109
+ const items = await ctx.client.get(`/v1/projects/${slug}/items`);
110
+ const view = {
111
+ project: ctx.project,
112
+ url: ctx.url,
113
+ role: roleOf(projects, ctx.project),
114
+ tracks: asArray(tracks),
115
+ items: asArray(items),
116
+ };
117
+ // The cache is warmed only once every read has answered, so a half-failed run can never
118
+ // leave a truncated view behind.
119
+ writeStatusCache(ctx.root, view);
120
+ gitignoreCache(ctx.root);
121
+ return flags.json ? jsonResult(view) : { ok: true, lines: render(view) };
122
+ }
123
+ catch (err) {
124
+ // Only `unreachable` is an offline condition. A refusal (forbidden, not_found) is an answer
125
+ // from the service and must be shown as one — printing a cached view then would be a lie.
126
+ if (err instanceof ApiError && err.code === 'unreachable') {
127
+ const cache = readStatusCache(ctx.root, { url: ctx.url, project: ctx.project });
128
+ if (cache) {
129
+ const view = fromCache(cache);
130
+ if (flags.json)
131
+ return jsonResult({ ...view, fetched_at: cache.fetched_at, cached: true });
132
+ return {
133
+ ok: true,
134
+ lines: [`cached from ${cache.fetched_at} — the service is unreachable`, ...render(view)],
135
+ };
136
+ }
137
+ }
138
+ return { ok: false, lines: [renderFailure(err, ctx.ctx)] };
139
+ }
140
+ }
141
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { TargetFlags } from '../config.js';
2
+ import { type CommandIo, type CommandResult } from './common.js';
3
+ /**
4
+ * `backlog sync [<track>]` — clears the drift flag on the service and brings the repo's copy
5
+ * of the contract back in line (DECISION-018).
6
+ *
7
+ * **`spec.md` only.** `plan.md` and `metadata.json` carry local state — the plan a human
8
+ * wrote, the loop step the Evaluate-Loop is on, the shipment stamp — and none of it is in the
9
+ * register. Re-rendering either would throw away work the CLI does not own, so a sync writes
10
+ * exactly one file and the tests assert the other two are byte-identical afterwards.
11
+ */
12
+ export interface SyncFlags extends TargetFlags {
13
+ /** Absent: every active track the register flags as drifted. */
14
+ track?: string | undefined;
15
+ json?: boolean | undefined;
16
+ }
17
+ export declare function runSync(io: CommandIo, flags: SyncFlags): Promise<CommandResult>;
@@ -0,0 +1,90 @@
1
+ import { existsSync, writeFileSync } from 'node:fs';
2
+ import { join, relative } from 'node:path';
3
+ import { renderTrackSpec, trackBaseName } from '../_core/index.js';
4
+ import { asArray, asRecord, jsonResult, runCommand, text, } from './common.js';
5
+ /** The newest criteria version the payload carries — what the re-render is taken at. */
6
+ function criteriaVersion(detail) {
7
+ let best = null;
8
+ for (const entry of asArray(detail.spec)) {
9
+ const version = asRecord(entry).criteria_version;
10
+ if (typeof version === 'number' && (best === null || version > best))
11
+ best = version;
12
+ }
13
+ return best;
14
+ }
15
+ function trackDir(ctx, slug) {
16
+ return join(ctx.root, ctx.conductorDir, 'tracks', slug);
17
+ }
18
+ /** One track: POST, re-render, and say what moved. */
19
+ async function syncOne(ctx, slug) {
20
+ const payload = await ctx.client.post(`/v1/tracks/${encodeURIComponent(ctx.project)}/${encodeURIComponent(slug)}/sync`);
21
+ const detail = asRecord(payload);
22
+ const driftFrom = detail.drift_from_version;
23
+ const lines = [
24
+ typeof driftFrom === 'number'
25
+ ? `drift before: spec taken at criteria v${driftFrom}`
26
+ : 'drift before: the register cleared the flag with this sync',
27
+ ];
28
+ const specPath = join(trackDir(ctx, slug), 'spec.md');
29
+ writeFileSync(specPath, renderTrackSpec(detail, {
30
+ name: trackBaseName(slug),
31
+ }), 'utf8');
32
+ const version = criteriaVersion(detail);
33
+ lines.push(`${relative(ctx.root, specPath)} re-rendered from criteria ${version === null ? 'the register' : `v${version}`}`);
34
+ const matrix = asRecord(detail.matrix);
35
+ const superseded = asArray(matrix.superseded).map((r) => text(r, '?'));
36
+ const added = asArray(matrix.added).map((r) => text(r, '?'));
37
+ for (const row of superseded)
38
+ lines.push(`⏭️ ${row} superseded`);
39
+ for (const row of added)
40
+ lines.push(`☐ ${row} added`);
41
+ if (superseded.length === 0 && added.length === 0)
42
+ lines.push('no matrix changes');
43
+ lines.push(asRecord(detail.track).spec_drift === true
44
+ ? `drift after: still drifted — re-read the items and run: backlog sync ${slug}`
45
+ : 'drift after: in line with the register');
46
+ return { lines, payload };
47
+ }
48
+ export async function runSync(io, flags) {
49
+ return runCommand('sync', io, flags, async (ctx) => {
50
+ const asked = flags.track?.trim();
51
+ if (asked) {
52
+ const dir = trackDir(ctx, asked);
53
+ if (!existsSync(dir)) {
54
+ return {
55
+ ok: false,
56
+ lines: [
57
+ `no track directory at ${relative(ctx.root, dir)} — run: backlog promote <ID…> --track ${asked}`,
58
+ ],
59
+ };
60
+ }
61
+ const { lines, payload } = await syncOne(ctx, asked);
62
+ return flags.json ? jsonResult(payload) : { ok: true, lines };
63
+ }
64
+ // No slug: every active track the register says has drifted. The list is the service's
65
+ // call, not a local scan — a directory on disk proves nothing about the contract.
66
+ const listed = asArray(await ctx.client.get(`/v1/projects/${encodeURIComponent(ctx.project)}/tracks?active_only=true`));
67
+ const drifted = listed
68
+ .map((entry) => asRecord(asRecord(entry).track))
69
+ .filter((track) => track.spec_drift === true)
70
+ .map((track) => text(track.slug, ''))
71
+ .filter((slug) => slug !== '');
72
+ if (drifted.length === 0) {
73
+ return { ok: true, lines: ['no active track has drifted — nothing to sync'] };
74
+ }
75
+ const lines = [];
76
+ const payloads = [];
77
+ for (const slug of drifted) {
78
+ const dir = trackDir(ctx, slug);
79
+ if (!existsSync(dir)) {
80
+ lines.push(`${slug}: no track directory at ${relative(ctx.root, dir)} — skipped (run: backlog promote … --track ${slug})`);
81
+ continue;
82
+ }
83
+ const one = await syncOne(ctx, slug);
84
+ payloads.push(one.payload);
85
+ lines.push(`track ${slug}:`, ...one.lines.map((l) => ` ${l}`));
86
+ }
87
+ return flags.json ? jsonResult(payloads) : { ok: true, lines };
88
+ });
89
+ }
90
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The two conductor registry files the plugin is allowed to edit: `tracks.md`'s tables and
3
+ * `index.md`'s Recent Completions (§7.3 — nothing else about conductor changes). Both are
4
+ * edited **line-wise, in place**: a row is appended or one cell is flipped, and every other
5
+ * byte of a human-maintained file survives. There is no re-render of these files anywhere.
6
+ */
7
+ export interface ActiveTrackRow {
8
+ track_id: string;
9
+ name: string;
10
+ type: string;
11
+ priority: string;
12
+ /** Defaults to `in_progress` — the only status a freshly promoted track can have. */
13
+ status?: string | undefined;
14
+ /** Defaults to the previous last active track, then `—`. */
15
+ depends_on?: string | undefined;
16
+ }
17
+ export declare const ACTIVE_HEADING = "## Active Tracks";
18
+ export declare const ACTIVE_COLUMNS = "| Track ID | Name | Type | Priority | Status | Depends On |";
19
+ /** What `promote` writes when a repo has no `conductor/tracks.md` yet. */
20
+ export declare const TRACKS_MD_SKELETON = "# Conductor Track Registry\n\n## Active Tracks\n| Track ID | Name | Type | Priority | Status | Depends On |\n|---|---|---|---|---|---|\n\n## Next (promote only when the backlog items are `locked`)\n| Track ID | Backlog IDs | Name |\n|---|---|---|\n";
21
+ export declare class RegistryError extends Error {
22
+ constructor(message: string);
23
+ }
24
+ /** The bottom-most Active row's track id — the conventional `Depends On` for a new track. */
25
+ export declare function lastActiveTrack(text: string | null): string | null;
26
+ /**
27
+ * Appends one Active row. `text: null` means the file does not exist and the skeleton is
28
+ * written around it. A track that already has a row is a refusal, not a duplicate row:
29
+ * `promote` has already refused the directory, so this can only be a half-done earlier run.
30
+ */
31
+ export declare function addActiveTrack(text: string | null, row: ActiveTrackRow): string;
32
+ /** Flips the Status cell of one Active row. `null` when the track has no row. */
33
+ export declare function setTrackStatus(text: string, trackId: string, status: string): string | null;
34
+ /**
35
+ * Drops the Next row this promotion consumed — by slug (with or without the date stamp), or
36
+ * by the backlog ids it lists. A row that names ids we did not promote is left alone.
37
+ */
38
+ export declare function dropNextRow(text: string, trackId: string, ids: readonly string[]): string;
39
+ export interface CompletionRow {
40
+ track: string;
41
+ date: string;
42
+ notes: string;
43
+ }
44
+ /** §6 step 4. `null` when `index.md` has no Recent Completions table to append to. */
45
+ export declare function addRecentCompletion(text: string, row: CompletionRow): string | null;
@@ -0,0 +1,164 @@
1
+ import { trackBaseName } from '../_core/index.js';
2
+ export const ACTIVE_HEADING = '## Active Tracks';
3
+ export const ACTIVE_COLUMNS = '| Track ID | Name | Type | Priority | Status | Depends On |';
4
+ const NEXT_HEADING_PREFIX = '## Next';
5
+ const COMPLETIONS_HEADING = '## Recent Completions';
6
+ const DASH = '—';
7
+ /** What `promote` writes when a repo has no `conductor/tracks.md` yet. */
8
+ export const TRACKS_MD_SKELETON = `# Conductor Track Registry
9
+
10
+ ${ACTIVE_HEADING}
11
+ ${ACTIVE_COLUMNS}
12
+ |---|---|---|---|---|---|
13
+
14
+ ${NEXT_HEADING_PREFIX} (promote only when the backlog items are \`locked\`)
15
+ | Track ID | Backlog IDs | Name |
16
+ |---|---|---|
17
+ `;
18
+ export class RegistryError extends Error {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = 'RegistryError';
22
+ }
23
+ }
24
+ function cells(line) {
25
+ return line
26
+ .replace(/^\|/, '')
27
+ .replace(/\|\s*$/, '')
28
+ .split('|')
29
+ .map((c) => c.trim());
30
+ }
31
+ function isRow(line) {
32
+ return line.trimStart().startsWith('|');
33
+ }
34
+ function isSeparator(line) {
35
+ return /^\|[\s:|-]+\|$/.test(line.trim());
36
+ }
37
+ /** The `[first, lastExclusive)` line range of the table under a heading, or null. */
38
+ function tableRange(lines, matches) {
39
+ const heading = lines.findIndex((l) => matches(l));
40
+ if (heading === -1)
41
+ return null;
42
+ let i = heading + 1;
43
+ while (i < lines.length && !isRow(lines[i]) && !lines[i].startsWith('## '))
44
+ i += 1;
45
+ if (i >= lines.length || !isRow(lines[i]))
46
+ return null;
47
+ const start = i;
48
+ while (i < lines.length && isRow(lines[i]))
49
+ i += 1;
50
+ return { start, end: i };
51
+ }
52
+ function activeRange(lines) {
53
+ return tableRange(lines, (l) => l.trim() === ACTIVE_HEADING);
54
+ }
55
+ function nextRange(lines) {
56
+ return tableRange(lines, (l) => l.startsWith(NEXT_HEADING_PREFIX));
57
+ }
58
+ /** Data rows only — the header and the `|---|` separator are not rows of the table's content. */
59
+ function dataRows(lines, range) {
60
+ const out = [];
61
+ for (let i = range.start; i < range.end; i += 1) {
62
+ if (isSeparator(lines[i]))
63
+ continue;
64
+ const first = cells(lines[i])[0] ?? '';
65
+ if (first === 'Track ID')
66
+ continue;
67
+ out.push(i);
68
+ }
69
+ return out;
70
+ }
71
+ /** The bottom-most Active row's track id — the conventional `Depends On` for a new track. */
72
+ export function lastActiveTrack(text) {
73
+ if (text === null)
74
+ return null;
75
+ const lines = text.split('\n');
76
+ const range = activeRange(lines);
77
+ if (!range)
78
+ return null;
79
+ const rows = dataRows(lines, range);
80
+ if (rows.length === 0)
81
+ return null;
82
+ return cells(lines[rows[rows.length - 1]])[0] ?? null;
83
+ }
84
+ /**
85
+ * Appends one Active row. `text: null` means the file does not exist and the skeleton is
86
+ * written around it. A track that already has a row is a refusal, not a duplicate row:
87
+ * `promote` has already refused the directory, so this can only be a half-done earlier run.
88
+ */
89
+ export function addActiveTrack(text, row) {
90
+ const body = text ?? TRACKS_MD_SKELETON;
91
+ const lines = body.split('\n');
92
+ const range = activeRange(lines);
93
+ if (!range) {
94
+ throw new RegistryError(`conductor/tracks.md has no "${ACTIVE_HEADING}" table — add one (or delete the file and re-run)`);
95
+ }
96
+ for (const i of dataRows(lines, range)) {
97
+ if (cells(lines[i])[0] === row.track_id) {
98
+ throw new RegistryError(`conductor/tracks.md already has a row for ${row.track_id}`);
99
+ }
100
+ }
101
+ const dependsOn = row.depends_on ?? lastActiveTrack(body) ?? DASH;
102
+ const rendered = `| ${row.track_id} | ${row.name} | ${row.type} | ${row.priority} | ${row.status ?? 'in_progress'} | ${dependsOn} |`;
103
+ lines.splice(range.end, 0, rendered);
104
+ return lines.join('\n');
105
+ }
106
+ /** Flips the Status cell of one Active row. `null` when the track has no row. */
107
+ export function setTrackStatus(text, trackId, status) {
108
+ const lines = text.split('\n');
109
+ const range = activeRange(lines);
110
+ if (!range)
111
+ return null;
112
+ for (const i of dataRows(lines, range)) {
113
+ const parts = cells(lines[i]);
114
+ if (parts[0] !== trackId)
115
+ continue;
116
+ if (parts.length < 5)
117
+ return null;
118
+ parts[4] = status;
119
+ lines[i] = `| ${parts.join(' | ')} |`;
120
+ return lines.join('\n');
121
+ }
122
+ return null;
123
+ }
124
+ /**
125
+ * Drops the Next row this promotion consumed — by slug (with or without the date stamp), or
126
+ * by the backlog ids it lists. A row that names ids we did not promote is left alone.
127
+ */
128
+ export function dropNextRow(text, trackId, ids) {
129
+ const lines = text.split('\n');
130
+ const range = nextRange(lines);
131
+ if (!range)
132
+ return text;
133
+ const promoted = new Set(ids);
134
+ const base = trackBaseName(trackId);
135
+ for (const i of dataRows(lines, range)) {
136
+ const parts = cells(lines[i]);
137
+ const slug = parts[0] ?? '';
138
+ const listed = (parts[1] ?? '')
139
+ .split(',')
140
+ .map((r) => r.trim())
141
+ .filter((r) => r !== '');
142
+ const bySlug = slug === trackId || slug === base || trackBaseName(slug) === base;
143
+ const byIds = listed.length > 0 && listed.every((r) => promoted.has(r));
144
+ if (bySlug || byIds) {
145
+ lines.splice(i, 1);
146
+ return lines.join('\n');
147
+ }
148
+ }
149
+ return text;
150
+ }
151
+ /** §6 step 4. `null` when `index.md` has no Recent Completions table to append to. */
152
+ export function addRecentCompletion(text, row) {
153
+ const lines = text.split('\n');
154
+ const range = tableRange(lines, (l) => l.trim() === COMPLETIONS_HEADING);
155
+ if (!range)
156
+ return null;
157
+ for (let i = range.start; i < range.end; i += 1) {
158
+ if (cells(lines[i])[0] === row.track)
159
+ return null;
160
+ }
161
+ lines.splice(range.end, 0, `| ${row.track} | ${row.date} | ${row.notes} |`);
162
+ return lines.join('\n');
163
+ }
164
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/conductor/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /** The committed, token-free project file (DECISION-026). */
2
+ export declare const CONFIG_FILENAME = "backlog.config.json";
3
+ /**
4
+ * Exactly three keys — a service URL, a project slug and the conductor directory.
5
+ * `prefixes`/`environments` are service state: a stale copy in a committed file is a bug,
6
+ * so they are deliberately absent (DECISION-026).
7
+ */
8
+ export interface BacklogConfig {
9
+ url: string;
10
+ project: string;
11
+ conductor_dir: string;
12
+ }
13
+ export interface LoadedConfig {
14
+ config: BacklogConfig;
15
+ path: string;
16
+ }
17
+ /** A refusal about the config file itself: always names the file and the offending key. */
18
+ export declare class ConfigError extends Error {
19
+ readonly path: string;
20
+ constructor(path: string, message: string);
21
+ }
22
+ /** Walks up from `cwd` looking for `backlog.config.json`. Returns its path, or null. */
23
+ export declare function findConfig(cwd: string): string | null;
24
+ /** Reads and validates one config file. Throws `ConfigError` on anything but the exact shape. */
25
+ export declare function readConfig(path: string): BacklogConfig;
26
+ /** Walk-up + read. Null when no config exists above `cwd`; throws when one exists but is bad. */
27
+ export declare function loadConfig(cwd: string): LoadedConfig | null;
28
+ /** Writes the three keys, validated first so a bad object can never reach a tracked file. */
29
+ export declare function writeConfig(path: string, config: BacklogConfig): void;
30
+ export interface TargetFlags {
31
+ url?: string | undefined;
32
+ project?: string | undefined;
33
+ }
34
+ export interface ResolvedTarget {
35
+ url: string;
36
+ project: string;
37
+ conductor_dir: string;
38
+ /** Where the project came from — the strongest source that contributed. */
39
+ source: 'flag' | 'env' | 'config';
40
+ configPath: string | null;
41
+ }
42
+ /**
43
+ * Precedence for every command: `--url`/`--project` → `BACKLOG_URL`/`BACKLOG_PROJECT` →
44
+ * the config file → a refusal pointing at `backlog init`. Resolved per field, so a flag
45
+ * can name a project while the URL still comes from the committed file.
46
+ */
47
+ export declare function resolveTarget(flags: TargetFlags, env: NodeJS.ProcessEnv, cwd: string): ResolvedTarget;