@basou/core 0.18.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,10 @@ 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
+ }>>;
413
417
  }, z.core.$strip>, z.ZodObject<{
414
418
  schema_version: z.ZodLiteral<"0.1.0">;
415
419
  id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
@@ -966,6 +970,10 @@ declare const DecisionRecordedEventSchema: z.ZodObject<{
966
970
  rejected_reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
967
971
  linked_events: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>>>;
968
972
  linked_files: z.ZodOptional<z.ZodArray<z.ZodString>>;
973
+ kind: z.ZodOptional<z.ZodEnum<{
974
+ decision: "decision";
975
+ track: "track";
976
+ }>>;
969
977
  }, z.core.$strip>;
970
978
  declare const TaskCreatedEventSchema: z.ZodObject<{
971
979
  schema_version: z.ZodLiteral<"0.1.0">;
@@ -1214,6 +1222,10 @@ declare const EventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1214
1222
  rejected_reason: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1215
1223
  linked_events: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>>>;
1216
1224
  linked_files: z.ZodOptional<z.ZodArray<z.ZodString>>;
1225
+ kind: z.ZodOptional<z.ZodEnum<{
1226
+ decision: "decision";
1227
+ track: "track";
1228
+ }>>;
1217
1229
  }, z.core.$strip>, z.ZodObject<{
1218
1230
  schema_version: z.ZodLiteral<"0.1.0">;
1219
1231
  id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
@@ -3298,6 +3310,8 @@ type OrientationRendererResult = {
3298
3310
  /** Tasks whose status is `planned` or `in_progress`. */
3299
3311
  inFlightTaskCount: number;
3300
3312
  decisionCount: number;
3313
+ /** Open (non-voided) `kind: "track"` decisions surfaced as strategic continuation. */
3314
+ openTrackCount: number;
3301
3315
  };
3302
3316
  type DecisionRecord = {
3303
3317
  decisionId: string;
@@ -3306,6 +3320,21 @@ type DecisionRecord = {
3306
3320
  sessionId: string;
3307
3321
  host: string | null;
3308
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
+ };
3309
3338
  type NoteRecord = {
3310
3339
  body: string;
3311
3340
  sessionId: string;
@@ -3370,6 +3399,16 @@ type OrientationSummary = {
3370
3399
  /** Most recent `decision_recorded` across all sessions; null when none. */
3371
3400
  latestDecision: DecisionRecord | null;
3372
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[];
3373
3412
  /**
3374
3413
  * Most recent `note_added` over non-archived sessions — the recorded next
3375
3414
  * step / handoff ("次の起点") surfaced in the forward section; null when none.
@@ -4695,6 +4734,8 @@ type ReportDecisionItem = {
4695
4734
  occurredAt: string;
4696
4735
  /** True when a later `decision_voided` event retracted this decision. */
4697
4736
  voided?: boolean;
4737
+ /** True when the decision was recorded as a strategic track (`kind: "track"`). */
4738
+ track?: boolean;
4698
4739
  };
4699
4740
  type ReportTaskItem = {
4700
4741
  id: string;
package/dist/index.js CHANGED
@@ -872,7 +872,17 @@ 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()
876
886
  });
877
887
  var DecisionVoidedEventSchema = BaseEventSchema.extend({
878
888
  type: z3.literal("decision_voided"),
@@ -1487,6 +1497,7 @@ async function renderDecisions(input) {
1487
1497
  rejectedReason: ev.rejected_reason,
1488
1498
  linkedEvents: ev.linked_events,
1489
1499
  linkedFiles: ev.linked_files,
1500
+ kind: ev.kind,
1490
1501
  voided: void 0
1491
1502
  });
1492
1503
  } else if (ev.type === "decision_voided") {
@@ -1542,18 +1553,22 @@ async function formatDecisionsBody(args) {
1542
1553
  return lines.join("\n");
1543
1554
  }
1544
1555
  for (const d of args.decisions) {
1556
+ const trackMark = d.kind === "track" ? " [TRACK]" : "";
1545
1557
  if (d.voided !== void 0) {
1546
- lines.push(`## ~~${d.decisionId}: ${d.title}~~ [VOIDED]`);
1558
+ lines.push(`## ~~${d.decisionId}: ${d.title}~~ [VOIDED]${trackMark}`);
1547
1559
  lines.push("");
1548
1560
  const supersededBy = d.voided.supersededBy !== void 0 ? `, superseded by ${d.voided.supersededBy}` : "";
1549
1561
  const reason = typeof d.voided.reason === "string" && d.voided.reason.length > 0 ? `: ${d.voided.reason}` : "";
1550
1562
  lines.push(`- \u26A0 VOIDED${reason}${supersededBy}`);
1551
1563
  } else {
1552
- lines.push(`## ${d.decisionId}: ${d.title}`);
1564
+ lines.push(`## ${d.decisionId}: ${d.title}${trackMark}`);
1553
1565
  lines.push("");
1554
1566
  }
1555
1567
  const occurredDate = d.occurredAt.slice(0, 10);
1556
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
+ }
1557
1572
  lines.push(`- session: ${shortDecisionSessionId(d.sessionId)}`);
1558
1573
  lines.push(`- \u5224\u65AD: ${d.title}`);
1559
1574
  if (typeof d.rationale === "string" && d.rationale.length > 0) {
@@ -3876,6 +3891,7 @@ async function renderHandoff(input) {
3876
3891
  if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
3877
3892
  const entries = await loadSessionEntries(input.paths, loadOpts);
3878
3893
  const decisions = [];
3894
+ const tracks = [];
3879
3895
  const voidedDecisionIds = /* @__PURE__ */ new Set();
3880
3896
  const tasksCreated = [];
3881
3897
  const tasksStatusChanged = [];
@@ -3901,6 +3917,15 @@ async function renderHandoff(input) {
3901
3917
  occurredAt: ev.occurred_at,
3902
3918
  sessionId: entry.sessionId
3903
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
+ }
3904
3929
  } else if (ev.type === "decision_voided") {
3905
3930
  voidedDecisionIds.add(ev.decision_id);
3906
3931
  } else if (ev.type === "task_created") {
@@ -3936,6 +3961,10 @@ async function renderHandoff(input) {
3936
3961
  break;
3937
3962
  }
3938
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
+ });
3939
3968
  tasksCreated.sort((a, b) => {
3940
3969
  const c = Date.parse(a.occurredAt) - Date.parse(b.occurredAt);
3941
3970
  return c !== 0 ? c : a.taskId.localeCompare(b.taskId);
@@ -3980,6 +4009,7 @@ async function renderHandoff(input) {
3980
4009
  latestActivityAt,
3981
4010
  decisions,
3982
4011
  latestDecision,
4012
+ openTracks,
3983
4013
  pendingApprovalsCount,
3984
4014
  suspectCount,
3985
4015
  displayedFiles,
@@ -4065,6 +4095,23 @@ function formatHandoffBody(args) {
4065
4095
  lines.push(`(${args.decisions.length} decisions total \u2014 see decisions.md)`);
4066
4096
  }
4067
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
+ }
4068
4115
  lines.push("## \u672A\u6C7A\u4E8B\u9805");
4069
4116
  lines.push("");
4070
4117
  if (args.pendingApprovalsCount > 0) {
@@ -4149,6 +4196,11 @@ function formatHandoffBody(args) {
4149
4196
  lines.push(sessionsLine);
4150
4197
  return lines.join("\n");
4151
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
+ }
4152
4204
  function suspectLabel(reason) {
4153
4205
  if (reason === "events_say_ended_but_yaml_running") return " \u26A0 ended (yaml stale)";
4154
4206
  if (reason === "running_no_end_event") return " \u26A0 no end event";
@@ -4502,6 +4554,7 @@ async function summarizeOrientation(input) {
4502
4554
  }
4503
4555
  ) : await loadSessionEntries(input.paths, loadOpts);
4504
4556
  const decisions = [];
4557
+ const tracks = [];
4505
4558
  const voidedDecisionIds = /* @__PURE__ */ new Set();
4506
4559
  let latestActivityAt = null;
4507
4560
  let latestNote = null;
@@ -4526,6 +4579,16 @@ async function summarizeOrientation(input) {
4526
4579
  sessionId: entry.sessionId,
4527
4580
  host: entry.host
4528
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
+ }
4529
4592
  } else if (ev.type === "decision_voided") {
4530
4593
  voidedDecisionIds.add(ev.decision_id);
4531
4594
  }
@@ -4557,6 +4620,10 @@ async function summarizeOrientation(input) {
4557
4620
  break;
4558
4621
  }
4559
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
+ });
4560
4627
  const taskLoadOpts = {};
4561
4628
  if (input.onTaskSkip !== void 0) taskLoadOpts.onSkip = input.onTaskSkip;
4562
4629
  const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);
@@ -4645,6 +4712,7 @@ async function summarizeOrientation(input) {
4645
4712
  latestSession,
4646
4713
  latestDecision: latestDecision ?? null,
4647
4714
  decisionCount: decisions.length,
4715
+ openTracks,
4648
4716
  latestNote,
4649
4717
  relatedFiles: { displayed, overflow, outOfRoot },
4650
4718
  inFlightTasks,
@@ -4672,7 +4740,8 @@ async function renderOrientation(input) {
4672
4740
  pendingApprovalsCount: summary.pendingApprovals.length,
4673
4741
  suspectCount: summary.suspects.length,
4674
4742
  inFlightTaskCount: summary.inFlightTasks.length,
4675
- decisionCount: summary.decisionCount
4743
+ decisionCount: summary.decisionCount,
4744
+ openTrackCount: summary.openTracks.length
4676
4745
  };
4677
4746
  }
4678
4747
  function formatOrientationBody(summary, opts) {
@@ -4779,6 +4848,26 @@ function formatOrientationBody(summary, opts) {
4779
4848
  lines.push("");
4780
4849
  lines.push("## \u3069\u3053\u3078\u5411\u304B\u3046");
4781
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
+ }
4782
4871
  if (summary.latestNote !== null) {
4783
4872
  const noteAge = relativeAgeJa(summary.latestNote.occurredAt, now);
4784
4873
  lines.push(
@@ -4794,7 +4883,7 @@ function formatOrientationBody(summary, opts) {
4794
4883
  for (const t of summary.plannedTasks) {
4795
4884
  lines.push(`- ${t.title} [${shortId(t.id)}]`);
4796
4885
  }
4797
- if (summary.latestNote === null && summary.plannedTasks.length === 0) {
4886
+ if (summary.openTracks.length === 0 && summary.latestNote === null && summary.plannedTasks.length === 0) {
4798
4887
  const dec = summary.latestDecision;
4799
4888
  if (dec === null) {
4800
4889
  lines.push("- (no planned tasks or recorded next step yet)");
@@ -4807,6 +4896,11 @@ function formatOrientationBody(summary, opts) {
4807
4896
  lines.push("- (no planned tasks \u2014 direction is inferred from recent decisions)");
4808
4897
  lines.push(` - \u76F4\u8FD1\u306E\u5224\u65AD: ${dec.title}`);
4809
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
+ }
4810
4904
  }
4811
4905
  lines.push("");
4812
4906
  lines.push("## \u3053\u308C\u306F\u6700\u65B0\u304B");
@@ -4932,6 +5026,11 @@ function noteSummary(body) {
4932
5026
  const oneLine = body.replace(/\s+/g, " ").trim();
4933
5027
  return oneLine.length > NOTE_SUMMARY_MAX ? `${oneLine.slice(0, NOTE_SUMMARY_MAX - 1)}\u2026` : oneLine;
4934
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
+ }
4935
5034
  function suspectText(reason) {
4936
5035
  if (reason === "events_say_ended_but_yaml_running") return "ended (yaml stale)";
4937
5036
  if (reason === "running_no_end_event") return "no end event";
@@ -5847,7 +5946,12 @@ async function renderReport(input) {
5847
5946
  onWarning: (w) => input.onWarning?.(w, entry.sessionId)
5848
5947
  })) {
5849
5948
  if (ev.type === "decision_recorded") {
5850
- 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
+ });
5851
5955
  } else if (ev.type === "decision_voided") {
5852
5956
  voidedDecisionIds.add(ev.decision_id);
5853
5957
  }
@@ -6033,8 +6137,9 @@ function formatReportBody(data) {
6033
6137
  lines.push("");
6034
6138
  }
6035
6139
  for (const d of shown) {
6140
+ const trackTag = d.track === true ? " [track]" : "";
6036
6141
  const voidedTag = d.voided === true ? " (voided)" : "";
6037
- lines.push(`- ${d.occurredAt.slice(0, 10)} \xB7 ${d.title}${voidedTag}`);
6142
+ lines.push(`- ${d.occurredAt.slice(0, 10)} \xB7 ${d.title}${trackTag}${voidedTag}`);
6038
6143
  }
6039
6144
  }
6040
6145
  lines.push("");