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/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # felenova-backlog
2
+
3
+ The repo-side half of Felenova's product register. One CLI plus the
4
+ `backlog-execution` plugin for Claude Code.
5
+
6
+ The register itself is a hosted service: items, their acceptance criteria and the
7
+ lock that protects them live there, not in your repo. This package is the bridge
8
+ between that service and the `conductor/` track you actually work in.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npx felenova-backlog init --url https://your-service --project your-project
14
+ ```
15
+
16
+ `init` is idempotent. It writes `backlog.config.json` (`url`, `project`,
17
+ `conductor_dir` and nothing else), stores your personal access token at
18
+ `~/.config/backlog/token` with mode 600, adds an `.mcp.json` entry that references
19
+ `${BACKLOG_TOKEN}` and never a literal token, scaffolds `conductor/` without
20
+ touching a file that already exists, appends one marked section to `CLAUDE.md`,
21
+ and installs the skills and `/backlog:*` commands. Run it twice and the second run
22
+ changes nothing.
23
+
24
+ ## Commands
25
+
26
+ | Command | What it does | Role needed |
27
+ |---|---|---|
28
+ | `backlog doctor` | Checks the config, the token's file mode, the MCP entry, the plugin install, and that your token can see the configured project | any |
29
+ | `backlog status` | The register's items grouped by status. Falls back to a cached view, marked `cached`, when the service is unreachable | any |
30
+ | `backlog next` | What is ready to start: locked, unblocked, unpromoted | any |
31
+ | `backlog promote <ID...>` | Opens a conductor track from locked items and writes `spec.md`, `plan.md` and `metadata.json` | Engineer and up |
32
+ | `backlog matrix init <track>` | Scaffolds one QA matrix row per acceptance criterion | Engineer and up |
33
+ | `backlog report <track> <step> <status>` | Records a loop step. The first `EXECUTE` moves the track's items to in-progress | Engineer and up |
34
+ | `backlog sync <track>` | Re-renders `spec.md` from the current locked criteria and clears spec drift | Engineer and up |
35
+ | `backlog ship <track>` | Closes the track with a commit and a production date | Product and up |
36
+ | `backlog export <outDir>` | Writes a markdown snapshot of the register | any |
37
+
38
+ Every refusal is rendered as one sentence. A role you do not have reads
39
+ `ship_track is Product+; you are engineer on <project>`, not a stack trace.
40
+
41
+ ## The plugin
42
+
43
+ `init` installs three skills and four commands into `.claude/`. The skills teach an
44
+ agent the register's vocabulary; the commands (`/backlog:next`, `/backlog:promote`,
45
+ `/backlog:report`, `/backlog:ship`) are thin wrappers over this CLI. The `.mcp.json`
46
+ entry points Claude Code at the service's MCP surface, where the register's write
47
+ tools live.
48
+
49
+ ## Your token
50
+
51
+ Mint a personal access token in the service's web UI, then either export
52
+ `BACKLOG_TOKEN` or let `init` save it to `~/.config/backlog/token`. It is never
53
+ written inside a repository, and `backlog doctor` fails loudly if a token-shaped
54
+ literal appears in a tracked file.
55
+
56
+ ## Licence
57
+
58
+ UNLICENSED. Published for Felenova's own use; no support is implied.
package/bin/backlog.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
@@ -0,0 +1,13 @@
1
+ import type { Item } from './schema.js';
2
+ export declare const SPEC_FIELDS: readonly ["title", "what", "why", "criteria", "out_of_scope", "dependencies_note", "critique"];
3
+ export type SpecField = (typeof SPEC_FIELDS)[number];
4
+ /** §4.3 / §5.4: draft → reviewed requires documented push-back. */
5
+ export declare function reviewCriteria(item: Item): Item;
6
+ export declare function lockCriteria(item: Item): Item;
7
+ /** §5.4 rule 2–3: the only backward move; reason required; version bumps; drift is the caller's flag to set on the track. */
8
+ export declare function unlockCriteria(item: Item, reason: string): {
9
+ item: Item;
10
+ flagDrift: boolean;
11
+ };
12
+ /** §5.4 rule 1: locked spec fields are immutable through every surface. */
13
+ export declare function editSpecFields(item: Item, patch: Partial<Pick<Item, SpecField>>): Item;
@@ -0,0 +1,44 @@
1
+ import { LockedError, ReasonRequiredError, TransitionError } from './errors.js';
2
+ export const SPEC_FIELDS = [
3
+ 'title',
4
+ 'what',
5
+ 'why',
6
+ 'criteria',
7
+ 'out_of_scope',
8
+ 'dependencies_note',
9
+ 'critique',
10
+ ];
11
+ /** §4.3 / §5.4: draft → reviewed requires documented push-back. */
12
+ export function reviewCriteria(item) {
13
+ if (item.criteria_status !== 'draft')
14
+ throw new TransitionError('criteria_status', item.criteria_status, 'reviewed');
15
+ if (item.critique.trim() === '')
16
+ throw new Error(`${item.id}: critique is empty — review requires push-back`);
17
+ return { ...item, criteria_status: 'reviewed' };
18
+ }
19
+ export function lockCriteria(item) {
20
+ if (item.criteria_status !== 'reviewed')
21
+ throw new TransitionError('criteria_status', item.criteria_status, 'locked');
22
+ if (item.criteria.length === 0)
23
+ throw new Error(`${item.id}: cannot lock without at least one criterion`);
24
+ return { ...item, criteria_status: 'locked' };
25
+ }
26
+ /** §5.4 rule 2–3: the only backward move; reason required; version bumps; drift is the caller's flag to set on the track. */
27
+ export function unlockCriteria(item, reason) {
28
+ if (item.criteria_status !== 'locked')
29
+ throw new TransitionError('criteria_status', item.criteria_status, 'reviewed');
30
+ if (!reason?.trim())
31
+ throw new ReasonRequiredError('unlock');
32
+ const flagDrift = item.status === 'promoted' || item.status === 'in-progress';
33
+ return {
34
+ item: { ...item, criteria_status: 'reviewed', criteria_version: item.criteria_version + 1 },
35
+ flagDrift,
36
+ };
37
+ }
38
+ /** §5.4 rule 1: locked spec fields are immutable through every surface. */
39
+ export function editSpecFields(item, patch) {
40
+ if (item.criteria_status === 'locked')
41
+ throw new LockedError(item.id);
42
+ return { ...item, ...patch };
43
+ }
44
+ //# sourceMappingURL=criteria.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"criteria.js","sourceRoot":"","sources":["../src/criteria.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGhF,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,OAAO;IACP,MAAM;IACN,KAAK;IACL,UAAU;IACV,cAAc;IACd,mBAAmB;IACnB,UAAU;CACF,CAAC;AAGX,mEAAmE;AACnE,MAAM,UAAU,cAAc,CAAC,IAAU;IACvC,IAAI,IAAI,CAAC,eAAe,KAAK,OAAO;QAClC,MAAM,IAAI,eAAe,CAAC,iBAAiB,EAAE,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IACjF,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE;QAC7B,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,EAAE,iDAAiD,CAAC,CAAC;IAC/E,OAAO,EAAE,GAAG,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAU;IACrC,IAAI,IAAI,CAAC,eAAe,KAAK,UAAU;QACrC,MAAM,IAAI,eAAe,CAAC,iBAAiB,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;IAC/E,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,EAAE,8CAA8C,CAAC,CAAC;IAC5E,OAAO,EAAE,GAAG,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC;AAChD,CAAC;AAED,6HAA6H;AAC7H,MAAM,UAAU,cAAc,CAAC,IAAU,EAAE,MAAc;IACvD,IAAI,IAAI,CAAC,eAAe,KAAK,QAAQ;QACnC,MAAM,IAAI,eAAe,CAAC,iBAAiB,EAAE,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IACjF,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;QAAE,MAAM,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,KAAK,UAAU,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa,CAAC;IAC9E,OAAO;QACL,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,GAAG,CAAC,EAAE;QAC3F,SAAS;KACV,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,IAAU,EAAE,KAAqC;IAC9E,IAAI,IAAI,CAAC,eAAe,KAAK,QAAQ;QAAE,MAAM,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,EAAE,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,53 @@
1
+ import type { CriteriaStatus, Role, Suggestion } from './schema.js';
2
+ export declare class TransitionError extends Error {
3
+ readonly kind: 'status' | 'criteria_status' | 'matrix_status' | 'suggestion_status';
4
+ readonly from: string;
5
+ readonly to: string;
6
+ constructor(kind: 'status' | 'criteria_status' | 'matrix_status' | 'suggestion_status', from: string, to: string);
7
+ }
8
+ export declare class PermissionError extends Error {
9
+ readonly role: Role;
10
+ readonly action: string;
11
+ constructor(role: Role, action: string, detail?: string);
12
+ }
13
+ export declare class NotLockedError extends Error {
14
+ readonly id: string;
15
+ readonly criteria_status: CriteriaStatus;
16
+ constructor(id: string, criteria_status: CriteriaStatus);
17
+ }
18
+ export declare class LockedError extends Error {
19
+ readonly id: string;
20
+ constructor(id: string);
21
+ }
22
+ export declare class ReasonRequiredError extends Error {
23
+ readonly action: string;
24
+ constructor(action: string);
25
+ }
26
+ /**
27
+ * A suggestion filed against an older `criteria_version`: its quoted text may no longer exist,
28
+ * so it is closed as `superseded` rather than applied to text it was never about
29
+ * (`docs/service/review-layer.md`).
30
+ */
31
+ export declare class SupersededError extends Error {
32
+ readonly suggestion: Suggestion;
33
+ readonly filedAgainst: number;
34
+ readonly current: number;
35
+ /**
36
+ * `suggestion` is the row *already marked* `superseded`, not the stale one the caller passed
37
+ * in: the service has to persist that closure from its catch block, and re-deriving it there
38
+ * would put the transition rule in two places.
39
+ */
40
+ constructor(suggestion: Suggestion, filedAgainst: number, current: number);
41
+ get suggestionId(): string;
42
+ }
43
+ /**
44
+ * A value outside a closed set the schema owns (a test type, an enforcement mode). Distinct from
45
+ * `TransitionError`, which is about a *move* between legal states; this one is about a value that
46
+ * was never legal. Maps to `invalid_request`.
47
+ */
48
+ export declare class InvalidValueError extends Error {
49
+ readonly field: string;
50
+ readonly value: string;
51
+ readonly allowed: readonly string[];
52
+ constructor(field: string, value: string, allowed: readonly string[]);
53
+ }
@@ -0,0 +1,91 @@
1
+ export class TransitionError extends Error {
2
+ kind;
3
+ from;
4
+ to;
5
+ constructor(kind, from, to) {
6
+ super(`${kind}: transition ${from}→${to} is not allowed`);
7
+ this.kind = kind;
8
+ this.from = from;
9
+ this.to = to;
10
+ this.name = 'TransitionError';
11
+ }
12
+ }
13
+ export class PermissionError extends Error {
14
+ role;
15
+ action;
16
+ constructor(role, action, detail) {
17
+ super(`role ${role} may not ${action}${detail ? ` (${detail})` : ''}`);
18
+ this.role = role;
19
+ this.action = action;
20
+ this.name = 'PermissionError';
21
+ }
22
+ }
23
+ export class NotLockedError extends Error {
24
+ id;
25
+ criteria_status;
26
+ constructor(id, criteria_status) {
27
+ super(`${id} is not locked (criteria_status: ${criteria_status})`);
28
+ this.id = id;
29
+ this.criteria_status = criteria_status;
30
+ this.name = 'NotLockedError';
31
+ }
32
+ }
33
+ export class LockedError extends Error {
34
+ id;
35
+ constructor(id) {
36
+ super(`${id} is locked; unlock it (with a reason) before editing spec fields`);
37
+ this.id = id;
38
+ this.name = 'LockedError';
39
+ }
40
+ }
41
+ export class ReasonRequiredError extends Error {
42
+ action;
43
+ constructor(action) {
44
+ super(`${action} requires a non-empty reason`);
45
+ this.action = action;
46
+ this.name = 'ReasonRequiredError';
47
+ }
48
+ }
49
+ /**
50
+ * A suggestion filed against an older `criteria_version`: its quoted text may no longer exist,
51
+ * so it is closed as `superseded` rather than applied to text it was never about
52
+ * (`docs/service/review-layer.md`).
53
+ */
54
+ export class SupersededError extends Error {
55
+ suggestion;
56
+ filedAgainst;
57
+ current;
58
+ /**
59
+ * `suggestion` is the row *already marked* `superseded`, not the stale one the caller passed
60
+ * in: the service has to persist that closure from its catch block, and re-deriving it there
61
+ * would put the transition rule in two places.
62
+ */
63
+ constructor(suggestion, filedAgainst, current) {
64
+ super(`suggestion ${suggestion.id} was filed against criteria_version ${filedAgainst}; the item is at ${current} — superseded`);
65
+ this.suggestion = suggestion;
66
+ this.filedAgainst = filedAgainst;
67
+ this.current = current;
68
+ this.name = 'SupersededError';
69
+ }
70
+ get suggestionId() {
71
+ return this.suggestion.id;
72
+ }
73
+ }
74
+ /**
75
+ * A value outside a closed set the schema owns (a test type, an enforcement mode). Distinct from
76
+ * `TransitionError`, which is about a *move* between legal states; this one is about a value that
77
+ * was never legal. Maps to `invalid_request`.
78
+ */
79
+ export class InvalidValueError extends Error {
80
+ field;
81
+ value;
82
+ allowed;
83
+ constructor(field, value, allowed) {
84
+ super(`${field}: ${JSON.stringify(value)} is not one of ${allowed.join(', ')}`);
85
+ this.field = field;
86
+ this.value = value;
87
+ this.allowed = allowed;
88
+ this.name = 'InvalidValueError';
89
+ }
90
+ }
91
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAE7B;IACA;IACA;IAHX,YACW,IAA0E,EAC1E,IAAY,EACZ,EAAU;QAEnB,KAAK,CAAC,GAAG,IAAI,gBAAgB,IAAI,IAAI,EAAE,iBAAiB,CAAC,CAAC;QAJjD,SAAI,GAAJ,IAAI,CAAsE;QAC1E,SAAI,GAAJ,IAAI,CAAQ;QACZ,OAAE,GAAF,EAAE,CAAQ;QAGnB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAE7B;IACA;IAFX,YACW,IAAU,EACV,MAAc,EACvB,MAAe;QAEf,KAAK,CAAC,QAAQ,IAAI,YAAY,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAJ9D,SAAI,GAAJ,IAAI,CAAM;QACV,WAAM,GAAN,MAAM,CAAQ;QAIvB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,KAAK;IAE5B;IACA;IAFX,YACW,EAAU,EACV,eAA+B;QAExC,KAAK,CAAC,GAAG,EAAE,oCAAoC,eAAe,GAAG,CAAC,CAAC;QAH1D,OAAE,GAAF,EAAE,CAAQ;QACV,oBAAe,GAAf,eAAe,CAAgB;QAGxC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,WAAY,SAAQ,KAAK;IACf;IAArB,YAAqB,EAAU;QAC7B,KAAK,CAAC,GAAG,EAAE,kEAAkE,CAAC,CAAC;QAD5D,OAAE,GAAF,EAAE,CAAQ;QAE7B,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACvB;IAArB,YAAqB,MAAc;QACjC,KAAK,CAAC,GAAG,MAAM,8BAA8B,CAAC,CAAC;QAD5B,WAAM,GAAN,MAAM,CAAQ;QAEjC,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAO7B;IACA;IACA;IARX;;;;OAIG;IACH,YACW,UAAsB,EACtB,YAAoB,EACpB,OAAe;QAExB,KAAK,CACH,cAAc,UAAU,CAAC,EAAE,uCAAuC,YAAY,oBAAoB,OAAO,eAAe,CACzH,CAAC;QANO,eAAU,GAAV,UAAU,CAAY;QACtB,iBAAY,GAAZ,YAAY,CAAQ;QACpB,YAAO,GAAP,OAAO,CAAQ;QAKxB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;IAC5B,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAE/B;IACA;IACA;IAHX,YACW,KAAa,EACb,KAAa,EACb,OAA0B;QAEnC,KAAK,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,kBAAkB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAJvE,UAAK,GAAL,KAAK,CAAQ;QACb,UAAK,GAAL,KAAK,CAAQ;QACb,YAAO,GAAP,OAAO,CAAmB;QAGnC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @felenova/backlog-core — the single place the backlog schema, state machines,
3
+ * permission matrix and audit types live (PRODUCT-SPEC §9). The service, CLI
4
+ * and UI must import from here and nowhere else.
5
+ */
6
+ export declare const CORE_VERSION = "0.3.0";
7
+ export declare function packageName(): string;
8
+ export * from './criteria.js';
9
+ export * from './errors.js';
10
+ export * from './loop.js';
11
+ export * from './markdown/items.js';
12
+ export * from './markdown/matrix.js';
13
+ export * from './markdown/track.js';
14
+ export * from './matrix.js';
15
+ export * from './matrix-status.js';
16
+ export * from './permissions.js';
17
+ export * from './promote.js';
18
+ export * from './ready.js';
19
+ export * from './review.js';
20
+ export * from './schema.js';
21
+ export * from './ship.js';
22
+ export * from './spec-diff.js';
23
+ export * from './status.js';
24
+ export * from './util/deep-equal.js';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @felenova/backlog-core — the single place the backlog schema, state machines,
3
+ * permission matrix and audit types live (PRODUCT-SPEC §9). The service, CLI
4
+ * and UI must import from here and nowhere else.
5
+ */
6
+ export const CORE_VERSION = '0.3.0';
7
+ export function packageName() {
8
+ return '@felenova/backlog-core';
9
+ }
10
+ export * from './criteria.js';
11
+ export * from './errors.js';
12
+ export * from './loop.js';
13
+ export * from './markdown/items.js';
14
+ export * from './markdown/matrix.js';
15
+ export * from './markdown/track.js';
16
+ export * from './matrix.js';
17
+ export * from './matrix-status.js';
18
+ export * from './permissions.js';
19
+ export * from './promote.js';
20
+ export * from './ready.js';
21
+ export * from './review.js';
22
+ export * from './schema.js';
23
+ export * from './ship.js';
24
+ export * from './spec-diff.js';
25
+ export * from './status.js';
26
+ export * from './util/deep-equal.js';
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC;AAEpC,MAAM,UAAU,WAAW;IACzB,OAAO,wBAAwB,CAAC;AAClC,CAAC;AACD,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1,73 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * The conductor Evaluate-Loop vocabulary (`conductor/workflow.md` v3), mirrored here because
4
+ * `report_progress` (§6.2) is the bridge between the two registers and the service must be able
5
+ * to validate a step without reading the repo.
6
+ */
7
+ export declare const LOOP_STEPS: readonly ["PLAN", "EVALUATE_PLAN", "EXECUTE", "EVALUATE_EXECUTION", "FIX", "COMPLETE"];
8
+ export declare const LOOP_STATUSES: readonly ["IN_PROGRESS", "PASS", "FAIL", "COMPLETE"];
9
+ export declare const LoopStep: z.ZodEnum<{
10
+ PLAN: "PLAN";
11
+ EVALUATE_PLAN: "EVALUATE_PLAN";
12
+ EXECUTE: "EXECUTE";
13
+ EVALUATE_EXECUTION: "EVALUATE_EXECUTION";
14
+ FIX: "FIX";
15
+ COMPLETE: "COMPLETE";
16
+ }>;
17
+ export declare const LoopStatus: z.ZodEnum<{
18
+ COMPLETE: "COMPLETE";
19
+ IN_PROGRESS: "IN_PROGRESS";
20
+ PASS: "PASS";
21
+ FAIL: "FAIL";
22
+ }>;
23
+ export type LoopStep = z.infer<typeof LoopStep>;
24
+ export type LoopStatus = z.infer<typeof LoopStatus>;
25
+ export declare const LoopState: z.ZodObject<{
26
+ step: z.ZodEnum<{
27
+ PLAN: "PLAN";
28
+ EVALUATE_PLAN: "EVALUATE_PLAN";
29
+ EXECUTE: "EXECUTE";
30
+ EVALUATE_EXECUTION: "EVALUATE_EXECUTION";
31
+ FIX: "FIX";
32
+ COMPLETE: "COMPLETE";
33
+ }>;
34
+ status: z.ZodEnum<{
35
+ COMPLETE: "COMPLETE";
36
+ IN_PROGRESS: "IN_PROGRESS";
37
+ PASS: "PASS";
38
+ FAIL: "FAIL";
39
+ }>;
40
+ }, z.core.$strip>;
41
+ export type LoopState = z.infer<typeof LoopState>;
42
+ export declare const ProgressReport: z.ZodObject<{
43
+ step: z.ZodEnum<{
44
+ PLAN: "PLAN";
45
+ EVALUATE_PLAN: "EVALUATE_PLAN";
46
+ EXECUTE: "EXECUTE";
47
+ EVALUATE_EXECUTION: "EVALUATE_EXECUTION";
48
+ FIX: "FIX";
49
+ COMPLETE: "COMPLETE";
50
+ }>;
51
+ status: z.ZodEnum<{
52
+ COMPLETE: "COMPLETE";
53
+ IN_PROGRESS: "IN_PROGRESS";
54
+ PASS: "PASS";
55
+ FAIL: "FAIL";
56
+ }>;
57
+ note: z.ZodDefault<z.ZodNullable<z.ZodString>>;
58
+ at: z.ZodString;
59
+ }, z.core.$strip>;
60
+ export type ProgressReport = z.infer<typeof ProgressReport>;
61
+ export interface TrackLoopState {
62
+ loop_step: LoopStep | null;
63
+ loop_status: LoopStatus | null;
64
+ last_report_at: string | null;
65
+ }
66
+ /**
67
+ * §6.2: "the first `EXECUTE` flips items to `in-progress`". `prev` is what the track already has —
68
+ * its last report, or the whole history when the caller has it (a `FIX → EXECUTE` bounce is then
69
+ * correctly *not* a first execute).
70
+ */
71
+ export declare function isFirstExecute(prev: LoopState | readonly LoopState[] | null | undefined, next: LoopState): boolean;
72
+ /** The three `tracks` columns the loop owns, derived from the reports. Latest by `at`, ties last. */
73
+ export declare function reportsToTrackState(reports: readonly ProgressReport[]): TrackLoopState;
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * The conductor Evaluate-Loop vocabulary (`conductor/workflow.md` v3), mirrored here because
4
+ * `report_progress` (§6.2) is the bridge between the two registers and the service must be able
5
+ * to validate a step without reading the repo.
6
+ */
7
+ export const LOOP_STEPS = [
8
+ 'PLAN',
9
+ 'EVALUATE_PLAN',
10
+ 'EXECUTE',
11
+ 'EVALUATE_EXECUTION',
12
+ 'FIX',
13
+ 'COMPLETE',
14
+ ];
15
+ export const LOOP_STATUSES = ['IN_PROGRESS', 'PASS', 'FAIL', 'COMPLETE'];
16
+ export const LoopStep = z.enum(LOOP_STEPS);
17
+ export const LoopStatus = z.enum(LOOP_STATUSES);
18
+ export const LoopState = z.object({ step: LoopStep, status: LoopStatus });
19
+ export const ProgressReport = z.object({
20
+ step: LoopStep,
21
+ status: LoopStatus,
22
+ note: z.string().nullable().default(null),
23
+ at: z.string().min(1),
24
+ });
25
+ /**
26
+ * §6.2: "the first `EXECUTE` flips items to `in-progress`". `prev` is what the track already has —
27
+ * its last report, or the whole history when the caller has it (a `FIX → EXECUTE` bounce is then
28
+ * correctly *not* a first execute).
29
+ */
30
+ export function isFirstExecute(prev, next) {
31
+ if (next.step !== 'EXECUTE')
32
+ return false;
33
+ const prior = prev == null ? [] : Array.isArray(prev) ? prev : [prev];
34
+ return !prior.some((r) => r.step === 'EXECUTE');
35
+ }
36
+ /** The three `tracks` columns the loop owns, derived from the reports. Latest by `at`, ties last. */
37
+ export function reportsToTrackState(reports) {
38
+ let latest = null;
39
+ for (const r of reports)
40
+ if (latest === null || r.at >= latest.at)
41
+ latest = r;
42
+ return latest === null
43
+ ? { loop_step: null, loop_status: null, last_report_at: null }
44
+ : { loop_step: latest.step, loop_status: latest.status, last_report_at: latest.at };
45
+ }
46
+ //# sourceMappingURL=loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.js","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,MAAM;IACN,eAAe;IACf,SAAS;IACT,oBAAoB;IACpB,KAAK;IACL,UAAU;CACF,CAAC;AACX,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AAElF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAC3C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AAIhD,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AAG1E,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,IAAI,EAAE,QAAQ;IACd,MAAM,EAAE,UAAU;IAClB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACzC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACtB,CAAC,CAAC;AASH;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAyD,EACzD,IAAe;IAEf,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAiB,CAAC,CAAC;IACnF,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;AAClD,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,mBAAmB,CAAC,OAAkC;IACpE,IAAI,MAAM,GAA0B,IAAI,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,OAAO;QAAE,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,EAAE,IAAI,MAAM,CAAC,EAAE;YAAE,MAAM,GAAG,CAAC,CAAC;IAC9E,OAAO,MAAM,KAAK,IAAI;QACpB,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE;QAC9D,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;AACxF,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { type Criterion, Item } from '../schema.js';
2
+ export interface ItemSource {
3
+ path: string;
4
+ frontmatterRaw: string;
5
+ bodyRaw: string;
6
+ }
7
+ export declare class ItemParseError extends Error {
8
+ readonly path: string;
9
+ readonly problems: string[];
10
+ constructor(path: string, problems: string[]);
11
+ }
12
+ /** §4.1 field order — also the canonical output order. */
13
+ export declare const FRONTMATTER_FIELDS: readonly ["id", "title", "status", "domain", "priority", "origin", "created", "updated", "prerequisites", "blocks", "criteria_status", "revisit_when", "track", "shipped_commit", "production_date"];
14
+ export declare const REQUIRED_SECTIONS: readonly ["What", "Why", "Acceptance criteria", "Critique / risks", "Out of scope", "Dependencies & sequencing"];
15
+ /**
16
+ * Parse an `## Acceptance criteria` section into its checkboxes and the prose around them.
17
+ *
18
+ * Real items wrap a criterion over indented continuation lines and hang sub-bullets or a
19
+ * numbered precedence list under it (liblib-meet RP-004, RP-032): an indented line that is not
20
+ * itself a checkbox continues the criterion above it and is folded in with a single space.
21
+ * Every other non-checkbox line is section prose (liblib-meet AI-001's `*Pending — …*` note,
22
+ * RP-032's `### ` group headings) and is kept in `notes` so canonical export loses nothing.
23
+ */
24
+ export declare function parseCriteriaSection(sectionText: string): {
25
+ criteria: Criterion[];
26
+ notes: string;
27
+ };
28
+ export declare function parseCriteria(sectionText: string): Criterion[];
29
+ export declare function parseItem(text: string, path: string): {
30
+ item: Item;
31
+ source: ItemSource;
32
+ };
33
+ export declare function serializeItemCanonical(item: Item): string;
34
+ export declare function itemMatchesSource(item: Item, source: ItemSource): boolean;
35
+ export declare function serializeItem(item: Item, source?: ItemSource): string;