@basou/core 0.17.0 → 0.19.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
@@ -410,6 +410,21 @@ declare const SessionImportPayloadSchema: z.ZodObject<{
410
410
  rejected_reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
411
411
  linked_events: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>>>;
412
412
  linked_files: z.ZodOptional<z.ZodArray<z.ZodString>>;
413
+ kind: z.ZodOptional<z.ZodEnum<{
414
+ decision: "decision";
415
+ track: "track";
416
+ }>>;
417
+ }, z.core.$strip>, z.ZodObject<{
418
+ schema_version: z.ZodLiteral<"0.1.0">;
419
+ id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
420
+ session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
421
+ occurred_at: z.ZodString;
422
+ source: z.ZodString;
423
+ prev_hash: z.ZodOptional<z.ZodString>;
424
+ type: z.ZodLiteral<"decision_voided">;
425
+ decision_id: z.ZodString & z.ZodType<`decision_${string}`, string, z.core.$ZodTypeInternals<`decision_${string}`, string>>;
426
+ reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
427
+ superseded_by: z.ZodOptional<z.ZodString & z.ZodType<`decision_${string}`, string, z.core.$ZodTypeInternals<`decision_${string}`, string>>>;
413
428
  }, z.core.$strip>, z.ZodObject<{
414
429
  schema_version: z.ZodLiteral<"0.1.0">;
415
430
  id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
@@ -955,6 +970,10 @@ declare const DecisionRecordedEventSchema: z.ZodObject<{
955
970
  rejected_reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
956
971
  linked_events: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>>>;
957
972
  linked_files: z.ZodOptional<z.ZodArray<z.ZodString>>;
973
+ kind: z.ZodOptional<z.ZodEnum<{
974
+ decision: "decision";
975
+ track: "track";
976
+ }>>;
958
977
  }, z.core.$strip>;
959
978
  declare const TaskCreatedEventSchema: z.ZodObject<{
960
979
  schema_version: z.ZodLiteral<"0.1.0">;
@@ -1203,6 +1222,21 @@ declare const EventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1203
1222
  rejected_reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1204
1223
  linked_events: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>>>;
1205
1224
  linked_files: z.ZodOptional<z.ZodArray<z.ZodString>>;
1225
+ kind: z.ZodOptional<z.ZodEnum<{
1226
+ decision: "decision";
1227
+ track: "track";
1228
+ }>>;
1229
+ }, z.core.$strip>, z.ZodObject<{
1230
+ schema_version: z.ZodLiteral<"0.1.0">;
1231
+ id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
1232
+ session_id: z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>;
1233
+ occurred_at: z.ZodString;
1234
+ source: z.ZodString;
1235
+ prev_hash: z.ZodOptional<z.ZodString>;
1236
+ type: z.ZodLiteral<"decision_voided">;
1237
+ decision_id: z.ZodString & z.ZodType<`decision_${string}`, string, z.core.$ZodTypeInternals<`decision_${string}`, string>>;
1238
+ reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1239
+ superseded_by: z.ZodOptional<z.ZodString & z.ZodType<`decision_${string}`, string, z.core.$ZodTypeInternals<`decision_${string}`, string>>>;
1206
1240
  }, z.core.$strip>, z.ZodObject<{
1207
1241
  schema_version: z.ZodLiteral<"0.1.0">;
1208
1242
  id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
@@ -3276,6 +3310,8 @@ type OrientationRendererResult = {
3276
3310
  /** Tasks whose status is `planned` or `in_progress`. */
3277
3311
  inFlightTaskCount: number;
3278
3312
  decisionCount: number;
3313
+ /** Open (non-voided) `kind: "track"` decisions surfaced as strategic continuation. */
3314
+ openTrackCount: number;
3279
3315
  };
3280
3316
  type DecisionRecord = {
3281
3317
  decisionId: string;
@@ -3284,6 +3320,21 @@ type DecisionRecord = {
3284
3320
  sessionId: string;
3285
3321
  host: string | null;
3286
3322
  };
3323
+ /**
3324
+ * An open (non-voided) decision recorded with `kind: "track"` — a strategic,
3325
+ * unfinished direction the forward section resurfaces every session until it is
3326
+ * closed via `decision void` / supersede. Carries the rationale (the WHY) so the
3327
+ * surfaced track answers not just "what to build next" but "and why", which is
3328
+ * exactly the intent that otherwise lives only in the conversation.
3329
+ */
3330
+ type TrackRecord = {
3331
+ decisionId: string;
3332
+ title: string;
3333
+ rationale: string | null;
3334
+ occurredAt: string;
3335
+ sessionId: string;
3336
+ host: string | null;
3337
+ };
3287
3338
  type NoteRecord = {
3288
3339
  body: string;
3289
3340
  sessionId: string;
@@ -3348,6 +3399,16 @@ type OrientationSummary = {
3348
3399
  /** Most recent `decision_recorded` across all sessions; null when none. */
3349
3400
  latestDecision: DecisionRecord | null;
3350
3401
  decisionCount: number;
3402
+ /**
3403
+ * Open (non-voided) `kind: "track"` decisions — strategic, unfinished
3404
+ * directions that the forward section ("どこへ向かう") resurfaces every session
3405
+ * until they are closed with `decision void` / supersede. Newest first. This
3406
+ * is the intent-continuity layer: distinct from the single latest decision
3407
+ * (point-in-time) and the recorded next step (`note`), an open track keeps
3408
+ * carrying "the next essential thing to build, and why" across sessions so it
3409
+ * does not sink into the flat decision list. Empty when none are open.
3410
+ */
3411
+ openTracks: TrackRecord[];
3351
3412
  /**
3352
3413
  * Most recent `note_added` over non-archived sessions — the recorded next
3353
3414
  * step / handoff ("次の起点") surfaced in the forward section; null when none.
@@ -4671,6 +4732,10 @@ type ReportDecisionItem = {
4671
4732
  id: string;
4672
4733
  title: string;
4673
4734
  occurredAt: string;
4735
+ /** True when a later `decision_voided` event retracted this decision. */
4736
+ voided?: boolean;
4737
+ /** True when the decision was recorded as a strategic track (`kind: "track"`). */
4738
+ track?: boolean;
4674
4739
  };
4675
4740
  type ReportTaskItem = {
4676
4741
  id: string;
package/dist/index.js CHANGED
@@ -872,7 +872,23 @@ var DecisionRecordedEventSchema = BaseEventSchema.extend({
872
872
  alternatives: z3.array(z3.string().min(1)).optional(),
873
873
  rejected_reason: z3.string().nullable().optional(),
874
874
  linked_events: z3.array(EventIdSchema).optional(),
875
- linked_files: z3.array(z3.string().min(1).max(4096)).optional()
875
+ linked_files: z3.array(z3.string().min(1).max(4096)).optional(),
876
+ // `track` promotes a decision to a strategic, unfinished DIRECTION ("the next
877
+ // essential thing to build, and why") that orientation/handoff resurface every
878
+ // time until it is explicitly closed with `decision void` / supersede — as
879
+ // opposed to a point-in-time `decision`, which is only ever surfaced as the
880
+ // single latest one. This is the intent-continuity layer: a direction agreed
881
+ // in conversation otherwise sinks into the flat decision list and never carries
882
+ // to the next session. Absent (the default) is a plain `decision`, so all
883
+ // pre-existing decision_recorded events round-trip unchanged (additive optional
884
+ // => no schema_version bump; mirrors `note_added.kind`).
885
+ kind: z3.enum(["decision", "track"]).optional()
886
+ });
887
+ var DecisionVoidedEventSchema = BaseEventSchema.extend({
888
+ type: z3.literal("decision_voided"),
889
+ decision_id: DecisionIdSchema,
890
+ reason: z3.string().nullable().optional(),
891
+ superseded_by: DecisionIdSchema.optional()
876
892
  });
877
893
  var TaskCreatedEventSchema = BaseEventSchema.extend({
878
894
  type: z3.literal("task_created"),
@@ -937,6 +953,7 @@ var EventSchema = z3.discriminatedUnion("type", [
937
953
  GitSnapshotEventSchema,
938
954
  FileChangedEventSchema,
939
955
  DecisionRecordedEventSchema,
956
+ DecisionVoidedEventSchema,
940
957
  TaskCreatedEventSchema,
941
958
  TaskStatusChangedEventSchema,
942
959
  TaskReconciledEventSchema,
@@ -1460,6 +1477,7 @@ async function renderDecisions(input) {
1460
1477
  if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
1461
1478
  const entries = await loadSessionEntries(input.paths, loadOpts);
1462
1479
  const decisions = [];
1480
+ const voids = /* @__PURE__ */ new Map();
1463
1481
  const knownEventIds = /* @__PURE__ */ new Set();
1464
1482
  for (const entry of entries) {
1465
1483
  const sessionDir = join6(input.paths.sessions, entry.sessionId);
@@ -1478,8 +1496,12 @@ async function renderDecisions(input) {
1478
1496
  alternatives: ev.alternatives,
1479
1497
  rejectedReason: ev.rejected_reason,
1480
1498
  linkedEvents: ev.linked_events,
1481
- linkedFiles: ev.linked_files
1499
+ linkedFiles: ev.linked_files,
1500
+ kind: ev.kind,
1501
+ voided: void 0
1482
1502
  });
1503
+ } else if (ev.type === "decision_voided") {
1504
+ voids.set(ev.decision_id, { reason: ev.reason, supersededBy: ev.superseded_by });
1483
1505
  }
1484
1506
  }
1485
1507
  } catch {
@@ -1488,6 +1510,10 @@ async function renderDecisions(input) {
1488
1510
  }
1489
1511
  }
1490
1512
  }
1513
+ for (const d of decisions) {
1514
+ const v = voids.get(d.decisionId);
1515
+ if (v !== void 0) d.voided = v;
1516
+ }
1491
1517
  decisions.sort((a, b) => {
1492
1518
  const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);
1493
1519
  return c !== 0 ? c : a.decisionId.localeCompare(b.decisionId);
@@ -1527,10 +1553,22 @@ async function formatDecisionsBody(args) {
1527
1553
  return lines.join("\n");
1528
1554
  }
1529
1555
  for (const d of args.decisions) {
1530
- lines.push(`## ${d.decisionId}: ${d.title}`);
1531
- lines.push("");
1556
+ const trackMark = d.kind === "track" ? " [TRACK]" : "";
1557
+ if (d.voided !== void 0) {
1558
+ lines.push(`## ~~${d.decisionId}: ${d.title}~~ [VOIDED]${trackMark}`);
1559
+ lines.push("");
1560
+ const supersededBy = d.voided.supersededBy !== void 0 ? `, superseded by ${d.voided.supersededBy}` : "";
1561
+ const reason = typeof d.voided.reason === "string" && d.voided.reason.length > 0 ? `: ${d.voided.reason}` : "";
1562
+ lines.push(`- \u26A0 VOIDED${reason}${supersededBy}`);
1563
+ } else {
1564
+ lines.push(`## ${d.decisionId}: ${d.title}${trackMark}`);
1565
+ lines.push("");
1566
+ }
1532
1567
  const occurredDate = d.occurredAt.slice(0, 10);
1533
1568
  lines.push(`- \u6C7A\u5B9A\u65E5: ${occurredDate}`);
1569
+ if (d.kind === "track" && d.voided === void 0) {
1570
+ lines.push("- \u7A2E\u5225: track (close \u307E\u3067 orient/handoff \u306B\u7D99\u7D9A\u8868\u793A)");
1571
+ }
1534
1572
  lines.push(`- session: ${shortDecisionSessionId(d.sessionId)}`);
1535
1573
  lines.push(`- \u5224\u65AD: ${d.title}`);
1536
1574
  if (typeof d.rationale === "string" && d.rationale.length > 0) {
@@ -3853,6 +3891,8 @@ async function renderHandoff(input) {
3853
3891
  if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
3854
3892
  const entries = await loadSessionEntries(input.paths, loadOpts);
3855
3893
  const decisions = [];
3894
+ const tracks = [];
3895
+ const voidedDecisionIds = /* @__PURE__ */ new Set();
3856
3896
  const tasksCreated = [];
3857
3897
  const tasksStatusChanged = [];
3858
3898
  let latestActivityAt = null;
@@ -3877,6 +3917,17 @@ async function renderHandoff(input) {
3877
3917
  occurredAt: ev.occurred_at,
3878
3918
  sessionId: entry.sessionId
3879
3919
  });
3920
+ if (ev.kind === "track") {
3921
+ tracks.push({
3922
+ decisionId: ev.decision_id,
3923
+ title: ev.title,
3924
+ rationale: ev.rationale ?? null,
3925
+ occurredAt: ev.occurred_at,
3926
+ sessionId: entry.sessionId
3927
+ });
3928
+ }
3929
+ } else if (ev.type === "decision_voided") {
3930
+ voidedDecisionIds.add(ev.decision_id);
3880
3931
  } else if (ev.type === "task_created") {
3881
3932
  tasksCreated.push({
3882
3933
  taskId: ev.task_id,
@@ -3902,6 +3953,18 @@ async function renderHandoff(input) {
3902
3953
  const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);
3903
3954
  return c !== 0 ? c : a.decisionId.localeCompare(b.decisionId);
3904
3955
  });
3956
+ let latestDecision;
3957
+ for (let i = decisions.length - 1; i >= 0; i -= 1) {
3958
+ const d = decisions[i];
3959
+ if (d !== void 0 && !voidedDecisionIds.has(d.decisionId)) {
3960
+ latestDecision = d;
3961
+ break;
3962
+ }
3963
+ }
3964
+ const openTracks = tracks.filter((t) => !voidedDecisionIds.has(t.decisionId)).sort((a, b) => {
3965
+ const c = Date.parse(b.occurredAt) - Date.parse(a.occurredAt);
3966
+ return c !== 0 ? c : b.decisionId.localeCompare(a.decisionId);
3967
+ });
3905
3968
  tasksCreated.sort((a, b) => {
3906
3969
  const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);
3907
3970
  return c !== 0 ? c : a.taskId.localeCompare(b.taskId);
@@ -3945,6 +4008,8 @@ async function renderHandoff(input) {
3945
4008
  latestSession,
3946
4009
  latestActivityAt,
3947
4010
  decisions,
4011
+ latestDecision,
4012
+ openTracks,
3948
4013
  pendingApprovalsCount,
3949
4014
  suspectCount,
3950
4015
  displayedFiles,
@@ -4011,10 +4076,10 @@ function formatHandoffBody(args) {
4011
4076
  lines.push("");
4012
4077
  lines.push("## \u76F4\u8FD1\u306E\u5224\u65AD");
4013
4078
  lines.push("");
4014
- if (args.decisions.length === 0) {
4079
+ if (args.latestDecision === void 0) {
4015
4080
  lines.push("(no decisions recorded yet)");
4016
4081
  } else {
4017
- const last = args.decisions[args.decisions.length - 1];
4082
+ const last = args.latestDecision;
4018
4083
  lines.push(`- ${last.title} [${shortIdWithPrefix(last.decisionId)}]`);
4019
4084
  if (args.latestActivityAt !== null && isTrailingStale(args.latestActivityAt, last.occurredAt)) {
4020
4085
  lines.push(
@@ -4030,6 +4095,23 @@ function formatHandoffBody(args) {
4030
4095
  lines.push(`(${args.decisions.length} decisions total \u2014 see decisions.md)`);
4031
4096
  }
4032
4097
  lines.push("");
4098
+ if (args.openTracks.length > 0) {
4099
+ const TRACK_DISPLAY_LIMIT = 10;
4100
+ const shown = args.openTracks.slice(0, TRACK_DISPLAY_LIMIT);
4101
+ const overflow = args.openTracks.length - shown.length;
4102
+ lines.push("## \u672A\u5B8C\u30C8\u30E9\u30C3\u30AF (close \u307E\u3067\u7D99\u7D9A\u8868\u793A)");
4103
+ lines.push("");
4104
+ for (const t of shown) {
4105
+ lines.push(`- ${t.title} [${shortIdWithPrefix(t.decisionId)}]`);
4106
+ if (t.rationale !== null && t.rationale.trim() !== "") {
4107
+ lines.push(` - \u7406\u7531: ${handoffRationale(t.rationale)}`);
4108
+ }
4109
+ }
4110
+ if (overflow > 0) lines.push(`- ... +${overflow} more (see decisions.md)`);
4111
+ lines.push("");
4112
+ lines.push("\u5B8C\u4E86\u3057\u305F\u3089 `basou decision void <decision_id>` \u3067\u9589\u3058\u3066\u304F\u3060\u3055\u3044\u3002");
4113
+ lines.push("");
4114
+ }
4033
4115
  lines.push("## \u672A\u6C7A\u4E8B\u9805");
4034
4116
  lines.push("");
4035
4117
  if (args.pendingApprovalsCount > 0) {
@@ -4114,6 +4196,11 @@ function formatHandoffBody(args) {
4114
4196
  lines.push(sessionsLine);
4115
4197
  return lines.join("\n");
4116
4198
  }
4199
+ var HANDOFF_TRACK_RATIONALE_MAX = 240;
4200
+ function handoffRationale(rationale) {
4201
+ const oneLine = rationale.replace(/\s+/g, " ").trim();
4202
+ return oneLine.length > HANDOFF_TRACK_RATIONALE_MAX ? `${oneLine.slice(0, HANDOFF_TRACK_RATIONALE_MAX - 1)}\u2026` : oneLine;
4203
+ }
4117
4204
  function suspectLabel(reason) {
4118
4205
  if (reason === "events_say_ended_but_yaml_running") return " \u26A0 ended (yaml stale)";
4119
4206
  if (reason === "running_no_end_event") return " \u26A0 no end event";
@@ -4467,6 +4554,8 @@ async function summarizeOrientation(input) {
4467
4554
  }
4468
4555
  ) : await loadSessionEntries(input.paths, loadOpts);
4469
4556
  const decisions = [];
4557
+ const tracks = [];
4558
+ const voidedDecisionIds = /* @__PURE__ */ new Set();
4470
4559
  let latestActivityAt = null;
4471
4560
  let latestNote = null;
4472
4561
  const noteActivity = (iso) => {
@@ -4490,6 +4579,18 @@ async function summarizeOrientation(input) {
4490
4579
  sessionId: entry.sessionId,
4491
4580
  host: entry.host
4492
4581
  });
4582
+ if (ev.kind === "track") {
4583
+ tracks.push({
4584
+ decisionId: ev.decision_id,
4585
+ title: ev.title,
4586
+ rationale: ev.rationale ?? null,
4587
+ occurredAt: ev.occurred_at,
4588
+ sessionId: entry.sessionId,
4589
+ host: entry.host
4590
+ });
4591
+ }
4592
+ } else if (ev.type === "decision_voided") {
4593
+ voidedDecisionIds.add(ev.decision_id);
4493
4594
  }
4494
4595
  if (counted && ev.type === "note_added" && ev.kind === "next_step") {
4495
4596
  if (latestNote === null || Date.parse(ev.occurred_at) > Date.parse(latestNote.occurredAt)) {
@@ -4511,7 +4612,18 @@ async function summarizeOrientation(input) {
4511
4612
  const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);
4512
4613
  return c !== 0 ? c : a.decisionId.localeCompare(b.decisionId);
4513
4614
  });
4514
- const latestDecision = decisions[decisions.length - 1];
4615
+ let latestDecision;
4616
+ for (let i = decisions.length - 1; i >= 0; i -= 1) {
4617
+ const d = decisions[i];
4618
+ if (d !== void 0 && !voidedDecisionIds.has(d.decisionId)) {
4619
+ latestDecision = d;
4620
+ break;
4621
+ }
4622
+ }
4623
+ const openTracks = tracks.filter((t) => !voidedDecisionIds.has(t.decisionId)).sort((a, b) => {
4624
+ const c = Date.parse(b.occurredAt) - Date.parse(a.occurredAt);
4625
+ return c !== 0 ? c : b.decisionId.localeCompare(a.decisionId);
4626
+ });
4515
4627
  const taskLoadOpts = {};
4516
4628
  if (input.onTaskSkip !== void 0) taskLoadOpts.onSkip = input.onTaskSkip;
4517
4629
  const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);
@@ -4600,6 +4712,7 @@ async function summarizeOrientation(input) {
4600
4712
  latestSession,
4601
4713
  latestDecision: latestDecision ?? null,
4602
4714
  decisionCount: decisions.length,
4715
+ openTracks,
4603
4716
  latestNote,
4604
4717
  relatedFiles: { displayed, overflow, outOfRoot },
4605
4718
  inFlightTasks,
@@ -4627,7 +4740,8 @@ async function renderOrientation(input) {
4627
4740
  pendingApprovalsCount: summary.pendingApprovals.length,
4628
4741
  suspectCount: summary.suspects.length,
4629
4742
  inFlightTaskCount: summary.inFlightTasks.length,
4630
- decisionCount: summary.decisionCount
4743
+ decisionCount: summary.decisionCount,
4744
+ openTrackCount: summary.openTracks.length
4631
4745
  };
4632
4746
  }
4633
4747
  function formatOrientationBody(summary, opts) {
@@ -4734,6 +4848,26 @@ function formatOrientationBody(summary, opts) {
4734
4848
  lines.push("");
4735
4849
  lines.push("## \u3069\u3053\u3078\u5411\u304B\u3046");
4736
4850
  lines.push("");
4851
+ if (summary.openTracks.length > 0) {
4852
+ const TRACK_DISPLAY_LIMIT = 10;
4853
+ const shownTracks = summary.openTracks.slice(0, TRACK_DISPLAY_LIMIT);
4854
+ const trackOverflow = summary.openTracks.length - shownTracks.length;
4855
+ lines.push(`### \u672A\u5B8C\u30C8\u30E9\u30C3\u30AF (close \u307E\u3067\u7D99\u7D9A\u8868\u793A) (${summary.openTracks.length})`);
4856
+ for (const t of shownTracks) {
4857
+ const trackAge = relativeAgeJa(t.occurredAt, now);
4858
+ lines.push(`- ${t.title} [${shortId(t.decisionId)}] (${trackAge})${hostSuffix(t.host)}`);
4859
+ if (t.rationale !== null && t.rationale.trim() !== "") {
4860
+ lines.push(` - \u7406\u7531: ${trackRationale(t.rationale)}`);
4861
+ }
4862
+ }
4863
+ if (trackOverflow > 0) {
4864
+ lines.push(`- ... +${trackOverflow} more (see decisions.md)`);
4865
+ }
4866
+ lines.push(
4867
+ "\u5B8C\u4E86\u3057\u305F\u3089 `basou decision void <decision_id>` \u3067\u9589\u3058\u3066\u304F\u3060\u3055\u3044\u3002\u9589\u3058\u308B\u307E\u3067\u6BCE\u56DE\u3053\u3053\u306B\u8868\u793A\u3055\u308C\u307E\u3059\u3002"
4868
+ );
4869
+ lines.push("");
4870
+ }
4737
4871
  if (summary.latestNote !== null) {
4738
4872
  const noteAge = relativeAgeJa(summary.latestNote.occurredAt, now);
4739
4873
  lines.push(
@@ -4749,7 +4883,7 @@ function formatOrientationBody(summary, opts) {
4749
4883
  for (const t of summary.plannedTasks) {
4750
4884
  lines.push(`- ${t.title} [${shortId(t.id)}]`);
4751
4885
  }
4752
- if (summary.latestNote === null && summary.plannedTasks.length === 0) {
4886
+ if (summary.openTracks.length === 0 && summary.latestNote === null && summary.plannedTasks.length === 0) {
4753
4887
  const dec = summary.latestDecision;
4754
4888
  if (dec === null) {
4755
4889
  lines.push("- (no planned tasks or recorded next step yet)");
@@ -4762,6 +4896,11 @@ function formatOrientationBody(summary, opts) {
4762
4896
  lines.push("- (no planned tasks \u2014 direction is inferred from recent decisions)");
4763
4897
  lines.push(` - \u76F4\u8FD1\u306E\u5224\u65AD: ${dec.title}`);
4764
4898
  }
4899
+ if (dec !== null) {
4900
+ lines.push(
4901
+ ' - \u6B21\u306B\u4F5C\u308B\u3079\u304D\u672C\u8CEA\u7684\u306A\u65B9\u5411\u6027\u304C\u5B9A\u307E\u3063\u305F\u3089 `basou decision capture` (`"kind":"track"`) / `basou decision record --track` \u3067 track \u5316\u3059\u308B\u3068\u3001close \u307E\u3067\u6BCE session \u3053\u3053\u306B\u7D99\u7D9A\u8868\u793A\u3055\u308C\u307E\u3059\u3002'
4902
+ );
4903
+ }
4765
4904
  }
4766
4905
  lines.push("");
4767
4906
  lines.push("## \u3053\u308C\u306F\u6700\u65B0\u304B");
@@ -4887,6 +5026,11 @@ function noteSummary(body) {
4887
5026
  const oneLine = body.replace(/\s+/g, " ").trim();
4888
5027
  return oneLine.length > NOTE_SUMMARY_MAX ? `${oneLine.slice(0, NOTE_SUMMARY_MAX - 1)}\u2026` : oneLine;
4889
5028
  }
5029
+ var TRACK_RATIONALE_MAX = 240;
5030
+ function trackRationale(rationale) {
5031
+ const oneLine = rationale.replace(/\s+/g, " ").trim();
5032
+ return oneLine.length > TRACK_RATIONALE_MAX ? `${oneLine.slice(0, TRACK_RATIONALE_MAX - 1)}\u2026` : oneLine;
5033
+ }
4890
5034
  function suspectText(reason) {
4891
5035
  if (reason === "events_say_ended_but_yaml_running") return "ended (yaml stale)";
4892
5036
  if (reason === "running_no_end_event") return "no end event";
@@ -5794,6 +5938,7 @@ async function renderReport(input) {
5794
5938
  const stats = await computeWorkStats(statsInput);
5795
5939
  const statsBySession = new Map(stats.sessions.map((s) => [s.sessionId, s]));
5796
5940
  const decisions = [];
5941
+ const voidedDecisionIds = /* @__PURE__ */ new Set();
5797
5942
  for (const entry of entries) {
5798
5943
  const sessionDir = join17(input.paths.sessions, entry.sessionId);
5799
5944
  try {
@@ -5801,7 +5946,14 @@ async function renderReport(input) {
5801
5946
  onWarning: (w) => input.onWarning?.(w, entry.sessionId)
5802
5947
  })) {
5803
5948
  if (ev.type === "decision_recorded") {
5804
- decisions.push({ id: ev.decision_id, title: ev.title, occurredAt: ev.occurred_at });
5949
+ decisions.push({
5950
+ id: ev.decision_id,
5951
+ title: ev.title,
5952
+ occurredAt: ev.occurred_at,
5953
+ ...ev.kind === "track" ? { track: true } : {}
5954
+ });
5955
+ } else if (ev.type === "decision_voided") {
5956
+ voidedDecisionIds.add(ev.decision_id);
5805
5957
  }
5806
5958
  }
5807
5959
  } catch {
@@ -5810,6 +5962,9 @@ async function renderReport(input) {
5810
5962
  }
5811
5963
  }
5812
5964
  }
5965
+ for (const d of decisions) {
5966
+ if (voidedDecisionIds.has(d.id)) d.voided = true;
5967
+ }
5813
5968
  decisions.sort((a, b) => {
5814
5969
  const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);
5815
5970
  return c !== 0 ? c : a.id.localeCompare(b.id);
@@ -5982,7 +6137,9 @@ function formatReportBody(data) {
5982
6137
  lines.push("");
5983
6138
  }
5984
6139
  for (const d of shown) {
5985
- lines.push(`- ${d.occurredAt.slice(0, 10)} \xB7 ${d.title}`);
6140
+ const trackTag = d.track === true ? " [track]" : "";
6141
+ const voidedTag = d.voided === true ? " (voided)" : "";
6142
+ lines.push(`- ${d.occurredAt.slice(0, 10)} \xB7 ${d.title}${trackTag}${voidedTag}`);
5986
6143
  }
5987
6144
  }
5988
6145
  lines.push("");