@basou/core 0.42.2 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -5803,8 +5803,20 @@ declare const SchemaVersionSchema: z.ZodString;
5803
5803
  /**
5804
5804
  * ISO 8601 timestamp with explicit timezone offset (e.g. `+09:00`).
5805
5805
  *
5806
- * The spec samples include offsets, so the default zod `.datetime()` (which
5807
- * rejects offsets) is insufficient; `{ offset: true }` is required.
5806
+ * The gate is `.regex`, not a `.refine`: zod emits a regex check as the
5807
+ * artifact's `pattern`, so there is one expression rather than a runtime check
5808
+ * and a `.meta` restating it. A `.refine` is opaque to JSON Schema generation
5809
+ * and would need the pattern written a second time, which is a second place to
5810
+ * forget. {@link SchemaVersionSchema} takes the same form for the same reason.
5811
+ *
5812
+ * It deliberately does NOT declare `format: "date-time"`. That format names
5813
+ * RFC 3339, and the two sets cross rather than nest: basou takes a timestamp
5814
+ * without seconds, which RFC 3339 does not, and refuses the lowercase
5815
+ * designators and the leap second that RFC 3339 allows. Declaring the format
5816
+ * made one artifact answer two ways from the same bytes — a validator
5817
+ * asserting it rejected `2026-09-16T01:23Z`, one treating it as an annotation
5818
+ * (the JSON Schema 2020-12 default) accepted it. The `pattern` is the
5819
+ * contract, and `description` says so, because no format name states this set.
5808
5820
  */
5809
5821
  declare const IsoTimestampSchema: z.ZodString;
5810
5822
  /** Workspace ID schema: validates `ws_<26-char ULID>`. */
package/dist/index.js CHANGED
@@ -363,7 +363,11 @@ var SchemaVersionSchema = z.string().regex(/^0\.\d+\.\d+$/, {
363
363
  message: "unsupported .basou format version: this basou reads format major 0 (0.x.y). If this workspace was written by a newer basou, upgrade basou to open it."
364
364
  });
365
365
  var CacheVersionSchema = z.literal("0.1.0");
366
- var IsoTimestampSchema = z.string().datetime({ offset: true });
366
+ var ISO_TIMESTAMP_PATTERN = "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$";
367
+ var isoTimestampRegex = new RegExp(ISO_TIMESTAMP_PATTERN);
368
+ var IsoTimestampSchema = z.string().regex(isoTimestampRegex, "Expected an ISO 8601 timestamp with a timezone offset").meta({
369
+ description: "Timestamp with an offset or Z. The pattern is normative under ECMA-262 semantics: RFC 3339 date-time with uppercase designators, no leap second, and optional seconds."
370
+ });
367
371
  var createPrefixedIdSchema = (prefix) => {
368
372
  const refiner = (value) => isValidPrefixedId(value) && value.startsWith(`${prefix}_`);
369
373
  return z.string().refine(refiner, { message: `Expected ${prefix}_<ULID>` }).meta({
@@ -1886,6 +1890,11 @@ async function readAllEvents(sessionDir, options = {}) {
1886
1890
  return out;
1887
1891
  }
1888
1892
 
1893
+ // src/lib/one-line.ts
1894
+ function oneLine(text) {
1895
+ return text.replace(/\s+/g, " ").trim();
1896
+ }
1897
+
1889
1898
  // src/project/relative-path.ts
1890
1899
  function normalizeRelativePath(p) {
1891
1900
  const trimmed = p.trim();
@@ -3098,13 +3107,13 @@ async function formatDecisionsBody(args) {
3098
3107
  for (const d of args.decisions) {
3099
3108
  const trackMark = d.kind === "track" ? " [TRACK]" : "";
3100
3109
  if (d.voided !== void 0) {
3101
- lines.push(`## ~~${d.decisionId}: ${d.title}~~ [VOIDED]${trackMark}`);
3110
+ lines.push(`## ~~${d.decisionId}: ${oneLine(d.title)}~~ [VOIDED]${trackMark}`);
3102
3111
  lines.push("");
3103
3112
  const supersededBy = d.voided.supersededBy !== void 0 ? `, superseded by ${d.voided.supersededBy}` : "";
3104
3113
  const reason = typeof d.voided.reason === "string" && d.voided.reason.length > 0 ? `: ${d.voided.reason}` : "";
3105
3114
  lines.push(`- \u26A0 VOIDED${reason}${supersededBy}`);
3106
3115
  } else {
3107
- lines.push(`## ${d.decisionId}: ${d.title}${trackMark}`);
3116
+ lines.push(`## ${d.decisionId}: ${oneLine(d.title)}${trackMark}`);
3108
3117
  lines.push("");
3109
3118
  }
3110
3119
  const occurredDate = d.occurredAt.slice(0, 10);
@@ -3113,7 +3122,7 @@ async function formatDecisionsBody(args) {
3113
3122
  lines.push(t.decisions.trackKindLine);
3114
3123
  }
3115
3124
  lines.push(`- session: ${shortDecisionSessionId(d.sessionId)}`);
3116
- lines.push(`- ${t.decisions.decisionLabel}: ${d.title}`);
3125
+ lines.push(`- ${t.decisions.decisionLabel}: ${oneLine(d.title)}`);
3117
3126
  if (typeof d.rationale === "string" && d.rationale.length > 0) {
3118
3127
  lines.push(`- rationale: ${d.rationale}`);
3119
3128
  }
@@ -3135,7 +3144,7 @@ async function formatDecisionsBody(args) {
3135
3144
  async (path2) => await args.fileExists(path2) ? path2 : `${path2} (missing)`
3136
3145
  )
3137
3146
  );
3138
- lines.push(`- linked_files: ${parts.join(", ")}`);
3147
+ lines.push(`- linked_files: ${oneLine(parts.join(", "))}`);
3139
3148
  }
3140
3149
  lines.push("");
3141
3150
  }
@@ -5595,7 +5604,7 @@ function formatHandoffBody(args) {
5595
5604
  const label = args.latestSession.session.session.label;
5596
5605
  const shortId2 = shortIdWithPrefix(args.latestSession.sessionId);
5597
5606
  if (label !== void 0 && label !== "") {
5598
- lines.push(`- ${t.common.lastSessionLabel}: ${label} (${status}) [${shortId2}]`);
5607
+ lines.push(`- ${t.common.lastSessionLabel}: ${oneLine(label)} (${status}) [${shortId2}]`);
5599
5608
  } else {
5600
5609
  lines.push(`- ${t.common.lastSessionLabel}: ${shortId2} (${status})`);
5601
5610
  }
@@ -5607,7 +5616,7 @@ function formatHandoffBody(args) {
5607
5616
  const linkedCount = args.latestTaskDoc?.task.task.linked_sessions?.length;
5608
5617
  const linkedSuffix = linkedCount !== void 0 && linkedCount > 1 ? `, linked_sessions: ${linkedCount}` : "";
5609
5618
  lines.push(
5610
- `- ${t.handoff.lastTaskLabel}: ${args.latestActivityRecord.title} (${statusLabel}${linkedSuffix}) [${shortIdWithPrefix(args.latestActivityRecord.taskId)}]`
5619
+ `- ${t.handoff.lastTaskLabel}: ${oneLine(args.latestActivityRecord.title)} (${statusLabel}${linkedSuffix}) [${shortIdWithPrefix(args.latestActivityRecord.taskId)}]`
5611
5620
  );
5612
5621
  } else {
5613
5622
  lines.push(`- ${t.handoff.lastTaskLabel}: (no tasks recorded yet)`);
@@ -5628,7 +5637,7 @@ function formatHandoffBody(args) {
5628
5637
  lines.push("(no decisions recorded yet)");
5629
5638
  } else {
5630
5639
  const last = args.latestDecision;
5631
- lines.push(`- ${last.title} [${last.decisionId}]`);
5640
+ lines.push(`- ${oneLine(last.title)} [${last.decisionId}]`);
5632
5641
  if (args.latestActivityAt !== null && isTrailingStale(args.latestActivityAt, last.occurredAt)) {
5633
5642
  lines.push(` - ${t.handoff.decisionStaleNote}`);
5634
5643
  }
@@ -5646,7 +5655,7 @@ function formatHandoffBody(args) {
5646
5655
  lines.push(t.handoff.headingOpenTracks);
5647
5656
  lines.push("");
5648
5657
  for (const track of shown) {
5649
- lines.push(`- ${track.title} [${track.decisionId}]`);
5658
+ lines.push(`- ${oneLine(track.title)} [${track.decisionId}]`);
5650
5659
  if (track.rationale !== null && track.rationale.trim() !== "") {
5651
5660
  lines.push(` - ${t.common.trackWhyLabel}: ${handoffRationale(track.rationale)}`);
5652
5661
  }
@@ -5680,7 +5689,7 @@ function formatHandoffBody(args) {
5680
5689
  } else {
5681
5690
  for (const t2 of args.pendingTasks) {
5682
5691
  lines.push(
5683
- `- ${t2.task.task.title} (${t2.task.task.status}) [${shortIdWithPrefix(t2.task.task.id)}]`
5692
+ `- ${oneLine(t2.task.task.title)} (${t2.task.task.status}) [${shortIdWithPrefix(t2.task.task.id)}]`
5684
5693
  );
5685
5694
  }
5686
5695
  }
@@ -5702,7 +5711,7 @@ function formatHandoffBody(args) {
5702
5711
  const sid = shortHandoffId(e.sessionId);
5703
5712
  const status = e.session.session.status + suspectLabel(e.suspectReason);
5704
5713
  const startedAt = e.session.session.started_at;
5705
- const label = e.session.session.label ?? "";
5714
+ const label = tableCell(e.session.session.label ?? "");
5706
5715
  lines.push(`| ${sid} | ${status} | ${startedAt} | ${label} |`);
5707
5716
  }
5708
5717
  }
@@ -5716,7 +5725,7 @@ function formatHandoffBody(args) {
5716
5725
  const sid = shortHandoffId(e.sessionId);
5717
5726
  const status = e.session.session.status + suspectLabel(e.suspectReason);
5718
5727
  const startedAt = e.session.session.started_at;
5719
- const label = e.session.session.label ?? "";
5728
+ const label = tableCell(e.session.session.label ?? "");
5720
5729
  lines.push(`| ${sid} | ${status} | ${startedAt} | ${label} |`);
5721
5730
  }
5722
5731
  }
@@ -5742,8 +5751,11 @@ function formatHandoffBody(args) {
5742
5751
  }
5743
5752
  var HANDOFF_TRACK_RATIONALE_MAX = 240;
5744
5753
  function handoffRationale(rationale) {
5745
- const oneLine = rationale.replace(/\s+/g, " ").trim();
5746
- return oneLine.length > HANDOFF_TRACK_RATIONALE_MAX ? `${oneLine.slice(0, HANDOFF_TRACK_RATIONALE_MAX - 1)}\u2026` : oneLine;
5754
+ const collapsed = oneLine(rationale);
5755
+ return collapsed.length > HANDOFF_TRACK_RATIONALE_MAX ? `${collapsed.slice(0, HANDOFF_TRACK_RATIONALE_MAX - 1)}\u2026` : collapsed;
5756
+ }
5757
+ function tableCell(text) {
5758
+ return oneLine(text).replace(/([\\|])/g, "\\$1");
5747
5759
  }
5748
5760
  function suspectLabel(reason) {
5749
5761
  if (reason === "events_say_ended_but_yaml_running") return " \u26A0 ended (yaml stale)";
@@ -6234,7 +6246,7 @@ function formatOrientationBody(summary, opts) {
6234
6246
  const sid = shortId(s.sessionId);
6235
6247
  if (s.label !== null && s.label !== "") {
6236
6248
  lines.push(
6237
- `- ${t.common.lastSessionLabel}: ${s.label} (${s.status}) [${sid}]${hostSuffix(s.host)}`
6249
+ `- ${t.common.lastSessionLabel}: ${oneLine(s.label)} (${s.status}) [${sid}]${hostSuffix(s.host)}`
6238
6250
  );
6239
6251
  } else {
6240
6252
  lines.push(`- ${t.common.lastSessionLabel}: ${sid} (${s.status})${hostSuffix(s.host)}`);
@@ -6246,7 +6258,7 @@ function formatOrientationBody(summary, opts) {
6246
6258
  const dec = summary.latestDecision;
6247
6259
  const decAge = t.relativeAge(dec.occurredAt, now);
6248
6260
  lines.push(
6249
- `- ${t.common.latestDecisionLabel}: ${dec.title} [${dec.decisionId}] (${decAge})${hostSuffix(dec.host)}`
6261
+ `- ${t.common.latestDecisionLabel}: ${oneLine(dec.title)} [${dec.decisionId}] (${decAge})${hostSuffix(dec.host)}`
6250
6262
  );
6251
6263
  const activityAt = summary.freshness.latestActivityAt;
6252
6264
  if (activityAt !== null && isTrailingStale(activityAt, dec.occurredAt)) {
@@ -6292,7 +6304,7 @@ function formatOrientationBody(summary, opts) {
6292
6304
  for (const s of summary.recentDirection) {
6293
6305
  const sid = shortId(s.sessionId);
6294
6306
  const age = t.relativeAge(s.occurredAt, now);
6295
- const head = s.label !== null && s.label !== "" ? s.label : sid;
6307
+ const head = s.label !== null && s.label !== "" ? oneLine(s.label) : sid;
6296
6308
  lines.push(`- ${head} (${age})${hostSuffix(s.host)}`);
6297
6309
  if (s.decisions.length > 0) {
6298
6310
  const more = s.decisionsOverflow > 0 ? ` (+${s.decisionsOverflow})` : "";
@@ -6317,7 +6329,7 @@ function formatOrientationBody(summary, opts) {
6317
6329
  } else {
6318
6330
  for (const t2 of summary.inFlightTasks) {
6319
6331
  const linkedSuffix = t2.linkedSessions > 1 ? ` \u2014 linked_sessions: ${t2.linkedSessions}` : "";
6320
- lines.push(`- ${t2.title} (${t2.status}) [${shortId(t2.id)}]${linkedSuffix}`);
6332
+ lines.push(`- ${oneLine(t2.title)} (${t2.status}) [${shortId(t2.id)}]${linkedSuffix}`);
6321
6333
  }
6322
6334
  }
6323
6335
  lines.push("");
@@ -6328,7 +6340,7 @@ function formatOrientationBody(summary, opts) {
6328
6340
  for (const a of summary.pendingApprovals) {
6329
6341
  const expired = a.expired ? " (expired)" : "";
6330
6342
  lines.push(
6331
- `- [${a.risk}] ${a.kind}: ${a.reason} \u2014 session ${shortId(a.sessionId)}, since ${a.createdAt}${expired}`
6343
+ `- [${a.risk}] ${a.kind}: ${oneLine(a.reason)} \u2014 session ${shortId(a.sessionId)}, since ${a.createdAt}${expired}`
6332
6344
  );
6333
6345
  }
6334
6346
  }
@@ -6353,7 +6365,9 @@ function formatOrientationBody(summary, opts) {
6353
6365
  lines.push(t.orientation.openTracksHeading(summary.openTracks.length));
6354
6366
  for (const track of shownTracks) {
6355
6367
  const trackAge = t.relativeAge(track.occurredAt, now);
6356
- lines.push(`- ${track.title} [${track.decisionId}] (${trackAge})${hostSuffix(track.host)}`);
6368
+ lines.push(
6369
+ `- ${oneLine(track.title)} [${track.decisionId}] (${trackAge})${hostSuffix(track.host)}`
6370
+ );
6357
6371
  if (track.rationale !== null && track.rationale.trim() !== "") {
6358
6372
  lines.push(` - ${t.common.trackWhyLabel}: ${trackRationale(track.rationale)}`);
6359
6373
  }
@@ -6375,7 +6389,7 @@ function formatOrientationBody(summary, opts) {
6375
6389
  }
6376
6390
  }
6377
6391
  for (const task of summary.plannedTasks) {
6378
- lines.push(`- ${task.title} [${shortId(task.id)}]`);
6392
+ lines.push(`- ${oneLine(task.title)} [${shortId(task.id)}]`);
6379
6393
  }
6380
6394
  if (summary.openTracks.length === 0 && summary.latestNote === null && summary.plannedTasks.length === 0) {
6381
6395
  const dec = summary.latestDecision;
@@ -6383,10 +6397,10 @@ function formatOrientationBody(summary, opts) {
6383
6397
  lines.push("- (no planned tasks or recorded next step yet)");
6384
6398
  } else if (isTrailingStale(summary.freshness.latestActivityAt, dec.occurredAt)) {
6385
6399
  lines.push(t.orientation.fallbackStaleDirection);
6386
- lines.push(` - ${t.orientation.fallbackStaleReferenceLabel}: ${dec.title}`);
6400
+ lines.push(` - ${t.orientation.fallbackStaleReferenceLabel}: ${oneLine(dec.title)}`);
6387
6401
  } else {
6388
6402
  lines.push("- (no planned tasks \u2014 direction is inferred from recent decisions)");
6389
- lines.push(` - ${t.common.latestDecisionLabel}: ${dec.title}`);
6403
+ lines.push(` - ${t.common.latestDecisionLabel}: ${oneLine(dec.title)}`);
6390
6404
  }
6391
6405
  if (dec !== null) {
6392
6406
  lines.push(` - ${t.orientation.trackNudge}`);
@@ -6505,13 +6519,13 @@ var NOTES_PER_DIGEST = 2;
6505
6519
  var FILES_PER_DIGEST = 3;
6506
6520
  var NOTE_SUMMARY_MAX = 200;
6507
6521
  function noteSummary(body) {
6508
- const oneLine = body.replace(/\s+/g, " ").trim();
6509
- return oneLine.length > NOTE_SUMMARY_MAX ? `${oneLine.slice(0, NOTE_SUMMARY_MAX - 1)}\u2026` : oneLine;
6522
+ const collapsed = oneLine(body);
6523
+ return collapsed.length > NOTE_SUMMARY_MAX ? `${collapsed.slice(0, NOTE_SUMMARY_MAX - 1)}\u2026` : collapsed;
6510
6524
  }
6511
6525
  var TRACK_RATIONALE_MAX = 240;
6512
6526
  function trackRationale(rationale) {
6513
- const oneLine = rationale.replace(/\s+/g, " ").trim();
6514
- return oneLine.length > TRACK_RATIONALE_MAX ? `${oneLine.slice(0, TRACK_RATIONALE_MAX - 1)}\u2026` : oneLine;
6527
+ const collapsed = oneLine(rationale);
6528
+ return collapsed.length > TRACK_RATIONALE_MAX ? `${collapsed.slice(0, TRACK_RATIONALE_MAX - 1)}\u2026` : collapsed;
6515
6529
  }
6516
6530
  function suspectText(reason) {
6517
6531
  if (reason === "events_say_ended_but_yaml_running") return "ended (yaml stale)";
@@ -7747,7 +7761,7 @@ function tallyTaskStatus(items) {
7747
7761
  }
7748
7762
  function formatReportBody(data, t) {
7749
7763
  const lines = [];
7750
- const titleSuffix = data.title !== void 0 ? ` \u2014 ${data.title}` : "";
7764
+ const titleSuffix = data.title !== void 0 ? ` \u2014 ${oneLine(data.title)}` : "";
7751
7765
  lines.push(`# Report${titleSuffix}`);
7752
7766
  lines.push("");
7753
7767
  const periodSuffix = data.period.from !== null && data.period.to !== null ? ` (${data.period.from.slice(0, 10)}..${data.period.to.slice(0, 10)})` : "";
@@ -7794,7 +7808,7 @@ function formatReportBody(data, t) {
7794
7808
  for (const d of shown) {
7795
7809
  const trackTag = d.track === true ? " [track]" : "";
7796
7810
  const voidedTag = d.voided === true ? " (voided)" : "";
7797
- lines.push(`- ${d.occurredAt.slice(0, 10)} \xB7 ${d.title}${trackTag}${voidedTag}`);
7811
+ lines.push(`- ${d.occurredAt.slice(0, 10)} \xB7 ${oneLine(d.title)}${trackTag}${voidedTag}`);
7798
7812
  }
7799
7813
  }
7800
7814
  lines.push("");
@@ -7809,7 +7823,7 @@ function formatReportBody(data, t) {
7809
7823
  );
7810
7824
  lines.push("");
7811
7825
  for (const item of data.approvals.items.slice(0, APPROVALS_MARKDOWN_LIMIT)) {
7812
- lines.push(`- ${item.reason} (${item.status}, ${item.riskLevel})`);
7826
+ lines.push(`- ${oneLine(item.reason)} (${item.status}, ${item.riskLevel})`);
7813
7827
  }
7814
7828
  const overflow = data.approvals.items.length - APPROVALS_MARKDOWN_LIMIT;
7815
7829
  if (overflow > 0) lines.push(`- ... +${overflow} more`);
@@ -7824,7 +7838,7 @@ function formatReportBody(data, t) {
7824
7838
  lines.push(`Tasks: ${data.tasks.total} (${breakdown})`);
7825
7839
  lines.push("");
7826
7840
  for (const item of data.tasks.items.slice(0, TASKS_MARKDOWN_LIMIT)) {
7827
- lines.push(`- ${item.title} (${item.status})`);
7841
+ lines.push(`- ${oneLine(item.title)} (${item.status})`);
7828
7842
  }
7829
7843
  const overflow = data.tasks.items.length - TASKS_MARKDOWN_LIMIT;
7830
7844
  if (overflow > 0) lines.push(`- ... +${overflow} more`);
@@ -9254,9 +9268,21 @@ function parseMarkers(content, markers = DEFAULT_MARKERS) {
9254
9268
  const after = body.slice(endLineEnd);
9255
9269
  return { kind: "ok", before, generated, after };
9256
9270
  }
9271
+ function defuseMarkerLines(body, markers) {
9272
+ const isMarker = (line) => {
9273
+ const bare = line.endsWith("\r") ? line.slice(0, -1) : line;
9274
+ return bare === markers.start || bare === markers.end;
9275
+ };
9276
+ const lines = body.split("\n");
9277
+ if (!lines.some(isMarker)) return body;
9278
+ return lines.map((line) => isMarker(line) ? ` ${line}` : line).join("\n");
9279
+ }
9257
9280
  function renderWithMarkers(existing, generated, fileLabel, markers = DEFAULT_MARKERS) {
9258
- const normalized = generated.endsWith("\n") ? generated : `${generated}
9259
- `;
9281
+ const normalized = defuseMarkerLines(
9282
+ generated.endsWith("\n") ? generated : `${generated}
9283
+ `,
9284
+ markers
9285
+ );
9260
9286
  if (existing === null) {
9261
9287
  return `${markers.start}
9262
9288
  ${normalized}${markers.end}