@basou/core 0.14.0 → 0.14.1

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
@@ -3170,6 +3170,7 @@ type DecisionRecord = {
3170
3170
  decisionId: string;
3171
3171
  title: string;
3172
3172
  occurredAt: string;
3173
+ sessionId: string;
3173
3174
  };
3174
3175
  type NoteRecord = {
3175
3176
  body: string;
package/dist/index.js CHANGED
@@ -2081,6 +2081,21 @@ function parseDiffNameStatus(raw) {
2081
2081
  // src/handoff/handoff-renderer.ts
2082
2082
  import { join as join13 } from "path";
2083
2083
 
2084
+ // src/lib/recency.ts
2085
+ var DECISION_TRAILING_ACTIVITY_GAP_MS = 60 * 60 * 1e3;
2086
+ function isTrailingStale(latestActivityAt, recordedAt) {
2087
+ if (latestActivityAt === null) return false;
2088
+ return Date.parse(latestActivityAt) - Date.parse(recordedAt) > DECISION_TRAILING_ACTIVITY_GAP_MS;
2089
+ }
2090
+ function pickLatestSubstantiveEntry(entries) {
2091
+ return [...entries].sort((a, b) => {
2092
+ const aSubstantive = (a.session.session.related_files?.length ?? 0) > 0 ? 1 : 0;
2093
+ const bSubstantive = (b.session.session.related_files?.length ?? 0) > 0 ? 1 : 0;
2094
+ if (aSubstantive !== bSubstantive) return bSubstantive - aSubstantive;
2095
+ return Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at);
2096
+ })[0];
2097
+ }
2098
+
2084
2099
  // src/storage/tasks.ts
2085
2100
  import { createHash as createHash2 } from "crypto";
2086
2101
  import { mkdir as mkdir3, readdir as readdir4, readFile as readFile7, rename as rename2, stat as stat3, unlink as unlink3 } from "fs/promises";
@@ -3799,12 +3814,21 @@ async function renderHandoff(input) {
3799
3814
  const decisions = [];
3800
3815
  const tasksCreated = [];
3801
3816
  const tasksStatusChanged = [];
3817
+ let latestActivityAt = null;
3818
+ const noteActivity = (iso) => {
3819
+ if (latestActivityAt === null || Date.parse(iso) > Date.parse(latestActivityAt)) {
3820
+ latestActivityAt = iso;
3821
+ }
3822
+ };
3802
3823
  for (const entry of entries) {
3803
3824
  const sessionDir = join13(input.paths.sessions, entry.sessionId);
3825
+ const counted = entry.session.session.status !== "archived";
3826
+ if (counted) noteActivity(entry.session.session.ended_at ?? entry.session.session.started_at);
3804
3827
  try {
3805
3828
  for await (const ev of replayEvents(sessionDir, {
3806
3829
  onWarning: (w) => input.onWarning?.(w, entry.sessionId)
3807
3830
  })) {
3831
+ if (counted) noteActivity(ev.occurred_at);
3808
3832
  if (ev.type === "decision_recorded") {
3809
3833
  decisions.push({
3810
3834
  decisionId: ev.decision_id,
@@ -3864,9 +3888,7 @@ async function renderHandoff(input) {
3864
3888
  const liveEntries = entries.filter(
3865
3889
  (e) => e.session.session.status !== "archived" && e.session.session.source.kind !== "import"
3866
3890
  );
3867
- const latestSession = [...liveEntries].sort(
3868
- (a, b) => Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at)
3869
- )[0];
3891
+ const latestSession = pickLatestSubstantiveEntry(liveEntries);
3870
3892
  const latestFiles = latestSession?.session.session.related_files ?? [];
3871
3893
  const sortedFiles = [...new Set(latestFiles)].sort();
3872
3894
  const displayedFiles = sortedFiles.slice(0, limit);
@@ -3880,6 +3902,7 @@ async function renderHandoff(input) {
3880
3902
  sessionRange,
3881
3903
  sessionCount: entries.length,
3882
3904
  latestSession,
3905
+ latestActivityAt,
3883
3906
  decisions,
3884
3907
  pendingApprovalsCount,
3885
3908
  suspectCount,
@@ -3952,6 +3975,16 @@ function formatHandoffBody(args) {
3952
3975
  } else {
3953
3976
  const last = args.decisions[args.decisions.length - 1];
3954
3977
  lines.push(`- ${last.title} [${shortIdWithPrefix(last.decisionId)}]`);
3978
+ if (args.latestActivityAt !== null && isTrailingStale(args.latestActivityAt, last.occurredAt)) {
3979
+ lines.push(
3980
+ " - \u6CE8: \u6700\u7D42\u6D3B\u52D5\u306F\u3053\u306E\u5224\u65AD\u3088\u308A\u5F8C\u3067\u3059\u3002\u4F1A\u8A71\u3067\u65E2\u306B\u89E3\u6C7A\u6E08\u307F\u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u305F\u3081\u3001\u518D\u958B\u524D\u306B\u7D99\u7D9A\u70B9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044(\u4F1A\u8A71\u3067\u306E\u610F\u601D\u6C7A\u5B9A\u306F\u81EA\u52D5\u8A18\u9332\u3055\u308C\u307E\u305B\u3093\u3002`basou decision capture` \u3067\u8A18\u9332\u3067\u304D\u307E\u3059)\u3002"
3981
+ );
3982
+ }
3983
+ if (args.latestSession !== void 0 && last.sessionId !== args.latestSession.sessionId) {
3984
+ lines.push(
3985
+ ` - \u6CE8: \u3053\u306E\u5224\u65AD\u306F\u6700\u7D42 session \u3068\u306F\u5225\u306E session [${shortIdWithPrefix(last.sessionId)}] \u306E\u3082\u306E\u3067\u3059\u3002`
3986
+ );
3987
+ }
3955
3988
  lines.push("");
3956
3989
  lines.push(`(${args.decisions.length} decisions total \u2014 see decisions.md)`);
3957
3990
  }
@@ -4305,7 +4338,6 @@ function hasErrorCode3(error) {
4305
4338
  }
4306
4339
 
4307
4340
  // src/orientation/orientation-renderer.ts
4308
- var DECISION_TRAILING_ACTIVITY_GAP_MS = 60 * 60 * 1e3;
4309
4341
  async function summarizeOrientation(input) {
4310
4342
  const limit = input.relatedFilesLimit ?? 10;
4311
4343
  const now = new Date(input.nowIso);
@@ -4333,7 +4365,8 @@ async function summarizeOrientation(input) {
4333
4365
  decisions.push({
4334
4366
  decisionId: ev.decision_id,
4335
4367
  title: ev.title,
4336
- occurredAt: ev.occurred_at
4368
+ occurredAt: ev.occurred_at,
4369
+ sessionId: entry.sessionId
4337
4370
  });
4338
4371
  }
4339
4372
  if (counted && ev.type === "note_added" && ev.kind === "next_step") {
@@ -4386,9 +4419,7 @@ async function summarizeOrientation(input) {
4386
4419
  const liveEntries = entries.filter(
4387
4420
  (e) => e.session.session.status !== "archived" && e.session.session.source.kind !== "import"
4388
4421
  );
4389
- const latestEntry = [...liveEntries].sort(
4390
- (a, b) => Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at)
4391
- )[0];
4422
+ const latestEntry = pickLatestSubstantiveEntry(liveEntries);
4392
4423
  const latestSession = latestEntry !== void 0 ? {
4393
4424
  sessionId: latestEntry.sessionId,
4394
4425
  label: latestEntry.session.session.label ?? null,
@@ -4474,16 +4505,20 @@ function formatOrientationBody(summary, opts) {
4474
4505
  lines.push("- \u6700\u7D42 session: (no live sessions)");
4475
4506
  }
4476
4507
  if (summary.latestDecision !== null) {
4477
- const decAge = relativeAgeJa(summary.latestDecision.occurredAt, now);
4478
- lines.push(
4479
- `- \u76F4\u8FD1\u306E\u5224\u65AD: ${summary.latestDecision.title} [${shortId(summary.latestDecision.decisionId)}] (${decAge})`
4480
- );
4508
+ const dec = summary.latestDecision;
4509
+ const decAge = relativeAgeJa(dec.occurredAt, now);
4510
+ lines.push(`- \u76F4\u8FD1\u306E\u5224\u65AD: ${dec.title} [${shortId(dec.decisionId)}] (${decAge})`);
4481
4511
  const activityAt = summary.freshness.latestActivityAt;
4482
- if (activityAt !== null && Date.parse(activityAt) - Date.parse(summary.latestDecision.occurredAt) > DECISION_TRAILING_ACTIVITY_GAP_MS) {
4512
+ if (activityAt !== null && isTrailingStale(activityAt, dec.occurredAt)) {
4483
4513
  lines.push(
4484
4514
  ` - \u6CE8: \u3053\u308C\u306F\u6700\u5F8C\u306B\u300C\u8A18\u9332\u3055\u308C\u305F\u300D\u5224\u65AD\u3067\u3059\u3002\u6700\u7D42\u6D3B\u52D5 (${relativeAgeJa(activityAt, now)}) \u306F\u3053\u308C\u3088\u308A\u5F8C\u306E\u305F\u3081\u3001\u73FE\u5728\u306E\u65B9\u91DD\u304C\u53CD\u6620\u3055\u308C\u3066\u3044\u306A\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059(\u4F1A\u8A71\u3067\u306E\u610F\u601D\u6C7A\u5B9A\u306F\u81EA\u52D5\u8A18\u9332\u3055\u308C\u307E\u305B\u3093\u3002\`basou decision capture\` \u3067\u3053\u306E session \u306E\u5224\u65AD\u3092\u8A18\u9332\u3067\u304D\u307E\u3059)\u3002`
4485
4515
  );
4486
4516
  }
4517
+ if (summary.latestSession !== null && dec.sessionId !== summary.latestSession.sessionId) {
4518
+ lines.push(
4519
+ ` - \u6CE8: \u3053\u306E\u5224\u65AD\u306F\u6700\u7D42 session \u3068\u306F\u5225\u306E session [${shortId(dec.sessionId)}] \u306E\u3082\u306E\u3067\u3059\u3002`
4520
+ );
4521
+ }
4487
4522
  if (summary.decisionCount > 1) {
4488
4523
  lines.push(` - ${summary.decisionCount} decisions total \u2014 see decisions.md`);
4489
4524
  }
@@ -4539,7 +4574,7 @@ function formatOrientationBody(summary, opts) {
4539
4574
  `- \u6B21\u306E\u8D77\u70B9 (\u8A18\u9332\u6E08\u307F, ${noteAge}): ${noteSummary(summary.latestNote.body)} [session ${shortId(summary.latestNote.sessionId)}]`
4540
4575
  );
4541
4576
  const activityAt = summary.freshness.latestActivityAt;
4542
- if (activityAt !== null && Date.parse(activityAt) - Date.parse(summary.latestNote.occurredAt) > DECISION_TRAILING_ACTIVITY_GAP_MS) {
4577
+ if (activityAt !== null && isTrailingStale(activityAt, summary.latestNote.occurredAt)) {
4543
4578
  lines.push(
4544
4579
  ` - \u6CE8: \u3053\u306E\u8D77\u70B9\u306E\u8A18\u9332\u5F8C (\u6700\u7D42\u6D3B\u52D5 ${relativeAgeJa(activityAt, now)}) \u3082\u4F5C\u696D\u304C\u7D9A\u3044\u3066\u3044\u307E\u3059\u3002\u518D\u958B\u70B9\u304C\u53E4\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002`
4545
4580
  );
@@ -4549,9 +4584,17 @@ function formatOrientationBody(summary, opts) {
4549
4584
  lines.push(`- ${t.title} [${shortId(t.id)}]`);
4550
4585
  }
4551
4586
  if (summary.latestNote === null && summary.plannedTasks.length === 0) {
4552
- lines.push("- (no planned tasks \u2014 direction is inferred from recent decisions)");
4553
- if (summary.latestDecision !== null) {
4554
- lines.push(` - \u76F4\u8FD1\u306E\u5224\u65AD: ${summary.latestDecision.title}`);
4587
+ const dec = summary.latestDecision;
4588
+ if (dec === null) {
4589
+ lines.push("- (no planned tasks or recorded next step yet)");
4590
+ } else if (isTrailingStale(summary.freshness.latestActivityAt, dec.occurredAt)) {
4591
+ lines.push(
4592
+ "- (no planned tasks or recorded next step \u2014 \u6700\u7D42\u6D3B\u52D5\u306F\u76F4\u8FD1\u306E\u5224\u65AD\u3088\u308A\u5F8C\u3067\u3059\u3002\u7D99\u7D9A\u70B9\u3092\u30E6\u30FC\u30B6\u306B\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044)"
4593
+ );
4594
+ lines.push(` - \u53C2\u8003 (\u53E4\u3044\u53EF\u80FD\u6027\u30FB\u65B9\u91DD\u3067\u306F\u306A\u3044): ${dec.title}`);
4595
+ } else {
4596
+ lines.push("- (no planned tasks \u2014 direction is inferred from recent decisions)");
4597
+ lines.push(` - \u76F4\u8FD1\u306E\u5224\u65AD: ${dec.title}`);
4555
4598
  }
4556
4599
  }
4557
4600
  lines.push("");