@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.
Files changed (107) hide show
  1. package/assets/bundle-metadata.json +1 -1
  2. package/assets/docs/cli-utilities/configuration.md +14 -1
  3. package/assets/docs/cli-utilities/project-log.md +63 -4
  4. package/assets/docs/contributing/code.md +8 -4
  5. package/assets/docs/contributing/skills.md +29 -17
  6. package/assets/docs/docs-tooling/commands.md +3 -1
  7. package/assets/docs/provider-sync/manifest-and-drift.md +2 -18
  8. package/assets/docs/workflows/projects/lifecycle.md +17 -0
  9. package/assets/docs/workflows/projects/picking-up-projects.md +18 -0
  10. package/assets/docs/workflows/skills/repo-improve.md +7 -3
  11. package/assets/public-package-versions.json +4 -4
  12. package/assets/skills/create-agnostic-skill/SKILL.md +19 -37
  13. package/assets/skills/create-agnostic-skill/references/docs/skills-guide.md +58 -15
  14. package/assets/skills/create-agnostic-skill/references/skill-template.md +17 -15
  15. package/assets/skills/oat-agent-instructions-apply/SKILL.md +1 -1
  16. package/assets/skills/oat-agent-instructions-apply/references/instruction-file-templates/glob-scoped-rule.md +1 -4
  17. package/assets/skills/oat-doctor/SKILL.md +1 -4
  18. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +1 -1
  19. package/assets/skills/oat-project-complete/SKILL.md +158 -30
  20. package/assets/skills/oat-project-complete/scripts/finalize-synced-archive.mjs +26 -7
  21. package/assets/skills/oat-project-complete/scripts/validate-durable-archive-receipt.mjs +183 -0
  22. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +1 -1
  23. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +1 -1
  24. package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +1 -1
  25. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +1 -1
  26. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +1 -1
  27. package/assets/skills/oat-project-retro/SKILL.md +1 -1
  28. package/assets/skills/oat-project-retro/references/apply-procedure.md +11 -0
  29. package/assets/skills/oat-project-review-provide/SKILL.md +4 -4
  30. package/assets/skills/oat-project-summary/SKILL.md +24 -2
  31. package/assets/skills/oat-repo-improve/SKILL.md +9 -2
  32. package/assets/skills/oat-repo-improve/references/plan-template.md +4 -6
  33. package/assets/skills/oat-repo-knowledge-index/SKILL.md +11 -9
  34. package/assets/skills/oat-wave-execute/SKILL.md +20 -6
  35. package/assets/skills/oat-wave-program/SKILL.md +9 -4
  36. package/dist/commands/config/index.d.ts +2 -1
  37. package/dist/commands/config/index.d.ts.map +1 -1
  38. package/dist/commands/config/index.js +133 -38
  39. package/dist/commands/docs/index-generate/index.d.ts.map +1 -1
  40. package/dist/commands/docs/index-generate/index.js +68 -12
  41. package/dist/commands/docs/init/index.d.ts +11 -0
  42. package/dist/commands/docs/init/index.d.ts.map +1 -1
  43. package/dist/commands/docs/init/index.js +19 -1
  44. package/dist/commands/gate/index.d.ts.map +1 -1
  45. package/dist/commands/gate/index.js +10 -2
  46. package/dist/commands/instructions/instructions.utils.d.ts.map +1 -1
  47. package/dist/commands/instructions/instructions.utils.js +42 -9
  48. package/dist/commands/internal/validate-skill-version-bumps.d.ts.map +1 -1
  49. package/dist/commands/internal/validate-skill-version-bumps.js +5 -5
  50. package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
  51. package/dist/commands/project/dispatch-ceiling/index.js +32 -18
  52. package/dist/commands/project/log/append.d.ts +48 -0
  53. package/dist/commands/project/log/append.d.ts.map +1 -1
  54. package/dist/commands/project/log/append.js +207 -30
  55. package/dist/commands/project/log/check.d.ts +87 -1
  56. package/dist/commands/project/log/check.d.ts.map +1 -1
  57. package/dist/commands/project/log/check.js +139 -5
  58. package/dist/commands/project/log/grammar.d.ts +165 -0
  59. package/dist/commands/project/log/grammar.d.ts.map +1 -1
  60. package/dist/commands/project/log/grammar.js +219 -1
  61. package/dist/commands/project/log/rollup.d.ts +16 -1
  62. package/dist/commands/project/log/rollup.d.ts.map +1 -1
  63. package/dist/commands/project/log/rollup.js +22 -2
  64. package/dist/commands/project/log/synthesize.d.ts +16 -0
  65. package/dist/commands/project/log/synthesize.d.ts.map +1 -1
  66. package/dist/commands/project/log/synthesize.js +85 -9
  67. package/dist/commands/status/index.d.ts.map +1 -1
  68. package/dist/commands/status/index.js +24 -2
  69. package/dist/commands/sync/apply.d.ts.map +1 -1
  70. package/dist/commands/sync/apply.js +6 -4
  71. package/dist/config/dispatch-matrix.d.ts.map +1 -1
  72. package/dist/config/dispatch-matrix.js +13 -5
  73. package/dist/config/oat-config.d.ts +15 -0
  74. package/dist/config/oat-config.d.ts.map +1 -1
  75. package/dist/config/oat-config.js +61 -7
  76. package/dist/config/own-keys.d.ts +22 -0
  77. package/dist/config/own-keys.d.ts.map +1 -0
  78. package/dist/config/own-keys.js +32 -0
  79. package/dist/config/resolve.d.ts +11 -0
  80. package/dist/config/resolve.d.ts.map +1 -1
  81. package/dist/config/resolve.js +35 -11
  82. package/dist/config/sync-config.d.ts.map +1 -1
  83. package/dist/config/sync-config.js +16 -0
  84. package/dist/drift/detector.d.ts.map +1 -1
  85. package/dist/drift/detector.js +52 -1
  86. package/dist/drift/skill-view-diagnostic.d.ts.map +1 -1
  87. package/dist/drift/skill-view-diagnostic.js +2 -23
  88. package/dist/engine/compute-plan.d.ts.map +1 -1
  89. package/dist/engine/compute-plan.js +22 -72
  90. package/dist/engine/managed-copy-hash.d.ts +69 -0
  91. package/dist/engine/managed-copy-hash.d.ts.map +1 -0
  92. package/dist/engine/managed-copy-hash.js +175 -0
  93. package/dist/fs/assets.d.ts +11 -0
  94. package/dist/fs/assets.d.ts.map +1 -1
  95. package/dist/fs/assets.js +1 -1
  96. package/dist/manifest/hash.d.ts +110 -0
  97. package/dist/manifest/hash.d.ts.map +1 -1
  98. package/dist/manifest/hash.js +111 -13
  99. package/dist/providers/ceiling/registry.d.ts.map +1 -1
  100. package/dist/providers/ceiling/registry.js +6 -1
  101. package/dist/providers/identity/dispatch-report.d.ts.map +1 -1
  102. package/dist/providers/identity/dispatch-report.js +4 -1
  103. package/dist/release/public-package-contract.d.ts.map +1 -1
  104. package/dist/release/public-package-contract.js +21 -0
  105. package/dist/validation/skills.d.ts.map +1 -1
  106. package/dist/validation/skills.js +305 -40
  107. 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(/^## [^\r\n]+/gm)];
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
- export interface ProjectLogRollupResult {
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;AAWpC,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,MAAM,WAAW,sBAAsB;IACrC,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,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,CAyCjC;AAmDD,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,OAAO,CAAC,mCAAmC,CAAM,GAC3D,OAAO,CAoBT"}
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 parsed = parseProjectLogEntries(await readFile(logPath, 'utf8'));
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;AASpC,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;CACjB;AAED,MAAM,WAAW,gCAAgC;IAC/C,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC9E;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;AA+CD,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,EAChC,SAAS,GAAE,OAAO,CAAC,gCAAgC,CAAM,GACxD,OAAO,CAAC,0BAA0B,CAAC,CAwDrC;AAqCD,wBAAgB,iCAAiC,CAC/C,SAAS,GAAE,OAAO,CAAC,uCAAuC,CAAM,GAC/D,OAAO,CAeT"}
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"}