changeledger 0.3.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 (44) hide show
  1. package/AGENTS.md +25 -0
  2. package/LICENSE +21 -0
  3. package/README.md +160 -0
  4. package/bin/changeledger.mjs +375 -0
  5. package/package.json +72 -0
  6. package/src/atomic-write.mjs +134 -0
  7. package/src/change.mjs +102 -0
  8. package/src/check.mjs +536 -0
  9. package/src/commands/agent.mjs +256 -0
  10. package/src/commands/check.mjs +48 -0
  11. package/src/commands/graduate.mjs +105 -0
  12. package/src/commands/init.mjs +43 -0
  13. package/src/commands/new.mjs +164 -0
  14. package/src/commands/register.mjs +28 -0
  15. package/src/commands/release.mjs +138 -0
  16. package/src/commands/view.mjs +52 -0
  17. package/src/config.mjs +76 -0
  18. package/src/contract.mjs +100 -0
  19. package/src/git.mjs +73 -0
  20. package/src/lifecycle.mjs +57 -0
  21. package/src/metrics.mjs +143 -0
  22. package/src/paths.mjs +12 -0
  23. package/src/registry.mjs +55 -0
  24. package/src/release.mjs +71 -0
  25. package/src/repo.mjs +122 -0
  26. package/src/slug.mjs +12 -0
  27. package/src/spec.mjs +12 -0
  28. package/src/viewer/domain.mjs +133 -0
  29. package/src/viewer/public/api.js +25 -0
  30. package/src/viewer/public/app-state.js +87 -0
  31. package/src/viewer/public/app.js +717 -0
  32. package/src/viewer/public/index.html +64 -0
  33. package/src/viewer/public/security.js +65 -0
  34. package/src/viewer/public/state.js +31 -0
  35. package/src/viewer/public/styles.css +1062 -0
  36. package/src/viewer/public/templates.js +9 -0
  37. package/src/viewer/public/view-parts.js +162 -0
  38. package/src/viewer/public/view-renderers.js +191 -0
  39. package/src/viewer/server/router.mjs +200 -0
  40. package/src/viewer/server/security.mjs +27 -0
  41. package/src/writer.mjs +151 -0
  42. package/src/yaml.mjs +75 -0
  43. package/templates/AGENTS.md +540 -0
  44. package/templates/config.yml +55 -0
package/AGENTS.md ADDED
@@ -0,0 +1,25 @@
1
+ # AGENTS.md — ChangeLedger (project's own contract)
2
+
3
+ This is the ChangeLedger repo itself. It dogfoods its own format: changes live
4
+ under `.changeledger/changes/`, persistent truth under `.changeledger/specs/`.
5
+
6
+ <!-- changeledger -->
7
+ > [!IMPORTANT]
8
+ > This repo uses **ChangeLedger**. Read and follow [`.changeledger/AGENTS.md`](.changeledger/AGENTS.md)
9
+ > (the change contract). If it is missing, run `changeledger register`.
10
+
11
+ The **canonical ChangeLedger contract** (the convention every consuming repo
12
+ follows) is shipped as [`templates/AGENTS.md`](templates/AGENTS.md) and linked
13
+ into each repo as `.changeledger/AGENTS.md`. Edit the convention there, not here.
14
+
15
+ ## Project-specific notes
16
+
17
+ - Managed with **pnpm**; lint/format via **Biome**. Runtime dependencies are
18
+ allowed only when they are mature and justified: the CLI uses `yaml` for
19
+ config/frontmatter parsing, and the viewer uses `lit-html`, `marked`,
20
+ `dompurify` and `mermaid` for templating, Markdown, sanitization and diagrams.
21
+ - `pnpm verify` (lint + test + `changeledger check`) is the full quality gate. The
22
+ versioned `hooks/pre-commit` runs `lint-staged`, `pnpm test` and `changeledger check`
23
+ so staged formatting stays compatible with partial commits.
24
+ - The contract you edit for the convention is `templates/AGENTS.md` — it is the
25
+ artifact that `changeledger init`/`register` symlink into other repos.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roberto Ruiz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # ChangeLedger (`changeledger`)
2
+
3
+ > Turn conversations into buildable changes.
4
+
5
+ ChangeLedger is a local-first workflow for planning software changes with coding
6
+ agents before implementation begins. Features, bugs, audits and refactors become
7
+ reviewable documents with an enforced lifecycle, acceptance criteria, tasks and
8
+ persistent product truth.
9
+
10
+ Its human layer is a local viewer: instead of reading scattered Markdown files,
11
+ you get a searchable board across all registered projects, rendered
12
+ specifications and dependency diagrams.
13
+
14
+ ## Quick start
15
+
16
+ ChangeLedger requires **Node.js 24 or newer**. Install the CLI globally:
17
+
18
+ ```sh
19
+ npm install --global changeledger
20
+ # or: pnpm add --global changeledger
21
+ ```
22
+
23
+ In a repository that already has an `AGENTS.md`:
24
+
25
+ ```sh
26
+ changeledger init
27
+ changeledger view
28
+ ```
29
+
30
+ `changeledger init` creates `.changeledger/`, gives the project a stable identity and links the
31
+ installed agent contract. The repository keeps only its configuration and
32
+ documents; the CLI and viewer remain in the global package.
33
+
34
+ ## The workflow
35
+
36
+ ```text
37
+ conversation → draft → human approval → implementation → review
38
+ → human validation → persistent specification
39
+ ```
40
+
41
+ A typical change starts like this:
42
+
43
+ ```sh
44
+ changeledger new feature oauth-login "Add OAuth login"
45
+ changeledger view
46
+ ```
47
+
48
+ The agent completes the generated stages under `.changeledger/changes/`. You approve the
49
+ draft in the viewer before implementation. From there, ChangeLedger validates
50
+ lifecycle transitions, task traceability and the final human acceptance gate.
51
+ When the work is done, its lasting truth graduates into `.changeledger/specs/`.
52
+
53
+ The contract is agent-agnostic: Codex, Claude Code, opencode, Copilot, Cursor and
54
+ other tools discover it through the repository's `AGENTS.md` reference.
55
+
56
+ ## Changes and specs
57
+
58
+ - **Changes** describe a delta: why it is needed, what was learned, the chosen
59
+ design, acceptance criteria, implementation tasks and execution log.
60
+ - **Specs** describe the current system after completed changes graduate. They
61
+ have no work lifecycle and remain concise, durable product truth.
62
+
63
+ With the default Definition of Ready policy, `changeledger check` verifies that acceptance
64
+ criteria are test-grade and mapped to actionable tasks. Repositories doing
65
+ exploratory work can set `tdd: false`; the complete rules live in
66
+ [`AGENTS.md`](AGENTS.md).
67
+
68
+ ## Essential commands
69
+
70
+ ### Set up and inspect
71
+
72
+ ```sh
73
+ changeledger init # initialize and register the current repository
74
+ changeledger register # relink a moved or freshly cloned repository
75
+ changeledger view # view every registered project
76
+ changeledger view . # view only the current project
77
+ changeledger check [id] # validate the repository or one change
78
+ ```
79
+
80
+ ### Work with changes
81
+
82
+ ```sh
83
+ changeledger new <type> <slug> <title> # create a draft change
84
+ changeledger list [--status S] [--type T]
85
+ changeledger show <id> [--json]
86
+ changeledger status <id> <status>
87
+ changeledger task <id> done|block <n> [reason]
88
+ changeledger log <id> <message>
89
+ changeledger review <id> pass
90
+ changeledger review <id> fail --retry "<reason>"
91
+ changeledger review <id> fail --block "<reason>"
92
+ changeledger discard <id> <reason>
93
+ ```
94
+
95
+ ### Preserve completed truth
96
+
97
+ ```sh
98
+ changeledger graduate <id> <spec-slug> # create a persistent spec
99
+ changeledger graduate <id> <spec-slug> --into # update an existing spec's provenance
100
+ changeledger graduate <id> --skip [reason] # record that no spec is needed
101
+ changeledger archive --graduated [--dry-run] # hide resolved changes from the board
102
+ ```
103
+
104
+ Run `changeledger --help` or `changeledger <command> --help` for the complete command reference.
105
+
106
+ ## Release planning
107
+
108
+ ChangeLedger can calculate a portable SemVer release from completed changes
109
+ without assuming a package ecosystem:
110
+
111
+ ```sh
112
+ changeledger release init 0.1.0 # adopt an existing published version once
113
+ changeledger release plan --json # calculate the next version without writing
114
+ changeledger release record 0.2.0 # record the calculated release manifest
115
+ ```
116
+
117
+ The CLI decides which changes belong to the release and calculates their
118
+ effective `release_impact`. The operating agent applies that version to the
119
+ project's own surfaces—`package.json`, `pubspec.yaml`, `Cargo.toml`, Gradle,
120
+ Xcode or a monorepo—then runs the local quality gates and release workflow.
121
+
122
+ ## Repository integration
123
+
124
+ `changeledger check` exits non-zero on contract errors, so it can be used in Git hooks and
125
+ CI:
126
+
127
+ ```sh
128
+ changeledger check || exit 1
129
+ ```
130
+
131
+ The contract itself ships with the CLI and is linked as `.changeledger/AGENTS.md`. It is
132
+ per-machine and gitignored, so upgrades do not leave committed copies behind.
133
+ Run `changeledger register` after cloning, moving a repository or updating the global
134
+ installation.
135
+
136
+ ## Compatibility and security
137
+
138
+ - Node.js **24+**.
139
+ - Tested on Linux, macOS and Windows.
140
+ - On Windows, contract linking uses a symlink when permitted and falls back to a
141
+ copy when Developer Mode or administrator privileges are unavailable.
142
+ - The viewer binds to loopback and treats repository content as untrusted input.
143
+ See [`SECURITY.md`](SECURITY.md) for the threat model and private reporting
144
+ instructions.
145
+
146
+ ## Project status
147
+
148
+ ChangeLedger is usable and self-hosting, but remains **pre-1.0**. Expect the
149
+ contract and CLI to evolve while the core workflow settles. Upgrade to the
150
+ latest `0.x` release to receive fixes.
151
+
152
+ Contributions are welcome. Development setup, quality gates and repository
153
+ conventions live in [`CONTRIBUTING.md`](CONTRIBUTING.md). The canonical agent
154
+ contract is [`AGENTS.md`](AGENTS.md).
155
+
156
+ ## Language policy
157
+
158
+ Structure is always English: frontmatter keys, enum values, stage headings, CLI
159
+ commands and filenames. Generated content follows the repository's configured
160
+ `language`.
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import {
4
+ archive,
5
+ archiveGraduated,
6
+ discard,
7
+ list,
8
+ log,
9
+ owner,
10
+ review,
11
+ show,
12
+ status,
13
+ task,
14
+ } from '../src/commands/agent.mjs';
15
+ import { check } from '../src/commands/check.mjs';
16
+ import { graduate, pendingGraduation, skipGraduation } from '../src/commands/graduate.mjs';
17
+ import { init } from '../src/commands/init.mjs';
18
+ import { newChange } from '../src/commands/new.mjs';
19
+ import { registerRepo } from '../src/commands/register.mjs';
20
+ import { initReleaseHistory, recordRelease, releasePlan } from '../src/commands/release.mjs';
21
+ import { view } from '../src/commands/view.mjs';
22
+ import { nowUtc } from '../src/paths.mjs';
23
+
24
+ const USAGE = `ChangeLedger (changeledger)
25
+
26
+ changeledger init set up .changeledger/ in the current repo (+ register it)
27
+ changeledger register (re)link this repo's path in the global registry
28
+ changeledger new <type> <slug> <title> scaffold a new change (slug is the English filename)
29
+ changeledger view [port] launch the local viewer (default port 4040)
30
+ changeledger check [id] [--json] validate the repo or one change
31
+ changeledger status <id> <status> move a change's lifecycle status
32
+ changeledger discard <id> "<reason>" discard a change (terminal; keeps the record)
33
+ changeledger review <id> pass independent review passed → in-validation
34
+ changeledger review <id> fail --retry|--block "<reason>" review failed → in-progress|blocked
35
+ changeledger owner <id> <name|-> set or clear a change's owner
36
+ changeledger archive <id> / unarchive <id> hide/show a change in the viewer
37
+ changeledger archive --graduated [--dry-run] archive done changes already graduated/skipped
38
+ changeledger log <id> <message> append a timestamped Log entry
39
+ changeledger task <id> done|block <n> [reason] mark a Plan task
40
+ changeledger list [--status S] [--type T] [--json] list changes
41
+ changeledger show <id> [--json] print a change
42
+ changeledger graduate <change-id> <spec-slug> graduate a change to a new spec
43
+ changeledger graduate <change-id> <spec-slug> --into graduate into an existing spec
44
+ changeledger graduate <change-id> --skip [reason] mark graduation reviewed, no spec
45
+ changeledger graduate --pending list done changes not yet reviewed
46
+ changeledger release init <version> initialize release history at X.Y.Z
47
+ changeledger release plan [--json] calculate the next portable SemVer release
48
+ changeledger release record <version> record the calculated release manifest`;
49
+
50
+ const program = new Command();
51
+
52
+ function action(fn) {
53
+ return async (...args) => {
54
+ try {
55
+ await fn(...args);
56
+ } catch (e) {
57
+ console.error(`Error: ${e.message}`);
58
+ process.exit(1);
59
+ }
60
+ };
61
+ }
62
+
63
+ program
64
+ .name('changeledger')
65
+ .description('ChangeLedger (changeledger)')
66
+ .helpOption('-h, --help', 'display help for command')
67
+ .addHelpText('after', `\n${USAGE}`);
68
+
69
+ program
70
+ .command('init')
71
+ .description('set up .changeledger/ in the current repo (+ register it)')
72
+ .action(
73
+ action(() => {
74
+ const dir = init();
75
+ console.log(`Initialized ChangeLedger at ${dir}`);
76
+ }),
77
+ );
78
+
79
+ program
80
+ .command('register')
81
+ .description("(re)link this repo's path in the global registry")
82
+ .action(
83
+ action(() => {
84
+ const { id, path: p } = registerRepo();
85
+ console.log(`Registered ${id} → ${p}`);
86
+ }),
87
+ );
88
+
89
+ program
90
+ .command('new')
91
+ .description('scaffold a new change')
92
+ .argument('<type>')
93
+ .argument('<slug>')
94
+ .argument('<title...>')
95
+ .option('--owner <name>', 'set the initial owner')
96
+ .action(
97
+ action((type, slug, titleParts, options) => {
98
+ const title = titleParts.join(' ').trim();
99
+ const file = newChange({ type, slug, title, owner: options.owner, now: nowUtc() });
100
+ console.log(`Created ${file}`);
101
+ }),
102
+ );
103
+
104
+ program
105
+ .command('view')
106
+ .description('launch the local viewer')
107
+ .argument('[args...]')
108
+ .action(action((args) => view(args)));
109
+
110
+ program
111
+ .command('check')
112
+ .description('validate the repo or one change')
113
+ .argument('[id]')
114
+ .option('--json', 'print JSON')
115
+ .action((id, options) => {
116
+ try {
117
+ const args = [...(id ? [id] : []), ...(options.json ? ['--json'] : [])];
118
+ process.exit(check(args));
119
+ } catch (e) {
120
+ console.error(`Error: ${e.message}`);
121
+ process.exit(1);
122
+ }
123
+ });
124
+
125
+ program
126
+ .command('status')
127
+ .description("move a change's lifecycle status")
128
+ .argument('<id>')
129
+ .argument('<status>')
130
+ .action(
131
+ action((id, st) => {
132
+ status(id, st);
133
+ console.log(`#${id} → ${st}`);
134
+ }),
135
+ );
136
+
137
+ program
138
+ .command('discard')
139
+ .description('discard a change (terminal; keeps the record and reason)')
140
+ .argument('<id>')
141
+ .argument('<reason...>')
142
+ .action(
143
+ action((id, reasonParts) => {
144
+ discard(id, reasonParts.join(' ').trim());
145
+ console.log(`#${id} → discarded`);
146
+ }),
147
+ );
148
+
149
+ program
150
+ .command('review')
151
+ .description('record an independent review verdict')
152
+ .argument('<id>')
153
+ .argument('<verdict>', 'pass|fail')
154
+ .argument('[reason...]')
155
+ .option('--retry', 'route a failed review back to in-progress')
156
+ .option('--block', 'route a failed review to blocked')
157
+ .addHelpText(
158
+ 'after',
159
+ [
160
+ '',
161
+ 'Examples:',
162
+ ' changeledger review <id> pass',
163
+ ' changeledger review <id> fail --retry "<reason>"',
164
+ ' changeledger review <id> fail --block "<reason>"',
165
+ ].join('\n'),
166
+ )
167
+ .action(
168
+ action((id, verdict, reasonParts, options) => {
169
+ const mode = options.retry ? 'retry' : options.block ? 'block' : undefined;
170
+ const reason = reasonParts.join(' ').trim() || undefined;
171
+ review(id, verdict, { mode, reason });
172
+ console.log(`#${id} review ${verdict}${mode ? ` --${mode}` : ''}`);
173
+ }),
174
+ );
175
+
176
+ program
177
+ .command('owner')
178
+ .description("set or clear a change's owner")
179
+ .argument('<id>')
180
+ .argument('<name>')
181
+ .action(
182
+ action((id, name) => {
183
+ owner(id, name);
184
+ console.log(`#${id} owner → ${name === '-' ? '(cleared)' : name}`);
185
+ }),
186
+ );
187
+
188
+ program
189
+ .command('archive')
190
+ .description('hide a change in the viewer, or archive all graduated done changes')
191
+ .argument('[id]')
192
+ .option('--graduated', 'archive done changes already graduated or skipped')
193
+ .option('--dry-run', 'show what would be archived without writing')
194
+ .action(
195
+ action((id, options) => {
196
+ if (options.graduated) {
197
+ if (id) throw new Error('archive --graduated does not take an id');
198
+ const archived = archiveGraduated({ dryRun: options.dryRun });
199
+ for (const c of archived) console.log(`#${c.id} ${c.title}`);
200
+ console.log(
201
+ `${options.dryRun ? 'Would archive' : 'Archived'} ${archived.length} change(s)`,
202
+ );
203
+ return;
204
+ }
205
+ if (options.dryRun) throw new Error('--dry-run requires --graduated');
206
+ if (!id) throw new Error('archive requires <id> or --graduated');
207
+ archive(id, true);
208
+ console.log(`#${id} archived`);
209
+ }),
210
+ );
211
+
212
+ program
213
+ .command('unarchive')
214
+ .description('show a change in the viewer')
215
+ .argument('<id>')
216
+ .action(
217
+ action((id) => {
218
+ archive(id, false);
219
+ console.log(`#${id} unarchived`);
220
+ }),
221
+ );
222
+
223
+ program
224
+ .command('log')
225
+ .description('append a timestamped Log entry')
226
+ .argument('<id>')
227
+ .argument('<message...>')
228
+ .action(
229
+ action((id, messageParts) => {
230
+ log(id, messageParts.join(' ').trim());
231
+ console.log(`logged on #${id}`);
232
+ }),
233
+ );
234
+
235
+ program
236
+ .command('task')
237
+ .description('mark a Plan task')
238
+ .argument('<id>')
239
+ .argument('<action>', 'done|block')
240
+ .argument('<n>')
241
+ .argument('[reason...]')
242
+ .action(
243
+ action((id, taskAction, nStr, reasonParts) => {
244
+ const n = Number(nStr);
245
+ task(id, taskAction, n, reasonParts.join(' ').trim());
246
+ console.log(`task #${n} on #${id} → ${taskAction}`);
247
+ }),
248
+ );
249
+
250
+ program
251
+ .command('list')
252
+ .description('list changes')
253
+ .option('--status <status>', 'filter by status')
254
+ .option('--type <type>', 'filter by type')
255
+ .option('--json', 'print JSON')
256
+ .action(
257
+ action((options) => {
258
+ const items = list({ status: options.status, type: options.type });
259
+ if (options.json) {
260
+ console.log(JSON.stringify(items, null, 2));
261
+ } else {
262
+ for (const c of items) console.log(`${String(c.status).padEnd(12)} #${c.id} ${c.title}`);
263
+ }
264
+ }),
265
+ );
266
+
267
+ program
268
+ .command('show')
269
+ .description('print a change')
270
+ .argument('<id>')
271
+ .option('--json', 'print JSON')
272
+ .action(
273
+ action((id, options) => {
274
+ const c = show(id);
275
+ if (options.json) console.log(JSON.stringify(c, null, 2));
276
+ else console.log(`#${c.id} ${c.frontmatter.title} [${c.frontmatter.status}]`);
277
+ }),
278
+ );
279
+
280
+ program
281
+ .command('graduate')
282
+ .description('graduate a done change to persistent truth')
283
+ .argument('[change-id]')
284
+ .argument('[spec-slug]')
285
+ .argument('[reason...]')
286
+ .option('--into', 'graduate into an existing spec')
287
+ .option('--skip', 'mark graduation reviewed without a spec')
288
+ .option('--pending', 'list done changes not yet reviewed')
289
+ .addHelpText(
290
+ 'after',
291
+ [
292
+ '',
293
+ 'Examples:',
294
+ ' changeledger graduate <change-id> <spec-slug>',
295
+ ' changeledger graduate <change-id> <spec-slug> --into',
296
+ ' changeledger graduate <change-id> --skip [reason]',
297
+ ' changeledger graduate --pending',
298
+ ].join('\n'),
299
+ )
300
+ .action(
301
+ action((id, slug, reasonParts, options) => {
302
+ if (options.pending) {
303
+ const items = pendingGraduation();
304
+ if (!items.length) console.log('No changes pending graduation.');
305
+ for (const c of items) console.log(`#${c.id} ${c.title}`);
306
+ return;
307
+ }
308
+ if (options.skip) {
309
+ if (!id) throw new Error('Usage: changeledger graduate <change-id> --skip [reason]');
310
+ const reason = [slug, ...reasonParts].filter(Boolean).join(' ').trim();
311
+ skipGraduation(id, reason);
312
+ console.log(`#${id} graduation skipped`);
313
+ return;
314
+ }
315
+ if (!id || !slug) throw new Error('Usage: changeledger graduate <change-id> <spec-slug>');
316
+ const file = graduate(id, slug, process.cwd(), { into: options.into });
317
+ console.log(`Graduated #${id} → ${file}`);
318
+ }),
319
+ );
320
+
321
+ const releaseCommand = program
322
+ .command('release')
323
+ .description('plan and record portable SemVer releases');
324
+
325
+ releaseCommand
326
+ .command('init')
327
+ .description('initialize release history from the current published version')
328
+ .argument('<version>')
329
+ .action(
330
+ action((version) => {
331
+ const { file, manifest } = initReleaseHistory(version);
332
+ console.log(`Initialized release ${manifest.version} baseline → ${file}`);
333
+ }),
334
+ );
335
+
336
+ releaseCommand
337
+ .command('plan')
338
+ .description('calculate the next release without writing files')
339
+ .option('--json', 'print a stable JSON plan')
340
+ .action(
341
+ action((options) => {
342
+ const plan = releasePlan();
343
+ if (options.json) {
344
+ console.log(JSON.stringify(plan, null, 2));
345
+ return;
346
+ }
347
+ if (!plan.releasable) {
348
+ console.log(
349
+ `No release required from ${plan.currentVersion}: ${plan.changes.length} pending change(s), highest impact none.`,
350
+ );
351
+ return;
352
+ }
353
+ console.log(`${plan.currentVersion} → ${plan.nextVersion} (${plan.impact})`);
354
+ for (const change of plan.changes) {
355
+ console.log(` #${change.id} [${change.releaseImpact}] ${change.title}`);
356
+ }
357
+ }),
358
+ );
359
+
360
+ releaseCommand
361
+ .command('record')
362
+ .description('record the currently calculated release')
363
+ .argument('<version>')
364
+ .action(
365
+ action((version) => {
366
+ const { file, manifest } = recordRelease(version);
367
+ console.log(`Recorded release ${manifest.version} → ${file}`);
368
+ }),
369
+ );
370
+
371
+ if (process.argv.length <= 2) {
372
+ console.log(USAGE);
373
+ } else {
374
+ program.parse();
375
+ }
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "changeledger",
3
+ "version": "0.3.0",
4
+ "description": "Turn conversations into buildable changes.",
5
+ "type": "module",
6
+ "bin": {
7
+ "changeledger": "bin/changeledger.mjs"
8
+ },
9
+ "homepage": "https://github.com/rarc88/changeledger#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/rarc88/changeledger.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/rarc88/changeledger/issues"
16
+ },
17
+ "author": "Roberto Ruiz (https://github.com/rarc88)",
18
+ "keywords": [
19
+ "spec",
20
+ "specification",
21
+ "documentation",
22
+ "sdd",
23
+ "spec-driven-development",
24
+ "change-management",
25
+ "requirements",
26
+ "tdd",
27
+ "agents",
28
+ "ai-agents",
29
+ "cli",
30
+ "workflow"
31
+ ],
32
+ "scripts": {
33
+ "lint": "biome check",
34
+ "format": "biome check --write",
35
+ "test": "node --test",
36
+ "test:coverage": "node --test --experimental-test-coverage",
37
+ "audit:deps": "pnpm audit --audit-level moderate",
38
+ "check": "node bin/changeledger.mjs check",
39
+ "verify": "pnpm lint && pnpm test && pnpm check",
40
+ "prepublishOnly": "pnpm verify"
41
+ },
42
+ "engines": {
43
+ "node": ">=24"
44
+ },
45
+ "packageManager": "pnpm@10.31.0",
46
+ "exports": {
47
+ "./package.json": "./package.json"
48
+ },
49
+ "files": [
50
+ "bin",
51
+ "src",
52
+ "templates",
53
+ "AGENTS.md"
54
+ ],
55
+ "license": "MIT",
56
+ "lint-staged": {
57
+ "*.{js,mjs,json,jsonc,css}": "biome check --write --no-errors-on-unmatched"
58
+ },
59
+ "devDependencies": {
60
+ "@biomejs/biome": "^2.5.0",
61
+ "jsdom": "^29.1.1",
62
+ "lint-staged": "^17.0.7"
63
+ },
64
+ "dependencies": {
65
+ "commander": "^14.0.3",
66
+ "dompurify": "^3.4.11",
67
+ "lit-html": "^3.3.3",
68
+ "marked": "^18.0.5",
69
+ "mermaid": "^11.15.0",
70
+ "yaml": "^2.9.0"
71
+ }
72
+ }