@basou/core 0.7.0 → 0.8.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
@@ -112,6 +112,7 @@ declare const SessionInnerImportSchema: z.ZodObject<{
112
112
  }>;
113
113
  version: z.ZodLiteral<"0.1.0">;
114
114
  external_id: z.ZodOptional<z.ZodString>;
115
+ source_size_bytes: z.ZodOptional<z.ZodNumber>;
115
116
  }, z.core.$strip>;
116
117
  started_at: z.ZodString;
117
118
  ended_at: z.ZodOptional<z.ZodString>;
@@ -172,6 +173,7 @@ declare const SessionImportPayloadSchema: z.ZodObject<{
172
173
  }>;
173
174
  version: z.ZodLiteral<"0.1.0">;
174
175
  external_id: z.ZodOptional<z.ZodString>;
176
+ source_size_bytes: z.ZodOptional<z.ZodNumber>;
175
177
  }, z.core.$strip>;
176
178
  started_at: z.ZodString;
177
179
  ended_at: z.ZodOptional<z.ZodString>;
@@ -451,6 +453,14 @@ type ClaudeTranscriptToPayloadOptions = {
451
453
  * back to the `sessionId` read from the records when omitted.
452
454
  */
453
455
  externalId?: string;
456
+ /**
457
+ * Byte size of the source transcript that produced `records`, stored as
458
+ * `session.source.source_size_bytes` so a later import can detect growth and
459
+ * re-import the session. The caller passes the size of the buffer it actually
460
+ * read (an immutable snapshot of the parsed bytes), so the stored size always
461
+ * matches the imported content. Omitted => the field is not recorded.
462
+ */
463
+ sourceSizeBytes?: number;
454
464
  };
455
465
  /**
456
466
  * Transform a Claude Code native transcript into a Basou
@@ -506,6 +516,14 @@ type CodexRolloutToPayloadOptions = {
506
516
  * to the id read from the rollout's `session_meta` record when omitted.
507
517
  */
508
518
  externalId?: string;
519
+ /**
520
+ * Byte size of the source rollout that produced `records`, stored as
521
+ * `session.source.source_size_bytes` so a later import can detect growth and
522
+ * re-import the session. The caller passes the size of the buffer it actually
523
+ * read (an immutable snapshot of the parsed bytes), so the stored size always
524
+ * matches the imported content. Omitted => the field is not recorded.
525
+ */
526
+ sourceSizeBytes?: number;
509
527
  };
510
528
  /**
511
529
  * Transform a Codex native rollout log into a Basou {@link SessionImportPayload},
@@ -1350,6 +1368,7 @@ declare const SessionSchema: z.ZodObject<{
1350
1368
  }>;
1351
1369
  version: z.ZodLiteral<"0.1.0">;
1352
1370
  external_id: z.ZodOptional<z.ZodString>;
1371
+ source_size_bytes: z.ZodOptional<z.ZodNumber>;
1353
1372
  }, z.core.$strip>;
1354
1373
  started_at: z.ZodString;
1355
1374
  ended_at: z.ZodOptional<z.ZodString>;
@@ -3306,6 +3325,44 @@ type ImportSessionResult = {
3306
3325
  * for `--verbose` rendering.
3307
3326
  */
3308
3327
  declare function importSessionFromJson(paths: BasouPaths, manifest: Manifest, payload: SessionImportPayload, options: ImportSessionOptions): Promise<ImportSessionResult>;
3328
+ /** Whether `source` is one of the known import-derived event sources. */
3329
+ declare function isImportDerivedSource(source: string): boolean;
3330
+ /** Options for {@link reimportPreservingId}. */
3331
+ type ReimportOptions = {
3332
+ /** Compute the re-import and return its preview without writing to disk. */
3333
+ dryRun?: boolean;
3334
+ };
3335
+ /** Result of {@link reimportPreservingId}. */
3336
+ type ReimportResult = {
3337
+ status: "reimported";
3338
+ sessionId: PrefixedId<"ses">;
3339
+ /** Total events written to the merged `events.jsonl`. */
3340
+ eventCount: number;
3341
+ /** Non-derived events (human / unknown source) carried over unchanged. */
3342
+ preservedCount: number;
3343
+ /** Derived events whose prior id (and decision_id) was reused. */
3344
+ reusedIdCount: number;
3345
+ } | {
3346
+ status: "skipped";
3347
+ reason: "prior_events_unreadable" | "prior_derived_dropped";
3348
+ };
3349
+ /**
3350
+ * Re-import a source whose native log GREW into the SAME Basou session,
3351
+ * preserving its id and any non-derived events, instead of skipping it (default
3352
+ * dedup) or deleting + recreating it (`--force`). The caller has already
3353
+ * validated `freshPayload` and confirmed (by source byte size) that the source
3354
+ * changed; this function re-derives the adapter's events, reuses prior derived
3355
+ * event ids for unchanged derivations (so `linked_events` references survive),
3356
+ * preserves human / unknown-source events, and rewrites `events.jsonl` +
3357
+ * `session.yaml` atomically under the session lock.
3358
+ *
3359
+ * The whole read-modify-write runs under {@link acquireLock} so a concurrent
3360
+ * writer cannot interleave; `dryRun` computes the result and writes nothing
3361
+ * (and takes no lock). If the prior `events.jsonl` has any line that cannot be
3362
+ * preserved (malformed / schema-invalid / half-written), the re-import is
3363
+ * ABORTED (`status: "skipped"`) rather than risk dropping data on the rewrite.
3364
+ */
3365
+ declare function reimportPreservingId(paths: BasouPaths, manifest: Manifest, priorSessionId: string, freshPayload: SessionImportPayload, options?: ReimportOptions): Promise<ReimportResult>;
3309
3366
 
3310
3367
  /**
3311
3368
  * Walk the cause chain (up to `depth` levels) looking for an Error whose
@@ -3407,4 +3464,4 @@ declare function overwriteYamlFile(filePath: string, value: unknown): Promise<vo
3407
3464
  */
3408
3465
  declare const BASOU_CORE_VERSION = "0.1.0";
3409
3466
 
3410
- export { ACTIVE_GAP_CAP_MS, type ActiveTimeBasis, type AdapterOutputEvent, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, ChildProcessRunner, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, type DayWorkStats, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, FailedToFinalizeError, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, IsoTimestampSchema, JSON_SCHEMA_VERSION, type JsonSchemaArtifact, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type MeasureAvailability, type NoteAddedEvent, type PrefixedId, type ProcessRunner, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReplayOptions, type ReplayWarning, type RiskLevel, RiskLevelSchema, type RunOptions, type RunResult, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type Session, type SessionEndedEvent, type SessionEntry, SessionIdSchema, type SessionImportPayload, SessionImportPayloadSchema, type SessionInnerImportInput, SessionInnerImportSchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type SuspectReason, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildStatusSnapshot, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, findErrorCode, getDiff, getSnapshot, importSessionFromJson, isLazyExpired, isValidPrefixedId, linkYamlFile, loadApproval, loadSessionEntries, loadTaskEntries, overwriteYamlFile, parseDuration, parseMarkers, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, reconcileAllTasks, reconcileTask, refreshTaskLinkedSessions, renderDecisions, renderHandoff, renderWithMarkers, replayEvents, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, tryRemoteUrl, ulid, updateTaskStatusWithEvent, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
3467
+ export { ACTIVE_GAP_CAP_MS, type ActiveTimeBasis, type AdapterOutputEvent, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, ChildProcessRunner, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, type DayWorkStats, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, FailedToFinalizeError, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, IsoTimestampSchema, JSON_SCHEMA_VERSION, type JsonSchemaArtifact, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type MeasureAvailability, type NoteAddedEvent, type PrefixedId, type ProcessRunner, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReimportOptions, type ReimportResult, type ReplayOptions, type ReplayWarning, type RiskLevel, RiskLevelSchema, type RunOptions, type RunResult, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type Session, type SessionEndedEvent, type SessionEntry, SessionIdSchema, type SessionImportPayload, SessionImportPayloadSchema, type SessionInnerImportInput, SessionInnerImportSchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type SuspectReason, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildStatusSnapshot, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, findErrorCode, getDiff, getSnapshot, importSessionFromJson, isImportDerivedSource, isLazyExpired, isValidPrefixedId, linkYamlFile, loadApproval, loadSessionEntries, loadTaskEntries, overwriteYamlFile, parseDuration, parseMarkers, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, reconcileAllTasks, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, renderDecisions, renderHandoff, renderWithMarkers, replayEvents, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, tryRemoteUrl, ulid, updateTaskStatusWithEvent, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
package/dist/index.js CHANGED
@@ -224,7 +224,8 @@ function claudeTranscriptToImportPayload(records, options) {
224
224
  source: {
225
225
  kind: CLAUDE_IMPORT_SOURCE,
226
226
  version: "0.1.0",
227
- ...externalId !== void 0 ? { external_id: externalId } : {}
227
+ ...externalId !== void 0 ? { external_id: externalId } : {},
228
+ ...options.sourceSizeBytes !== void 0 ? { source_size_bytes: options.sourceSizeBytes } : {}
228
229
  },
229
230
  started_at: minTs,
230
231
  ended_at: maxTs,
@@ -457,7 +458,8 @@ function codexRolloutToImportPayload(records, options) {
457
458
  source: {
458
459
  kind: CODEX_IMPORT_SOURCE,
459
460
  version: "0.1.0",
460
- ...externalId !== void 0 ? { external_id: externalId } : {}
461
+ ...externalId !== void 0 ? { external_id: externalId } : {},
462
+ ...options.sourceSizeBytes !== void 0 ? { source_size_bytes: options.sourceSizeBytes } : {}
461
463
  },
462
464
  started_at: minTs,
463
465
  ended_at: maxTs,
@@ -1032,7 +1034,15 @@ var SessionSourceSchema = z4.object({
1032
1034
  // Optional id of the originating session in the SOURCE tool's own
1033
1035
  // namespace (e.g. the Claude Code session UUID for a `claude-code-import`).
1034
1036
  // Lets re-imports of the same source be deduplicated; absent for live runs.
1035
- external_id: z4.string().optional()
1037
+ external_id: z4.string().optional(),
1038
+ // Byte size of the source native log at import time, recorded so a later
1039
+ // import can detect that an append-only transcript GREW and re-import it
1040
+ // (scoped, preserving the session id) instead of skipping it as already
1041
+ // imported. Additive optional => no schema_version bump (precedent:
1042
+ // external_id, metrics). Absent on sessions imported before this field
1043
+ // existed (treated as legacy: never auto-re-imported, populated on the next
1044
+ // fresh import or `--force`).
1045
+ source_size_bytes: z4.number().int().nonnegative().optional()
1036
1046
  });
1037
1047
  var InvocationSchema = z4.object({
1038
1048
  command: z4.string().min(1),
@@ -3922,7 +3932,13 @@ var SessionInnerImportSchema = z10.object({
3922
3932
  version: z10.literal("0.1.0"),
3923
3933
  // Source-tool-native id (e.g. Claude Code session UUID), retained so
3924
3934
  // re-imports of the same source can be deduplicated.
3925
- external_id: z10.string().optional()
3935
+ external_id: z10.string().optional(),
3936
+ // Byte size of the source native log at import time. Declared here too
3937
+ // (not only in session.schema.ts) because this inner `source` object is
3938
+ // a plain z.object: zod strips keys it does not declare, so a field
3939
+ // absent here would be dropped from the parsed payload before persist
3940
+ // and the size could never be stored.
3941
+ source_size_bytes: z10.number().int().nonnegative().optional()
3926
3942
  }),
3927
3943
  started_at: IsoTimestampSchema,
3928
3944
  ended_at: IsoTimestampSchema.optional(),
@@ -4731,6 +4747,130 @@ function buildSessionRecord(input, manifest, newSessionId, options) {
4731
4747
  }
4732
4748
  };
4733
4749
  }
4750
+ var IMPORT_DERIVED_SOURCES = /* @__PURE__ */ new Set([
4751
+ "claude-code-import",
4752
+ "codex-import"
4753
+ ]);
4754
+ function isImportDerivedSource(source) {
4755
+ return IMPORT_DERIVED_SOURCES.has(source);
4756
+ }
4757
+ function derivedEventContentKey(event) {
4758
+ const base = `${event.type}\0${event.occurred_at}`;
4759
+ switch (event.type) {
4760
+ case "command_executed":
4761
+ return `${base}\0${event.command}\0${event.args.join("")}\0${event.cwd}`;
4762
+ case "file_changed":
4763
+ return `${base}\0${event.path}\0${event.change_type}`;
4764
+ case "decision_recorded":
4765
+ return `${base}\0${event.title}`;
4766
+ default:
4767
+ return base;
4768
+ }
4769
+ }
4770
+ function reuseDerivedIds(priorDerived, freshDerived, sessionId) {
4771
+ const priorStarted = priorDerived.find((e) => e.type === "session_started");
4772
+ const priorEnded = priorDerived.find((e) => e.type === "session_ended");
4773
+ let startedUsed = false;
4774
+ let endedUsed = false;
4775
+ const middleByKey = /* @__PURE__ */ new Map();
4776
+ for (const e of priorDerived) {
4777
+ if (e.type === "session_started" || e.type === "session_ended") continue;
4778
+ const key = derivedEventContentKey(e);
4779
+ const list = middleByKey.get(key);
4780
+ if (list === void 0) middleByKey.set(key, [e]);
4781
+ else list.push(e);
4782
+ }
4783
+ let reusedIdCount = 0;
4784
+ const withReusedId = (fresh, prior) => {
4785
+ reusedIdCount++;
4786
+ if (fresh.type === "decision_recorded" && prior.type === "decision_recorded") {
4787
+ return { ...fresh, id: prior.id, session_id: sessionId, decision_id: prior.decision_id };
4788
+ }
4789
+ return { ...fresh, id: prior.id, session_id: sessionId };
4790
+ };
4791
+ const events = freshDerived.map((fresh) => {
4792
+ if (fresh.type === "session_started") {
4793
+ if (priorStarted !== void 0) {
4794
+ startedUsed = true;
4795
+ return withReusedId(fresh, priorStarted);
4796
+ }
4797
+ return { ...fresh, id: prefixedUlid("evt"), session_id: sessionId };
4798
+ }
4799
+ if (fresh.type === "session_ended") {
4800
+ if (priorEnded !== void 0) {
4801
+ endedUsed = true;
4802
+ return withReusedId(fresh, priorEnded);
4803
+ }
4804
+ return { ...fresh, id: prefixedUlid("evt"), session_id: sessionId };
4805
+ }
4806
+ const match = middleByKey.get(derivedEventContentKey(fresh))?.shift();
4807
+ if (match !== void 0) return withReusedId(fresh, match);
4808
+ return { ...fresh, id: prefixedUlid("evt"), session_id: sessionId };
4809
+ });
4810
+ const droppedPriorDerived = priorStarted !== void 0 && !startedUsed || priorEnded !== void 0 && !endedUsed || [...middleByKey.values()].some((q) => q.length > 0);
4811
+ return { events, reusedIdCount, droppedPriorDerived };
4812
+ }
4813
+ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayload, options = {}) {
4814
+ const sessionId = priorSessionId;
4815
+ const importSource = freshPayload.session.source.kind;
4816
+ const sessionDir = join14(paths.sessions, priorSessionId);
4817
+ const lock = options.dryRun === true ? null : await acquireLock(paths, "session", priorSessionId);
4818
+ try {
4819
+ let priorUnreadable = false;
4820
+ const priorEvents = await readAllEvents(sessionDir, {
4821
+ onWarning: () => {
4822
+ priorUnreadable = true;
4823
+ }
4824
+ });
4825
+ if (priorUnreadable) {
4826
+ return { status: "skipped", reason: "prior_events_unreadable" };
4827
+ }
4828
+ const priorDerived = priorEvents.filter((e) => e.source === importSource);
4829
+ const preserved = priorEvents.filter((e) => e.source !== importSource);
4830
+ const {
4831
+ events: rederived,
4832
+ reusedIdCount,
4833
+ droppedPriorDerived
4834
+ } = reuseDerivedIds(priorDerived, freshPayload.events, sessionId);
4835
+ if (droppedPriorDerived) {
4836
+ return { status: "skipped", reason: "prior_derived_dropped" };
4837
+ }
4838
+ const mergedEvents = [...rederived, ...preserved].sort(
4839
+ (a, b) => Date.parse(a.occurred_at) - Date.parse(b.occurred_at)
4840
+ );
4841
+ assertChronologicalOrder(mergedEvents);
4842
+ const prior = await readSessionYaml(paths, priorSessionId);
4843
+ const { record } = buildSessionRecord(freshPayload.session, manifest, sessionId, {});
4844
+ const preservedInner = {
4845
+ ...record.session,
4846
+ // A human may have linked this imported session to a task
4847
+ // (`basou task link` updates session.yaml.task_id even for imported
4848
+ // sessions); never drop that link on a re-derive.
4849
+ task_id: prior.session.task_id ?? null,
4850
+ // Re-derivation always yields a null summary; keep a prior non-null one.
4851
+ summary: prior.session.summary ?? record.session.summary ?? null
4852
+ };
4853
+ const updatedRecord = { schema_version: "0.1.0", session: preservedInner };
4854
+ if (options.dryRun !== true) {
4855
+ await writeEventsBulk(sessionDir, mergedEvents);
4856
+ try {
4857
+ await overwriteYamlFile(join14(sessionDir, "session.yaml"), updatedRecord);
4858
+ } catch (error) {
4859
+ await writeEventsBulk(sessionDir, priorEvents).catch(() => void 0);
4860
+ throw error;
4861
+ }
4862
+ }
4863
+ return {
4864
+ status: "reimported",
4865
+ sessionId,
4866
+ eventCount: mergedEvents.length,
4867
+ preservedCount: preserved.length,
4868
+ reusedIdCount
4869
+ };
4870
+ } finally {
4871
+ await lock?.release();
4872
+ }
4873
+ }
4734
4874
 
4735
4875
  // src/index.ts
4736
4876
  var BASOU_CORE_VERSION = "0.1.0";
@@ -4798,6 +4938,7 @@ export {
4798
4938
  getDiff,
4799
4939
  getSnapshot,
4800
4940
  importSessionFromJson,
4941
+ isImportDerivedSource,
4801
4942
  isLazyExpired,
4802
4943
  isValidPrefixedId,
4803
4944
  linkYamlFile,
@@ -4819,6 +4960,7 @@ export {
4819
4960
  reconcileAllTasks,
4820
4961
  reconcileTask,
4821
4962
  refreshTaskLinkedSessions,
4963
+ reimportPreservingId,
4822
4964
  renderDecisions,
4823
4965
  renderHandoff,
4824
4966
  renderWithMarkers,