@basou/core 0.20.0 → 0.22.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 +42 -4
- package/dist/index.js +115 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -570,10 +570,14 @@ type ClaudeTranscriptToPayloadOptions = {
|
|
|
570
570
|
* - `command_executed` from each `Bash` tool use, recorded as `bash -c "<cmd>"`
|
|
571
571
|
* (the transcript carries the shell line, not a parsed argv).
|
|
572
572
|
* - `file_changed` from each `Edit` / `Write` / `NotebookEdit` tool use.
|
|
573
|
-
* - `decision_recorded` from each `AskUserQuestion` tool use
|
|
574
|
-
*
|
|
575
|
-
*
|
|
576
|
-
* question
|
|
573
|
+
* - `decision_recorded` from each `AskUserQuestion` tool use, but ONLY when the
|
|
574
|
+
* recorded answer is a confirmed SELECTION — it exactly matches an option the
|
|
575
|
+
* question offered (`input.questions[].options[].label`). One decision per
|
|
576
|
+
* such question, titled `<question> -> <chosen answer>`. The answer is read
|
|
577
|
+
* from the paired result record's `toolUseResult.answers` map; a question with
|
|
578
|
+
* no recorded answer, or a free-text "Other" reply that matches no offered
|
|
579
|
+
* label, is skipped (it is not a decision, and would otherwise pollute
|
|
580
|
+
* decisions.md / orientation's latest-decision surface).
|
|
577
581
|
*
|
|
578
582
|
* Exit codes and per-command durations are not present in the transcript, so
|
|
579
583
|
* `command_executed.exit_code` is `null` and `duration_ms` is `0`.
|
|
@@ -3397,6 +3401,31 @@ type NoteRecord = {
|
|
|
3397
3401
|
occurredAt: string;
|
|
3398
3402
|
host: string | null;
|
|
3399
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
|
+
};
|
|
3400
3429
|
type PendingApproval = {
|
|
3401
3430
|
id: string;
|
|
3402
3431
|
risk: string;
|
|
@@ -3470,6 +3499,15 @@ type OrientationSummary = {
|
|
|
3470
3499
|
* step / handoff ("次の起点") surfaced in the forward section; null when none.
|
|
3471
3500
|
*/
|
|
3472
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[];
|
|
3473
3511
|
/**
|
|
3474
3512
|
* related_files of the latest session, deduped + sorted + capped at the
|
|
3475
3513
|
* display limit. `outOfRoot` lists the entries (over the FULL deduped set,
|
package/dist/index.js
CHANGED
|
@@ -246,11 +246,16 @@ function claudeTranscriptToImportPayload(records, options) {
|
|
|
246
246
|
const useId = readString2(item.id);
|
|
247
247
|
const answers = useId !== void 0 ? askAnswers.get(useId) : void 0;
|
|
248
248
|
if (answers !== void 0) {
|
|
249
|
+
const offeredByQuestion = readOfferedOptions(input);
|
|
249
250
|
for (const [question, answer] of Object.entries(answers)) {
|
|
250
251
|
if (question.length === 0) continue;
|
|
251
252
|
const answerStr = typeof answer === "string" && answer.length > 0 ? answer : void 0;
|
|
252
|
-
|
|
253
|
-
|
|
253
|
+
if (answerStr === void 0) continue;
|
|
254
|
+
const offered = offeredByQuestion.get(question);
|
|
255
|
+
if (offered === void 0 || !offered.has(answerStr.trim())) continue;
|
|
256
|
+
derived.push(
|
|
257
|
+
decisionRecordedEvent(ts, placeholderSessionId, `${question} -> ${answerStr}`)
|
|
258
|
+
);
|
|
254
259
|
}
|
|
255
260
|
}
|
|
256
261
|
continue;
|
|
@@ -410,6 +415,24 @@ function indexAskAnswers(records) {
|
|
|
410
415
|
}
|
|
411
416
|
return byId;
|
|
412
417
|
}
|
|
418
|
+
function readOfferedOptions(input) {
|
|
419
|
+
const byQuestion = /* @__PURE__ */ new Map();
|
|
420
|
+
const questions = Array.isArray(input.questions) ? input.questions : [];
|
|
421
|
+
for (const q of questions) {
|
|
422
|
+
if (!isObject2(q)) continue;
|
|
423
|
+
const text = readString2(q.question);
|
|
424
|
+
if (text === void 0) continue;
|
|
425
|
+
const labels = /* @__PURE__ */ new Set();
|
|
426
|
+
const options = Array.isArray(q.options) ? q.options : [];
|
|
427
|
+
for (const o of options) {
|
|
428
|
+
if (!isObject2(o)) continue;
|
|
429
|
+
const label = readString2(o.label);
|
|
430
|
+
if (label !== void 0) labels.add(label.trim());
|
|
431
|
+
}
|
|
432
|
+
byQuestion.set(text, labels);
|
|
433
|
+
}
|
|
434
|
+
return byQuestion;
|
|
435
|
+
}
|
|
413
436
|
|
|
414
437
|
// src/adapters/codex/rollout-importer.ts
|
|
415
438
|
var CODEX_IMPORT_SOURCE = "codex-import";
|
|
@@ -4623,6 +4646,16 @@ async function summarizeOrientation(input) {
|
|
|
4623
4646
|
const decisions = [];
|
|
4624
4647
|
const tracks = [];
|
|
4625
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
|
+
};
|
|
4626
4659
|
let latestActivityAt = null;
|
|
4627
4660
|
let latestNote = null;
|
|
4628
4661
|
const noteActivity = (iso) => {
|
|
@@ -4646,6 +4679,12 @@ async function summarizeOrientation(input) {
|
|
|
4646
4679
|
sessionId: entry.sessionId,
|
|
4647
4680
|
host: entry.host
|
|
4648
4681
|
});
|
|
4682
|
+
if (counted) {
|
|
4683
|
+
recordDirection(entry.sessionId, "decision", {
|
|
4684
|
+
decisionId: ev.decision_id,
|
|
4685
|
+
title: ev.title
|
|
4686
|
+
});
|
|
4687
|
+
}
|
|
4649
4688
|
if (ev.kind === "track") {
|
|
4650
4689
|
tracks.push({
|
|
4651
4690
|
decisionId: ev.decision_id,
|
|
@@ -4660,6 +4699,7 @@ async function summarizeOrientation(input) {
|
|
|
4660
4699
|
voidedDecisionIds.add(ev.decision_id);
|
|
4661
4700
|
}
|
|
4662
4701
|
if (counted && ev.type === "note_added" && ev.kind === "next_step") {
|
|
4702
|
+
recordDirection(entry.sessionId, "note", ev.body);
|
|
4663
4703
|
if (latestNote === null || Date.parse(ev.occurred_at) > Date.parse(latestNote.occurredAt)) {
|
|
4664
4704
|
latestNote = {
|
|
4665
4705
|
body: ev.body,
|
|
@@ -4691,6 +4731,29 @@ async function summarizeOrientation(input) {
|
|
|
4691
4731
|
const c = Date.parse(b.occurredAt) - Date.parse(a.occurredAt);
|
|
4692
4732
|
return c !== 0 ? c : b.decisionId.localeCompare(a.decisionId);
|
|
4693
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
|
+
});
|
|
4694
4757
|
const taskLoadOpts = {};
|
|
4695
4758
|
if (input.onTaskSkip !== void 0) taskLoadOpts.onSkip = input.onTaskSkip;
|
|
4696
4759
|
const taskEntries = await loadTaskEntries(input.paths, taskLoadOpts);
|
|
@@ -4781,6 +4844,7 @@ async function summarizeOrientation(input) {
|
|
|
4781
4844
|
decisionCount: decisions.length,
|
|
4782
4845
|
openTracks,
|
|
4783
4846
|
latestNote,
|
|
4847
|
+
recentDirection,
|
|
4784
4848
|
relatedFiles: { displayed, overflow, outOfRoot },
|
|
4785
4849
|
inFlightTasks,
|
|
4786
4850
|
plannedTasks,
|
|
@@ -4825,6 +4889,11 @@ function formatOrientationBody(summary, opts) {
|
|
|
4825
4889
|
lines.push(`> hosts: local, ${summary.hosts.join(", ")}`);
|
|
4826
4890
|
}
|
|
4827
4891
|
lines.push("");
|
|
4892
|
+
const banner = stalenessBanner(opts.staleness);
|
|
4893
|
+
if (banner.length > 0) {
|
|
4894
|
+
for (const line of banner) lines.push(line);
|
|
4895
|
+
lines.push("");
|
|
4896
|
+
}
|
|
4828
4897
|
lines.push("## \u4ECA\u3069\u3053\u306B\u3044\u308B");
|
|
4829
4898
|
lines.push("");
|
|
4830
4899
|
if (summary.latestSession !== null) {
|
|
@@ -4878,6 +4947,29 @@ function formatOrientationBody(summary, opts) {
|
|
|
4878
4947
|
lines.push("- \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: (none recorded)");
|
|
4879
4948
|
}
|
|
4880
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("");
|
|
4881
4973
|
lines.push("## \u4F55\u304C\u52D5\u304F");
|
|
4882
4974
|
lines.push("");
|
|
4883
4975
|
lines.push(`### \u9032\u884C\u4E2D task (${summary.inFlightTasks.length})`);
|
|
@@ -5024,6 +5116,23 @@ function toolDisplayName(kind) {
|
|
|
5024
5116
|
return kind ?? "\u4E0D\u660E";
|
|
5025
5117
|
}
|
|
5026
5118
|
}
|
|
5119
|
+
function stalenessBanner(staleness) {
|
|
5120
|
+
if (staleness === null) return [];
|
|
5121
|
+
if ((staleness.unverifiableSessions ?? 0) > 0) {
|
|
5122
|
+
return [
|
|
5123
|
+
`> \u26A0\uFE0F **\u6700\u65B0\u3067\u306F\u306A\u3044\u53EF\u80FD\u6027** \u2014 \u5909\u5316\u3057\u305F\u304C\u5B89\u5168\u306B\u53D6\u308A\u8FBC\u3081\u306A\u3044\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${staleness.unverifiableSessions} \u4EF6\u3042\u308A\u307E\u3059\u3002\u7740\u624B\u524D\u306B \`basou verify\` / \`basou refresh --force\`(\u8A73\u7D30\u306F\u672B\u5C3E\u300C\u3053\u308C\u306F\u6700\u65B0\u304B\u300D)\u3002`
|
|
5124
|
+
];
|
|
5125
|
+
}
|
|
5126
|
+
if (staleness.newSessions > 0 || staleness.updatedSessions > 0) {
|
|
5127
|
+
const parts = [];
|
|
5128
|
+
if (staleness.newSessions > 0) parts.push(`\u65B0\u898F ${staleness.newSessions} \u4EF6`);
|
|
5129
|
+
if (staleness.updatedSessions > 0) parts.push(`\u66F4\u65B0 ${staleness.updatedSessions} \u4EF6`);
|
|
5130
|
+
return [
|
|
5131
|
+
`> \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`
|
|
5132
|
+
];
|
|
5133
|
+
}
|
|
5134
|
+
return [];
|
|
5135
|
+
}
|
|
5027
5136
|
function freshnessVerdict(summary, staleness, now) {
|
|
5028
5137
|
if (staleness !== null && (staleness.unverifiableSessions ?? 0) > 0) {
|
|
5029
5138
|
return [
|
|
@@ -5088,6 +5197,10 @@ function relativeAge(startedAt, now) {
|
|
|
5088
5197
|
if (ms < 1e3) return "just now";
|
|
5089
5198
|
return `${formatDurationMs(ms)} ago`;
|
|
5090
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;
|
|
5091
5204
|
var NOTE_SUMMARY_MAX = 200;
|
|
5092
5205
|
function noteSummary(body) {
|
|
5093
5206
|
const oneLine = body.replace(/\s+/g, " ").trim();
|