@open-agent-toolkit/cli 0.2.66 → 0.2.67
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/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/configuration.md +14 -1
- package/assets/docs/cli-utilities/project-log.md +63 -4
- package/assets/docs/contributing/code.md +8 -4
- package/assets/docs/contributing/skills.md +29 -17
- package/assets/docs/docs-tooling/commands.md +3 -1
- package/assets/docs/provider-sync/manifest-and-drift.md +2 -18
- package/assets/docs/workflows/projects/lifecycle.md +17 -0
- package/assets/docs/workflows/projects/picking-up-projects.md +18 -0
- package/assets/docs/workflows/skills/repo-improve.md +7 -3
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/create-agnostic-skill/SKILL.md +19 -37
- package/assets/skills/create-agnostic-skill/references/docs/skills-guide.md +58 -15
- package/assets/skills/create-agnostic-skill/references/skill-template.md +17 -15
- package/assets/skills/oat-agent-instructions-apply/SKILL.md +1 -1
- package/assets/skills/oat-agent-instructions-apply/references/instruction-file-templates/glob-scoped-rule.md +1 -4
- package/assets/skills/oat-doctor/SKILL.md +1 -4
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +1 -1
- package/assets/skills/oat-project-complete/SKILL.md +158 -30
- package/assets/skills/oat-project-complete/scripts/finalize-synced-archive.mjs +26 -7
- package/assets/skills/oat-project-complete/scripts/validate-durable-archive-receipt.mjs +183 -0
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +1 -1
- package/assets/skills/oat-project-retro/SKILL.md +1 -1
- package/assets/skills/oat-project-retro/references/apply-procedure.md +11 -0
- package/assets/skills/oat-project-review-provide/SKILL.md +4 -4
- package/assets/skills/oat-project-summary/SKILL.md +24 -2
- package/assets/skills/oat-repo-improve/SKILL.md +9 -2
- package/assets/skills/oat-repo-improve/references/plan-template.md +4 -6
- package/assets/skills/oat-repo-knowledge-index/SKILL.md +11 -9
- package/assets/skills/oat-wave-execute/SKILL.md +20 -6
- package/assets/skills/oat-wave-program/SKILL.md +9 -4
- package/dist/commands/config/index.d.ts +2 -1
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +133 -38
- package/dist/commands/docs/index-generate/index.d.ts.map +1 -1
- package/dist/commands/docs/index-generate/index.js +68 -12
- package/dist/commands/docs/init/index.d.ts +11 -0
- package/dist/commands/docs/init/index.d.ts.map +1 -1
- package/dist/commands/docs/init/index.js +19 -1
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +10 -2
- package/dist/commands/instructions/instructions.utils.d.ts.map +1 -1
- package/dist/commands/instructions/instructions.utils.js +42 -9
- package/dist/commands/internal/validate-skill-version-bumps.d.ts.map +1 -1
- package/dist/commands/internal/validate-skill-version-bumps.js +5 -5
- package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
- package/dist/commands/project/dispatch-ceiling/index.js +32 -18
- package/dist/commands/project/log/append.d.ts +48 -0
- package/dist/commands/project/log/append.d.ts.map +1 -1
- package/dist/commands/project/log/append.js +207 -30
- package/dist/commands/project/log/check.d.ts +87 -1
- package/dist/commands/project/log/check.d.ts.map +1 -1
- package/dist/commands/project/log/check.js +139 -5
- package/dist/commands/project/log/grammar.d.ts +165 -0
- package/dist/commands/project/log/grammar.d.ts.map +1 -1
- package/dist/commands/project/log/grammar.js +219 -1
- package/dist/commands/project/log/rollup.d.ts +16 -1
- package/dist/commands/project/log/rollup.d.ts.map +1 -1
- package/dist/commands/project/log/rollup.js +22 -2
- package/dist/commands/project/log/synthesize.d.ts +16 -0
- package/dist/commands/project/log/synthesize.d.ts.map +1 -1
- package/dist/commands/project/log/synthesize.js +85 -9
- package/dist/commands/status/index.d.ts.map +1 -1
- package/dist/commands/status/index.js +24 -2
- package/dist/commands/sync/apply.d.ts.map +1 -1
- package/dist/commands/sync/apply.js +6 -4
- package/dist/config/dispatch-matrix.d.ts.map +1 -1
- package/dist/config/dispatch-matrix.js +13 -5
- package/dist/config/oat-config.d.ts +15 -0
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +61 -7
- package/dist/config/own-keys.d.ts +22 -0
- package/dist/config/own-keys.d.ts.map +1 -0
- package/dist/config/own-keys.js +32 -0
- package/dist/config/resolve.d.ts +11 -0
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +35 -11
- package/dist/config/sync-config.d.ts.map +1 -1
- package/dist/config/sync-config.js +16 -0
- package/dist/drift/detector.d.ts.map +1 -1
- package/dist/drift/detector.js +52 -1
- package/dist/drift/skill-view-diagnostic.d.ts.map +1 -1
- package/dist/drift/skill-view-diagnostic.js +2 -23
- package/dist/engine/compute-plan.d.ts.map +1 -1
- package/dist/engine/compute-plan.js +22 -72
- package/dist/engine/managed-copy-hash.d.ts +69 -0
- package/dist/engine/managed-copy-hash.d.ts.map +1 -0
- package/dist/engine/managed-copy-hash.js +175 -0
- package/dist/fs/assets.d.ts +11 -0
- package/dist/fs/assets.d.ts.map +1 -1
- package/dist/fs/assets.js +1 -1
- package/dist/manifest/hash.d.ts +110 -0
- package/dist/manifest/hash.d.ts.map +1 -1
- package/dist/manifest/hash.js +111 -13
- package/dist/providers/ceiling/registry.d.ts.map +1 -1
- package/dist/providers/ceiling/registry.js +6 -1
- package/dist/providers/identity/dispatch-report.d.ts.map +1 -1
- package/dist/providers/identity/dispatch-report.js +4 -1
- package/dist/release/public-package-contract.d.ts.map +1 -1
- package/dist/release/public-package-contract.js +21 -0
- package/dist/validation/skills.d.ts.map +1 -1
- package/dist/validation/skills.js +305 -40
- package/package.json +2 -2
|
@@ -13,7 +13,103 @@ export interface ProjectLogSection {
|
|
|
13
13
|
}
|
|
14
14
|
export declare function isProjectLogSectionMarker(line: string): boolean;
|
|
15
15
|
export declare function isProjectLogEntryMarker(line: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Every character JavaScript treats as a LineTerminator, and therefore every
|
|
18
|
+
* character a multiline `^` anchors after: LF, CR, U+2028 LINE SEPARATOR, and
|
|
19
|
+
* U+2029 PARAGRAPH SEPARATOR. VT, FF, and U+0085 NEL are deliberately absent —
|
|
20
|
+
* ECMAScript does not count them, so `^` does not match after them either.
|
|
21
|
+
*
|
|
22
|
+
* The set is exported rather than inlined because the append validator and this
|
|
23
|
+
* parser must enumerate exactly the same characters. A validator that split on
|
|
24
|
+
* fewer terminators than the parser anchored after is precisely how a body
|
|
25
|
+
* could smuggle a `## ` past validation and have it become a real section
|
|
26
|
+
* boundary, silently truncating the parsed entries region and voiding the
|
|
27
|
+
* completion seal.
|
|
28
|
+
*/
|
|
29
|
+
export declare const PROJECT_LOG_LINE_TERMINATOR_RE: RegExp;
|
|
30
|
+
/**
|
|
31
|
+
* A *lone* line terminator: a carriage return not followed by a line feed, or a
|
|
32
|
+
* U+2028 / U+2029 separator.
|
|
33
|
+
*
|
|
34
|
+
* CRLF is deliberately excluded, and that carve-out is the whole point. Every
|
|
35
|
+
* reader in this module treats `\r\n` as exactly one boundary — `splitProjectLogLines`,
|
|
36
|
+
* the `/\r?\n/` scans, and `parseProjectLogEntries` — so a CRLF body has one
|
|
37
|
+
* reading and is accepted, as it was before this guard existed. A *lone* CR has
|
|
38
|
+
* two readings (a boundary to a multiline `^`, body text to everything here),
|
|
39
|
+
* and so do U+2028 and U+2029, so those stay refused.
|
|
40
|
+
*
|
|
41
|
+
* Written as one pattern rather than a strip-then-test so the rule is legible at
|
|
42
|
+
* the point of use: `\r(?!\n)` is "a carriage return that is not part of CRLF".
|
|
43
|
+
*/
|
|
44
|
+
export declare const PROJECT_LOG_LONE_TERMINATOR_RE: RegExp;
|
|
45
|
+
/**
|
|
46
|
+
* Canonicalizes accepted line endings to LF.
|
|
47
|
+
*
|
|
48
|
+
* The command parses leniently and writes strictly: CRLF is accepted from a
|
|
49
|
+
* caller — a `--body -` fed from a file, tool output, or a pasted buffer — and
|
|
50
|
+
* stored as LF, so a log this command writes never contains a terminator other
|
|
51
|
+
* than LF. That makes the file's single reading a property of its bytes rather
|
|
52
|
+
* than of every reader agreeing about them, which is the weaker guarantee this
|
|
53
|
+
* module kept failing to hold.
|
|
54
|
+
*
|
|
55
|
+
* It is not needed to satisfy `rollup`'s `^…$/m` ledger scan: a multiline `$`
|
|
56
|
+
* matches before a CR as readily as before an LF, so that scan reads a CRLF log
|
|
57
|
+
* correctly and always did. Storing LF is a narrowing of what future readers
|
|
58
|
+
* must cope with, not a repair of a present disagreement.
|
|
59
|
+
*/
|
|
60
|
+
export declare function normalizeProjectLogLineEndings(value: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Splits on every LineTerminator, treating CRLF as one boundary.
|
|
63
|
+
*
|
|
64
|
+
* Validators use this rather than a `/\r?\n/` split so that a lone CR, a
|
|
65
|
+
* U+2028, or a U+2029 starts a new line for validation just as it would for a
|
|
66
|
+
* regular expression's multiline `^`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function splitProjectLogLines(content: string): string[];
|
|
69
|
+
/**
|
|
70
|
+
* The log's `## ` section boundaries, where a boundary is a `## ` that begins
|
|
71
|
+
* the content or directly follows a line feed.
|
|
72
|
+
*
|
|
73
|
+
* LF is the only boundary on purpose. The previous `/^## [^\r\n]+/gm` anchored
|
|
74
|
+
* after all four LineTerminators while the append validator only split on
|
|
75
|
+
* `/\r?\n/`, so a judgment body reading `carrier<CR>## Injected` passed
|
|
76
|
+
* validation and then became a real section boundary — truncating `## Entries`
|
|
77
|
+
* so `check` reported an already-written seal as `sealed: false` and appends
|
|
78
|
+
* kept succeeding onto a sealed log. Anchoring on LF alone keeps this parser
|
|
79
|
+
* and `parseProjectLogEntries`, which splits its section on `/\r?\n/`, reading
|
|
80
|
+
* the same file the same way, and it holds for a log written by hand as well as one
|
|
81
|
+
* the command wrote.
|
|
82
|
+
*/
|
|
16
83
|
export declare function findProjectLogSections(content: string): ProjectLogSection[];
|
|
84
|
+
/**
|
|
85
|
+
* Whether the log contains a heading that a `^…$/m` reader starts a line at but
|
|
86
|
+
* an LF-only reader does not — that is, whether the file has two readings.
|
|
87
|
+
*
|
|
88
|
+
* The two competing readers are enumerated rather than approximated: the
|
|
89
|
+
* pre-fix section scan `/^## [^\r\n]+/gm`, and `rollup.ts`'s ledger scan, which
|
|
90
|
+
* recognizes only the complete dated judgment grammar. A `### ` line that
|
|
91
|
+
* neither recognizes — `carrier<CR>### Notes` — is body text to every reader and
|
|
92
|
+
* is deliberately *not* flagged; refusing it would strand logs a previous
|
|
93
|
+
* release could legitimately have written.
|
|
94
|
+
*
|
|
95
|
+
* Mutators consult this and refuse rather than guess, because a file with two
|
|
96
|
+
* readings cannot answer "is this sealed?". Tightening the section parser to LF
|
|
97
|
+
* alone would otherwise silently *widen* such logs: a hand-written
|
|
98
|
+
* `preamble<U+2028>## Entries` used to parse, so its seal was found and a
|
|
99
|
+
* replayed seal was deduplicated, and under an LF-only reading the same file has
|
|
100
|
+
* no entries region, reports unsealed, and accepts a second seal. Refusing is
|
|
101
|
+
* the only resolution that is not weaker than the reading it replaces.
|
|
102
|
+
*
|
|
103
|
+
* CRLF is deliberately not caught, and the entries parser now earns that
|
|
104
|
+
* exemption rather than merely asserting it. The character immediately before a
|
|
105
|
+
* marker on a `\r\n`-terminated line is the LF, so a `## ` section boundary was
|
|
106
|
+
* always unambiguous; `### ` entries and the structural heading pattern were not,
|
|
107
|
+
* because their `$` cannot match before a trailing `\r`, so a CRLF log parsed to
|
|
108
|
+
* zero entries and an existing seal was invisible. `parseProjectLogEntries` now
|
|
109
|
+
* splits on `/\r?\n/`, which makes all three agree and makes "CRLF has exactly
|
|
110
|
+
* one reading" a fact about this module rather than a hope.
|
|
111
|
+
*/
|
|
112
|
+
export declare function containsAmbiguousProjectLogMarker(content: string): boolean;
|
|
17
113
|
export declare function composeJudgmentHeading(input: {
|
|
18
114
|
date: string;
|
|
19
115
|
scope: ProjectLogScope;
|
|
@@ -25,4 +121,73 @@ export declare function composeStructuralHeading(input: {
|
|
|
25
121
|
producer: string;
|
|
26
122
|
ref: string;
|
|
27
123
|
}): string;
|
|
124
|
+
/**
|
|
125
|
+
* The producer and ref that together identify the completion seal — the last
|
|
126
|
+
* entry a project log may ever receive.
|
|
127
|
+
*
|
|
128
|
+
* Both fields are required. A `seal` ref from another producer is an ordinary
|
|
129
|
+
* structural entry, and an `oat-project-complete` entry with another ref is an
|
|
130
|
+
* ordinary lifecycle entry; treating either as a seal would let a foreign
|
|
131
|
+
* append freeze a log that was never completed.
|
|
132
|
+
*/
|
|
133
|
+
export declare const PROJECT_LOG_SEAL_PRODUCER = "oat-project-complete";
|
|
134
|
+
export declare const PROJECT_LOG_SEAL_REF = "seal";
|
|
135
|
+
/**
|
|
136
|
+
* The prefix of the seal's idempotency token (`oat-seal:<project>`).
|
|
137
|
+
*
|
|
138
|
+
* The token is what makes a replayed seal recognize its own entry, so it is
|
|
139
|
+
* prefixed rather than bare: a project name alone could occur in ordinary prose
|
|
140
|
+
* and match an unrelated body.
|
|
141
|
+
*/
|
|
142
|
+
export declare const PROJECT_LOG_SEAL_KEY_PREFIX = "oat-seal:";
|
|
143
|
+
/**
|
|
144
|
+
* The single definition of "this entry is the completion seal", shared by the
|
|
145
|
+
* `check` probe and the `append` guard.
|
|
146
|
+
*
|
|
147
|
+
* Both callers route on this one predicate on purpose: two definitions of
|
|
148
|
+
* sealed is the regression that lets a log report itself unsealed to one
|
|
149
|
+
* command and sealed to the other.
|
|
150
|
+
*/
|
|
151
|
+
export declare function isProjectLogSealEntry(entry: {
|
|
152
|
+
producer: string;
|
|
153
|
+
ref: string;
|
|
154
|
+
}): boolean;
|
|
155
|
+
/**
|
|
156
|
+
* Whether a composed or parsed entry *heading* is the completion seal's.
|
|
157
|
+
*
|
|
158
|
+
* The heading-level counterpart of `isProjectLogSealEntry`, and it delegates to
|
|
159
|
+
* it rather than restating the rule. One predicate serves both directions —
|
|
160
|
+
* "is this request the seal?" and "does this already-appended heading name a
|
|
161
|
+
* seal?" — so the two can never drift into disagreeing about what a seal
|
|
162
|
+
* heading is. It reads the producer and ref back out of the heading text rather
|
|
163
|
+
* than from a caller's flags, which is the same normalization
|
|
164
|
+
* `parseProjectLogEntries` applies.
|
|
165
|
+
*/
|
|
166
|
+
export declare function isProjectLogSealHeading(heading: string): boolean;
|
|
167
|
+
/**
|
|
168
|
+
* The raw line of `content` that is a completion-seal heading, or undefined.
|
|
169
|
+
*
|
|
170
|
+
* Deliberately independent of `parseProjectLogEntries`: it answers "is a seal
|
|
171
|
+
* physically in this file?" where the parser answers "can this file's structure
|
|
172
|
+
* be read to contain a seal?". The gap between those two questions is the state
|
|
173
|
+
* every mutator must refuse, and all of them ask it the same way.
|
|
174
|
+
*
|
|
175
|
+
* The offset is returned alongside the text because callers must locate the
|
|
176
|
+
* seal, not merely recognize it: prose inside `## Entries` can quote a seal
|
|
177
|
+
* heading verbatim, so a substring test would place a seal that really sits
|
|
178
|
+
* under a later section inside the entries region and blame the wrong cause.
|
|
179
|
+
*/
|
|
180
|
+
export declare function findProjectLogSealHeadingLine(content: string): {
|
|
181
|
+
line: string;
|
|
182
|
+
index: number;
|
|
183
|
+
} | undefined;
|
|
184
|
+
/**
|
|
185
|
+
* Whether a seal body carries the canonical stand-alone seal token.
|
|
186
|
+
*
|
|
187
|
+
* `idempotencyToken` records the whole whitespace-delimited word containing a
|
|
188
|
+
* key, so a key glued to the varying completion timestamp never matches its own
|
|
189
|
+
* earlier append. A seal written before this convention carries no token at
|
|
190
|
+
* all, which is exactly why the seal is also recognized structurally.
|
|
191
|
+
*/
|
|
192
|
+
export declare function carriesProjectLogSealKey(body: string): boolean;
|
|
28
193
|
//# sourceMappingURL=grammar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/log/grammar.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,YAC5B,KAAK,EACL,UAAU,EACV,aAAa,EACb,UAAU,CACF,CAAC;AACX,eAAO,MAAM,kBAAkB,YAAI,SAAS,EAAE,SAAS,CAAU,CAAC;AAElE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAChE,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,6BAA6B,WAAM,CAAC;AAEjD,eAAO,MAAM,mBAAmB,QACqE,CAAC;AACtG,eAAO,MAAM,qBAAqB,QACoC,CAAC;AAEvE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAO3E;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,eAAe,CAAC;IACvB,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CAET;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,MAAM,CAET"}
|
|
1
|
+
{"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/log/grammar.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,YAC5B,KAAK,EACL,UAAU,EACV,aAAa,EACb,UAAU,CACF,CAAC;AACX,eAAO,MAAM,kBAAkB,YAAI,SAAS,EAAE,SAAS,CAAU,CAAC;AAElE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAChE,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,6BAA6B,WAAM,CAAC;AAEjD,eAAO,MAAM,mBAAmB,QACqE,CAAC;AACtG,eAAO,MAAM,qBAAqB,QACoC,CAAC;AAEvE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,8BAA8B,QAAuB,CAAC;AAEnE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,8BAA8B,QAA4B,CAAC;AAExE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAO3E;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAc1E;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,eAAe,CAAC;IACvB,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CAET;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,MAAM,CAET;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,yBAAyB,CAAC;AAChE,eAAO,MAAM,oBAAoB,SAAS,CAAC;AAE3C;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,cAAc,CAAC;AAEvD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,OAAO,CAKV;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAShE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,MAAM,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAY7C;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQ9D"}
|
|
@@ -15,17 +15,235 @@ export function isProjectLogSectionMarker(line) {
|
|
|
15
15
|
export function isProjectLogEntryMarker(line) {
|
|
16
16
|
return /^### .+$/.test(line);
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Every character JavaScript treats as a LineTerminator, and therefore every
|
|
20
|
+
* character a multiline `^` anchors after: LF, CR, U+2028 LINE SEPARATOR, and
|
|
21
|
+
* U+2029 PARAGRAPH SEPARATOR. VT, FF, and U+0085 NEL are deliberately absent —
|
|
22
|
+
* ECMAScript does not count them, so `^` does not match after them either.
|
|
23
|
+
*
|
|
24
|
+
* The set is exported rather than inlined because the append validator and this
|
|
25
|
+
* parser must enumerate exactly the same characters. A validator that split on
|
|
26
|
+
* fewer terminators than the parser anchored after is precisely how a body
|
|
27
|
+
* could smuggle a `## ` past validation and have it become a real section
|
|
28
|
+
* boundary, silently truncating the parsed entries region and voiding the
|
|
29
|
+
* completion seal.
|
|
30
|
+
*/
|
|
31
|
+
export const PROJECT_LOG_LINE_TERMINATOR_RE = /[\n\r\u2028\u2029]/;
|
|
32
|
+
/**
|
|
33
|
+
* A *lone* line terminator: a carriage return not followed by a line feed, or a
|
|
34
|
+
* U+2028 / U+2029 separator.
|
|
35
|
+
*
|
|
36
|
+
* CRLF is deliberately excluded, and that carve-out is the whole point. Every
|
|
37
|
+
* reader in this module treats `\r\n` as exactly one boundary — `splitProjectLogLines`,
|
|
38
|
+
* the `/\r?\n/` scans, and `parseProjectLogEntries` — so a CRLF body has one
|
|
39
|
+
* reading and is accepted, as it was before this guard existed. A *lone* CR has
|
|
40
|
+
* two readings (a boundary to a multiline `^`, body text to everything here),
|
|
41
|
+
* and so do U+2028 and U+2029, so those stay refused.
|
|
42
|
+
*
|
|
43
|
+
* Written as one pattern rather than a strip-then-test so the rule is legible at
|
|
44
|
+
* the point of use: `\r(?!\n)` is "a carriage return that is not part of CRLF".
|
|
45
|
+
*/
|
|
46
|
+
export const PROJECT_LOG_LONE_TERMINATOR_RE = /\r(?!\n)|[\u2028\u2029]/;
|
|
47
|
+
/**
|
|
48
|
+
* Canonicalizes accepted line endings to LF.
|
|
49
|
+
*
|
|
50
|
+
* The command parses leniently and writes strictly: CRLF is accepted from a
|
|
51
|
+
* caller — a `--body -` fed from a file, tool output, or a pasted buffer — and
|
|
52
|
+
* stored as LF, so a log this command writes never contains a terminator other
|
|
53
|
+
* than LF. That makes the file's single reading a property of its bytes rather
|
|
54
|
+
* than of every reader agreeing about them, which is the weaker guarantee this
|
|
55
|
+
* module kept failing to hold.
|
|
56
|
+
*
|
|
57
|
+
* It is not needed to satisfy `rollup`'s `^…$/m` ledger scan: a multiline `$`
|
|
58
|
+
* matches before a CR as readily as before an LF, so that scan reads a CRLF log
|
|
59
|
+
* correctly and always did. Storing LF is a narrowing of what future readers
|
|
60
|
+
* must cope with, not a repair of a present disagreement.
|
|
61
|
+
*/
|
|
62
|
+
export function normalizeProjectLogLineEndings(value) {
|
|
63
|
+
return value.replaceAll('\r\n', '\n');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Splits on every LineTerminator, treating CRLF as one boundary.
|
|
67
|
+
*
|
|
68
|
+
* Validators use this rather than a `/\r?\n/` split so that a lone CR, a
|
|
69
|
+
* U+2028, or a U+2029 starts a new line for validation just as it would for a
|
|
70
|
+
* regular expression's multiline `^`.
|
|
71
|
+
*/
|
|
72
|
+
export function splitProjectLogLines(content) {
|
|
73
|
+
return content.split(/\r\n|[\n\r\u2028\u2029]/);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The log's `## ` section boundaries, where a boundary is a `## ` that begins
|
|
77
|
+
* the content or directly follows a line feed.
|
|
78
|
+
*
|
|
79
|
+
* LF is the only boundary on purpose. The previous `/^## [^\r\n]+/gm` anchored
|
|
80
|
+
* after all four LineTerminators while the append validator only split on
|
|
81
|
+
* `/\r?\n/`, so a judgment body reading `carrier<CR>## Injected` passed
|
|
82
|
+
* validation and then became a real section boundary — truncating `## Entries`
|
|
83
|
+
* so `check` reported an already-written seal as `sealed: false` and appends
|
|
84
|
+
* kept succeeding onto a sealed log. Anchoring on LF alone keeps this parser
|
|
85
|
+
* and `parseProjectLogEntries`, which splits its section on `/\r?\n/`, reading
|
|
86
|
+
* the same file the same way, and it holds for a log written by hand as well as one
|
|
87
|
+
* the command wrote.
|
|
88
|
+
*/
|
|
18
89
|
export function findProjectLogSections(content) {
|
|
19
|
-
const matches = [...content.matchAll(
|
|
90
|
+
const matches = [...content.matchAll(/(?<=^|\n)## [^\r\n]+/g)];
|
|
20
91
|
return matches.map((match, index) => ({
|
|
21
92
|
heading: match[0],
|
|
22
93
|
start: match.index,
|
|
23
94
|
end: matches[index + 1]?.index ?? content.length,
|
|
24
95
|
}));
|
|
25
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* The judgment-heading scan a `^…$/m` reader performs — the same pattern
|
|
99
|
+
* `rollup.ts` uses to key its ledger.
|
|
100
|
+
*
|
|
101
|
+
* Declared here so `containsAmbiguousProjectLogMarker` can ask the competing
|
|
102
|
+
* reader what *it* would see, rather than approximating it. `matchAll` builds a
|
|
103
|
+
* fresh regex per call, so the `g` flag's `lastIndex` is never shared.
|
|
104
|
+
*/
|
|
105
|
+
/**
|
|
106
|
+
* The single-character form of "a terminator other than LF", for testing the
|
|
107
|
+
* one character that precedes a match. A lookahead-based rule cannot be used on
|
|
108
|
+
* a lone character; it does not need to be, because the character before a
|
|
109
|
+
* heading on a CRLF line is the LF, so CRLF never reaches this test.
|
|
110
|
+
*/
|
|
111
|
+
const PRECEDING_NON_LINE_FEED_TERMINATOR_RE = /[\r\u2028\u2029]/;
|
|
112
|
+
const JUDGMENT_HEADING_SCAN_RE = /^### (\d{4}-\d{2}-\d{2}) \u00b7 (project|general) \u00b7 (bug|friction|worked-well|feedback) \u00b7 ([^\u00b7\r\n]+)$/gm;
|
|
113
|
+
/**
|
|
114
|
+
* Whether the log contains a heading that a `^…$/m` reader starts a line at but
|
|
115
|
+
* an LF-only reader does not — that is, whether the file has two readings.
|
|
116
|
+
*
|
|
117
|
+
* The two competing readers are enumerated rather than approximated: the
|
|
118
|
+
* pre-fix section scan `/^## [^\r\n]+/gm`, and `rollup.ts`'s ledger scan, which
|
|
119
|
+
* recognizes only the complete dated judgment grammar. A `### ` line that
|
|
120
|
+
* neither recognizes — `carrier<CR>### Notes` — is body text to every reader and
|
|
121
|
+
* is deliberately *not* flagged; refusing it would strand logs a previous
|
|
122
|
+
* release could legitimately have written.
|
|
123
|
+
*
|
|
124
|
+
* Mutators consult this and refuse rather than guess, because a file with two
|
|
125
|
+
* readings cannot answer "is this sealed?". Tightening the section parser to LF
|
|
126
|
+
* alone would otherwise silently *widen* such logs: a hand-written
|
|
127
|
+
* `preamble<U+2028>## Entries` used to parse, so its seal was found and a
|
|
128
|
+
* replayed seal was deduplicated, and under an LF-only reading the same file has
|
|
129
|
+
* no entries region, reports unsealed, and accepts a second seal. Refusing is
|
|
130
|
+
* the only resolution that is not weaker than the reading it replaces.
|
|
131
|
+
*
|
|
132
|
+
* CRLF is deliberately not caught, and the entries parser now earns that
|
|
133
|
+
* exemption rather than merely asserting it. The character immediately before a
|
|
134
|
+
* marker on a `\r\n`-terminated line is the LF, so a `## ` section boundary was
|
|
135
|
+
* always unambiguous; `### ` entries and the structural heading pattern were not,
|
|
136
|
+
* because their `$` cannot match before a trailing `\r`, so a CRLF log parsed to
|
|
137
|
+
* zero entries and an existing seal was invisible. `parseProjectLogEntries` now
|
|
138
|
+
* splits on `/\r?\n/`, which makes all three agree and makes "CRLF has exactly
|
|
139
|
+
* one reading" a fact about this module rather than a hope.
|
|
140
|
+
*/
|
|
141
|
+
export function containsAmbiguousProjectLogMarker(content) {
|
|
142
|
+
if (/(?<=[\r\u2028\u2029])## [^\r\n]+/.test(content)) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
for (const match of content.matchAll(JUDGMENT_HEADING_SCAN_RE)) {
|
|
146
|
+
const preceding = content[match.index - 1];
|
|
147
|
+
if (preceding !== undefined &&
|
|
148
|
+
PRECEDING_NON_LINE_FEED_TERMINATOR_RE.test(preceding)) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
26
154
|
export function composeJudgmentHeading(input) {
|
|
27
155
|
return `### ${input.date} · ${input.scope} · ${input.type} · ${input.area}`;
|
|
28
156
|
}
|
|
29
157
|
export function composeStructuralHeading(input) {
|
|
30
158
|
return `### ${input.date} · structural · ${input.producer} · ${input.ref}`;
|
|
31
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* The producer and ref that together identify the completion seal — the last
|
|
162
|
+
* entry a project log may ever receive.
|
|
163
|
+
*
|
|
164
|
+
* Both fields are required. A `seal` ref from another producer is an ordinary
|
|
165
|
+
* structural entry, and an `oat-project-complete` entry with another ref is an
|
|
166
|
+
* ordinary lifecycle entry; treating either as a seal would let a foreign
|
|
167
|
+
* append freeze a log that was never completed.
|
|
168
|
+
*/
|
|
169
|
+
export const PROJECT_LOG_SEAL_PRODUCER = 'oat-project-complete';
|
|
170
|
+
export const PROJECT_LOG_SEAL_REF = 'seal';
|
|
171
|
+
/**
|
|
172
|
+
* The prefix of the seal's idempotency token (`oat-seal:<project>`).
|
|
173
|
+
*
|
|
174
|
+
* The token is what makes a replayed seal recognize its own entry, so it is
|
|
175
|
+
* prefixed rather than bare: a project name alone could occur in ordinary prose
|
|
176
|
+
* and match an unrelated body.
|
|
177
|
+
*/
|
|
178
|
+
export const PROJECT_LOG_SEAL_KEY_PREFIX = 'oat-seal:';
|
|
179
|
+
/**
|
|
180
|
+
* The single definition of "this entry is the completion seal", shared by the
|
|
181
|
+
* `check` probe and the `append` guard.
|
|
182
|
+
*
|
|
183
|
+
* Both callers route on this one predicate on purpose: two definitions of
|
|
184
|
+
* sealed is the regression that lets a log report itself unsealed to one
|
|
185
|
+
* command and sealed to the other.
|
|
186
|
+
*/
|
|
187
|
+
export function isProjectLogSealEntry(entry) {
|
|
188
|
+
return (entry.producer === PROJECT_LOG_SEAL_PRODUCER &&
|
|
189
|
+
entry.ref === PROJECT_LOG_SEAL_REF);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Whether a composed or parsed entry *heading* is the completion seal's.
|
|
193
|
+
*
|
|
194
|
+
* The heading-level counterpart of `isProjectLogSealEntry`, and it delegates to
|
|
195
|
+
* it rather than restating the rule. One predicate serves both directions —
|
|
196
|
+
* "is this request the seal?" and "does this already-appended heading name a
|
|
197
|
+
* seal?" — so the two can never drift into disagreeing about what a seal
|
|
198
|
+
* heading is. It reads the producer and ref back out of the heading text rather
|
|
199
|
+
* than from a caller's flags, which is the same normalization
|
|
200
|
+
* `parseProjectLogEntries` applies.
|
|
201
|
+
*/
|
|
202
|
+
export function isProjectLogSealHeading(heading) {
|
|
203
|
+
const parsed = STRUCTURAL_HEADING_RE.exec(heading);
|
|
204
|
+
return (parsed !== null &&
|
|
205
|
+
isProjectLogSealEntry({
|
|
206
|
+
producer: parsed[2].trim(),
|
|
207
|
+
ref: parsed[3].trim(),
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The raw line of `content` that is a completion-seal heading, or undefined.
|
|
212
|
+
*
|
|
213
|
+
* Deliberately independent of `parseProjectLogEntries`: it answers "is a seal
|
|
214
|
+
* physically in this file?" where the parser answers "can this file's structure
|
|
215
|
+
* be read to contain a seal?". The gap between those two questions is the state
|
|
216
|
+
* every mutator must refuse, and all of them ask it the same way.
|
|
217
|
+
*
|
|
218
|
+
* The offset is returned alongside the text because callers must locate the
|
|
219
|
+
* seal, not merely recognize it: prose inside `## Entries` can quote a seal
|
|
220
|
+
* heading verbatim, so a substring test would place a seal that really sits
|
|
221
|
+
* under a later section inside the entries region and blame the wrong cause.
|
|
222
|
+
*/
|
|
223
|
+
export function findProjectLogSealHeadingLine(content) {
|
|
224
|
+
let cursor = 0;
|
|
225
|
+
for (const line of splitProjectLogLines(content)) {
|
|
226
|
+
if (isProjectLogSealHeading(line.trim())) {
|
|
227
|
+
return { line, index: content.indexOf(line, cursor) };
|
|
228
|
+
}
|
|
229
|
+
// Advance past this line and the terminator that ended it. The terminator's
|
|
230
|
+
// width varies (CRLF is two units), so it is measured rather than assumed.
|
|
231
|
+
const next = content.indexOf(line, cursor) + line.length;
|
|
232
|
+
cursor = next + (content.startsWith('\r\n', next) ? 2 : 1);
|
|
233
|
+
}
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Whether a seal body carries the canonical stand-alone seal token.
|
|
238
|
+
*
|
|
239
|
+
* `idempotencyToken` records the whole whitespace-delimited word containing a
|
|
240
|
+
* key, so a key glued to the varying completion timestamp never matches its own
|
|
241
|
+
* earlier append. A seal written before this convention carries no token at
|
|
242
|
+
* all, which is exactly why the seal is also recognized structurally.
|
|
243
|
+
*/
|
|
244
|
+
export function carriesProjectLogSealKey(body) {
|
|
245
|
+
return body
|
|
246
|
+
.split(/\s+/)
|
|
247
|
+
.some((word) => word.startsWith(PROJECT_LOG_SEAL_KEY_PREFIX) &&
|
|
248
|
+
word.length > PROJECT_LOG_SEAL_KEY_PREFIX.length);
|
|
249
|
+
}
|
|
@@ -14,12 +14,27 @@ export interface RollupProjectLogInput {
|
|
|
14
14
|
summaryPath?: string;
|
|
15
15
|
artifactTarget?: ProjectLogArtifactTarget;
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
/** The outcome of a roll-up that actually read the log. */
|
|
18
|
+
export interface ProjectLogRollupSuccess {
|
|
18
19
|
status: 'ok' | 'failed';
|
|
19
20
|
summarySection: 'written' | 'updated';
|
|
20
21
|
ledgerOutcome: 'appended' | 'deduplicated' | 'skipped_permitted' | 'failed';
|
|
21
22
|
entriesRolledUp: number;
|
|
22
23
|
}
|
|
24
|
+
export type ProjectLogRollupResult = ProjectLogRollupSuccess
|
|
25
|
+
/**
|
|
26
|
+
* The log has two readings, so its entries cannot be rolled up honestly.
|
|
27
|
+
*
|
|
28
|
+
* A separate member rather than an extra field, because the success member's
|
|
29
|
+
* counts would otherwise have to be filled in with zeros — and reporting
|
|
30
|
+
* `entriesRolledUp: 0` for an unreadable log is exactly the conflation this
|
|
31
|
+
* refusal exists to remove: it was byte-identical to the output for an empty
|
|
32
|
+
* one.
|
|
33
|
+
*/
|
|
34
|
+
| {
|
|
35
|
+
status: 'ambiguous';
|
|
36
|
+
ambiguity: string;
|
|
37
|
+
};
|
|
23
38
|
export interface RollupProjectLogDependencies {
|
|
24
39
|
resolveActiveProject: (repoRoot: string) => Promise<ActiveProjectResolution>;
|
|
25
40
|
resolveEffectiveConfig: (repoRoot: string, userConfigDir: string, env?: NodeJS.ProcessEnv) => Promise<ResolvedConfig>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rollup.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/log/rollup.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAA0B,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG9E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"rollup.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/log/rollup.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAA0B,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG9E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,mCAAmC,EAAE,wBAGjD,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,wBAAwB,CAAC;CAC3C;AAED,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,IAAI,GAAG,QAAQ,CAAC;IACxB,cAAc,EAAE,SAAS,GAAG,SAAS,CAAC;IACtC,aAAa,EAAE,UAAU,GAAG,cAAc,GAAG,mBAAmB,GAAG,QAAQ,CAAC;IAC5E,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,sBAAsB,GAC9B,uBAAuB;AACzB;;;;;;;;GAQG;GACD;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C,MAAM,WAAW,4BAA4B;IAC3C,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC7E,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,KACpB,OAAO,CAAC,cAAc,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,mCAAoC,SAAQ,4BAA4B;IACvF,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACtD;AAiFD,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,EAAE,OAAO,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAsB,6BAA6B,CACjD,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,IAAI,CAChB,4BAA4B,EAC5B,wBAAwB,CACK,GAC9B,OAAO,CAAC,sBAAsB,CAAC,CAiBjC;AAoED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,qBAAqB,EAC5B,SAAS,GAAE,OAAO,CAAC,4BAA4B,CAAM,GACpD,OAAO,CAAC,sBAAsB,CAAC,CAqDjC;AA4DD,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,OAAO,CAAC,mCAAmC,CAAM,GAC3D,OAAO,CAoBT"}
|
|
@@ -8,7 +8,7 @@ import { resolveEffectiveConfig } from '../../../config/resolve.js';
|
|
|
8
8
|
import { dirExists, fileExists } from '../../../fs/io.js';
|
|
9
9
|
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
10
10
|
import { Command } from 'commander';
|
|
11
|
-
import { parseProjectLogEntries, PROJECT_LOG_FILENAME, } from './check.js';
|
|
11
|
+
import { findProjectLogAmbiguity, parseProjectLogEntries, PROJECT_LOG_FILENAME, } from './check.js';
|
|
12
12
|
const DEFAULT_LEDGER_PATH = '.oat/repo/reference/project-observations.md';
|
|
13
13
|
const WORKFLOW_OBSERVATIONS_HEADING = '## Workflow Observations';
|
|
14
14
|
export const DEFAULT_PROJECT_LOG_ARTIFACT_TARGET = {
|
|
@@ -145,7 +145,17 @@ export async function rollupProjectLog(input, overrides = {}) {
|
|
|
145
145
|
if (!(await fileExists(summaryPath))) {
|
|
146
146
|
throw new Error(`summary.md does not exist: ${summaryPath}. Author the project summary before roll-up.`);
|
|
147
147
|
}
|
|
148
|
-
const
|
|
148
|
+
const content = await readFile(logPath, 'utf8');
|
|
149
|
+
// The same question `check`, both append paths and `synthesize` ask, asked
|
|
150
|
+
// before anything is written. `rollup` was the one surface without it: on a
|
|
151
|
+
// log the other four refused it rewrote `summary.md` and reported
|
|
152
|
+
// `status: "ok"` with `entriesRolledUp: 0`, indistinguishable from a clean
|
|
153
|
+
// empty log. It is a public command an operator can run directly.
|
|
154
|
+
const ambiguity = findProjectLogAmbiguity(content, logPath);
|
|
155
|
+
if (ambiguity !== undefined) {
|
|
156
|
+
return { status: 'ambiguous', ambiguity };
|
|
157
|
+
}
|
|
158
|
+
const parsed = parseProjectLogEntries(content);
|
|
149
159
|
const summaryUpdate = writeSummarySection(await readFile(summaryPath, 'utf8'), artifactTarget.summaryHeading ?? WORKFLOW_OBSERVATIONS_HEADING, renderEntries(parsed.entries));
|
|
150
160
|
await writeFile(summaryPath, summaryUpdate.content, 'utf8');
|
|
151
161
|
const ledgerOutcome = await rollupLedger(input, parsed.entries, dependencies);
|
|
@@ -165,6 +175,16 @@ async function runRollupCommand(options, context, dependencies) {
|
|
|
165
175
|
project: options.project,
|
|
166
176
|
summaryPath: options.summaryPath,
|
|
167
177
|
}, dependencies);
|
|
178
|
+
if (result.status === 'ambiguous') {
|
|
179
|
+
if (context.json) {
|
|
180
|
+
context.logger.json(result);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
context.logger.error(result.ambiguity);
|
|
184
|
+
}
|
|
185
|
+
process.exitCode = 1;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
168
188
|
if (context.json) {
|
|
169
189
|
context.logger.json(result);
|
|
170
190
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
|
|
2
2
|
import { type ActiveProjectResolution } from '../../../config/oat-config.js';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
+
import { type ProjectLogLockDependencies } from './append.js';
|
|
4
5
|
export interface SynthesizeProjectLogInput {
|
|
5
6
|
repoRoot: string;
|
|
6
7
|
project?: string;
|
|
@@ -9,9 +10,24 @@ export interface SynthesizeProjectLogInput {
|
|
|
9
10
|
export interface ProjectLogSynthesizeResult {
|
|
10
11
|
status: 'synthesized';
|
|
11
12
|
logPath: string;
|
|
13
|
+
/**
|
|
14
|
+
* Present and true when the accepted `--body` carried CRLF and was stored as
|
|
15
|
+
* LF, so the bytes on disk differ from the bytes the caller passed.
|
|
16
|
+
*/
|
|
17
|
+
normalizedLineEndings?: true;
|
|
12
18
|
}
|
|
13
19
|
export interface SynthesizeProjectLogDependencies {
|
|
14
20
|
resolveActiveProject: (repoRoot: string) => Promise<ActiveProjectResolution>;
|
|
21
|
+
/**
|
|
22
|
+
* The log's own read/write primitives, mirroring `appendProjectLog`.
|
|
23
|
+
* Injectable so a test can hold this writer inside its rewrite window while
|
|
24
|
+
* another enters, which is the only way to prove the serialization rather
|
|
25
|
+
* than assert it.
|
|
26
|
+
*/
|
|
27
|
+
readLog: (path: string) => Promise<string>;
|
|
28
|
+
writeLog: (path: string, content: string) => Promise<void>;
|
|
29
|
+
/** Advisory-lock overrides for the synthesis window. */
|
|
30
|
+
lock: Partial<ProjectLogLockDependencies>;
|
|
15
31
|
}
|
|
16
32
|
export interface ProjectLogSynthesizeCommandDependencies extends SynthesizeProjectLogDependencies {
|
|
17
33
|
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"synthesize.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/log/synthesize.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"synthesize.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/log/synthesize.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAEL,KAAK,0BAA0B,EAEhC,MAAM,UAAU,CAAC;AAkBlB,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,gCAAgC;IAC/C,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC7E;;;;;OAKG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,wDAAwD;IACxD,IAAI,EAAE,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,uCAAwC,SAAQ,gCAAgC;IAC/F,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAClC;AAoDD,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,EAChC,SAAS,GAAE,OAAO,CAAC,gCAAgC,CAAM,GACxD,OAAO,CAAC,0BAA0B,CAAC,CAkDrC;AA+HD,wBAAgB,iCAAiC,CAC/C,SAAS,GAAE,OAAO,CAAC,uCAAuC,CAAM,GAC/D,OAAO,CAeT"}
|