changeledger 0.6.3 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/bin/changeledger.mjs +27 -5
- package/package.json +1 -1
- package/src/commands/graduate.mjs +56 -38
- package/templates/contract/close.md +17 -9
- package/templates/contract/core.md +3 -2
package/README.md
CHANGED
|
@@ -96,9 +96,11 @@ changeledger discard <id> <reason>
|
|
|
96
96
|
### Preserve completed truth
|
|
97
97
|
|
|
98
98
|
```sh
|
|
99
|
-
changeledger graduate <id> <spec-slug>
|
|
100
|
-
|
|
99
|
+
changeledger graduate <id> <spec-slug> --new # create a marked scaffold; remains pending
|
|
100
|
+
# refine the scaffold and remove its marker
|
|
101
|
+
changeledger graduate <id> <spec-slug> --into # finalize an existing refined spec
|
|
101
102
|
changeledger graduate <id> --skip [reason] # record that no spec is needed
|
|
103
|
+
changeledger graduate --pending # list unresolved graduation decisions
|
|
102
104
|
changeledger archive --graduated [--dry-run] # hide resolved changes from the board
|
|
103
105
|
```
|
|
104
106
|
|
package/bin/changeledger.mjs
CHANGED
|
@@ -15,7 +15,12 @@ import {
|
|
|
15
15
|
} from '../src/commands/agent.mjs';
|
|
16
16
|
import { check } from '../src/commands/check.mjs';
|
|
17
17
|
import { context } from '../src/commands/context.mjs';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
graduate,
|
|
20
|
+
pendingGraduation,
|
|
21
|
+
scaffoldSpec,
|
|
22
|
+
skipGraduation,
|
|
23
|
+
} from '../src/commands/graduate.mjs';
|
|
19
24
|
import { init } from '../src/commands/init.mjs';
|
|
20
25
|
import { newChange } from '../src/commands/new.mjs';
|
|
21
26
|
import { registerRepo } from '../src/commands/register.mjs';
|
|
@@ -46,7 +51,7 @@ const USAGE = `ChangeLedger (changeledger)
|
|
|
46
51
|
changeledger task <id> done|block <n> [reason] mark a Plan task
|
|
47
52
|
changeledger list [--status S] [--type T] [--json] list changes
|
|
48
53
|
changeledger show <id> [--json] print a change
|
|
49
|
-
changeledger graduate <change-id> <spec-slug>
|
|
54
|
+
changeledger graduate <change-id> <spec-slug> --new create a spec scaffold to refine
|
|
50
55
|
changeledger graduate <change-id> <spec-slug> --into graduate into an existing spec
|
|
51
56
|
changeledger graduate <change-id> --skip [reason] mark graduation reviewed, no spec
|
|
52
57
|
changeledger graduate --pending list done changes not yet reviewed
|
|
@@ -298,7 +303,8 @@ program
|
|
|
298
303
|
.argument('[change-id]')
|
|
299
304
|
.argument('[spec-slug]')
|
|
300
305
|
.argument('[reason...]')
|
|
301
|
-
.option('--
|
|
306
|
+
.option('--new', 'create a spec scaffold without resolving graduation')
|
|
307
|
+
.option('--into', 'finalize graduation into an existing refined spec')
|
|
302
308
|
.option('--skip', 'mark graduation reviewed without a spec')
|
|
303
309
|
.option('--pending', 'list done changes not yet reviewed')
|
|
304
310
|
.addHelpText(
|
|
@@ -306,7 +312,7 @@ program
|
|
|
306
312
|
[
|
|
307
313
|
'',
|
|
308
314
|
'Examples:',
|
|
309
|
-
' changeledger graduate <change-id> <spec-slug>',
|
|
315
|
+
' changeledger graduate <change-id> <spec-slug> --new',
|
|
310
316
|
' changeledger graduate <change-id> <spec-slug> --into',
|
|
311
317
|
' changeledger graduate <change-id> --skip [reason]',
|
|
312
318
|
' changeledger graduate --pending',
|
|
@@ -314,7 +320,15 @@ program
|
|
|
314
320
|
)
|
|
315
321
|
.action(
|
|
316
322
|
action((id, slug, reasonParts, options) => {
|
|
323
|
+
const modeCount = [options.new, options.into, options.skip, options.pending].filter(
|
|
324
|
+
Boolean,
|
|
325
|
+
).length;
|
|
326
|
+
const modeUsage =
|
|
327
|
+
'Usage: changeledger graduate requires exactly one mode: --new, --into, --skip, or --pending';
|
|
328
|
+
if (modeCount !== 1) throw new Error(modeUsage);
|
|
329
|
+
|
|
317
330
|
if (options.pending) {
|
|
331
|
+
if (id || slug || reasonParts.length) throw new Error(modeUsage);
|
|
318
332
|
const items = pendingGraduation();
|
|
319
333
|
if (!items.length) console.log('No changes pending graduation.');
|
|
320
334
|
for (const c of items) console.log(`#${c.id} ${c.title}`);
|
|
@@ -327,7 +341,15 @@ program
|
|
|
327
341
|
console.log(`#${id} graduation skipped`);
|
|
328
342
|
return;
|
|
329
343
|
}
|
|
330
|
-
|
|
344
|
+
|
|
345
|
+
if (!id || !slug || reasonParts.length) throw new Error(modeUsage);
|
|
346
|
+
if (options.new) {
|
|
347
|
+
const file = scaffoldSpec(id, slug);
|
|
348
|
+
console.log(
|
|
349
|
+
`Created spec scaffold ${file}. Refine it, then run: changeledger graduate ${id} ${slug} --into`,
|
|
350
|
+
);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
331
353
|
const file = graduate(id, slug, process.cwd(), { into: options.into });
|
|
332
354
|
console.log(`Graduated #${id} → ${file}`);
|
|
333
355
|
}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Graduation is intentionally two-phase for a new spec. `scaffoldSpec` creates
|
|
2
|
+
// an editable seed without resolving the change; `graduate --into` links only
|
|
3
|
+
// after the durable wording has been reviewed.
|
|
4
4
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
@@ -13,55 +13,73 @@ import { slugify } from '../slug.mjs';
|
|
|
13
13
|
import { appendLog, setReviewed, setSpecUpdated } from '../writer.mjs';
|
|
14
14
|
import { serializeScalar } from '../yaml.mjs';
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
// `updated` and links it back, but leaves the body to the agent (who knows what
|
|
18
|
-
// to refine). Without `into`, a new spec is scaffolded and an existing one is an
|
|
19
|
-
// error. Both routes share the same change-side record (marker + reviewed).
|
|
20
|
-
export function graduate(id, slug, cwd = process.cwd(), { into = false } = {}) {
|
|
21
|
-
const { config, repoRoot, file: changeFile } = resolveChange(cwd, id);
|
|
16
|
+
const SPEC_SCAFFOLD_MARKER = '<!-- changeledger:spec-scaffold -->';
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
function graduationTarget(id, slug, cwd) {
|
|
19
|
+
const resolved = resolveChange(cwd, id);
|
|
20
|
+
const specsDir = resolveSpecsDir(resolved.repoRoot, resolved.config);
|
|
24
21
|
const specName = `${slugify(slug)}.md`;
|
|
25
|
-
|
|
22
|
+
return { ...resolved, specsDir, specName, specFile: path.join(specsDir, specName) };
|
|
23
|
+
}
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
function requireDone(changeText) {
|
|
26
|
+
const change = parseChange(changeText);
|
|
27
|
+
if (change.frontmatter.status !== 'done') {
|
|
28
|
+
throw new Error('only done changes can be graduated/skipped');
|
|
29
|
+
}
|
|
30
|
+
return change;
|
|
31
|
+
}
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
throw new Error('only done changes can be graduated/skipped');
|
|
38
|
-
}
|
|
33
|
+
export function scaffoldSpec(id, slug, cwd = process.cwd()) {
|
|
34
|
+
const { file: changeFile, specsDir, specName, specFile } = graduationTarget(id, slug, cwd);
|
|
35
|
+
if (fs.existsSync(specFile)) throw new Error(`Spec "${specName}" already exists`);
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const seed = seedStage ? seedStage.body : '';
|
|
48
|
-
const title = change.frontmatter.title;
|
|
49
|
-
|
|
50
|
-
const content = `---
|
|
51
|
-
title: ${serializeScalar(title)}
|
|
37
|
+
const change = requireDone(fs.readFileSync(changeFile, 'utf8'));
|
|
38
|
+
const seedStage =
|
|
39
|
+
change.stages.find((stage) => stage.key === 'specification') ??
|
|
40
|
+
change.stages.find((stage) => stage.key === 'proposal');
|
|
41
|
+
const seed = seedStage ? seedStage.body : '';
|
|
42
|
+
const content = `---
|
|
43
|
+
title: ${serializeScalar(change.frontmatter.title)}
|
|
52
44
|
updated: ${nowUtc()}
|
|
53
45
|
tags: [${change.frontmatter.type}]
|
|
54
46
|
---
|
|
55
47
|
|
|
56
|
-
# ${title}
|
|
48
|
+
# ${change.frontmatter.title}
|
|
57
49
|
|
|
58
|
-
|
|
50
|
+
${SPEC_SCAFFOLD_MARKER}
|
|
51
|
+
|
|
52
|
+
> Scaffold from change ${id}; replace this seed with durable current truth before --into.
|
|
59
53
|
|
|
60
54
|
${seed}
|
|
61
55
|
`;
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
|
|
57
|
+
fs.mkdirSync(specsDir, { recursive: true });
|
|
58
|
+
writeFileAtomic(specFile, content);
|
|
59
|
+
return specFile;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Finalizes graduation into an EXISTING, manually refined spec. The command
|
|
63
|
+
// refreshes `updated` and links it back, but never overwrites the body.
|
|
64
|
+
export function graduate(id, slug, cwd = process.cwd(), { into = false } = {}) {
|
|
65
|
+
if (!into) {
|
|
66
|
+
throw new Error('graduation mode required: use --new, --into, or --skip');
|
|
67
|
+
}
|
|
68
|
+
const { file: changeFile, specName, specFile } = graduationTarget(id, slug, cwd);
|
|
69
|
+
|
|
70
|
+
if (!fs.existsSync(specFile)) {
|
|
71
|
+
throw new Error(`Spec "${specName}" does not exist — use --new to create a scaffold`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
mutateFileAtomic(changeFile, (changeText) => {
|
|
75
|
+
requireDone(changeText);
|
|
76
|
+
const specText = fs.readFileSync(specFile, 'utf8');
|
|
77
|
+
if (specText.includes(SPEC_SCAFFOLD_MARKER)) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Spec "${specName}" still contains the scaffold marker — refine it and remove the marker before --into`,
|
|
80
|
+
);
|
|
64
81
|
}
|
|
82
|
+
writeFileAtomic(specFile, setSpecUpdated(specText, nowUtc()));
|
|
65
83
|
|
|
66
84
|
let text = appendLog(changeText, nowUtc(), `graduado a spec \`${specName}\``);
|
|
67
85
|
text = setReviewed(text, true);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Closing Accepted Work
|
|
2
2
|
|
|
3
|
-
The human accepted this change. Resolve persistent truth before archiving.
|
|
3
|
+
The human accepted this change. Resolve persistent truth before archiving. Run
|
|
4
|
+
`changeledger context <id>` after acceptance even if the base context was loaded
|
|
5
|
+
earlier; this lifecycle-specific close overlay is not part of the base context.
|
|
4
6
|
Changes describe a journey; `.changeledger/specs/*.md` describe the current
|
|
5
7
|
capability, architecture or domain truth that code reflects.
|
|
6
8
|
|
|
@@ -15,19 +17,25 @@ tags: []
|
|
|
15
17
|
---
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
Choose one graduation
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
Choose exactly one explicit graduation mode. A positional slug without a mode
|
|
21
|
+
is an error, so words such as `skip` or `skip-*` can never silently become specs.
|
|
22
|
+
|
|
23
|
+
- For a new spec, run `changeledger graduate <id> <spec-slug> --new`. This creates
|
|
24
|
+
a seed from the change's Specification or Proposal but leaves graduation
|
|
25
|
+
pending. Rewrite it as concise durable current truth and remove the explicit
|
|
26
|
+
scaffold marker. Then run `changeledger graduate <id> <spec-slug> --into` to
|
|
27
|
+
finalize it. `--into` refuses an unrefined marked scaffold.
|
|
28
|
+
- For an existing spec, the agent edits its body first, then runs
|
|
29
|
+
`changeledger graduate <id> <spec-slug> --into`. It refreshes `updated`, records
|
|
30
|
+
the link and does not overwrite the body.
|
|
24
31
|
- `changeledger graduate <id> --skip [reason]` records that no persistent truth
|
|
25
32
|
changed.
|
|
26
33
|
- `changeledger graduate --pending` lists accepted changes whose decision is
|
|
27
34
|
unresolved.
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
Finalization with `--into` and skip both set `reviewed: true` on the change;
|
|
37
|
+
`--new` does not. The boolean means the persistent-truth question was settled,
|
|
38
|
+
not necessarily that a spec was created.
|
|
31
39
|
The graduation link remains derivable from the Log marker `graduado a spec`,
|
|
32
40
|
which carries the spec link, rather than from the boolean flag.
|
|
33
41
|
|
|
@@ -26,8 +26,9 @@ modifying files.
|
|
|
26
26
|
6. For types that require review, use a fresh clean-context reviewer before
|
|
27
27
|
human validation.
|
|
28
28
|
7. Stop at `in-validation`. The agent never accepts on the human's behalf.
|
|
29
|
-
8. After human acceptance,
|
|
30
|
-
then
|
|
29
|
+
8. After human acceptance, reload `changeledger context <id>` for the `done`
|
|
30
|
+
change, then graduate persistent truth or run
|
|
31
|
+
`changeledger graduate <id> --skip [reason]`; archive only after that decision.
|
|
31
32
|
|
|
32
33
|
If no approved or in-progress change applies, do not silently edit repository
|
|
33
34
|
files. Create or update a change, or ask the human whether a purely operational,
|