@basou/core 0.21.0 → 0.23.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
@@ -3401,6 +3401,31 @@ type NoteRecord = {
3401
3401
  occurredAt: string;
3402
3402
  host: string | null;
3403
3403
  };
3404
+ /**
3405
+ * One recent session condensed to its direction signal, for the "最近の流れ"
3406
+ * (recent direction) section. Across the last N non-archived sessions
3407
+ * (newest first), this surfaces the ARC of recent intent — the decision titles
3408
+ * and next-step notes recorded in EACH session — rather than orientation's
3409
+ * single latest decision/note, which can be stale or missing. When a session
3410
+ * recorded neither a decision nor a note, its top changed files stand in as the
3411
+ * activity signal, so the trajectory stays visible even when explicit capture
3412
+ * was missed (the read-side safety net for the intent-leak gap).
3413
+ */
3414
+ type RecentSessionDigest = {
3415
+ sessionId: string;
3416
+ label: string | null;
3417
+ /** Session boundary (ended_at ?? started_at); the basis for its relative age. */
3418
+ occurredAt: string;
3419
+ host: string | null;
3420
+ /** Non-voided decision titles recorded in this session, chronological, capped. */
3421
+ decisions: string[];
3422
+ /** Count of this session's non-voided decisions beyond those in `decisions`. */
3423
+ decisionsOverflow: number;
3424
+ /** next_step note bodies recorded in this session, chronological, capped. */
3425
+ notes: string[];
3426
+ /** Top changed files, populated ONLY when the session has no decision and no note. */
3427
+ files: string[];
3428
+ };
3404
3429
  type PendingApproval = {
3405
3430
  id: string;
3406
3431
  risk: string;
@@ -3474,6 +3499,15 @@ type OrientationSummary = {
3474
3499
  * step / handoff ("次の起点") surfaced in the forward section; null when none.
3475
3500
  */
3476
3501
  latestNote: NoteRecord | null;
3502
+ /**
3503
+ * The last N non-archived sessions (newest first) condensed to their direction
3504
+ * signal — the "最近の流れ" (recent direction) arc. Distinct from the single
3505
+ * latest decision/note: it shows the trajectory of intent across recent
3506
+ * sessions, and falls back to each session's changed files when no decision or
3507
+ * note was captured, so a resuming agent has grounding even when explicit
3508
+ * capture was missed. Empty when no non-archived sessions exist.
3509
+ */
3510
+ recentDirection: RecentSessionDigest[];
3477
3511
  /**
3478
3512
  * related_files of the latest session, deduped + sorted + capped at the
3479
3513
  * display limit. `outOfRoot` lists the entries (over the FULL deduped set,
package/dist/index.js CHANGED
@@ -4646,6 +4646,16 @@ async function summarizeOrientation(input) {
4646
4646
  const decisions = [];
4647
4647
  const tracks = [];
4648
4648
  const voidedDecisionIds = /* @__PURE__ */ new Set();
4649
+ const perSession = /* @__PURE__ */ new Map();
4650
+ const recordDirection = (sessionId, kind, value) => {
4651
+ let bucket = perSession.get(sessionId);
4652
+ if (bucket === void 0) {
4653
+ bucket = { decisions: [], notes: [] };
4654
+ perSession.set(sessionId, bucket);
4655
+ }
4656
+ if (kind === "decision" && typeof value !== "string") bucket.decisions.push(value);
4657
+ else if (kind === "note" && typeof value === "string") bucket.notes.push(value);
4658
+ };
4649
4659
  let latestActivityAt = null;
4650
4660
  let latestNote = null;
4651
4661
  const noteActivity = (iso) => {
@@ -4669,6 +4679,12 @@ async function summarizeOrientation(input) {
4669
4679
  sessionId: entry.sessionId,
4670
4680
  host: entry.host
4671
4681
  });
4682
+ if (counted) {
4683
+ recordDirection(entry.sessionId, "decision", {
4684
+ decisionId: ev.decision_id,
4685
+ title: ev.title
4686
+ });
4687
+ }
4672
4688
  if (ev.kind === "track") {
4673
4689
  tracks.push({
4674
4690
  decisionId: ev.decision_id,
@@ -4683,6 +4699,7 @@ async function summarizeOrientation(input) {
4683
4699
  voidedDecisionIds.add(ev.decision_id);
4684
4700
  }
4685
4701
  if (counted && ev.type === "note_added" && ev.kind === "next_step") {
4702
+ recordDirection(entry.sessionId, "note", ev.body);
4686
4703
  if (latestNote === null || Date.parse(ev.occurred_at) > Date.parse(latestNote.occurredAt)) {
4687
4704
  latestNote = {
4688
4705
  body: ev.body,
@@ -4714,6 +4731,29 @@ async function summarizeOrientation(input) {
4714
4731
  const c = Date.parse(b.occurredAt) - Date.parse(a.occurredAt);
4715
4732
  return c !== 0 ? c : b.decisionId.localeCompare(a.decisionId);
4716
4733
  });
4734
+ const recentDirection = entries.filter((e) => e.session.session.status !== "archived").map((e) => ({
4735
+ entry: e,
4736
+ boundary: e.session.session.ended_at ?? e.session.session.started_at
4737
+ })).sort((a, b) => {
4738
+ const c = Date.parse(b.boundary) - Date.parse(a.boundary);
4739
+ return c !== 0 ? c : b.entry.sessionId.localeCompare(a.entry.sessionId);
4740
+ }).slice(0, RECENT_DIRECTION_SESSIONS).map(({ entry, boundary }) => {
4741
+ const bucket = perSession.get(entry.sessionId);
4742
+ const decisionTitles = (bucket?.decisions ?? []).filter((d) => !voidedDecisionIds.has(d.decisionId)).map((d) => d.title);
4743
+ const notes = bucket?.notes ?? [];
4744
+ const hasIntent = decisionTitles.length > 0 || notes.length > 0;
4745
+ const files = hasIntent ? [] : [...new Set(entry.session.session.related_files ?? [])].sort().slice(0, FILES_PER_DIGEST);
4746
+ return {
4747
+ sessionId: entry.sessionId,
4748
+ label: entry.session.session.label ?? null,
4749
+ occurredAt: boundary,
4750
+ host: entry.host,
4751
+ decisions: decisionTitles.slice(0, DECISIONS_PER_DIGEST),
4752
+ decisionsOverflow: Math.max(0, decisionTitles.length - DECISIONS_PER_DIGEST),
4753
+ notes: notes.slice(0, NOTES_PER_DIGEST),
4754
+ files
4755
+ };
4756
+ });
4717
4757
  const taskLoadOpts = {};
4718
4758
  if (input.onTaskSkip !== void 0) taskLoadOpts.onSkip = input.onTaskSkip;
4719
4759
  const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);
@@ -4804,6 +4844,7 @@ async function summarizeOrientation(input) {
4804
4844
  decisionCount: decisions.length,
4805
4845
  openTracks,
4806
4846
  latestNote,
4847
+ recentDirection,
4807
4848
  relatedFiles: { displayed, overflow, outOfRoot },
4808
4849
  inFlightTasks,
4809
4850
  plannedTasks,
@@ -4906,6 +4947,29 @@ function formatOrientationBody(summary, opts) {
4906
4947
  lines.push("- \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: (none recorded)");
4907
4948
  }
4908
4949
  lines.push("");
4950
+ lines.push(`## \u6700\u8FD1\u306E\u6D41\u308C (\u76F4\u8FD1 ${RECENT_DIRECTION_SESSIONS} session)`);
4951
+ lines.push("");
4952
+ if (summary.recentDirection.length === 0) {
4953
+ lines.push("- (\u307E\u3060\u8A18\u9332\u304C\u3042\u308A\u307E\u305B\u3093)");
4954
+ } else {
4955
+ for (const s of summary.recentDirection) {
4956
+ const sid = shortId(s.sessionId);
4957
+ const age = relativeAgeJa(s.occurredAt, now);
4958
+ const head = s.label !== null && s.label !== "" ? s.label : sid;
4959
+ lines.push(`- ${head} (${age})${hostSuffix(s.host)}`);
4960
+ if (s.decisions.length > 0) {
4961
+ const more = s.decisionsOverflow > 0 ? ` (+${s.decisionsOverflow})` : "";
4962
+ lines.push(` - \u5224\u65AD: ${s.decisions.map(noteSummary).join("; ")}${more}`);
4963
+ }
4964
+ for (const note of s.notes) {
4965
+ lines.push(` - \u6B21\u306E\u8D77\u70B9: ${noteSummary(note)}`);
4966
+ }
4967
+ if (s.files.length > 0) {
4968
+ lines.push(` - \u5909\u66F4: ${s.files.join(", ")}`);
4969
+ }
4970
+ }
4971
+ }
4972
+ lines.push("");
4909
4973
  lines.push("## \u4F55\u304C\u52D5\u304F");
4910
4974
  lines.push("");
4911
4975
  lines.push(`### \u9032\u884C\u4E2D task (${summary.inFlightTasks.length})`);
@@ -5064,7 +5128,7 @@ function stalenessBanner(staleness) {
5064
5128
  if (staleness.newSessions > 0) parts.push(`\u65B0\u898F ${staleness.newSessions} \u4EF6`);
5065
5129
  if (staleness.updatedSessions > 0) parts.push(`\u66F4\u65B0 ${staleness.updatedSessions} \u4EF6`);
5066
5130
  return [
5067
- `> \u26A0\uFE0F **\u53E4\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093** \u2014 \u672A\u53D6\u308A\u8FBC\u307F\u306E\u4F5C\u696D\u304C\u3042\u308A\u307E\u3059(${parts.join("\u30FB")})\u3002\u7740\u624B\u524D\u306B \`basou refresh\` \u3067\u66F4\u65B0\u3057\u3066\u304F\u3060\u3055\u3044(\u8A73\u7D30\u306F\u672B\u5C3E\u300C\u3053\u308C\u306F\u6700\u65B0\u304B\u300D)\u3002`
5131
+ `> \u26A0\uFE0F **\u53E4\u3044\u3067\u3059\uFF08\u672A\u53D6\u308A\u8FBC\u307F ${parts.join("\u30FB")}\uFF09** \u2014 \u7740\u624B\u524D\u306B\u5FC5\u305A \`basou refresh\` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044(\u8A73\u7D30\u306F\u672B\u5C3E\u300C\u3053\u308C\u306F\u6700\u65B0\u304B\u300D)\u3002`
5068
5132
  ];
5069
5133
  }
5070
5134
  return [];
@@ -5081,8 +5145,8 @@ function freshnessVerdict(summary, staleness, now) {
5081
5145
  if (staleness.newSessions > 0) parts.push(`\u65B0\u898F ${staleness.newSessions} \u4EF6`);
5082
5146
  if (staleness.updatedSessions > 0) parts.push(`\u66F4\u65B0 ${staleness.updatedSessions} \u4EF6`);
5083
5147
  return [
5084
- `\u26A0\uFE0F \u53E4\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\u6700\u5F8C\u306E\u53D6\u308A\u8FBC\u307F\u4EE5\u964D\u306B\u672A\u53D6\u308A\u8FBC\u307F\u306E\u4F5C\u696D\u304C\u3042\u308A\u307E\u3059(${parts.join("\u30FB")})\u3002`,
5085
- "`basou refresh` \u3067\u66F4\u65B0\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
5148
+ `\u26A0\uFE0F \u53E4\u3044\u3067\u3059\u3002\u6700\u5F8C\u306E\u53D6\u308A\u8FBC\u307F\u4EE5\u964D\u306B\u672A\u53D6\u308A\u8FBC\u307F\u306E\u4F5C\u696D\u304C\u3042\u308A\u307E\u3059(${parts.join("\u30FB")})\u3002`,
5149
+ "\u7740\u624B\u524D\u306B\u5FC5\u305A `basou refresh` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
5086
5150
  ];
5087
5151
  }
5088
5152
  if (summary.freshness.newestStartedAt === null) {
@@ -5133,6 +5197,10 @@ function relativeAge(startedAt, now) {
5133
5197
  if (ms < 1e3) return "just now";
5134
5198
  return `${formatDurationMs(ms)} ago`;
5135
5199
  }
5200
+ var RECENT_DIRECTION_SESSIONS = 5;
5201
+ var DECISIONS_PER_DIGEST = 3;
5202
+ var NOTES_PER_DIGEST = 2;
5203
+ var FILES_PER_DIGEST = 3;
5136
5204
  var NOTE_SUMMARY_MAX = 200;
5137
5205
  function noteSummary(body) {
5138
5206
  const oneLine = body.replace(/\s+/g, " ").trim();