ushman-ledger 1.1.0 → 1.2.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.
- package/AGENTS.md +11 -7
- package/CHANGELOG.md +6 -0
- package/README.md +22 -3
- package/dist/blobs.js +3 -3
- package/dist/builders.d.ts +43 -0
- package/dist/builders.d.ts.map +1 -1
- package/dist/builders.js +7 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +222 -41
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +104 -4
- package/dist/handle.d.ts +3 -1
- package/dist/handle.d.ts.map +1 -1
- package/dist/handle.js +19 -1
- package/dist/helpers.d.ts +7 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +38 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/list.d.ts +43 -1
- package/dist/list.d.ts.map +1 -1
- package/dist/note.d.ts +20 -0
- package/dist/note.d.ts.map +1 -1
- package/dist/note.js +5 -0
- package/dist/patch-resolver.d.ts +27 -0
- package/dist/patch-resolver.d.ts.map +1 -0
- package/dist/patch-resolver.js +184 -0
- package/dist/read-index.d.ts +7 -7
- package/dist/record.d.ts.map +1 -1
- package/dist/record.js +15 -39
- package/dist/render/migration-log.d.ts +3 -0
- package/dist/render/migration-log.d.ts.map +1 -0
- package/dist/render/migration-log.js +72 -0
- package/dist/render/retro.d.ts.map +1 -1
- package/dist/render/retro.js +40 -21
- package/dist/render/workspace-narrative.d.ts +6 -0
- package/dist/render/workspace-narrative.d.ts.map +1 -0
- package/dist/render/workspace-narrative.js +69 -0
- package/dist/schema/entry-core.d.ts +110 -0
- package/dist/schema/entry-core.d.ts.map +1 -0
- package/dist/schema/entry-core.js +143 -0
- package/dist/schema/entry-migrations.d.ts +3 -0
- package/dist/schema/entry-migrations.d.ts.map +1 -0
- package/dist/schema/entry-migrations.js +48 -0
- package/dist/schema/entry-read.d.ts +694 -0
- package/dist/schema/entry-read.d.ts.map +1 -0
- package/dist/schema/entry-read.js +92 -0
- package/dist/schema/entry-write.d.ts +865 -0
- package/dist/schema/entry-write.d.ts.map +1 -0
- package/dist/schema/entry-write.js +105 -0
- package/dist/schema/entry.d.ts +6 -1369
- package/dist/schema/entry.d.ts.map +1 -1
- package/dist/schema/entry.js +9 -286
- package/dist/schema/note.d.ts +1 -1
- package/dist/schema/note.d.ts.map +1 -1
- package/dist/schema/note.js +12 -1
- package/dist/storage/filesystem.d.ts +2 -0
- package/dist/storage/filesystem.d.ts.map +1 -1
- package/dist/storage/filesystem.js +2 -0
- package/dist/storage/lock-reclaimer.d.ts +2 -0
- package/dist/storage/lock-reclaimer.d.ts.map +1 -0
- package/dist/storage/lock-reclaimer.js +45 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/AGENTS.md
CHANGED
|
@@ -23,13 +23,17 @@ An append-only ledger library and CLI for ushman v4 workspaces. It owns ledger s
|
|
|
23
23
|
## Read order
|
|
24
24
|
|
|
25
25
|
1. `README.md`
|
|
26
|
-
2. `src/schema/entry.ts`
|
|
27
|
-
3. `src/
|
|
28
|
-
4. `src/
|
|
29
|
-
5. `src/
|
|
30
|
-
6. `src/
|
|
31
|
-
7. `src/
|
|
32
|
-
8. `src/
|
|
26
|
+
2. `src/schema/entry-core.ts`
|
|
27
|
+
3. `src/schema/entry-read.ts`
|
|
28
|
+
4. `src/schema/entry-write.ts`
|
|
29
|
+
5. `src/schema/entry-migrations.ts`
|
|
30
|
+
6. `src/schema/entry.ts`
|
|
31
|
+
7. `src/storage/filesystem.ts`
|
|
32
|
+
8. `src/record.ts`
|
|
33
|
+
9. `src/handle.ts`
|
|
34
|
+
10. `src/coverage.ts`
|
|
35
|
+
11. `src/doctor.ts`
|
|
36
|
+
12. `src/cli.ts`
|
|
33
37
|
|
|
34
38
|
## Commands
|
|
35
39
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.0] - 2026-05-23
|
|
4
|
+
|
|
5
|
+
- Added `change-log` records, narrative note subkinds, and `migration-log-md` / `workspace-narrative-md` render targets.
|
|
6
|
+
- Split entry schema parsing into focused read/write/core modules and moved compatibility coercions behind dedicated parse migrations.
|
|
7
|
+
- Extracted patch-source resolution behind a dedicated resolver with focused tests and more actionable hash mismatch failures.
|
|
8
|
+
|
|
3
9
|
## [1.0.1] - 2026-05-16
|
|
4
10
|
|
|
5
11
|
- Tightened the schema surface to the ledger primitives the v4 orchestrator actively uses.
|
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Append-only workspace ledger library and CLI for ushman v4 workspaces.
|
|
|
9
9
|
- Content-hash idempotency
|
|
10
10
|
- Patch blob storage
|
|
11
11
|
- Retro / JSONL / timeline / dependency-graph rendering
|
|
12
|
+
- Change-log / workspace narrative markdown rendering
|
|
12
13
|
- Archive integrity output
|
|
13
14
|
- Doctor and coverage helpers
|
|
14
15
|
|
|
@@ -58,6 +59,7 @@ await ledger.record(
|
|
|
58
59
|
);
|
|
59
60
|
|
|
60
61
|
await ledger.render({ to: 'retro' });
|
|
62
|
+
await ledger.render({ to: 'migration-log-md' });
|
|
61
63
|
await ledger.archive('/tmp/ledger.tgz');
|
|
62
64
|
```
|
|
63
65
|
|
|
@@ -67,22 +69,37 @@ await ledger.archive('/tmp/ledger.tgz');
|
|
|
67
69
|
ushman-ledger record --workspace=<ws> --kind=tool-invocation --phase=capture --summary="capture started"
|
|
68
70
|
ushman-ledger record --workspace=<ws> --kind=agent-patch --phase=cleanup --summary="capture git diff" --rationale="track working tree change" --diff-from-git=HEAD
|
|
69
71
|
ushman-ledger record --workspace=<ws> --kind=operator-decision --phase=cleanup --summary="manual override" --action=ledger-hand-edit --check-id=manual-review --rationale="operator edited the ledger after audit"
|
|
72
|
+
ushman-ledger record --workspace=<ws> --kind=change-log --subkind=semantic-cleanup --phase=cleanup --summary="split schema modules" --diff=/tmp/change.patch --hypothesis="smaller schema modules keep the public API stable" --commands=$'bun test\nbun run typecheck' --smoke-result=pass --parity-status=green --rollback-plan="revert the schema split"
|
|
70
73
|
ushman-ledger note regression --workspace=<ws> --phase=cleanup --summary="runtime drift" --body=/tmp/note.md
|
|
74
|
+
ushman-ledger note cleanup-wave --workspace=<ws> --phase=cleanup --summary="wave 1" --body=/tmp/narrative.md
|
|
71
75
|
ushman-ledger list --workspace=<ws> --json
|
|
72
76
|
ushman-ledger render --workspace=<ws> --to=retro
|
|
77
|
+
ushman-ledger render --workspace=<ws> --to=migration-log-md
|
|
78
|
+
ushman-ledger render --workspace=<ws> --to=workspace-narrative-md
|
|
73
79
|
ushman-ledger render --workspace=<ws> --to=jsonl --out=/tmp/ledger.jsonl
|
|
74
80
|
ushman-ledger render --workspace=<ws> --to=dependency-graph --out=/tmp/ledger.mmd
|
|
75
81
|
ushman-ledger archive --workspace=<ws> --out=/tmp/ledger.tgz
|
|
76
82
|
ushman-ledger doctor --workspace=<ws>
|
|
77
83
|
```
|
|
78
84
|
|
|
79
|
-
Valid record kinds: `tool-invocation`, `agent-patch`, `operator-patch`, `operator-decision`, `validator-result`, `runtime-event`, `note`, `correction`, `strip-decision-reverted`
|
|
85
|
+
Valid record kinds: `tool-invocation`, `agent-patch`, `operator-patch`, `operator-decision`, `validator-result`, `runtime-event`, `note`, `correction`, `strip-decision-reverted`, `change-log`
|
|
80
86
|
|
|
81
87
|
Valid phases: `capture`, `intake`, `seed`, `vendor-extract`, `cleanup`, `parity`, `characterize`, `equiv`, `analyze`, `recover`, `ship`, `migration`
|
|
82
88
|
|
|
83
|
-
Valid note subkinds: `regression`, `automation`, `retro`, `operator`, `tooling-gap`
|
|
89
|
+
Valid note subkinds: `regression`, `automation`, `retro`, `operator`, `tooling-gap`, `cleanup-wave`, `verified-flow`, `open-issue`, `decomposition-wave`, `semantic-cleanup-summary`
|
|
84
90
|
|
|
85
|
-
Valid render targets: `retro`, `jsonl`, `timeline-html`, `dependency-graph`
|
|
91
|
+
Valid render targets: `retro`, `jsonl`, `timeline-html`, `dependency-graph`, `migration-log-md`, `workspace-narrative-md`
|
|
92
|
+
|
|
93
|
+
## Change-log records
|
|
94
|
+
|
|
95
|
+
`change-log` entries are append-only structured narrative records for migration and cleanup work. They accept:
|
|
96
|
+
|
|
97
|
+
- `subkind`: `pre-change-checkpoint`, `semantic-cleanup`, `vendor-extract`, `decomposition`, `rollback`, `hotfix`, `smoke`
|
|
98
|
+
- `filesChanged`: explicit CSV paths via `--files-changed`, or automatic diffstat-style derivation from `--diff` / `--diff-from-git`
|
|
99
|
+
- optional narrative fields: `hypothesis`, `commandsRun`, `smokeResult`, `smokeNotes`, `parityStatus`, `rollbackPlan`
|
|
100
|
+
- `rollsBack`: required when `subkind=rollback`
|
|
101
|
+
|
|
102
|
+
`migration-log-md` renders only `change-log` entries. `workspace-narrative-md` renders only the dedicated narrative note subkinds.
|
|
86
103
|
|
|
87
104
|
## Workspace prerequisite
|
|
88
105
|
|
|
@@ -164,7 +181,9 @@ Valid render targets: `retro`, `jsonl`, `timeline-html`, `dependency-graph`
|
|
|
164
181
|
pending-archives/
|
|
165
182
|
blobs/
|
|
166
183
|
render.md
|
|
184
|
+
render.migration-log.md
|
|
167
185
|
render.timeline.html
|
|
186
|
+
render.workspace-narrative.md
|
|
168
187
|
<phase>/
|
|
169
188
|
<timestamp>-<sequence>-<hash>.json
|
|
170
189
|
```
|
package/dist/blobs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile
|
|
1
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { sha256File, sha256Hex } from "./json.js";
|
|
4
4
|
import { resolveLedgerPaths, writeAtomicTextFile } from "./storage/filesystem.js";
|
|
@@ -34,11 +34,11 @@ export const storePatchBlob = async (workspaceRoot, patchText) => {
|
|
|
34
34
|
const blobPath = buildBlobPath(workspaceRoot, blobSha256);
|
|
35
35
|
let shouldWrite = true;
|
|
36
36
|
try {
|
|
37
|
-
await stat(blobPath);
|
|
38
37
|
shouldWrite = (await sha256File(blobPath)) !== blobSha256;
|
|
39
38
|
}
|
|
40
39
|
catch (error) {
|
|
41
|
-
|
|
40
|
+
const code = error.code;
|
|
41
|
+
if (code !== 'ENOENT') {
|
|
42
42
|
throw error;
|
|
43
43
|
}
|
|
44
44
|
}
|
package/dist/builders.d.ts
CHANGED
|
@@ -104,6 +104,49 @@ export declare const buildCorrectionRecord: (input: BuildRecordInput<Extract<Led
|
|
|
104
104
|
phase: "capture" | "intake" | "seed" | "vendor-extract" | "cleanup" | "parity" | "characterize" | "equiv" | "analyze" | "recover" | "ship" | "migration";
|
|
105
105
|
summary: string;
|
|
106
106
|
};
|
|
107
|
+
/** Build a `change-log` record. */
|
|
108
|
+
export declare const buildChangeLogRecord: (input: BuildRecordInput<Extract<LedgerRecord, {
|
|
109
|
+
kind: "change-log";
|
|
110
|
+
}>>) => {
|
|
111
|
+
commandsRun?: string[] | undefined;
|
|
112
|
+
filesChanged: {
|
|
113
|
+
added?: number | undefined;
|
|
114
|
+
path: string;
|
|
115
|
+
removed?: number | undefined;
|
|
116
|
+
}[];
|
|
117
|
+
hypothesis?: string | undefined;
|
|
118
|
+
kind: "change-log";
|
|
119
|
+
parityStatus?: "not-run" | "green" | "yellow" | "red" | undefined;
|
|
120
|
+
rollbackPlan?: string | undefined;
|
|
121
|
+
rollsBack?: string | undefined;
|
|
122
|
+
smokeNotes?: string | undefined;
|
|
123
|
+
smokeResult?: "pass" | "fail" | "partial" | "not-run" | undefined;
|
|
124
|
+
subkind: "vendor-extract" | "pre-change-checkpoint" | "semantic-cleanup" | "decomposition" | "rollback" | "hotfix" | "smoke";
|
|
125
|
+
details?: {
|
|
126
|
+
[x: string]: unknown;
|
|
127
|
+
} | undefined;
|
|
128
|
+
emitter: {
|
|
129
|
+
tool: string;
|
|
130
|
+
user?: string | undefined;
|
|
131
|
+
version: string;
|
|
132
|
+
};
|
|
133
|
+
idempotencyKey?: string | undefined;
|
|
134
|
+
links?: ({
|
|
135
|
+
affectedFiles?: string[] | undefined;
|
|
136
|
+
blobs?: string[] | undefined;
|
|
137
|
+
briefId?: string | undefined;
|
|
138
|
+
correctsLedgerId?: string | undefined;
|
|
139
|
+
gitRef?: string | undefined;
|
|
140
|
+
idempotencyKey?: string | undefined;
|
|
141
|
+
stripDecisionId?: string | undefined;
|
|
142
|
+
supersedesLedgerId?: string | undefined;
|
|
143
|
+
validatorVerdictId?: string | undefined;
|
|
144
|
+
} & {
|
|
145
|
+
[key: string]: unknown;
|
|
146
|
+
}) | undefined;
|
|
147
|
+
phase: "capture" | "intake" | "seed" | "vendor-extract" | "cleanup" | "parity" | "characterize" | "equiv" | "analyze" | "recover" | "ship" | "migration";
|
|
148
|
+
summary: string;
|
|
149
|
+
};
|
|
107
150
|
/** Build a `strip-decision-reverted` record. */
|
|
108
151
|
export declare const buildStripDecisionRevertedRecord: (input: BuildRecordInput<Extract<LedgerRecord, {
|
|
109
152
|
kind: "strip-decision-reverted";
|
package/dist/builders.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../src/builders.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../src/builders.ts"],"names":[],"mappings":"AACA,OAAO,EAGH,KAAK,YAAY,EAIpB,MAAM,mBAAmB,CAAC;AAE3B,KAAK,gBAAgB,CAAC,OAAO,SAAS,YAAY,IAAI,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG;IAC1E,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;CACnC,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,2BAA2B,GACpC,OAAO,gBAAgB,CAAC,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAM3E,CAAC;AAEP,yCAAyC;AACzC,eAAO,MAAM,0BAA0B,GACnC,OAAO,gBAAgB,CAAC,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAK1E,CAAC;AAEP,sEAAsE;AACtE,eAAO,MAAM,qBAAqB,GAAI,OAAO,gBAAgB,CAAC,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;CAItG,CAAC;AAEP,mCAAmC;AACnC,eAAO,MAAM,oBAAoB,GAAI,OAAO,gBAAgB,CAAC,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAIrG,CAAC;AAEP,gDAAgD;AAChD,eAAO,MAAM,gCAAgC,GACzC,OAAO,gBAAgB,CAAC,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,yBAAyB,CAAA;CAAE,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAKjF,CAAC"}
|
package/dist/builders.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as v from 'valibot';
|
|
2
|
-
import {
|
|
2
|
+
import { ChangeLogRecordSchema, CorrectionRecordSchema, OperatorDecisionRecordSchema, StripDecisionRevertedRecordSchema, ValidatorResultRecordSchema, } from "./schema/entry.js";
|
|
3
3
|
/** Build an `operator-decision` record with a validated structured payload. */
|
|
4
4
|
export const buildOperatorDecisionRecord = (input) => v.parse(OperatorDecisionRecordSchema, {
|
|
5
5
|
...input,
|
|
6
6
|
kind: 'operator-decision',
|
|
7
|
-
payload:
|
|
7
|
+
payload: input.payload,
|
|
8
8
|
});
|
|
9
9
|
/** Build a `validator-result` record. */
|
|
10
10
|
export const buildValidatorResultRecord = (input) => v.parse(ValidatorResultRecordSchema, {
|
|
@@ -16,6 +16,11 @@ export const buildCorrectionRecord = (input) => v.parse(CorrectionRecordSchema,
|
|
|
16
16
|
...input,
|
|
17
17
|
kind: 'correction',
|
|
18
18
|
});
|
|
19
|
+
/** Build a `change-log` record. */
|
|
20
|
+
export const buildChangeLogRecord = (input) => v.parse(ChangeLogRecordSchema, {
|
|
21
|
+
...input,
|
|
22
|
+
kind: 'change-log',
|
|
23
|
+
});
|
|
19
24
|
/** Build a `strip-decision-reverted` record. */
|
|
20
25
|
export const buildStripDecisionRevertedRecord = (input) => v.parse(StripDecisionRevertedRecordSchema, {
|
|
21
26
|
...input,
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAmDA,KAAK,UAAU,GAAG;IACd,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE;QACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC5B,CAAC;IACF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,cAAc,GAAG,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC,CAAC;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;CAC1C,CAAC;AAiwBF,eAAO,MAAM,YAAY,GAAU,MAAM,SAAS,MAAM,EAAE,EAAE,UAAS,OAAO,CAAC,UAAU,CAAM,KAAG,OAAO,CAAC,MAAM,CAqC7G,CAAC;AAEF,eAAO,MAAM,IAAI,GAAU,OAAM,SAAS,MAAM,EAA0B,KAAG,OAAO,CAAC,MAAM,CAE1F,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -7,12 +7,33 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
8
|
import * as v from 'valibot';
|
|
9
9
|
import { openLedger } from "./handle.js";
|
|
10
|
-
import {
|
|
10
|
+
import { deriveFilesChangedFromPatch } from "./patch-resolver.js";
|
|
11
|
+
import { ChangeLogParityStatusSchema, ChangeLogSmokeResultSchema, ChangeLogSubkindSchema, LEDGER_KINDS, LEDGER_PHASES, parseLedgerRecord, WorkspaceRelativePathSchema, } from "./schema/entry.js";
|
|
11
12
|
import { NoteSubkindSchema } from "./schema/note.js";
|
|
12
13
|
import { LEDGER_LIBRARY_VERSION } from "./version.js";
|
|
13
14
|
const execFileAsync = promisify(execFile);
|
|
15
|
+
const GIT_DIFF_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
14
16
|
const GIT_DIFF_TIMEOUT_MS = 30_000;
|
|
15
|
-
const RENDER_TARGETS = [
|
|
17
|
+
const RENDER_TARGETS = [
|
|
18
|
+
'retro',
|
|
19
|
+
'jsonl',
|
|
20
|
+
'timeline-html',
|
|
21
|
+
'dependency-graph',
|
|
22
|
+
'migration-log-md',
|
|
23
|
+
'workspace-narrative-md',
|
|
24
|
+
];
|
|
25
|
+
const CHANGE_LOG_RECORD_ONLY_FLAGS = [
|
|
26
|
+
'commands',
|
|
27
|
+
'commands-from',
|
|
28
|
+
'files-changed',
|
|
29
|
+
'hypothesis',
|
|
30
|
+
'parity-status',
|
|
31
|
+
'rollback-plan',
|
|
32
|
+
'rolls-back',
|
|
33
|
+
'smoke-notes',
|
|
34
|
+
'smoke-result',
|
|
35
|
+
'subkind',
|
|
36
|
+
];
|
|
16
37
|
class CliUsageError extends Error {
|
|
17
38
|
}
|
|
18
39
|
const DEFAULT_CONTEXT = {
|
|
@@ -34,12 +55,12 @@ const renderValidValues = () => `Valid values:
|
|
|
34
55
|
const renderHelp = (commandName) => `${commandName}
|
|
35
56
|
|
|
36
57
|
Commands:
|
|
37
|
-
${commandName} record [--workspace=<ws>] --kind=<kind> --phase=<phase> --summary="..." [--rationale="..."] [--action=<operator-action>] [--check-id=<check-id>] [--diff=<patch-file>] [--diff-from-git=<ref>] [--idempotency-key=<key>] [--from-stdin]
|
|
58
|
+
${commandName} record [--workspace=<ws>] --kind=<kind> --phase=<phase> --summary="..." [--rationale="..."] [--action=<operator-action>] [--check-id=<check-id>] [--diff=<patch-file>] [--diff-from-git=<ref>] [--idempotency-key=<key>] [--subkind=<change-log-subkind>] [--files-changed=<csv>] [--hypothesis="..."] [--commands="cmd1\ncmd2" | --commands-from=<file>] [--smoke-result=<result>] [--smoke-notes="..."] [--parity-status=<status>] [--rollback-plan="..."] [--rolls-back=<entry-id>] [--from-stdin]
|
|
38
59
|
${commandName} note <subkind> [--workspace=<ws>] --phase=<phase> --summary="..." [--body=<markdown-file>] [--from-stdin]
|
|
39
60
|
${commandName} list [--workspace=<ws>] [--phase=<phase>] [--kind=<kind>] [--since=<iso>] [--limit=<n>] [--json]
|
|
40
61
|
${commandName} show [--workspace=<ws>] <entry-id>
|
|
41
62
|
${commandName} tail [--workspace=<ws>] [--phase=<phase>] [--limit=<n>]
|
|
42
|
-
${commandName} render [--workspace=<ws>] [--to=retro|jsonl|timeline-html|dependency-graph] [--phase=<phase>] [--out=<file>]
|
|
63
|
+
${commandName} render [--workspace=<ws>] [--to=retro|jsonl|timeline-html|dependency-graph|migration-log-md|workspace-narrative-md] [--phase=<phase>] [--since=<iso>] [--limit=<n>] [--out=<file>]
|
|
43
64
|
${commandName} archive [--workspace=<ws>] --out=<file.tgz>
|
|
44
65
|
${commandName} doctor [--workspace=<ws>]
|
|
45
66
|
${commandName} --version
|
|
@@ -48,7 +69,7 @@ ${renderValidValues()}`;
|
|
|
48
69
|
const renderCommandHelp = (commandName, command) => {
|
|
49
70
|
switch (command) {
|
|
50
71
|
case 'record':
|
|
51
|
-
return `${commandName} record [--workspace=<ws>] --kind=<kind> --phase=<phase> --summary="..." [--rationale="..."] [--action=<operator-action>] [--check-id=<check-id>] [--diff=<patch-file>] [--diff-from-git=<ref>] [--idempotency-key=<key>] [--from-stdin]
|
|
72
|
+
return `${commandName} record [--workspace=<ws>] --kind=<kind> --phase=<phase> --summary="..." [--rationale="..."] [--action=<operator-action>] [--check-id=<check-id>] [--diff=<patch-file>] [--diff-from-git=<ref>] [--idempotency-key=<key>] [--subkind=<change-log-subkind>] [--files-changed=<csv>] [--hypothesis="..."] [--commands="cmd1\ncmd2" | --commands-from=<file>] [--smoke-result=<result>] [--smoke-notes="..."] [--parity-status=<status>] [--rollback-plan="..."] [--rolls-back=<entry-id>] [--from-stdin]
|
|
52
73
|
|
|
53
74
|
${renderValidValues()}`;
|
|
54
75
|
case 'note':
|
|
@@ -60,7 +81,7 @@ ${renderValidValues()}`;
|
|
|
60
81
|
|
|
61
82
|
${renderValidValues()}`;
|
|
62
83
|
case 'render':
|
|
63
|
-
return `${commandName} render [--workspace=<ws>] [--to=<target>] [--phase=<phase>] [--out=<file>]
|
|
84
|
+
return `${commandName} render [--workspace=<ws>] [--to=<target>] [--phase=<phase>] [--since=<iso>] [--limit=<n>] [--out=<file>]
|
|
64
85
|
|
|
65
86
|
${renderValidValues()}`;
|
|
66
87
|
default:
|
|
@@ -99,6 +120,9 @@ const parseArgv = (argv) => {
|
|
|
99
120
|
};
|
|
100
121
|
const getFlag = (flags, name) => {
|
|
101
122
|
const value = flags[name];
|
|
123
|
+
if (value === true) {
|
|
124
|
+
throw new CliUsageError(`Missing value for --${name}.`);
|
|
125
|
+
}
|
|
102
126
|
return typeof value === 'string' ? value : undefined;
|
|
103
127
|
};
|
|
104
128
|
const hasFlag = (flags, name) => flags[name] === true || typeof flags[name] === 'string';
|
|
@@ -140,7 +164,7 @@ const materializeGitDiff = async (workspaceRoot, gitRef) => {
|
|
|
140
164
|
try {
|
|
141
165
|
const { stdout } = await execFileAsync('git', ['diff', gitRef], {
|
|
142
166
|
cwd: workspaceRoot,
|
|
143
|
-
maxBuffer:
|
|
167
|
+
maxBuffer: GIT_DIFF_MAX_BUFFER_BYTES,
|
|
144
168
|
timeout: GIT_DIFF_TIMEOUT_MS,
|
|
145
169
|
});
|
|
146
170
|
tempDir = await mkdtemp(path.join(os.tmpdir(), 'ushman-ledger-git-diff-'));
|
|
@@ -234,18 +258,121 @@ const parseRenderTarget = (flags) => {
|
|
|
234
258
|
}
|
|
235
259
|
return target;
|
|
236
260
|
};
|
|
261
|
+
const splitCommandLines = (value) => value
|
|
262
|
+
.split(/\r?\n/u)
|
|
263
|
+
.map((command) => command.trim())
|
|
264
|
+
.filter((command) => command.length > 0);
|
|
265
|
+
const getUsedFlags = (flags, names) => names.filter((name) => hasFlag(flags, name)).map((name) => `--${name}`);
|
|
266
|
+
const rejectUnsupportedFlags = (supportedKindLabel, flags, names) => {
|
|
267
|
+
const usedFlags = getUsedFlags(flags, names);
|
|
268
|
+
if (usedFlags.length === 0) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
throw new CliUsageError(`${usedFlags.join(', ')} ${usedFlags.length === 1 ? 'is' : 'are'} only supported for ${supportedKindLabel} records.`);
|
|
272
|
+
};
|
|
273
|
+
const parseRequiredChangeLogSubkind = (flags) => {
|
|
274
|
+
const subkind = getRequiredString(flags, 'subkind');
|
|
275
|
+
if (!ChangeLogSubkindSchema.options.includes(subkind)) {
|
|
276
|
+
throw new CliUsageError(`Invalid --subkind value: ${subkind}. Expected one of: ${ChangeLogSubkindSchema.options.join(', ')}.`);
|
|
277
|
+
}
|
|
278
|
+
return subkind;
|
|
279
|
+
};
|
|
280
|
+
const parseOptionalChangeLogPicklist = ({ flagName, flags, options, }) => {
|
|
281
|
+
const value = getFlag(flags, flagName);
|
|
282
|
+
if (!value) {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
if (!options.includes(value)) {
|
|
286
|
+
throw new CliUsageError(`Invalid --${flagName} value: ${value}. Expected one of: ${options.join(', ')}.`);
|
|
287
|
+
}
|
|
288
|
+
return value;
|
|
289
|
+
};
|
|
290
|
+
const parseFilesChangedCsv = (raw) => {
|
|
291
|
+
const uniquePaths = [
|
|
292
|
+
...new Set(raw
|
|
293
|
+
.split(',')
|
|
294
|
+
.map((value) => value.trim())
|
|
295
|
+
.filter((value) => value.length > 0)
|
|
296
|
+
.map((filePath) => {
|
|
297
|
+
try {
|
|
298
|
+
return v.parse(WorkspaceRelativePathSchema, filePath);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
throw new CliUsageError(`--files-changed path is not a normalized workspace-relative path: ${filePath}`);
|
|
302
|
+
}
|
|
303
|
+
})),
|
|
304
|
+
];
|
|
305
|
+
if (uniquePaths.length === 0) {
|
|
306
|
+
throw new CliUsageError('--files-changed must include at least one workspace-relative path.');
|
|
307
|
+
}
|
|
308
|
+
return uniquePaths.map((filePath) => ({ path: filePath }));
|
|
309
|
+
};
|
|
310
|
+
const readCommandLines = async (flags) => {
|
|
311
|
+
const inlineCommands = getFlag(flags, 'commands');
|
|
312
|
+
const commandsFrom = getFlag(flags, 'commands-from');
|
|
313
|
+
if (inlineCommands && commandsFrom) {
|
|
314
|
+
throw new CliUsageError('Use either --commands or --commands-from, not both.');
|
|
315
|
+
}
|
|
316
|
+
if (commandsFrom) {
|
|
317
|
+
const commandsPath = path.resolve(commandsFrom);
|
|
318
|
+
await ensureFileExists(commandsPath, '--commands-from');
|
|
319
|
+
return splitCommandLines(await readFile(commandsPath, 'utf8'));
|
|
320
|
+
}
|
|
321
|
+
if (inlineCommands) {
|
|
322
|
+
return splitCommandLines(inlineCommands);
|
|
323
|
+
}
|
|
324
|
+
return undefined;
|
|
325
|
+
};
|
|
326
|
+
const resolveDiffInput = async ({ flags, kind, workspaceRoot, }) => {
|
|
327
|
+
const diffPath = getFlag(flags, 'diff');
|
|
328
|
+
const diffFromGit = getFlag(flags, 'diff-from-git');
|
|
329
|
+
if (diffPath && diffFromGit) {
|
|
330
|
+
throw new CliUsageError('Use either --diff or --diff-from-git, not both.');
|
|
331
|
+
}
|
|
332
|
+
if (!diffPath && !diffFromGit) {
|
|
333
|
+
return {};
|
|
334
|
+
}
|
|
335
|
+
if (kind !== 'agent-patch' && kind !== 'operator-patch' && kind !== 'change-log') {
|
|
336
|
+
throw new CliUsageError('--diff and --diff-from-git are only supported for patch and change-log records.');
|
|
337
|
+
}
|
|
338
|
+
if (diffPath) {
|
|
339
|
+
const resolvedDiffPath = path.resolve(diffPath);
|
|
340
|
+
await ensureFileExists(resolvedDiffPath, '--diff');
|
|
341
|
+
return {
|
|
342
|
+
diffPath: resolvedDiffPath,
|
|
343
|
+
patchText: kind === 'change-log' ? await readFile(resolvedDiffPath, 'utf8') : undefined,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
const materialized = await materializeGitDiff(workspaceRoot, diffFromGit ?? '');
|
|
347
|
+
return {
|
|
348
|
+
cleanupTempDir: materialized.tempDir,
|
|
349
|
+
diffPath: materialized.patchPath,
|
|
350
|
+
gitRef: diffFromGit,
|
|
351
|
+
patchText: kind === 'change-log' ? await readFile(materialized.patchPath, 'utf8') : undefined,
|
|
352
|
+
};
|
|
353
|
+
};
|
|
237
354
|
const validateRecordStdinFlags = (flags) => {
|
|
238
355
|
const conflictingFlags = [
|
|
239
356
|
'agent',
|
|
240
357
|
'action',
|
|
241
358
|
'check-id',
|
|
359
|
+
'commands',
|
|
360
|
+
'commands-from',
|
|
242
361
|
'diff',
|
|
243
362
|
'diff-from-git',
|
|
363
|
+
'files-changed',
|
|
364
|
+
'hypothesis',
|
|
244
365
|
'idempotency-key',
|
|
245
366
|
'kind',
|
|
246
367
|
'operator',
|
|
368
|
+
'parity-status',
|
|
247
369
|
'phase',
|
|
248
370
|
'rationale',
|
|
371
|
+
'rollback-plan',
|
|
372
|
+
'rolls-back',
|
|
373
|
+
'smoke-notes',
|
|
374
|
+
'smoke-result',
|
|
375
|
+
'subkind',
|
|
249
376
|
'summary',
|
|
250
377
|
].filter((flagName) => hasFlag(flags, flagName));
|
|
251
378
|
if (conflictingFlags.length === 0) {
|
|
@@ -257,6 +384,9 @@ const validateRecordStdinFlags = (flags) => {
|
|
|
257
384
|
};
|
|
258
385
|
const buildBaseRecordFromFlags = (parsed, context) => {
|
|
259
386
|
const kind = getRequiredKind(parsed.flags);
|
|
387
|
+
if (kind !== 'change-log') {
|
|
388
|
+
rejectUnsupportedFlags('change-log', parsed.flags, CHANGE_LOG_RECORD_ONLY_FLAGS);
|
|
389
|
+
}
|
|
260
390
|
const record = {
|
|
261
391
|
emitter: {
|
|
262
392
|
tool: context.defaultEmitter.tool,
|
|
@@ -296,43 +426,68 @@ const buildBaseRecordFromFlags = (parsed, context) => {
|
|
|
296
426
|
rationale: rationale ?? '',
|
|
297
427
|
};
|
|
298
428
|
}
|
|
429
|
+
if (kind === 'change-log') {
|
|
430
|
+
record.subkind = parseRequiredChangeLogSubkind(parsed.flags);
|
|
431
|
+
}
|
|
299
432
|
const idempotencyKey = getFlag(parsed.flags, 'idempotency-key');
|
|
300
433
|
if (idempotencyKey) {
|
|
301
434
|
record.idempotencyKey = idempotencyKey;
|
|
302
435
|
}
|
|
303
436
|
return { kind, record };
|
|
304
437
|
};
|
|
305
|
-
const applyPatchInputToRecord = async ({ kind,
|
|
306
|
-
const diffPath = getFlag(parsed.flags, 'diff');
|
|
307
|
-
if (diffPath) {
|
|
308
|
-
if (kind !== 'agent-patch' && kind !== 'operator-patch') {
|
|
309
|
-
throw new CliUsageError('--diff is only supported for patch records.');
|
|
310
|
-
}
|
|
311
|
-
await ensureFileExists(path.resolve(diffPath), '--diff');
|
|
312
|
-
record.diffPath = diffPath;
|
|
313
|
-
}
|
|
314
|
-
const diffFromGit = getFlag(parsed.flags, 'diff-from-git');
|
|
315
|
-
if (!diffFromGit) {
|
|
316
|
-
return {};
|
|
317
|
-
}
|
|
438
|
+
const applyPatchInputToRecord = async ({ kind, patchInput, record, }) => {
|
|
318
439
|
if (kind !== 'agent-patch' && kind !== 'operator-patch') {
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
if (diffPath) {
|
|
322
|
-
throw new CliUsageError('Use either --diff or --diff-from-git, not both.');
|
|
440
|
+
return {};
|
|
323
441
|
}
|
|
324
|
-
const materialized = await materializeGitDiff(workspaceRoot, diffFromGit);
|
|
325
442
|
return {
|
|
326
|
-
cleanupTempDir:
|
|
443
|
+
cleanupTempDir: patchInput.cleanupTempDir,
|
|
327
444
|
record: {
|
|
328
445
|
...record,
|
|
329
|
-
diffPath:
|
|
330
|
-
links:
|
|
331
|
-
|
|
332
|
-
|
|
446
|
+
diffPath: patchInput.diffPath,
|
|
447
|
+
links: patchInput.gitRef
|
|
448
|
+
? {
|
|
449
|
+
...record.links,
|
|
450
|
+
gitRef: patchInput.gitRef,
|
|
451
|
+
}
|
|
452
|
+
: record.links,
|
|
333
453
|
},
|
|
334
454
|
};
|
|
335
455
|
};
|
|
456
|
+
const applyChangeLogFlagsToRecord = async ({ flags, patchInput, record, }) => {
|
|
457
|
+
const filesChangedFlag = getFlag(flags, 'files-changed');
|
|
458
|
+
if (filesChangedFlag && patchInput.patchText) {
|
|
459
|
+
throw new CliUsageError('Use either --files-changed or --diff/--diff-from-git to populate change-log files.');
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
...record,
|
|
463
|
+
commandsRun: await readCommandLines(flags),
|
|
464
|
+
filesChanged: filesChangedFlag
|
|
465
|
+
? parseFilesChangedCsv(filesChangedFlag)
|
|
466
|
+
: patchInput.patchText
|
|
467
|
+
? deriveFilesChangedFromPatch(patchInput.patchText)
|
|
468
|
+
: undefined,
|
|
469
|
+
hypothesis: getFlag(flags, 'hypothesis'),
|
|
470
|
+
links: patchInput.gitRef
|
|
471
|
+
? {
|
|
472
|
+
...record.links,
|
|
473
|
+
gitRef: patchInput.gitRef,
|
|
474
|
+
}
|
|
475
|
+
: record.links,
|
|
476
|
+
parityStatus: parseOptionalChangeLogPicklist({
|
|
477
|
+
flagName: 'parity-status',
|
|
478
|
+
flags,
|
|
479
|
+
options: ChangeLogParityStatusSchema.options,
|
|
480
|
+
}),
|
|
481
|
+
rollbackPlan: getFlag(flags, 'rollback-plan'),
|
|
482
|
+
rollsBack: getFlag(flags, 'rolls-back'),
|
|
483
|
+
smokeNotes: getFlag(flags, 'smoke-notes'),
|
|
484
|
+
smokeResult: parseOptionalChangeLogPicklist({
|
|
485
|
+
flagName: 'smoke-result',
|
|
486
|
+
flags,
|
|
487
|
+
options: ChangeLogSmokeResultSchema.options,
|
|
488
|
+
}),
|
|
489
|
+
};
|
|
490
|
+
};
|
|
336
491
|
const buildRecordFromFlags = async (parsed, context) => {
|
|
337
492
|
const workspaceRoot = getWorkspaceRoot(parsed.flags);
|
|
338
493
|
if (hasFlag(parsed.flags, 'from-stdin')) {
|
|
@@ -344,17 +499,41 @@ const buildRecordFromFlags = async (parsed, context) => {
|
|
|
344
499
|
};
|
|
345
500
|
}
|
|
346
501
|
const { kind, record } = buildBaseRecordFromFlags(parsed, context);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
502
|
+
let diffInput;
|
|
503
|
+
try {
|
|
504
|
+
diffInput = await resolveDiffInput({
|
|
505
|
+
flags: parsed.flags,
|
|
506
|
+
kind,
|
|
507
|
+
workspaceRoot,
|
|
508
|
+
});
|
|
509
|
+
if (kind === 'change-log') {
|
|
510
|
+
return {
|
|
511
|
+
cleanupTempDir: diffInput.cleanupTempDir,
|
|
512
|
+
record: await applyChangeLogFlagsToRecord({
|
|
513
|
+
flags: parsed.flags,
|
|
514
|
+
patchInput: diffInput,
|
|
515
|
+
record,
|
|
516
|
+
}),
|
|
517
|
+
workspaceRoot,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
const patchInput = await applyPatchInputToRecord({
|
|
521
|
+
kind,
|
|
522
|
+
patchInput: diffInput,
|
|
523
|
+
record,
|
|
524
|
+
});
|
|
525
|
+
return {
|
|
526
|
+
cleanupTempDir: patchInput.cleanupTempDir,
|
|
527
|
+
record: patchInput.record ?? record,
|
|
528
|
+
workspaceRoot,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
if (diffInput?.cleanupTempDir) {
|
|
533
|
+
await rm(diffInput.cleanupTempDir, { force: true, recursive: true });
|
|
534
|
+
}
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
358
537
|
};
|
|
359
538
|
const buildNoteRecord = async (parsed, context) => {
|
|
360
539
|
const [subkind] = parsed.positionals;
|
|
@@ -462,8 +641,10 @@ const runRenderCli = async (parsed, context) => {
|
|
|
462
641
|
const target = parseRenderTarget(parsed.flags);
|
|
463
642
|
const ledger = await openLedger(getWorkspaceRoot(parsed.flags));
|
|
464
643
|
const content = await ledger.render({
|
|
644
|
+
limit: parseLimit(parsed.flags),
|
|
465
645
|
out: getFlag(parsed.flags, 'out'),
|
|
466
646
|
phase: parseOptionalPhase(parsed.flags),
|
|
647
|
+
since: parseSince(parsed.flags),
|
|
467
648
|
to: target,
|
|
468
649
|
});
|
|
469
650
|
if (!getFlag(parsed.flags, 'out')) {
|
package/dist/doctor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAMA,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAMA,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AA8V1E,eAAO,MAAM,eAAe,GACxB,eAAe,MAAM,EACrB,UAAS;IAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,mBAAmB,CAAA;CAAO;;;EAyCzF,CAAC"}
|