ushman-ledger 1.1.0 → 1.2.1

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 (68) hide show
  1. package/AGENTS.md +11 -7
  2. package/CHANGELOG.md +6 -0
  3. package/README.md +79 -8
  4. package/dist/blobs.js +3 -3
  5. package/dist/builders.d.ts +44 -2
  6. package/dist/builders.d.ts.map +1 -1
  7. package/dist/builders.js +7 -2
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +346 -62
  10. package/dist/doctor.d.ts.map +1 -1
  11. package/dist/doctor.js +104 -4
  12. package/dist/handle.d.ts +28 -6
  13. package/dist/handle.d.ts.map +1 -1
  14. package/dist/handle.js +105 -11
  15. package/dist/helpers.d.ts +7 -0
  16. package/dist/helpers.d.ts.map +1 -0
  17. package/dist/helpers.js +38 -0
  18. package/dist/index.d.ts +5 -3
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +5 -3
  21. package/dist/list.d.ts +44 -2
  22. package/dist/list.d.ts.map +1 -1
  23. package/dist/list.js +7 -5
  24. package/dist/note.d.ts +27 -0
  25. package/dist/note.d.ts.map +1 -1
  26. package/dist/note.js +11 -0
  27. package/dist/patch-resolver.d.ts +39 -0
  28. package/dist/patch-resolver.d.ts.map +1 -0
  29. package/dist/patch-resolver.js +196 -0
  30. package/dist/read-index.d.ts +7 -7
  31. package/dist/read-index.d.ts.map +1 -1
  32. package/dist/record.d.ts.map +1 -1
  33. package/dist/record.js +15 -40
  34. package/dist/render/migration-log.d.ts +10 -0
  35. package/dist/render/migration-log.d.ts.map +1 -0
  36. package/dist/render/migration-log.js +79 -0
  37. package/dist/render/retro.d.ts.map +1 -1
  38. package/dist/render/retro.js +34 -21
  39. package/dist/render/workspace-narrative.d.ts +12 -0
  40. package/dist/render/workspace-narrative.d.ts.map +1 -0
  41. package/dist/render/workspace-narrative.js +137 -0
  42. package/dist/schema/entry-core.d.ts +110 -0
  43. package/dist/schema/entry-core.d.ts.map +1 -0
  44. package/dist/schema/entry-core.js +143 -0
  45. package/dist/schema/entry-migrations.d.ts +3 -0
  46. package/dist/schema/entry-migrations.d.ts.map +1 -0
  47. package/dist/schema/entry-migrations.js +48 -0
  48. package/dist/schema/entry-read.d.ts +694 -0
  49. package/dist/schema/entry-read.d.ts.map +1 -0
  50. package/dist/schema/entry-read.js +92 -0
  51. package/dist/schema/entry-write.d.ts +865 -0
  52. package/dist/schema/entry-write.d.ts.map +1 -0
  53. package/dist/schema/entry-write.js +105 -0
  54. package/dist/schema/entry.d.ts +6 -1369
  55. package/dist/schema/entry.d.ts.map +1 -1
  56. package/dist/schema/entry.js +9 -286
  57. package/dist/schema/note.d.ts +1 -1
  58. package/dist/schema/note.d.ts.map +1 -1
  59. package/dist/schema/note.js +12 -1
  60. package/dist/storage/filesystem.d.ts +9 -0
  61. package/dist/storage/filesystem.d.ts.map +1 -1
  62. package/dist/storage/filesystem.js +82 -5
  63. package/dist/storage/lock-reclaimer.d.ts +2 -0
  64. package/dist/storage/lock-reclaimer.d.ts.map +1 -0
  65. package/dist/storage/lock-reclaimer.js +45 -0
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.js +1 -1
  68. package/package.json +3 -3
package/dist/note.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { appendRecord } from "./record.js";
2
2
  import { LEDGER_LIBRARY_VERSION } from "./version.js";
3
+ /** Append a typed note entry using the library emitter metadata. */
3
4
  export const appendNote = async (workspaceRoot, subkind, noteBody) => {
4
5
  return appendRecord(workspaceRoot, {
5
6
  body: noteBody.body,
@@ -13,3 +14,13 @@ export const appendNote = async (workspaceRoot, subkind, noteBody) => {
13
14
  summary: noteBody.summary,
14
15
  });
15
16
  };
17
+ /** Append a `cleanup-wave` narrative note. */
18
+ export const appendCleanupWaveNote = (workspaceRoot, noteBody) => appendNote(workspaceRoot, 'cleanup-wave', noteBody);
19
+ /** Append a `verified-flow` narrative note. */
20
+ export const appendVerifiedFlowNote = (workspaceRoot, noteBody) => appendNote(workspaceRoot, 'verified-flow', noteBody);
21
+ /** Append an `open-issue` narrative note. */
22
+ export const appendOpenIssueNote = (workspaceRoot, noteBody) => appendNote(workspaceRoot, 'open-issue', noteBody);
23
+ /** Append a `decomposition-wave` narrative note. */
24
+ export const appendDecompositionWaveNote = (workspaceRoot, noteBody) => appendNote(workspaceRoot, 'decomposition-wave', noteBody);
25
+ /** Append the current `semantic-cleanup-summary` note for workspace narrative rendering. */
26
+ export const appendSemanticCleanupSummaryNote = (workspaceRoot, noteBody) => appendNote(workspaceRoot, 'semantic-cleanup-summary', noteBody);
@@ -0,0 +1,39 @@
1
+ import { type AgentPatchDiff, type ChangeLogFileChange, type LedgerLinks, type LedgerRecord } from './schema/entry.ts';
2
+ type AgentPatchRecord = Extract<LedgerRecord, {
3
+ kind: 'agent-patch';
4
+ }>;
5
+ type OperatorPatchRecord = Extract<LedgerRecord, {
6
+ kind: 'operator-patch';
7
+ }>;
8
+ type ResolvedAgentPatchRecord = Omit<AgentPatchRecord, 'diffPath' | 'diffText' | 'links'> & {
9
+ readonly diff: AgentPatchDiff;
10
+ readonly links: LedgerLinks;
11
+ };
12
+ type ResolvedOperatorPatchRecord = Omit<OperatorPatchRecord, 'diffPath' | 'diffText' | 'links'> & {
13
+ readonly diff: AgentPatchDiff;
14
+ readonly links: LedgerLinks;
15
+ };
16
+ export type ResolvedPatchRecord = ResolvedAgentPatchRecord | ResolvedOperatorPatchRecord;
17
+ /**
18
+ * Derive `change-log.filesChanged` metadata from a git-style unified diff.
19
+ *
20
+ * Example:
21
+ * `deriveFilesChangedFromPatch(await readFile('/tmp/change.patch', 'utf8'))`
22
+ *
23
+ * Known limits:
24
+ * - Requires `diff --git` headers to identify file boundaries.
25
+ * - Counts only textual hunk `+` and `-` lines, so binary or mode-only diffs can yield zero line counts.
26
+ * - Uses the post-image path when available and rejects diff blocks that cannot be mapped to a normalized
27
+ * workspace-relative path.
28
+ */
29
+ export declare const deriveFilesChangedFromPatch: (patchText: string) => ChangeLogFileChange[];
30
+ export declare function resolvePatchRecord(args: {
31
+ readonly record: AgentPatchRecord;
32
+ readonly workspaceRoot: string;
33
+ }): Promise<ResolvedAgentPatchRecord>;
34
+ export declare function resolvePatchRecord(args: {
35
+ readonly record: OperatorPatchRecord;
36
+ readonly workspaceRoot: string;
37
+ }): Promise<ResolvedOperatorPatchRecord>;
38
+ export {};
39
+ //# sourceMappingURL=patch-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-resolver.d.ts","sourceRoot":"","sources":["../src/patch-resolver.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,YAAY,EAEpB,MAAM,mBAAmB,CAAC;AAI3B,KAAK,gBAAgB,GAAG,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC,CAAC;AACvE,KAAK,mBAAmB,GAAG,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAAC;AAQ7E,KAAK,wBAAwB,GAAG,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,UAAU,GAAG,OAAO,CAAC,GAAG;IACxF,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;CAC/B,CAAC;AACF,KAAK,2BAA2B,GAAG,IAAI,CAAC,mBAAmB,EAAE,UAAU,GAAG,UAAU,GAAG,OAAO,CAAC,GAAG;IAC9F,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,wBAAwB,GAAG,2BAA2B,CAAC;AAqKzF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,GAAI,WAAW,MAAM,KAAG,mBAAmB,EAuClF,CAAC;AAmBF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAClC,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;AACtC,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAClC,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAC"}
@@ -0,0 +1,196 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import * as v from 'valibot';
4
+ import { resolveBlobPath, storePatchBlob } from "./blobs.js";
5
+ import { WorkspaceRelativePathSchema, } from "./schema/entry.js";
6
+ const readBlobText = async (workspaceRoot, blobSha256) => readFile(resolveBlobPath(workspaceRoot, blobSha256), 'utf8');
7
+ const formatRecordContext = (record) => `${record.kind} "${record.summary}" [${record.phase}]`;
8
+ const normalizeWorkspaceRelativePath = (value) => v.parse(WorkspaceRelativePathSchema, value.replaceAll('\\', '/'));
9
+ const stripGitPathPrefix = (value) => {
10
+ const normalized = value.trim().replace(/^"([^"]*)"$/u, '$1');
11
+ if (normalized === '/dev/null') {
12
+ return null;
13
+ }
14
+ if (normalized.startsWith('a/') || normalized.startsWith('b/')) {
15
+ return normalized.slice(2);
16
+ }
17
+ return normalized;
18
+ };
19
+ const inferHeaderPath = (line) => {
20
+ const match = /^diff --git (?:"a\/([^"]+)"|a\/(\S+)) (?:"b\/([^"]+)"|b\/(\S+))$/u.exec(line.trim());
21
+ if (!match) {
22
+ return null;
23
+ }
24
+ const rightPath = match[3] ?? match[4];
25
+ return rightPath ? stripGitPathPrefix(`b/${rightPath}`) : null;
26
+ };
27
+ const finalizeFileChange = (current, filesChanged) => {
28
+ if (!current) {
29
+ return;
30
+ }
31
+ const resolvedPath = current.plusPath ?? current.minusPath ?? current.headerPath;
32
+ if (!resolvedPath) {
33
+ throw new Error('Unable to derive a workspace-relative path from a diff block.');
34
+ }
35
+ filesChanged.push({
36
+ added: current.added > 0 ? current.added : undefined,
37
+ path: normalizeWorkspaceRelativePath(resolvedPath),
38
+ removed: current.removed > 0 ? current.removed : undefined,
39
+ });
40
+ };
41
+ const createDiffFileAccumulator = (line) => ({
42
+ added: 0,
43
+ headerPath: inferHeaderPath(line),
44
+ minusPath: null,
45
+ plusPath: null,
46
+ removed: 0,
47
+ });
48
+ const applyPatchMetadataLine = (current, line) => {
49
+ if (line.startsWith('+++ ')) {
50
+ return {
51
+ next: {
52
+ ...current,
53
+ plusPath: stripGitPathPrefix(line.slice(4)),
54
+ },
55
+ };
56
+ }
57
+ if (line.startsWith('--- ')) {
58
+ return {
59
+ next: {
60
+ ...current,
61
+ minusPath: stripGitPathPrefix(line.slice(4)),
62
+ },
63
+ };
64
+ }
65
+ if (line.startsWith('@@')) {
66
+ return {
67
+ next: current,
68
+ startHunk: true,
69
+ };
70
+ }
71
+ return {
72
+ next: current,
73
+ };
74
+ };
75
+ const applyPatchHunkLine = (current, line) => {
76
+ if (line.startsWith('+')) {
77
+ return {
78
+ ...current,
79
+ added: current.added + 1,
80
+ };
81
+ }
82
+ if (line.startsWith('-')) {
83
+ return {
84
+ ...current,
85
+ removed: current.removed + 1,
86
+ };
87
+ }
88
+ return current;
89
+ };
90
+ const mergeBlobLink = (links, blobSha256) => ({
91
+ ...links,
92
+ blobs: [...new Set([...(links?.blobs ?? []), blobSha256])],
93
+ });
94
+ const resolvePatchSource = async ({ record, workspaceRoot, }) => {
95
+ if (record.diffPath) {
96
+ const resolvedPath = path.resolve(workspaceRoot, record.diffPath);
97
+ return {
98
+ patchText: await readFile(resolvedPath, 'utf8'),
99
+ source: 'file',
100
+ sourceLabel: resolvedPath,
101
+ };
102
+ }
103
+ if (record.diffText) {
104
+ return {
105
+ patchText: record.diffText,
106
+ source: 'inline',
107
+ sourceLabel: 'inline diff text',
108
+ };
109
+ }
110
+ if (record.diff) {
111
+ try {
112
+ return {
113
+ patchText: await readBlobText(workspaceRoot, record.diff.blobSha256),
114
+ source: 'blob',
115
+ sourceLabel: `blob ${record.diff.blobSha256}`,
116
+ };
117
+ }
118
+ catch (error) {
119
+ if (error.code === 'ENOENT') {
120
+ throw new Error(`Patch blob ${record.diff.blobSha256} was not found for ${formatRecordContext(record)}. Store it first or use diffPath.`);
121
+ }
122
+ throw error;
123
+ }
124
+ }
125
+ throw new Error(`${record.kind} records require diff, diffPath, or diffText.`);
126
+ };
127
+ /**
128
+ * Derive `change-log.filesChanged` metadata from a git-style unified diff.
129
+ *
130
+ * Example:
131
+ * `deriveFilesChangedFromPatch(await readFile('/tmp/change.patch', 'utf8'))`
132
+ *
133
+ * Known limits:
134
+ * - Requires `diff --git` headers to identify file boundaries.
135
+ * - Counts only textual hunk `+` and `-` lines, so binary or mode-only diffs can yield zero line counts.
136
+ * - Uses the post-image path when available and rejects diff blocks that cannot be mapped to a normalized
137
+ * workspace-relative path.
138
+ */
139
+ export const deriveFilesChangedFromPatch = (patchText) => {
140
+ const filesChanged = [];
141
+ let current;
142
+ let insideHunk = false;
143
+ for (const line of patchText.split(/\r?\n/u)) {
144
+ if (line.startsWith('diff --git ')) {
145
+ finalizeFileChange(current, filesChanged);
146
+ current = createDiffFileAccumulator(line);
147
+ insideHunk = false;
148
+ continue;
149
+ }
150
+ if (!current) {
151
+ continue;
152
+ }
153
+ if (insideHunk) {
154
+ if (line.startsWith('@@')) {
155
+ continue;
156
+ }
157
+ current = applyPatchHunkLine(current, line);
158
+ continue;
159
+ }
160
+ const metadataUpdate = applyPatchMetadataLine(current, line);
161
+ current = metadataUpdate.next;
162
+ if (metadataUpdate.startHunk) {
163
+ insideHunk = true;
164
+ continue;
165
+ }
166
+ if (!insideHunk) {
167
+ continue;
168
+ }
169
+ current = applyPatchHunkLine(current, line);
170
+ }
171
+ finalizeFileChange(current, filesChanged);
172
+ return filesChanged;
173
+ };
174
+ const buildResolvedPatchRecord = ({ links, record, storedDiff, }) => {
175
+ const { diffPath: _diffPath, diffText: _diffText, ...rest } = record;
176
+ return {
177
+ ...rest,
178
+ diff: storedDiff,
179
+ links,
180
+ };
181
+ };
182
+ export async function resolvePatchRecord({ record, workspaceRoot, }) {
183
+ const { patchText, sourceLabel } = await resolvePatchSource({
184
+ record,
185
+ workspaceRoot,
186
+ });
187
+ const storedDiff = await storePatchBlob(workspaceRoot, patchText);
188
+ if (record.diff && record.diff.blobSha256 !== storedDiff.blobSha256) {
189
+ throw new Error(`Provided diff blob ${record.diff.blobSha256} from ${sourceLabel} does not match patch text hash ${storedDiff.blobSha256} for ${formatRecordContext(record)}.`);
190
+ }
191
+ return buildResolvedPatchRecord({
192
+ links: mergeBlobLink(record.links, storedDiff.blobSha256),
193
+ record,
194
+ storedDiff,
195
+ });
196
+ }
@@ -3,14 +3,14 @@ import { type LedgerEntry, type LedgerKind, type LedgerPhase } from './schema/en
3
3
  import type { LedgerManifest } from './schema/manifest.ts';
4
4
  declare const ReadIndexEntrySchema: v.ObjectSchema<{
5
5
  readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
6
- readonly kind: v.PicklistSchema<readonly ["tool-invocation", "agent-patch", "operator-patch", "operator-decision", "validator-result", "runtime-event", "note", "correction", "strip-decision-reverted"], undefined>;
6
+ readonly kind: v.PicklistSchema<readonly ["tool-invocation", "agent-patch", "operator-patch", "operator-decision", "validator-result", "runtime-event", "note", "correction", "strip-decision-reverted", "change-log"], undefined>;
7
7
  readonly ts: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, undefined>]>;
8
8
  }, undefined>;
9
9
  declare const LedgerReadIndexSchema: v.SchemaWithPipe<readonly [v.ObjectSchema<{
10
10
  readonly coveredFiles: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, readonly []>;
11
11
  readonly entries: v.OptionalSchema<v.ArraySchema<v.ObjectSchema<{
12
12
  readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
13
- readonly kind: v.PicklistSchema<readonly ["tool-invocation", "agent-patch", "operator-patch", "operator-decision", "validator-result", "runtime-event", "note", "correction", "strip-decision-reverted"], undefined>;
13
+ readonly kind: v.PicklistSchema<readonly ["tool-invocation", "agent-patch", "operator-patch", "operator-decision", "validator-result", "runtime-event", "note", "correction", "strip-decision-reverted", "change-log"], undefined>;
14
14
  readonly ts: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, undefined>]>;
15
15
  }, undefined>, undefined>, readonly []>;
16
16
  readonly entryCount: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
@@ -21,7 +21,7 @@ declare const LedgerReadIndexSchema: v.SchemaWithPipe<readonly [v.ObjectSchema<{
21
21
  coveredFiles: string[];
22
22
  entries: {
23
23
  id: string;
24
- kind: "tool-invocation" | "agent-patch" | "operator-patch" | "operator-decision" | "validator-result" | "runtime-event" | "note" | "correction" | "strip-decision-reverted";
24
+ kind: "operator-decision" | "validator-result" | "change-log" | "tool-invocation" | "agent-patch" | "operator-patch" | "runtime-event" | "note" | "correction" | "strip-decision-reverted";
25
25
  ts: string;
26
26
  }[];
27
27
  entryCount: number;
@@ -32,7 +32,7 @@ declare const LedgerReadIndexSchema: v.SchemaWithPipe<readonly [v.ObjectSchema<{
32
32
  coveredFiles: string[];
33
33
  entries: {
34
34
  id: string;
35
- kind: "tool-invocation" | "agent-patch" | "operator-patch" | "operator-decision" | "validator-result" | "runtime-event" | "note" | "correction" | "strip-decision-reverted";
35
+ kind: "operator-decision" | "validator-result" | "change-log" | "tool-invocation" | "agent-patch" | "operator-patch" | "runtime-event" | "note" | "correction" | "strip-decision-reverted";
36
36
  ts: string;
37
37
  }[];
38
38
  entryCount: number;
@@ -50,7 +50,7 @@ export declare const buildReadIndexFromManifest: (workspaceRoot: string, manifes
50
50
  coveredFiles: string[];
51
51
  entries: {
52
52
  id: string;
53
- kind: "tool-invocation" | "agent-patch" | "operator-patch" | "operator-decision" | "validator-result" | "runtime-event" | "note" | "correction" | "strip-decision-reverted";
53
+ kind: "operator-decision" | "validator-result" | "change-log" | "tool-invocation" | "agent-patch" | "operator-patch" | "runtime-event" | "note" | "correction" | "strip-decision-reverted";
54
54
  ts: string;
55
55
  }[];
56
56
  entryCount: number;
@@ -65,7 +65,7 @@ export declare const ensureReadIndexUnderLock: (workspaceRoot: string, manifest:
65
65
  coveredFiles: string[];
66
66
  entries: {
67
67
  id: string;
68
- kind: "tool-invocation" | "agent-patch" | "operator-patch" | "operator-decision" | "validator-result" | "runtime-event" | "note" | "correction" | "strip-decision-reverted";
68
+ kind: "operator-decision" | "validator-result" | "change-log" | "tool-invocation" | "agent-patch" | "operator-patch" | "runtime-event" | "note" | "correction" | "strip-decision-reverted";
69
69
  ts: string;
70
70
  }[];
71
71
  entryCount: number;
@@ -81,7 +81,7 @@ export declare const appendEntryToReadIndex: ({ entry, readIndex, sequence, }: {
81
81
  coveredFiles: string[];
82
82
  entries: {
83
83
  id: string;
84
- kind: "tool-invocation" | "agent-patch" | "operator-patch" | "operator-decision" | "validator-result" | "runtime-event" | "note" | "correction" | "strip-decision-reverted";
84
+ kind: "operator-decision" | "validator-result" | "change-log" | "tool-invocation" | "agent-patch" | "operator-patch" | "runtime-event" | "note" | "correction" | "strip-decision-reverted";
85
85
  ts: string;
86
86
  }[];
87
87
  entryCount: number;
@@ -1 +1 @@
1
- {"version":3,"file":"read-index.d.ts","sourceRoot":"","sources":["../src/read-index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAG7B,OAAO,EAAgB,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAoB,MAAM,mBAAmB,CAAC;AACxH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAO3D,QAAA,MAAM,oBAAoB;;;;aAIxB,CAAC;AAEH,QAAA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sDAiB1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC1E,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACxE,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AA0HhG,eAAO,MAAM,0BAA0B,GAAU,eAAe,MAAM,EAAE,UAAU,cAAc;;;;;;;;;;;EAuB/F,CAAC;AAYF,eAAO,MAAM,kBAAkB,GAAI,OAAO,eAAe,EAAE,UAAU,cAAc,YAG1C,CAAC;AAE1C,eAAO,MAAM,aAAa,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAWzF,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,eAAe,MAAM,EAAE,WAAW,eAAe,kBAGpF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,eAAe,MAAM,EAAE,UAAU,cAAc;;;;;;;;;;;EAiB7F,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,iCAIpC;IACC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC7B;;;;;;;;;;;CAYA,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,uCAIpC;IACC,QAAQ,CAAC,MAAM,EAAE;QACb,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;QAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;QAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CAC3C,YAeA,CAAC"}
1
+ {"version":3,"file":"read-index.d.ts","sourceRoot":"","sources":["../src/read-index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAG7B,OAAO,EAAgB,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAoB,MAAM,mBAAmB,CAAC;AACxH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAO3D,QAAA,MAAM,oBAAoB;;;;aAIxB,CAAC;AAEH,QAAA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sDAc1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC1E,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACxE,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AA0HhG,eAAO,MAAM,0BAA0B,GAAU,eAAe,MAAM,EAAE,UAAU,cAAc;;;;;;;;;;;EAuB/F,CAAC;AAYF,eAAO,MAAM,kBAAkB,GAAI,OAAO,eAAe,EAAE,UAAU,cAAc,YAG1C,CAAC;AAE1C,eAAO,MAAM,aAAa,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAWzF,CAAC;AAEF,eAAO,MAAM,aAAa,GAAU,eAAe,MAAM,EAAE,WAAW,eAAe,kBAGpF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,eAAe,MAAM,EAAE,UAAU,cAAc;;;;;;;;;;;EAiB7F,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,iCAIpC;IACC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC7B;;;;;;;;;;;CAYA,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,uCAIpC;IACC,QAAQ,CAAC,MAAM,EAAE;QACb,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;QAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;QAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;CAC3C,YAeA,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"AAQA,OAAO,EAEH,KAAK,WAAW,EAKnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAU3D,KAAK,qBAAqB,GAAG;IACzB,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,QAAQ,CAAC,uBAAuB,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACpF,CAAC;AAUF,eAAO,MAAM,wBAAwB,GAAI,eAAe,MAAM,EAAE,OAAO,qBAAqB,GAAG,IAAI,SAOlG,CAAC;AAoHF,eAAO,MAAM,YAAY,GACrB,eAAe,MAAM,EACrB,OAAO,OAAO,KACf,OAAO,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAqF5C,CAAC;AAEF,eAAO,MAAM,aAAa,GACtB,eAAe,MAAM,EACrB,UAAU,cAAc,EACxB,SAAS,MAAM,KAChB,OAAO,CAAC,WAAW,CAMrB,CAAC"}
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"AAQA,OAAO,EACH,KAAK,WAAW,EAMnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAU3D,KAAK,qBAAqB,GAAG;IACzB,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,QAAQ,CAAC,uBAAuB,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACpF,CAAC;AAOF,eAAO,MAAM,wBAAwB,GAAI,eAAe,MAAM,EAAE,OAAO,qBAAqB,GAAG,IAAI,SAOlG,CAAC;AA0EF,eAAO,MAAM,YAAY,GACrB,eAAe,MAAM,EACrB,OAAO,OAAO,KACf,OAAO,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAsF5C,CAAC;AAEF,eAAO,MAAM,aAAa,GACtB,eAAe,MAAM,EACrB,UAAU,cAAc,EACxB,SAAS,MAAM,KAChB,OAAO,CAAC,WAAW,CASrB,CAAC"}
package/dist/record.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import * as v from 'valibot';
4
- import { resolveBlobPath, storePatchBlob } from "./blobs.js";
5
4
  import { sha256Hex, stableStringify } from "./json.js";
6
5
  import { updateManifestForEntry } from "./manifest-update.js";
6
+ import { resolvePatchRecord } from "./patch-resolver.js";
7
7
  import { appendEntryToReadIndex, saveReadIndex } from "./read-index.js";
8
8
  import { reconcileLedgerStateUnderLock, removePendingCommit, writePendingCommit } from "./recovery.js";
9
- import { LedgerEntrySchema, parseLedgerEntry, parseLedgerRecord, } from "./schema/entry.js";
9
+ import { LedgerEntrySchema, LedgerSchemaVersion, parseLedgerEntry, parseLedgerRecord, } from "./schema/entry.js";
10
10
  import { ensureLedgerDirectories, readManifest, resolveLedgerPaths, saveManifest, writeEntryFile, } from "./storage/filesystem.js";
11
11
  import { acquireLock } from "./storage/lock.js";
12
12
  const appendRecordTestHooks = new Map();
@@ -20,7 +20,6 @@ export const setAppendRecordTestHooks = (workspaceRoot, hooks) => {
20
20
  }
21
21
  appendRecordTestHooks.set(key, hooks);
22
22
  };
23
- const readBlobText = async (workspaceRoot, diff) => readFile(resolveBlobPath(workspaceRoot, diff.blobSha256), 'utf8');
24
23
  const addIdempotencyMetadata = (record) => {
25
24
  if (!record.idempotencyKey) {
26
25
  return record;
@@ -52,47 +51,22 @@ const buildEntryId = (entryWithoutId, sequence) => {
52
51
  const bodyHash = sha256Hex(stableStringify(entryWithoutId));
53
52
  return `${toEntryIdTimestamp(entryWithoutId.ts)}-${sequence.toString().padStart(8, '0')}-${bodyHash.slice(0, 12)}`;
54
53
  };
55
- const loadPatchText = async ({ record, workspaceRoot, }) => {
56
- if (record.diffPath) {
57
- return readFile(path.resolve(record.diffPath), 'utf8');
58
- }
59
- if (record.diffText) {
60
- return record.diffText;
54
+ const normalizeRecord = async ({ record, workspaceRoot, }) => {
55
+ if (record.kind === 'agent-patch') {
56
+ return resolvePatchRecord({ record, workspaceRoot });
61
57
  }
62
- if (record.diff) {
63
- try {
64
- return await readBlobText(workspaceRoot, record.diff);
65
- }
66
- catch (error) {
67
- if (error.code === 'ENOENT') {
68
- throw new Error(`Patch blob ${record.diff.blobSha256} was not found. Store it first or use diffPath.`);
69
- }
70
- throw error;
71
- }
58
+ if (record.kind === 'operator-patch') {
59
+ return resolvePatchRecord({ record, workspaceRoot });
72
60
  }
73
- throw new Error(`${record.kind} records require diff, diffPath, or diffText.`);
61
+ return record;
74
62
  };
75
- const resolvePatchRecord = async ({ record, workspaceRoot, }) => {
76
- const patchText = await loadPatchText({ record, workspaceRoot });
77
- const storedDiff = await storePatchBlob(workspaceRoot, patchText);
78
- if (record.diff && record.diff.blobSha256 !== storedDiff.blobSha256) {
79
- throw new Error(`Provided diff blob ${record.diff.blobSha256} does not match patch text hash ${storedDiff.blobSha256}.`);
63
+ const assertRollbackTargetExists = (record, manifest) => {
64
+ if (record.kind !== 'change-log' || record.subkind !== 'rollback' || !record.rollsBack) {
65
+ return;
80
66
  }
81
- const { diffPath: _diffPath, diffText: _diffText, ...rest } = record;
82
- return {
83
- ...rest,
84
- diff: storedDiff,
85
- links: {
86
- ...record.links,
87
- blobs: [...new Set([...(record.links?.blobs ?? []), storedDiff.blobSha256])],
88
- },
89
- };
90
- };
91
- const normalizeRecord = async ({ record, workspaceRoot, }) => {
92
- if (record.kind === 'agent-patch' || record.kind === 'operator-patch') {
93
- return resolvePatchRecord({ record, workspaceRoot });
67
+ if (!manifest.entryLocations[record.rollsBack]) {
68
+ throw new Error(`change-log rollback target was not found in the ledger: ${record.rollsBack}`);
94
69
  }
95
- return record;
96
70
  };
97
71
  export const appendRecord = async (workspaceRoot, input) => {
98
72
  const parsed = parseLedgerRecord(input);
@@ -105,6 +79,7 @@ export const appendRecord = async (workspaceRoot, input) => {
105
79
  const lock = await acquireLock(paths.manifestLockFile);
106
80
  try {
107
81
  const { manifest, readIndex } = await reconcileLedgerStateUnderLock(workspaceRoot);
82
+ assertRollbackTargetExists(recordWithMetadata, manifest);
108
83
  const manifestFingerprint = fingerprintManifest(manifest);
109
84
  const keyedLogicalHash = parsed.idempotencyKey ? buildLogicalHash(recordWithMetadata) : null;
110
85
  if (keyedLogicalHash) {
@@ -136,7 +111,7 @@ export const appendRecord = async (workspaceRoot, input) => {
136
111
  ...recordWithoutIdempotencyKey,
137
112
  links: recordWithoutIdempotencyKey.links ?? {},
138
113
  prevEntryId: manifest.perPhaseLatest[normalizedRecord.phase] ?? null,
139
- schemaVersion: 'ushman-ledger-entry/v1',
114
+ schemaVersion: LedgerSchemaVersion,
140
115
  ts: new Date().toISOString(),
141
116
  };
142
117
  const entry = v.parse(LedgerEntrySchema, {
@@ -0,0 +1,10 @@
1
+ import type { LedgerEntry } from '../schema/entry.ts';
2
+ export declare const writeMigrationLogMarkdown: ({ entries, write, }: {
3
+ readonly entries: AsyncIterable<LedgerEntry>;
4
+ readonly write: (chunk: string) => Promise<void> | void;
5
+ }) => Promise<void>;
6
+ export declare const renderMigrationLogMarkdown: ({ entries, write, }: {
7
+ readonly entries: AsyncIterable<LedgerEntry>;
8
+ readonly write?: (chunk: string) => Promise<void> | void;
9
+ }) => Promise<string>;
10
+ //# sourceMappingURL=migration-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-log.d.ts","sourceRoot":"","sources":["../../src/render/migration-log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuC,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAmE3F,eAAO,MAAM,yBAAyB,GAAU,qBAG7C;IACC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC3D,kBAaA,CAAC;AAEF,eAAO,MAAM,0BAA0B,GAAU,qBAG9C;IACC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC7C,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC5D,KAAG,OAAO,CAAC,MAAM,CASjB,CAAC"}
@@ -0,0 +1,79 @@
1
+ const getFence = (lines) => {
2
+ const longestBacktickRun = lines.reduce((longest, line) => {
3
+ const matches = line.match(/`+/gu) ?? [];
4
+ return Math.max(longest, ...matches.map((match) => match.length), 3);
5
+ }, 3);
6
+ return '`'.repeat(longestBacktickRun + 1);
7
+ };
8
+ const toCodeBlock = (commandsRun) => {
9
+ const lines = commandsRun && commandsRun.length > 0 ? commandsRun : ['# None recorded'];
10
+ const fence = getFence(lines);
11
+ return [fence + 'bash', ...lines, fence].join('\n');
12
+ };
13
+ const renderFilesChanged = (filesChanged) => filesChanged.length === 0
14
+ ? '- None recorded.'
15
+ : filesChanged
16
+ .map((fileChange) => {
17
+ const stats = [`+${fileChange.added ?? 0}`, `-${fileChange.removed ?? 0}`].join(' ');
18
+ return `- \`${fileChange.path}\` (${stats})`;
19
+ })
20
+ .join('\n');
21
+ const renderOptionalLine = (label, value) => `**${label}**: ${value ?? 'Not recorded.'}`;
22
+ const renderSmokeLine = (entry) => {
23
+ if (entry.smokeResult && entry.smokeNotes) {
24
+ return `**Smoke result**: ${entry.smokeResult} - ${entry.smokeNotes}`;
25
+ }
26
+ if (entry.smokeResult) {
27
+ return `**Smoke result**: ${entry.smokeResult}`;
28
+ }
29
+ if (entry.smokeNotes) {
30
+ return ['**Smoke result**: Not recorded.', '', `**Smoke notes**: ${entry.smokeNotes}`].join('\n');
31
+ }
32
+ return '**Smoke result**: Not recorded.';
33
+ };
34
+ const isChangeLogEntry = (entry) => entry.kind === 'change-log';
35
+ const renderChangeLogSection = (entry) => [
36
+ `## ${entry.ts} - ${entry.summary}`,
37
+ '',
38
+ `**Type**: ${entry.subkind}`,
39
+ '',
40
+ '**Files changed**:',
41
+ renderFilesChanged(entry.filesChanged),
42
+ '',
43
+ renderOptionalLine('Hypothesis', entry.hypothesis),
44
+ '',
45
+ '**Commands run**:',
46
+ toCodeBlock(entry.commandsRun),
47
+ '',
48
+ renderSmokeLine(entry),
49
+ '',
50
+ renderOptionalLine('Parity status', entry.parityStatus),
51
+ '',
52
+ renderOptionalLine('Rollback plan', entry.rollbackPlan),
53
+ '',
54
+ '---',
55
+ '',
56
+ ].join('\n');
57
+ export const writeMigrationLogMarkdown = async ({ entries, write, }) => {
58
+ await write('# Ushman Ledger Migration Log\n\n');
59
+ let hasEntries = false;
60
+ for await (const entry of entries) {
61
+ if (!isChangeLogEntry(entry)) {
62
+ continue;
63
+ }
64
+ hasEntries = true;
65
+ await write(renderChangeLogSection(entry));
66
+ }
67
+ if (!hasEntries) {
68
+ await write('No change-log entries recorded.');
69
+ }
70
+ };
71
+ export const renderMigrationLogMarkdown = async ({ entries, write, }) => {
72
+ const chunks = [];
73
+ const resolvedWriter = write ??
74
+ ((chunk) => {
75
+ chunks.push(chunk);
76
+ });
77
+ await writeMigrationLogMarkdown({ entries, write: resolvedWriter });
78
+ return chunks.join('');
79
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"retro.d.ts","sourceRoot":"","sources":["../../src/render/retro.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA6GtD,eAAO,MAAM,mBAAmB,GAAU,wBAGvC;IACC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC9B,KAAG,OAAO,CAAC,MAAM,CA6CjB,CAAC"}
1
+ {"version":3,"file":"retro.d.ts","sourceRoot":"","sources":["../../src/render/retro.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA4HtD,eAAO,MAAM,mBAAmB,GAAU,wBAGvC;IACC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC9B,KAAG,OAAO,CAAC,MAAM,CAgDjB,CAAC"}
@@ -4,6 +4,7 @@ const toSingleLine = (value) => value.replaceAll(/\s+/gu, ' ').trim();
4
4
  const formatEntry = (entry) => `${entry.ts} [${entry.phase}] ${toSingleLine(entry.summary)}`;
5
5
  const createRetroBuckets = () => ({
6
6
  correctionEntries: [],
7
+ dedicatedNarrativeEntries: [],
7
8
  operatorEntries: [],
8
9
  problemEntries: [],
9
10
  retroEntries: [],
@@ -18,6 +19,9 @@ const isToolingEntry = (entry) => (entry.kind === 'note' && entry.subkind === 'a
18
19
  (entry.kind === 'note' && entry.subkind === 'tooling-gap');
19
20
  const isRetroNote = (entry) => entry.kind === 'note' && entry.subkind === 'retro';
20
21
  const isOperatorNote = (entry) => entry.kind === 'note' && entry.subkind === 'operator';
22
+ const isDedicatedNarrativeEntry = (entry) => entry.kind === 'change-log' ||
23
+ (entry.kind === 'note' &&
24
+ ['cleanup-wave', 'verified-flow', 'open-issue', 'decomposition-wave', 'semantic-cleanup-summary'].includes(entry.subkind));
21
25
  const resolveRenderedAt = (entryCount, lastEntryTimestamp, manifest) => {
22
26
  if (entryCount > 0) {
23
27
  return lastEntryTimestamp ?? 'n/a';
@@ -36,6 +40,32 @@ const normalizeManifestForHash = (manifest) => typeof manifest === 'object' && m
36
40
  const formatValidatorEntry = (entry) => entry.kind === 'validator-result'
37
41
  ? `${entry.ts} [${entry.validator}/${entry.verdict}] ${entry.summary}`
38
42
  : formatEntry(entry);
43
+ const bucketRetroEntry = (buckets, entry) => {
44
+ if (isToolEntry(entry)) {
45
+ buckets.toolEntries.push(formatEntry(entry));
46
+ }
47
+ if (isProblemEntry(entry)) {
48
+ buckets.problemEntries.push(formatEntry(entry));
49
+ }
50
+ if (isToolingEntry(entry)) {
51
+ buckets.toolingEntries.push(formatEntry(entry));
52
+ }
53
+ if (isRetroNote(entry)) {
54
+ buckets.retroEntries.push(formatEntry(entry));
55
+ }
56
+ if (entry.kind === 'validator-result') {
57
+ buckets.validatorEntries.push(formatValidatorEntry(entry));
58
+ }
59
+ if (entry.kind === 'correction') {
60
+ buckets.correctionEntries.push(formatEntry(entry));
61
+ }
62
+ if (isOperatorNote(entry)) {
63
+ buckets.operatorEntries.push(formatEntry(entry));
64
+ }
65
+ if (isDedicatedNarrativeEntry(entry)) {
66
+ buckets.dedicatedNarrativeEntries.push(formatEntry(entry));
67
+ }
68
+ };
39
69
  const collectRetroState = async (entries) => {
40
70
  const buckets = createRetroBuckets();
41
71
  const tools = new Set();
@@ -45,27 +75,7 @@ const collectRetroState = async (entries) => {
45
75
  entryCount += 1;
46
76
  lastEntryTimestamp = entry.ts;
47
77
  tools.add(entry.emitter.tool);
48
- if (isToolEntry(entry)) {
49
- buckets.toolEntries.push(formatEntry(entry));
50
- }
51
- if (isProblemEntry(entry)) {
52
- buckets.problemEntries.push(formatEntry(entry));
53
- }
54
- if (isToolingEntry(entry)) {
55
- buckets.toolingEntries.push(formatEntry(entry));
56
- }
57
- if (isRetroNote(entry)) {
58
- buckets.retroEntries.push(formatEntry(entry));
59
- }
60
- if (entry.kind === 'validator-result') {
61
- buckets.validatorEntries.push(formatValidatorEntry(entry));
62
- }
63
- if (entry.kind === 'correction') {
64
- buckets.correctionEntries.push(formatEntry(entry));
65
- }
66
- if (isOperatorNote(entry)) {
67
- buckets.operatorEntries.push(formatEntry(entry));
68
- }
78
+ bucketRetroEntry(buckets, entry);
69
79
  }
70
80
  return {
71
81
  buckets,
@@ -117,5 +127,8 @@ export const renderRetroMarkdown = async ({ entries, manifest, }) => {
117
127
  '## 10. Triage signals',
118
128
  renderList(buckets.validatorEntries),
119
129
  '',
130
+ '## 11. Dedicated narrative/change-log entries',
131
+ renderList(buckets.dedicatedNarrativeEntries),
132
+ '',
120
133
  ].join('\n');
121
134
  };
@@ -0,0 +1,12 @@
1
+ import type { LedgerEntry } from '../schema/entry.ts';
2
+ export declare const writeWorkspaceNarrativeMarkdown: ({ entries, workspaceName, write, }: {
3
+ readonly entries: AsyncIterable<LedgerEntry>;
4
+ readonly workspaceName: string;
5
+ readonly write: (chunk: string) => Promise<void> | void;
6
+ }) => Promise<void>;
7
+ export declare const renderWorkspaceNarrativeMarkdown: ({ entries, workspaceName, write, }: {
8
+ readonly entries: AsyncIterable<LedgerEntry>;
9
+ readonly workspaceName: string;
10
+ readonly write?: (chunk: string) => Promise<void> | void;
11
+ }) => Promise<string>;
12
+ //# sourceMappingURL=workspace-narrative.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-narrative.d.ts","sourceRoot":"","sources":["../../src/render/workspace-narrative.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,oBAAoB,CAAC;AAuIjE,eAAO,MAAM,+BAA+B,GAAU,oCAInD;IACC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC3D,kBA+BA,CAAC;AAEF,eAAO,MAAM,gCAAgC,GAAU,oCAIpD;IACC,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC5D,KAAG,OAAO,CAAC,MAAM,CAajB,CAAC"}