@basou/core 0.16.0 → 0.17.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
@@ -3161,6 +3161,71 @@ type SanitizeRelatedFilesResult = {
3161
3161
  */
3162
3162
  declare function sanitizeRelatedFiles(paths: ReadonlyArray<string>, opts: SanitizePathOptions): SanitizeRelatedFilesResult;
3163
3163
 
3164
+ /**
3165
+ * Cross-project boundary classification: split a session's `related_files`
3166
+ * into those that resolve INSIDE the project's declared `source_roots` and
3167
+ * those that confidently resolve OUTSIDE all of them.
3168
+ *
3169
+ * Why this exists: the claude-code adapter records every file a transcript
3170
+ * edited, regardless of where the file lives. A session is attributed to a
3171
+ * project by its recorded cwd (the import-time cwd guard), but a session that
3172
+ * legitimately belongs to project A can still have edited files under an
3173
+ * unrelated repo B. Those B paths then surface in `basou orient`'s "recent
3174
+ * files" and can mislead a resuming agent into continuing the wrong project's
3175
+ * work. This helper is the read-only primitive both the import warning and the
3176
+ * orientation advisory use to flag that boundary crossing — it never mutates
3177
+ * the trail.
3178
+ *
3179
+ * Resolution is realpath-aware so a file recorded through a workspace-view
3180
+ * symlink (e.g. `~/projects/foo-workspace/foo -> ../foo`) is NOT mis-flagged as
3181
+ * out-of-root. The bias is deliberately toward NOT crying wolf: a path is only
3182
+ * reported out-of-root when it confidently resolves outside every source root.
3183
+ * Anything that cannot be resolved with confidence stays classified in-root.
3184
+ */
3185
+ /**
3186
+ * The agent's / basou's own tooling directories. Edits here (plans, memory,
3187
+ * the trail store itself) are routine infrastructure, not another project's
3188
+ * work, so callers pass these as `extraInRoot` to keep them out of the
3189
+ * cross-project out-of-root flag.
3190
+ */
3191
+ declare const AGENT_INFRA_DIRS: readonly string[];
3192
+ /** Result of {@link classifyFilesBySourceRoot}: a partition of the input. */
3193
+ type SourceRootScope = {
3194
+ /** Entries (verbatim, as passed in) that resolve under a source root, or that could not be resolved with confidence. */
3195
+ inRoot: string[];
3196
+ /** Entries (verbatim) that confidently resolve outside every source root. */
3197
+ outOfRoot: string[];
3198
+ };
3199
+ /**
3200
+ * Partition `files` into in-root / out-of-root against the project's
3201
+ * `source_roots`.
3202
+ *
3203
+ * - `sourceRoots` are the manifest's `import.source_roots` (relative to
3204
+ * `masterRoot`). An absent/empty list means "the whole repo root" — matching
3205
+ * the effective-source-roots rule elsewhere — so a solo project never reports
3206
+ * anything out-of-root.
3207
+ * - `masterRoot` is the absolute repository root the source roots resolve
3208
+ * against (the parent of `.basou`).
3209
+ *
3210
+ * Returns `{ inRoot, outOfRoot }` preserving the original entry strings. Empty
3211
+ * input or zero resolvable roots yields everything in-root (no false alarms).
3212
+ */
3213
+ declare function classifyFilesBySourceRoot(input: {
3214
+ files: readonly string[];
3215
+ workingDirectory: string;
3216
+ sourceRoots: readonly string[] | null | undefined;
3217
+ masterRoot: string;
3218
+ /**
3219
+ * Extra directories (absolute or `~`-prefixed) that also count as in-root.
3220
+ * Callers pass the agent's own tooling dirs (`~/.claude`, `~/.codex`,
3221
+ * `~/.basou`) so routine plan / memory / store edits are NOT flagged as
3222
+ * another project's work — they are infrastructure, not a cross-project
3223
+ * crossing. Resolved against the home directory, not `masterRoot`.
3224
+ */
3225
+ extraInRoot?: readonly string[];
3226
+ homedir?: string;
3227
+ }): Promise<SourceRootScope>;
3228
+
3164
3229
  /** Input contract for {@link renderOrientation} and {@link summarizeOrientation}. */
3165
3230
  type OrientationRendererInput = {
3166
3231
  paths: BasouPaths;
@@ -3288,10 +3353,19 @@ type OrientationSummary = {
3288
3353
  * step / handoff ("次の起点") surfaced in the forward section; null when none.
3289
3354
  */
3290
3355
  latestNote: NoteRecord | null;
3291
- /** related_files of the latest session, deduped + sorted + capped at the display limit. */
3356
+ /**
3357
+ * related_files of the latest session, deduped + sorted + capped at the
3358
+ * display limit. `outOfRoot` lists the entries (over the FULL deduped set,
3359
+ * not just `displayed`) that resolve OUTSIDE the project's `source_roots` — a
3360
+ * cross-project boundary crossing worth flagging so a resuming agent does not
3361
+ * mistake another repo's edits for this project's work. Empty unless the
3362
+ * latest session is local (a federated host's source_roots are not loaded
3363
+ * here) and confidently has out-of-root edits.
3364
+ */
3292
3365
  relatedFiles: {
3293
3366
  displayed: string[];
3294
3367
  overflow: number;
3368
+ outOfRoot: string[];
3295
3369
  };
3296
3370
  /** Tasks whose status is `planned` or `in_progress`. */
3297
3371
  inFlightTasks: InFlightTask[];
@@ -5380,4 +5454,4 @@ declare function overwriteYamlFile(filePath: string, value: unknown): Promise<vo
5380
5454
  */
5381
5455
  declare const BASOU_CORE_VERSION = "0.1.0";
5382
5456
 
5383
- export { ACTIVE_GAP_CAP_MS, type ActiveTimeBasis, type AdapterOutputEvent, type AdoptCandidate, type AdoptCandidateKind, type AppendBasouGitignoreOptions, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchivePlan, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, type BulkChainResult, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, type ChainBreakReason, type ChainTailState, type ChainVerdict, type ChainVerdictStatus, type ChainedEvents, ChildProcessRunner, type CitedReview, 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, type ExistingViewLink, FailedToFinalizeError, type FederatedRoot, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type GitignorePlanSummary, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, type InstructionFileFact, type InstructionSymlinkFact, type InstructionSymlinkState, IsoTimestampSchema, JSON_SCHEMA_VERSION, type JsonSchemaArtifact, type LoadFederatedOptions, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type Markers, type MeasureAvailability, type NoteAddedEvent, type OrientationRendererInput, type OrientationRendererResult, type OrientationSummary, PROTOCOL_END, PROTOCOL_START, type PrefixedId, type PresetAction, type PresetCollision, type PresetMarkerConflict, type PresetMarkerKind, type PresetPlanSummary, type PresetRepo, type ProcessRunner, type PublishKind, type PublishTarget, type RechainOptions, type RechainResult, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReimportOptions, type ReimportResult, type RenamePlan, type ReplayOptions, type ReplayWarning, type RepoEntry, type RepoGitignoreFacts, type RepoGitignorePlan, type RepoLanguage, type RepoPresetFacts, type RepoPresetPlan, type RepoSymlinkFacts, type RepoSymlinkPlan, type RepoVisibility, type RepoWiringFacts, type ReportApprovalItem, type ReportData, type ReportDecisionItem, type ReportRendererInput, type ReportRendererResult, type ReportSessionItem, type ReportTaskItem, type ReviewGapRepoSummary, type ReviewGapUnit, type ReviewGapVerdict, type ReviewGapsInput, type ReviewGapsSummary, type RiskLevel, RiskLevelSchema, type RosterAdoptionPlan, type RosterDriftSummary, 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 SessionIntegrity, SessionIntegritySchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceRootsReconcile, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type SuspectReason, type SymlinkCollision, type SymlinkConflict, type SymlinkPlanSummary, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, type TaskStatusCount, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type ViewCollision, type ViewConflict, type ViewLinkState, type ViewRepoFact, type ViewStrayUnknown, type WiringRisk, type WiringSummary, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WorkspaceViewPlan, type WriteEventsBulkOptions, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendChainedEvent, appendChainedEventLocked, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildStatusSnapshot, chainEvents, chainRawJsonLines, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, finalizeSessionYaml, findErrorCode, findReviewGaps, formatDurationMs, genesisHash, getDiff, getSnapshot, importSessionFromJson, inspectChainTail, isGitNotFound, isImportDerivedSource, isLazyExpired, isRenderable, isValidPrefixedId, lineHash, linkYamlFile, loadApproval, loadFederatedSessionEntries, loadSessionEntries, loadTaskEntries, normalizeRepoKey, normalizeRepoPath, overwriteYamlFile, parseDuration, parseMarkers, pathBasename, planArchive, planGitignore, planRename, planRosterAdoption, planWorkspaceView, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, rechainSessionInPlace, reconcileAllTasks, reconcileSourceRoots, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, removeMarkerSection, renderDecisions, renderHandoff, renderOrientation, renderPresetBlock, renderReport, renderWithMarkers, replayEvents, resolveBasouRepositoryRoot, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, safeSimpleGit, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, serializeEventLine, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, summarizeOrientation, summarizePresetPlan, summarizeRosterDrift, summarizeSymlinkPlan, summarizeWiring, tryRemoteUrl, ulid, unknownManifestKeys, updateTaskStatusWithEvent, verifyEventsChain, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
5457
+ export { ACTIVE_GAP_CAP_MS, AGENT_INFRA_DIRS, type ActiveTimeBasis, type AdapterOutputEvent, type AdoptCandidate, type AdoptCandidateKind, type AppendBasouGitignoreOptions, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchivePlan, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, type BulkChainResult, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, type ChainBreakReason, type ChainTailState, type ChainVerdict, type ChainVerdictStatus, type ChainedEvents, ChildProcessRunner, type CitedReview, 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, type ExistingViewLink, FailedToFinalizeError, type FederatedRoot, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type GitignorePlanSummary, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, type InstructionFileFact, type InstructionSymlinkFact, type InstructionSymlinkState, IsoTimestampSchema, JSON_SCHEMA_VERSION, type JsonSchemaArtifact, type LoadFederatedOptions, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type Markers, type MeasureAvailability, type NoteAddedEvent, type OrientationRendererInput, type OrientationRendererResult, type OrientationSummary, PROTOCOL_END, PROTOCOL_START, type PrefixedId, type PresetAction, type PresetCollision, type PresetMarkerConflict, type PresetMarkerKind, type PresetPlanSummary, type PresetRepo, type ProcessRunner, type PublishKind, type PublishTarget, type RechainOptions, type RechainResult, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReimportOptions, type ReimportResult, type RenamePlan, type ReplayOptions, type ReplayWarning, type RepoEntry, type RepoGitignoreFacts, type RepoGitignorePlan, type RepoLanguage, type RepoPresetFacts, type RepoPresetPlan, type RepoSymlinkFacts, type RepoSymlinkPlan, type RepoVisibility, type RepoWiringFacts, type ReportApprovalItem, type ReportData, type ReportDecisionItem, type ReportRendererInput, type ReportRendererResult, type ReportSessionItem, type ReportTaskItem, type ReviewGapRepoSummary, type ReviewGapUnit, type ReviewGapVerdict, type ReviewGapsInput, type ReviewGapsSummary, type RiskLevel, RiskLevelSchema, type RosterAdoptionPlan, type RosterDriftSummary, 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 SessionIntegrity, SessionIntegritySchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceRootScope, type SourceRootsReconcile, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type SuspectReason, type SymlinkCollision, type SymlinkConflict, type SymlinkPlanSummary, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, type TaskStatusCount, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type ViewCollision, type ViewConflict, type ViewLinkState, type ViewRepoFact, type ViewStrayUnknown, type WiringRisk, type WiringSummary, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WorkspaceViewPlan, type WriteEventsBulkOptions, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendChainedEvent, appendChainedEventLocked, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildStatusSnapshot, chainEvents, chainRawJsonLines, classifyFilesBySourceRoot, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, finalizeSessionYaml, findErrorCode, findReviewGaps, formatDurationMs, genesisHash, getDiff, getSnapshot, importSessionFromJson, inspectChainTail, isGitNotFound, isImportDerivedSource, isLazyExpired, isRenderable, isValidPrefixedId, lineHash, linkYamlFile, loadApproval, loadFederatedSessionEntries, loadSessionEntries, loadTaskEntries, normalizeRepoKey, normalizeRepoPath, overwriteYamlFile, parseDuration, parseMarkers, pathBasename, planArchive, planGitignore, planRename, planRosterAdoption, planWorkspaceView, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, rechainSessionInPlace, reconcileAllTasks, reconcileSourceRoots, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, removeMarkerSection, renderDecisions, renderHandoff, renderOrientation, renderPresetBlock, renderReport, renderWithMarkers, replayEvents, resolveBasouRepositoryRoot, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, safeSimpleGit, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, serializeEventLine, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, summarizeOrientation, summarizePresetPlan, summarizeRosterDrift, summarizeSymlinkPlan, summarizeWiring, tryRemoteUrl, ulid, unknownManifestKeys, updateTaskStatusWithEvent, verifyEventsChain, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
package/dist/index.js CHANGED
@@ -11,10 +11,10 @@ async function resolveClaudeCodeCommand(lookup = isOnPath) {
11
11
  throw new Error("Claude Code CLI not found in PATH. Install claude-code (or claude) first.");
12
12
  }
13
13
  async function isOnPath(command) {
14
- return new Promise((resolve2) => {
14
+ return new Promise((resolve3) => {
15
15
  const child = spawn("which", [command], { stdio: "ignore" });
16
- child.on("error", () => resolve2(false));
17
- child.on("exit", (code) => resolve2(code === 0));
16
+ child.on("error", () => resolve3(false));
17
+ child.on("exit", (code) => resolve3(code === 0));
18
18
  });
19
19
  }
20
20
  function summarizeAdapterOutput(_stream, _raw) {
@@ -4231,8 +4231,82 @@ async function resolveIdInternal(paths, input, kind, options = {}) {
4231
4231
  return matches[0];
4232
4232
  }
4233
4233
 
4234
+ // src/lib/source-root-scope.ts
4235
+ import { promises as fs } from "fs";
4236
+ import { homedir as osHomedir } from "os";
4237
+ import { basename as basename2, dirname as dirname3, isAbsolute, join as join14, normalize, relative, resolve as resolve2 } from "path";
4238
+ var AGENT_INFRA_DIRS = ["~/.claude", "~/.codex", "~/.basou"];
4239
+ async function realpathBestEffort(absPath) {
4240
+ let current = normalize(absPath);
4241
+ const tail = [];
4242
+ for (let guard = 0; guard < 4096; guard += 1) {
4243
+ try {
4244
+ const real = await fs.realpath(current);
4245
+ return tail.length > 0 ? join14(real, ...tail.reverse()) : real;
4246
+ } catch (error) {
4247
+ const code = error?.code;
4248
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
4249
+ return normalize(absPath);
4250
+ }
4251
+ const parent = dirname3(current);
4252
+ if (parent === current) return normalize(absPath);
4253
+ tail.push(basename2(current));
4254
+ current = parent;
4255
+ }
4256
+ }
4257
+ return normalize(absPath);
4258
+ }
4259
+ function expandTilde(p, homedir4) {
4260
+ if (p === "~") return homedir4;
4261
+ if (p.startsWith("~/")) return join14(homedir4, p.slice(2));
4262
+ return p;
4263
+ }
4264
+ function toAbsolute(p, workingDirAbs, homedir4) {
4265
+ const expanded = expandTilde(p, homedir4);
4266
+ if (isAbsolute(expanded)) return normalize(expanded);
4267
+ return normalize(resolve2(workingDirAbs, expanded));
4268
+ }
4269
+ function isUnder(child, parent) {
4270
+ if (child === parent) return true;
4271
+ const rel = relative(parent, child);
4272
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
4273
+ }
4274
+ async function classifyFilesBySourceRoot(input) {
4275
+ const inRoot = [];
4276
+ const outOfRoot = [];
4277
+ if (input.files.length === 0) return { inRoot, outOfRoot };
4278
+ const homedir4 = input.homedir ?? osHomedir();
4279
+ const workingDirAbs = toAbsolute(input.workingDirectory, homedir4, homedir4);
4280
+ const declared = input.sourceRoots && input.sourceRoots.length > 0 ? [...input.sourceRoots] : ["."];
4281
+ const rootsAbs = [];
4282
+ for (const r of declared) {
4283
+ const expanded = expandTilde(r, homedir4);
4284
+ const abs = isAbsolute(expanded) ? normalize(expanded) : normalize(resolve2(input.masterRoot, expanded));
4285
+ rootsAbs.push(await realpathBestEffort(abs));
4286
+ }
4287
+ for (const e of input.extraInRoot ?? []) {
4288
+ const expanded = expandTilde(e, homedir4);
4289
+ const abs = isAbsolute(expanded) ? normalize(expanded) : normalize(resolve2(homedir4, expanded));
4290
+ rootsAbs.push(await realpathBestEffort(abs));
4291
+ }
4292
+ if (rootsAbs.length === 0) {
4293
+ return { inRoot: [...input.files], outOfRoot };
4294
+ }
4295
+ for (const file of input.files) {
4296
+ try {
4297
+ const abs = toAbsolute(file, workingDirAbs, homedir4);
4298
+ const real = await realpathBestEffort(abs);
4299
+ const within = rootsAbs.some((root) => isUnder(real, root));
4300
+ (within ? inRoot : outOfRoot).push(file);
4301
+ } catch {
4302
+ inRoot.push(file);
4303
+ }
4304
+ }
4305
+ return { inRoot, outOfRoot };
4306
+ }
4307
+
4234
4308
  // src/orientation/orientation-renderer.ts
4235
- import { join as join14 } from "path";
4309
+ import { dirname as dirname4, join as join15 } from "path";
4236
4310
 
4237
4311
  // src/storage/manifest.ts
4238
4312
  import { lstat as lstat3 } from "fs/promises";
@@ -4401,7 +4475,7 @@ async function summarizeOrientation(input) {
4401
4475
  }
4402
4476
  };
4403
4477
  for (const entry of entries) {
4404
- const sessionDir = join14(entry.sourceRoot.sessions, entry.sessionId);
4478
+ const sessionDir = join15(entry.sourceRoot.sessions, entry.sessionId);
4405
4479
  const counted = entry.session.session.status !== "archived";
4406
4480
  if (counted) noteActivity(entry.session.session.ended_at ?? entry.session.session.started_at);
4407
4481
  try {
@@ -4499,8 +4573,24 @@ async function summarizeOrientation(input) {
4499
4573
  }
4500
4574
  const latestFiles = latestEntry?.session.session.related_files ?? [];
4501
4575
  const uniqueFiles = new Set(latestFiles);
4502
- const displayed = [...uniqueFiles].sort().slice(0, limit);
4576
+ const sortedFiles = [...uniqueFiles].sort();
4577
+ const displayed = sortedFiles.slice(0, limit);
4503
4578
  const overflow = Math.max(0, uniqueFiles.size - limit);
4579
+ let outOfRoot = [];
4580
+ if (latestEntry !== void 0 && latestEntry.host === null && sortedFiles.length > 0 && sourceRoots !== null && sourceRoots.length > 0) {
4581
+ try {
4582
+ const scope = await classifyFilesBySourceRoot({
4583
+ files: sortedFiles,
4584
+ workingDirectory: latestEntry.session.session.working_directory,
4585
+ sourceRoots,
4586
+ masterRoot: dirname4(input.paths.root),
4587
+ extraInRoot: AGENT_INFRA_DIRS
4588
+ });
4589
+ outOfRoot = scope.outOfRoot;
4590
+ } catch {
4591
+ outOfRoot = [];
4592
+ }
4593
+ }
4504
4594
  const hosts = [
4505
4595
  ...new Set(entries.map((e) => e.host).filter((h) => h !== null))
4506
4596
  ].sort();
@@ -4511,7 +4601,7 @@ async function summarizeOrientation(input) {
4511
4601
  latestDecision: latestDecision ?? null,
4512
4602
  decisionCount: decisions.length,
4513
4603
  latestNote,
4514
- relatedFiles: { displayed, overflow },
4604
+ relatedFiles: { displayed, overflow, outOfRoot },
4515
4605
  inFlightTasks,
4516
4606
  plannedTasks,
4517
4607
  pendingApprovals,
@@ -4594,6 +4684,15 @@ function formatOrientationBody(summary, opts) {
4594
4684
  const shown = summary.relatedFiles.displayed.join(", ");
4595
4685
  const more = summary.relatedFiles.overflow > 0 ? ` (... +${summary.relatedFiles.overflow} more)` : "";
4596
4686
  lines.push(`- \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: ${shown}${more}`);
4687
+ if (summary.relatedFiles.outOfRoot.length > 0) {
4688
+ const OUT_OF_ROOT_DISPLAY = 10;
4689
+ const out = summary.relatedFiles.outOfRoot;
4690
+ const shownOut = out.slice(0, OUT_OF_ROOT_DISPLAY).join(", ");
4691
+ const outMore = out.length > OUT_OF_ROOT_DISPLAY ? ` (... +${out.length - OUT_OF_ROOT_DISPLAY} more)` : "";
4692
+ lines.push(
4693
+ ` - \u26A0 source_roots \u5916 ${out.length} \u4EF6 (\u5225\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u306E\u53EF\u80FD\u6027): ${shownOut}${outMore}`
4694
+ );
4695
+ }
4597
4696
  } else {
4598
4697
  lines.push("- \u76F4\u8FD1\u306E\u5909\u66F4\u30D5\u30A1\u30A4\u30EB: (none recorded)");
4599
4698
  }
@@ -5375,10 +5474,10 @@ function planWorkspaceView(facts, existing = [], rosterNames = []) {
5375
5474
  }
5376
5475
 
5377
5476
  // src/report/report-renderer.ts
5378
- import { join as join16 } from "path";
5477
+ import { join as join17 } from "path";
5379
5478
 
5380
5479
  // src/stats/work-stats.ts
5381
- import { join as join15 } from "path";
5480
+ import { join as join16 } from "path";
5382
5481
  function resolveTimeZone(timeZone) {
5383
5482
  if (timeZone !== void 0 && timeZone.length > 0) return timeZone;
5384
5483
  return Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -5409,7 +5508,7 @@ async function computeWorkStats(input) {
5409
5508
  const events = [];
5410
5509
  let eventsUnreadable = false;
5411
5510
  try {
5412
- for await (const ev of replayEvents(join15(input.paths.sessions, entry.sessionId), {
5511
+ for await (const ev of replayEvents(join16(input.paths.sessions, entry.sessionId), {
5413
5512
  onWarning: (w) => input.onWarning?.(w, entry.sessionId)
5414
5513
  })) {
5415
5514
  events.push(ev);
@@ -5696,7 +5795,7 @@ async function renderReport(input) {
5696
5795
  const statsBySession = new Map(stats.sessions.map((s) => [s.sessionId, s]));
5697
5796
  const decisions = [];
5698
5797
  for (const entry of entries) {
5699
- const sessionDir = join16(input.paths.sessions, entry.sessionId);
5798
+ const sessionDir = join17(input.paths.sessions, entry.sessionId);
5700
5799
  try {
5701
5800
  for await (const ev of replayEvents(sessionDir, {
5702
5801
  onWarning: (w) => input.onWarning?.(w, entry.sessionId)
@@ -5977,7 +6076,7 @@ function formatInt(n) {
5977
6076
  // src/review/review-gaps.ts
5978
6077
  import { existsSync, realpathSync } from "fs";
5979
6078
  import { homedir as homedir2 } from "os";
5980
- import { basename as basename2, isAbsolute, join as join17 } from "path";
6079
+ import { basename as basename3, isAbsolute as isAbsolute2, join as join18 } from "path";
5981
6080
  function stripQuotes(s) {
5982
6081
  if (s.length >= 2 && (s[0] === '"' && s.at(-1) === '"' || s[0] === "'" && s.at(-1) === "'")) {
5983
6082
  return s.slice(1, -1);
@@ -6001,7 +6100,7 @@ var repoRootCache = /* @__PURE__ */ new Map();
6001
6100
  function isRepoRoot(realPath) {
6002
6101
  const cached = repoRootCache.get(realPath);
6003
6102
  if (cached !== void 0) return cached;
6004
- const result = existsSync(join17(realPath, ".git"));
6103
+ const result = existsSync(join18(realPath, ".git"));
6005
6104
  repoRootCache.set(realPath, result);
6006
6105
  return result;
6007
6106
  }
@@ -6010,7 +6109,7 @@ function normalizeRepoPath(p) {
6010
6109
  let s = stripQuotes(p.trim()).replace(/\/+$/, "");
6011
6110
  if (s.length === 0 || s === "~") return null;
6012
6111
  if (s.startsWith("~/")) s = homedir2() + s.slice(1);
6013
- if (isAbsolute(s)) {
6112
+ if (isAbsolute2(s)) {
6014
6113
  const real = resolveRealpath(s);
6015
6114
  if (real !== null) {
6016
6115
  return isRepoRoot(real) ? real : null;
@@ -6024,7 +6123,7 @@ function normalizeRepoPath(p) {
6024
6123
  }
6025
6124
  function normalizeRepoKey(p) {
6026
6125
  const full = normalizeRepoPath(p);
6027
- return full === null ? null : basename2(full);
6126
+ return full === null ? null : basename3(full);
6028
6127
  }
6029
6128
  function inspectCommand(args) {
6030
6129
  const a = args.join(" ");
@@ -6038,7 +6137,7 @@ function inspectCommand(args) {
6038
6137
  let m;
6039
6138
  while ((m = re.exec(a)) !== null) {
6040
6139
  const f = m[1];
6041
- if (f !== void 0) files.add(basename2(f));
6140
+ if (f !== void 0) files.add(basename3(f));
6042
6141
  }
6043
6142
  }
6044
6143
  return { files: [...files], examinedDiff };
@@ -6055,7 +6154,7 @@ function commitFiles(args) {
6055
6154
  const a = args.join(" ");
6056
6155
  const add = a.match(/git add\s+([^&|;]+)/);
6057
6156
  if (!add?.[1]) return [];
6058
- return add[1].split(/\s+/).filter((t) => /\.[A-Za-z]/.test(t) && !t.startsWith("-")).map((t) => basename2(t));
6157
+ return add[1].split(/\s+/).filter((t) => /\.[A-Za-z]/.test(t) && !t.startsWith("-")).map((t) => basename3(t));
6059
6158
  }
6060
6159
  var REVIEW_SOURCE = "codex-import";
6061
6160
  var DEFAULT_WINDOW_HOURS = 24;
@@ -6071,7 +6170,7 @@ async function findReviewGaps(input) {
6071
6170
  const workUnits = /* @__PURE__ */ new Map();
6072
6171
  const unknownCommits = /* @__PURE__ */ new Map();
6073
6172
  for (const entry of entries) {
6074
- const sessionDir = join17(input.paths.sessions, entry.sessionId);
6173
+ const sessionDir = join18(input.paths.sessions, entry.sessionId);
6075
6174
  const isReview = entry.session.session.source.kind === REVIEW_SOURCE;
6076
6175
  const reviewRepos = /* @__PURE__ */ new Map();
6077
6176
  let reviewEnd = null;
@@ -6120,7 +6219,7 @@ async function findReviewGaps(input) {
6120
6219
  let newestCommit = null;
6121
6220
  for (const [sessionId, byRepo] of workUnits) {
6122
6221
  for (const [repoPath, commits] of byRepo) {
6123
- const label = basename2(repoPath);
6222
+ const label = basename3(repoPath);
6124
6223
  if (scope !== null && !scope.includes(label)) continue;
6125
6224
  const times = commits.map((c) => c.at).sort((a, b) => a - b);
6126
6225
  const first = times[0] ?? null;
@@ -6279,7 +6378,7 @@ var ChildProcessRunner = class {
6279
6378
  if (killTimer !== null) clearTimeout(killTimer);
6280
6379
  options.signal?.removeEventListener("abort", onAbort);
6281
6380
  };
6282
- return new Promise((resolve2, reject) => {
6381
+ return new Promise((resolve3, reject) => {
6283
6382
  child.once("error", (error) => {
6284
6383
  if (settled) return;
6285
6384
  settled = true;
@@ -6291,7 +6390,7 @@ var ChildProcessRunner = class {
6291
6390
  settled = true;
6292
6391
  cleanup();
6293
6392
  const ended_at = /* @__PURE__ */ new Date();
6294
- resolve2({
6393
+ resolve3({
6295
6394
  command: snapshotCommand,
6296
6395
  args: snapshotArgs,
6297
6396
  cwd: snapshotCwd,
@@ -6446,28 +6545,28 @@ function serializeJsonSchema(schema) {
6446
6545
 
6447
6546
  // src/storage/basou-dir.ts
6448
6547
  import { lstat as lstat4, mkdir as mkdir4 } from "fs/promises";
6449
- import { join as join18 } from "path";
6548
+ import { join as join19 } from "path";
6450
6549
  function basouPaths(repositoryRoot) {
6451
- const root = join18(repositoryRoot, ".basou");
6452
- const approvalsBase = join18(root, "approvals");
6550
+ const root = join19(repositoryRoot, ".basou");
6551
+ const approvalsBase = join19(root, "approvals");
6453
6552
  return {
6454
6553
  root,
6455
- sessions: join18(root, "sessions"),
6456
- tasks: join18(root, "tasks"),
6554
+ sessions: join19(root, "sessions"),
6555
+ tasks: join19(root, "tasks"),
6457
6556
  approvals: {
6458
- pending: join18(approvalsBase, "pending"),
6459
- resolved: join18(approvalsBase, "resolved")
6557
+ pending: join19(approvalsBase, "pending"),
6558
+ resolved: join19(approvalsBase, "resolved")
6460
6559
  },
6461
- locks: join18(root, "locks"),
6462
- logs: join18(root, "logs"),
6463
- raw: join18(root, "raw"),
6464
- tmp: join18(root, "tmp"),
6560
+ locks: join19(root, "locks"),
6561
+ logs: join19(root, "logs"),
6562
+ raw: join19(root, "raw"),
6563
+ tmp: join19(root, "tmp"),
6465
6564
  files: {
6466
- manifest: join18(root, "manifest.yaml"),
6467
- status: join18(root, "status.json"),
6468
- handoff: join18(root, "handoff.md"),
6469
- decisions: join18(root, "decisions.md"),
6470
- orientation: join18(root, "orientation.md")
6565
+ manifest: join19(root, "manifest.yaml"),
6566
+ status: join19(root, "status.json"),
6567
+ handoff: join19(root, "handoff.md"),
6568
+ decisions: join19(root, "decisions.md"),
6569
+ orientation: join19(root, "orientation.md")
6471
6570
  }
6472
6571
  };
6473
6572
  }
@@ -6524,12 +6623,12 @@ function hasErrorCode4(error) {
6524
6623
 
6525
6624
  // src/storage/gitignore.ts
6526
6625
  import { readFile as readFile8, writeFile as writeFile2 } from "fs/promises";
6527
- import { join as join19 } from "path";
6626
+ import { join as join20 } from "path";
6528
6627
  var MARKER = "# Basou - default ignore";
6529
6628
  var BASOU_GITIGNORE_BLOCK = "# Basou - default ignore\n.basou/logs/\n.basou/raw/\n.basou/tmp/\n.basou/locks/\n.basou/status.json\n.basou/orientation.md\n.basou/sessions/*/events.jsonl\n.basou/sessions/*/artifacts/\n.basou/approvals/pending/\n.basou/approvals/resolved/\n\n# Basou - default commit\n# .basou/manifest.yaml\n# .basou/handoff.md\n# .basou/decisions.md\n# .basou/tasks/\n# .basou/sessions/*/session.yaml\n# .basou/sessions/*/transcript.md\n# .basou/sessions/*/changed-files.json\n";
6530
6629
  var BASOU_GITIGNORE_BLOCK_LOCAL_ONLY = "# Basou - default ignore\n# Local-only: basou's trail is never committed (personal/local state,\n# regenerable by re-importing from the agents' own logs). Recommended for\n# monitored repos and any workspace kept out of version control.\n.basou/\n";
6531
6630
  async function appendBasouGitignore(repositoryRoot, options = {}) {
6532
- const gitignorePath = join19(repositoryRoot, ".gitignore");
6631
+ const gitignorePath = join20(repositoryRoot, ".gitignore");
6533
6632
  let body;
6534
6633
  let existed;
6535
6634
  try {
@@ -6702,7 +6801,7 @@ function hasErrorCode6(error) {
6702
6801
  // src/storage/session-import.ts
6703
6802
  import { mkdir as mkdir5, readFile as readFile10, rm as rm2 } from "fs/promises";
6704
6803
  import { homedir as homedir3 } from "os";
6705
- import { join as join20 } from "path";
6804
+ import { join as join21 } from "path";
6706
6805
  async function importSessionFromJson(paths, manifest, payload, options) {
6707
6806
  if (options.taskIdOverride !== void 0 && !TaskIdSchema.safeParse(options.taskIdOverride).success) {
6708
6807
  throw new Error(`Invalid task_id: ${options.taskIdOverride}`);
@@ -6727,7 +6826,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
6727
6826
  pathSanitizeReport
6728
6827
  };
6729
6828
  }
6730
- const sessionDir = join20(paths.sessions, newSessionId);
6829
+ const sessionDir = join21(paths.sessions, newSessionId);
6731
6830
  try {
6732
6831
  await mkdir5(sessionDir, { recursive: true });
6733
6832
  } catch (error) {
@@ -6741,7 +6840,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
6741
6840
  throw error;
6742
6841
  }
6743
6842
  try {
6744
- const sessionYamlPath = join20(sessionDir, "session.yaml");
6843
+ const sessionYamlPath = join21(sessionDir, "session.yaml");
6745
6844
  await linkYamlFile(sessionYamlPath, withIntegrity(sessionRecord, chainResult));
6746
6845
  } catch (error) {
6747
6846
  await rm2(sessionDir, { recursive: true, force: true }).catch(() => void 0);
@@ -6909,7 +7008,7 @@ function reuseDerivedIds(priorDerived, freshDerived, sessionId) {
6909
7008
  async function reimportPreservingId(paths, manifest, priorSessionId, freshPayload, options = {}) {
6910
7009
  const sessionId = priorSessionId;
6911
7010
  const importSource = freshPayload.session.source.kind;
6912
- const sessionDir = join20(paths.sessions, priorSessionId);
7011
+ const sessionDir = join21(paths.sessions, priorSessionId);
6913
7012
  const lock = options.dryRun === true ? null : await acquireLock(paths, "session", priorSessionId);
6914
7013
  try {
6915
7014
  const priorVerdict = await verifyEventsChain(paths, priorSessionId);
@@ -6951,7 +7050,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
6951
7050
  };
6952
7051
  const updatedRecord = { schema_version: "0.1.0", session: preservedInner };
6953
7052
  if (options.dryRun !== true) {
6954
- const eventsPath = join20(sessionDir, "events.jsonl");
7053
+ const eventsPath = join21(sessionDir, "events.jsonl");
6955
7054
  let priorEventsRaw = null;
6956
7055
  try {
6957
7056
  priorEventsRaw = await readFile10(eventsPath);
@@ -6963,7 +7062,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
6963
7062
  const chainResult = await writeEventsBulk(sessionDir, mergedEvents, { chain: true });
6964
7063
  try {
6965
7064
  await overwriteYamlFile(
6966
- join20(sessionDir, "session.yaml"),
7065
+ join21(sessionDir, "session.yaml"),
6967
7066
  withIntegrity(updatedRecord, chainResult)
6968
7067
  );
6969
7068
  } catch (error) {
@@ -6987,7 +7086,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
6987
7086
  }
6988
7087
  }
6989
7088
  async function rechainSessionInPlace(paths, sessionId, options = {}) {
6990
- const sessionDir = join20(paths.sessions, sessionId);
7089
+ const sessionDir = join21(paths.sessions, sessionId);
6991
7090
  let lock;
6992
7091
  try {
6993
7092
  lock = await acquireLock(paths, "session", sessionId);
@@ -7020,7 +7119,7 @@ async function rechainSessionInPlace(paths, sessionId, options = {}) {
7020
7119
  if (verdict.status !== "unchained") {
7021
7120
  return { status: "skipped", reason: "tampered" };
7022
7121
  }
7023
- const eventsPath = join20(sessionDir, "events.jsonl");
7122
+ const eventsPath = join21(sessionDir, "events.jsonl");
7024
7123
  let priorRaw;
7025
7124
  try {
7026
7125
  priorRaw = await readFile10(eventsPath);
@@ -7068,7 +7167,7 @@ async function rechainSessionInPlace(paths, sessionId, options = {}) {
7068
7167
  }
7069
7168
  try {
7070
7169
  await overwriteYamlFile(
7071
- join20(sessionDir, "session.yaml"),
7170
+ join21(sessionDir, "session.yaml"),
7072
7171
  withIntegrity(record, { headHash: chainResult.headHash, count: chainResult.count })
7073
7172
  );
7074
7173
  } catch (error) {
@@ -7085,6 +7184,7 @@ async function rechainSessionInPlace(paths, sessionId, options = {}) {
7085
7184
  var BASOU_CORE_VERSION = "0.1.0";
7086
7185
  export {
7087
7186
  ACTIVE_GAP_CAP_MS,
7187
+ AGENT_INFRA_DIRS,
7088
7188
  ApprovalIdSchema,
7089
7189
  ApprovalSchema,
7090
7190
  ApprovalStatusSchema,
@@ -7135,6 +7235,7 @@ export {
7135
7235
  buildStatusSnapshot,
7136
7236
  chainEvents,
7137
7237
  chainRawJsonLines,
7238
+ classifyFilesBySourceRoot,
7138
7239
  classifySuspect,
7139
7240
  claudeCodeAdapterMetadata,
7140
7241
  claudeTranscriptToImportPayload,