@basou/core 0.35.1 → 0.36.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
@@ -579,6 +579,9 @@ declare const SessionImportPayloadSchema: z.ZodObject<{
579
579
  type: z.ZodLiteral<"review_recorded">;
580
580
  reviewer: z.ZodString;
581
581
  target: z.ZodString;
582
+ repos: z.ZodOptional<z.ZodArray<z.ZodString>>;
583
+ repos_resolved: z.ZodOptional<z.ZodArray<z.ZodString>>;
584
+ commits: z.ZodOptional<z.ZodArray<z.ZodString>>;
582
585
  verdict: z.ZodOptional<z.ZodEnum<{
583
586
  pass: "pass";
584
587
  "needs-attention": "needs-attention";
@@ -1325,6 +1328,9 @@ declare const ReviewRecordedEventSchema: z.ZodObject<{
1325
1328
  type: z.ZodLiteral<"review_recorded">;
1326
1329
  reviewer: z.ZodString;
1327
1330
  target: z.ZodString;
1331
+ repos: z.ZodOptional<z.ZodArray<z.ZodString>>;
1332
+ repos_resolved: z.ZodOptional<z.ZodArray<z.ZodString>>;
1333
+ commits: z.ZodOptional<z.ZodArray<z.ZodString>>;
1328
1334
  verdict: z.ZodOptional<z.ZodEnum<{
1329
1335
  pass: "pass";
1330
1336
  "needs-attention": "needs-attention";
@@ -1614,6 +1620,9 @@ declare const EventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1614
1620
  type: z.ZodLiteral<"review_recorded">;
1615
1621
  reviewer: z.ZodString;
1616
1622
  target: z.ZodString;
1623
+ repos: z.ZodOptional<z.ZodArray<z.ZodString>>;
1624
+ repos_resolved: z.ZodOptional<z.ZodArray<z.ZodString>>;
1625
+ commits: z.ZodOptional<z.ZodArray<z.ZodString>>;
1617
1626
  verdict: z.ZodOptional<z.ZodEnum<{
1618
1627
  pass: "pass";
1619
1628
  "needs-attention": "needs-attention";
@@ -5983,6 +5992,14 @@ declare function renderReport(input: ReportRendererInput): Promise<ReportRendere
5983
5992
  * - `unknown` the repo or time could not be derived; abstain rather than
5984
5993
  * guess (an abstention is never counted as a clear).
5985
5994
  *
5995
+ * A `review_recorded` event (written by `basou review record`) is a SELF-REPORT:
5996
+ * the agent's own claim that a review ran, with nothing corroborating it. Such a
5997
+ * record is bound to a unit by the repo paths it names and surfaced as a label,
5998
+ * but it NEVER changes that unit's verdict — a gap stays a gap, a candidate
5999
+ * stays a candidate — otherwise an empty record would become a way to make the
6000
+ * gap count go down, the same weakness the Stop-gate has. It re-labels; it does
6001
+ * not clear.
6002
+ *
5986
6003
  * It reads only captured provenance and writes nothing.
5987
6004
  */
5988
6005
  type ReviewGapVerdict = "omission" | "near_unbound" | "candidate" | "unknown";
@@ -5995,6 +6012,28 @@ type CitedReview = {
5995
6012
  files: string[];
5996
6013
  endedAt: string | null;
5997
6014
  };
6015
+ /**
6016
+ * A `review_recorded` self-report bound to a unit by the repo paths it named.
6017
+ * Carries no corroboration: it is what the agent said it did, not what the
6018
+ * capture observed.
6019
+ */
6020
+ type SelfReportedReview = {
6021
+ sessionId: string;
6022
+ eventId: string;
6023
+ reviewer: string;
6024
+ target: string;
6025
+ recordedAt: string;
6026
+ /** Commit SHAs the record claimed to cover; display only, never a binding key. */
6027
+ commits: string[];
6028
+ /**
6029
+ * The record was written after this unit's first commit, so it cannot have
6030
+ * gated the work. Surfaced rather than hidden — a claim made after the fact is
6031
+ * still the operator's own note about what happened, and the label can never
6032
+ * reduce the gap count — but kept distinguishable, because when a record was
6033
+ * written is part of what the operator is judging.
6034
+ */
6035
+ recordedAfterCommit: boolean;
6036
+ };
5998
6037
  /** One unit of work (a committing session's commits in one repo) and its verdict. */
5999
6038
  type ReviewGapUnit = {
6000
6039
  repo: string;
@@ -6006,6 +6045,33 @@ type ReviewGapUnit = {
6006
6045
  verdict: ReviewGapVerdict;
6007
6046
  /** For `candidate` / `near_unbound`: the review sessions considered. */
6008
6047
  reviews: CitedReview[];
6048
+ /**
6049
+ * `review_recorded` self-reports naming this repo in the window. Present on
6050
+ * every repo-keyed unit; it re-labels the unit and NEVER alters `verdict`, so
6051
+ * a self-reported gap is still a gap.
6052
+ */
6053
+ selfReports: SelfReportedReview[];
6054
+ };
6055
+ /** Recorded reviews that reached no unit of work, broken down by cause. */
6056
+ type UnattachedSelfReports = {
6057
+ total: number;
6058
+ /** The record named no repository at all. */
6059
+ noRepos: number;
6060
+ /**
6061
+ * At least one repository it named could not be verified as a repo root on
6062
+ * this machine. ANY unverifiable entry puts the record here, even alongside
6063
+ * one that resolved: a half-checkable claim is refused whole, so that
6064
+ * everything that does get paired was checkable in full.
6065
+ */
6066
+ unresolvableRepo: number;
6067
+ /** It named a resolvable repository, but no unit of work fell in the window. */
6068
+ noMatchingUnit: number;
6069
+ /**
6070
+ * Work WAS captured in the window, but the unit's own repository path could
6071
+ * not be verified, so the pairing could not be checked either way. Distinct
6072
+ * from {@link noMatchingUnit}, which would deny that the work exists.
6073
+ */
6074
+ unverifiableUnit: number;
6009
6075
  };
6010
6076
  type ReviewGapRepoSummary = {
6011
6077
  repo: string;
@@ -6014,6 +6080,8 @@ type ReviewGapRepoSummary = {
6014
6080
  nearUnboundUnits: number;
6015
6081
  candidateUnits: number;
6016
6082
  unknownUnits: number;
6083
+ /** Of the units with no bound trail, how many carry a self-report only. */
6084
+ selfReportedGapUnits: number;
6017
6085
  };
6018
6086
  type ReviewGapsSummary = {
6019
6087
  generatedAt: string;
@@ -6027,6 +6095,30 @@ type ReviewGapsSummary = {
6027
6095
  candidates: ReviewGapUnit[];
6028
6096
  /** Units whose repo/time could not be derived from the captured command; abstained, not cleared. */
6029
6097
  unknowns: ReviewGapUnit[];
6098
+ /**
6099
+ * Recorded reviews that changed nothing in this report — the answer to "I ran
6100
+ * `basou review record` and the omission is still there". Reported with the
6101
+ * reason for each, because basou must not assert a cause it has not
6102
+ * established; "no `repos` field" and "a `repos` that does not resolve" are
6103
+ * different mistakes with different fixes.
6104
+ *
6105
+ * Unlike {@link unknowns} this is NOT suppressed under a `--repo` scope, and
6106
+ * attachment is computed against every unit rather than the scoped ones. It is
6107
+ * a caveat about the tool's own input handling, not repo-dimensioned data, and
6108
+ * a completeness caveat that disappears under a filter is how silence starts
6109
+ * looking like success again — the very failure this surfacer exists to catch.
6110
+ */
6111
+ unattachedSelfReports: UnattachedSelfReports;
6112
+ /**
6113
+ * How many (record, unit) pairings fell inside a unit's window but could not
6114
+ * be checked, because that unit's own repository path was never verified.
6115
+ *
6116
+ * Counted per PAIRING, not per record, and reported even when the record
6117
+ * attached to some other unit: {@link unattachedSelfReports} only speaks for
6118
+ * records that changed nothing at all, so a record that landed once and was
6119
+ * refused elsewhere would otherwise leave the refusal invisible.
6120
+ */
6121
+ refusedPairings: number;
6030
6122
  /** Newest captured commit considered; commits not yet imported are invisible. */
6031
6123
  newestCommitAt: string | null;
6032
6124
  };
@@ -6057,6 +6149,38 @@ type ReviewGapsSummary = {
6057
6149
  * process lifetime.
6058
6150
  */
6059
6151
  declare function normalizeRepoPath(p: string | null | undefined): string | null;
6152
+ /** Why a hand-typed repository path cannot become a binding key. */
6153
+ type RepoPathProblem = "relative" | "absent" | "not_a_repo_root";
6154
+ /** A `repos` entry that cannot bind, and why. */
6155
+ type UnbindableRepo = {
6156
+ repo: string;
6157
+ index: number;
6158
+ problem: RepoPathProblem;
6159
+ };
6160
+ /**
6161
+ * Strict repo-root resolution for HAND-TYPED input (a record's `repos`), as
6162
+ * opposed to {@link normalizeRepoPath}, which reads paths basou itself captured.
6163
+ *
6164
+ * The difference is the string fallback. `normalizeRepoPath` keeps one for
6165
+ * captured data: a historical `cd` target whose repo has since moved is still
6166
+ * the best key available, and refusing it would lose an observation basou
6167
+ * genuinely made. Typed input has no such claim on the benefit of the doubt — a
6168
+ * relative path, a typo, or a subdirectory would mint a key that no commit can
6169
+ * ever match, and the record would then be accepted, stored, and silently
6170
+ * unbindable forever. So this verifies against the disk and returns null
6171
+ * otherwise.
6172
+ *
6173
+ * The asymmetry runs the safe way: everything this accepts, `normalizeRepoPath`
6174
+ * resolves to the same key, so a record the writer took is a record the reader
6175
+ * can bind.
6176
+ */
6177
+ declare function resolveRepoRoot(p: string | null | undefined): string | null;
6178
+ /**
6179
+ * The `repos` entries that could never bind to a unit of work, for the writer to
6180
+ * reject before the record is stored. Sharing {@link classifyRepoPath} with the
6181
+ * reader is the point: the writer must not accept a path the reader cannot use.
6182
+ */
6183
+ declare function findUnbindableRepos(repos: readonly string[]): UnbindableRepo[];
6060
6184
  /**
6061
6185
  * Short repo key (the final path segment) for DISPLAY and `--scope` matching.
6062
6186
  * Binding uses {@link normalizeRepoPath} to avoid basename collisions; this is
@@ -6103,6 +6227,19 @@ type ReviewRecordInput = {
6103
6227
  reviewer: string;
6104
6228
  /** What was reviewed (e.g. "working-tree", a git ref, "PR #145"). Required. */
6105
6229
  target: string;
6230
+ /**
6231
+ * Repository paths the review examined. Optional, but it is the ONLY thing
6232
+ * that can bind this record to the reviewed repo: the record lands in an
6233
+ * ad-hoc session whose location is the planning repo it was written from, not
6234
+ * the repo under review. Without it `review-gaps` cannot associate the record
6235
+ * with a unit of work.
6236
+ */
6237
+ repos?: string[];
6238
+ /**
6239
+ * Commit SHAs the review examined. Optional; recorded as the reviewer's own
6240
+ * claim about coverage.
6241
+ */
6242
+ commits?: string[];
6106
6243
  /** Overall outcome. Optional. */
6107
6244
  verdict?: "pass" | "needs-attention" | "fail";
6108
6245
  /** Findings surfaced by the review. Optional. */
@@ -6133,6 +6270,13 @@ declare function buildReviewRecordedEvent(input: {
6133
6270
  sessionId: PrefixedId<"ses">;
6134
6271
  occurredAt: string;
6135
6272
  review: ReviewRecordInput;
6273
+ /**
6274
+ * The canonical repository roots `review.repos` resolved to on this machine.
6275
+ * A CALLER-DERIVED value, not part of the piped input: what a path resolves to
6276
+ * is something basou observes, and the whole point of keeping it is that the
6277
+ * author's spelling may be a symlink whose target changes later.
6278
+ */
6279
+ reposResolved?: string[];
6136
6280
  }): Event;
6137
6281
  /** Ad-hoc session label for a recorded review: `Ad-hoc review: <reviewer> -> <target>`. */
6138
6282
  declare function buildReviewRecordLabel(review: ReviewRecordInput): string;
@@ -6722,4 +6866,4 @@ declare function overwriteYamlFile(filePath: string, value: unknown): Promise<vo
6722
6866
  */
6723
6867
  declare const BASOU_CORE_VERSION = "0.1.0";
6724
6868
 
6725
- export { ACTIVE_GAP_CAP_MS, AGENT_INFRA_DIRS, type ActiveTimeBasis, type AdapterOutputEvent, type AdoptCandidate, type AdoptCandidateKind, type AnchorStarterInput, type AnchorStarterRepo, 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 BuildStopHookCommandOptions, type BulkChainResult, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, type ChainBreakReason, type ChainTailState, type ChainVerdict, type ChainVerdictStatus, type ChainedEvents, ChildProcessRunner, type CitedReview, type ClaudeSettings, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexCommandLookup, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, DEFAULT_STOP_HOOK_MIN_EDITS, 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 IncompleteWiring, 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 MissingCanonical, type NoteAddedEvent, ORIENTATION_END, ORIENTATION_START, 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 PresetStrings, type ProcessRunner, type PublishKind, type PublishTarget, REVIEW_RECORD_NO_INPUT_HINT, 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 RepoInstructions, 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 RetrofitAction, type RetrofitAgentsState, type RetrofitFacts, type RetrofitPlan, type RetrofitReason, type ReviewBlocked, type ReviewFinding, type ReviewGapRepoSummary, type ReviewGapUnit, type ReviewGapVerdict, type ReviewGapsInput, type ReviewGapsSummary, type ReviewGateResult, type ReviewGateSilentReason, type ReviewRecordBlockedInput, type ReviewRecordFindingInput, type ReviewRecordInput, type ReviewRecordedEvent, type RiskLevel, RiskLevelSchema, type RosterAdoptionPlan, type RosterDriftSummary, type RunOptions, type RunResult, STOP_HOOK_TIMEOUT_SECONDS, 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 StopHookEvaluation, type StopHookEvaluationInput, type StopHookRemoval, type StopHookSilentReason, type StopHookUpsert, 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 ViewLanguage, type ViewLinkState, type ViewPresetInput, type ViewPresetRepo, type ViewRepoFact, type ViewStrayUnknown, type ViewStrings, type ViewWiringFacts, type WiringCollision, type WiringConflict, type WiringDriftSummary, 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, buildReviewRecordLabel, buildReviewRecordedEvent, buildStatusSnapshot, buildStopHookCommand, chainEvents, chainRawJsonLines, classifyFilesBySourceRoot, classifyRetrofit, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexAdapterMetadata, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, evaluateStopHook, finalizeSessionYaml, findBasouStopHookCommand, findErrorCode, findReviewGaps, formatDurationMs, genesisHash, getDiff, getSnapshot, importSessionFromJson, inspectChainTail, instructionMode, isBasouStopHookCommand, isGitNotFound, isImportDerivedSource, isLazyExpired, isRenderable, isValidPrefixedId, lineHash, linkYamlFile, loadApproval, loadFederatedSessionEntries, loadSessionEntries, loadTaskEntries, normalizeRepoKey, normalizeRepoPath, overwriteYamlFile, parseDuration, parseMarkers, parseReviewRecordInput, pathBasename, planArchive, planGitignore, planRename, planRosterAdoption, planWorkspaceView, prefixedUlid, presetStrings, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, rechainSessionInPlace, reconcileAllTasks, reconcileSourceRoots, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, removeMarkerSection, removeStopHook, renderAnchorStarter, renderDecisions, renderHandoff, renderOrientation, renderPresetBlock, renderReport, renderViewPresetBlock, renderWithMarkers, replayEvents, resolveAnchorContentLanguage, resolveBasouRepositoryRoot, resolveClaudeCodeCommand, resolveCodexCommand, resolveRepoContentLanguage, resolveRepositoryRoot, resolveSessionId, resolveTaskId, resolveViewLanguage, resolveViewLanguageFromPaths, safeSimpleGit, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, seedMarkers, serializeEventLine, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, summarizeOrientation, summarizePresetPlan, summarizeRosterDrift, summarizeSymlinkPlan, summarizeWiring, summarizeWiringDrift, tryRemoteUrl, ulid, unknownManifestKeys, updateTaskStatusWithEvent, upsertStopHook, verifyEventsChain, viewStrings, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
6869
+ export { ACTIVE_GAP_CAP_MS, AGENT_INFRA_DIRS, type ActiveTimeBasis, type AdapterOutputEvent, type AdoptCandidate, type AdoptCandidateKind, type AnchorStarterInput, type AnchorStarterRepo, 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 BuildStopHookCommandOptions, type BulkChainResult, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, type ChainBreakReason, type ChainTailState, type ChainVerdict, type ChainVerdictStatus, type ChainedEvents, ChildProcessRunner, type CitedReview, type ClaudeSettings, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexCommandLookup, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, DEFAULT_STOP_HOOK_MIN_EDITS, 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 IncompleteWiring, 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 MissingCanonical, type NoteAddedEvent, ORIENTATION_END, ORIENTATION_START, 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 PresetStrings, type ProcessRunner, type PublishKind, type PublishTarget, REVIEW_RECORD_NO_INPUT_HINT, 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 RepoInstructions, type RepoLanguage, type RepoPathProblem, 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 RetrofitAction, type RetrofitAgentsState, type RetrofitFacts, type RetrofitPlan, type RetrofitReason, type ReviewBlocked, type ReviewFinding, type ReviewGapRepoSummary, type ReviewGapUnit, type ReviewGapVerdict, type ReviewGapsInput, type ReviewGapsSummary, type ReviewGateResult, type ReviewGateSilentReason, type ReviewRecordBlockedInput, type ReviewRecordFindingInput, type ReviewRecordInput, type ReviewRecordedEvent, type RiskLevel, RiskLevelSchema, type RosterAdoptionPlan, type RosterDriftSummary, type RunOptions, type RunResult, STOP_HOOK_TIMEOUT_SECONDS, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type SelfReportedReview, 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 StopHookEvaluation, type StopHookEvaluationInput, type StopHookRemoval, type StopHookSilentReason, type StopHookUpsert, 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 UnattachedSelfReports, type UnbindableRepo, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type ViewCollision, type ViewConflict, type ViewLanguage, type ViewLinkState, type ViewPresetInput, type ViewPresetRepo, type ViewRepoFact, type ViewStrayUnknown, type ViewStrings, type ViewWiringFacts, type WiringCollision, type WiringConflict, type WiringDriftSummary, 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, buildReviewRecordLabel, buildReviewRecordedEvent, buildStatusSnapshot, buildStopHookCommand, chainEvents, chainRawJsonLines, classifyFilesBySourceRoot, classifyRetrofit, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexAdapterMetadata, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, evaluateStopHook, finalizeSessionYaml, findBasouStopHookCommand, findErrorCode, findReviewGaps, findUnbindableRepos, formatDurationMs, genesisHash, getDiff, getSnapshot, importSessionFromJson, inspectChainTail, instructionMode, isBasouStopHookCommand, isGitNotFound, isImportDerivedSource, isLazyExpired, isRenderable, isValidPrefixedId, lineHash, linkYamlFile, loadApproval, loadFederatedSessionEntries, loadSessionEntries, loadTaskEntries, normalizeRepoKey, normalizeRepoPath, overwriteYamlFile, parseDuration, parseMarkers, parseReviewRecordInput, pathBasename, planArchive, planGitignore, planRename, planRosterAdoption, planWorkspaceView, prefixedUlid, presetStrings, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, rechainSessionInPlace, reconcileAllTasks, reconcileSourceRoots, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, removeMarkerSection, removeStopHook, renderAnchorStarter, renderDecisions, renderHandoff, renderOrientation, renderPresetBlock, renderReport, renderViewPresetBlock, renderWithMarkers, replayEvents, resolveAnchorContentLanguage, resolveBasouRepositoryRoot, resolveClaudeCodeCommand, resolveCodexCommand, resolveRepoContentLanguage, resolveRepoRoot, resolveRepositoryRoot, resolveSessionId, resolveTaskId, resolveViewLanguage, resolveViewLanguageFromPaths, safeSimpleGit, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, seedMarkers, serializeEventLine, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, summarizeOrientation, summarizePresetPlan, summarizeRosterDrift, summarizeSymlinkPlan, summarizeWiring, summarizeWiringDrift, tryRemoteUrl, ulid, unknownManifestKeys, updateTaskStatusWithEvent, upsertStopHook, verifyEventsChain, viewStrings, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
package/dist/index.js CHANGED
@@ -1534,6 +1534,9 @@ var ReviewRecordedEventSchema = BaseEventSchema.extend({
1534
1534
  type: z3.literal("review_recorded"),
1535
1535
  reviewer: z3.string().min(1),
1536
1536
  target: z3.string().min(1),
1537
+ repos: z3.array(z3.string().min(1)).optional(),
1538
+ repos_resolved: z3.array(z3.string().min(1)).optional(),
1539
+ commits: z3.array(z3.string().min(1)).optional(),
1537
1540
  verdict: z3.enum(["pass", "needs-attention", "fail"]).optional(),
1538
1541
  findings: z3.array(ReviewFindingSchema).optional(),
1539
1542
  blocked: z3.array(ReviewBlockedSchema).optional()
@@ -7539,6 +7542,29 @@ function normalizeRepoPath(p) {
7539
7542
  if (/-workspace$/.test(seg) || seg.includes("$")) return null;
7540
7543
  return s;
7541
7544
  }
7545
+ function recordRepoKey(p) {
7546
+ return resolveRepoRoot(p);
7547
+ }
7548
+ function resolveRepoRoot(p) {
7549
+ return classifyRepoPath(p).resolved;
7550
+ }
7551
+ function classifyRepoPath(p) {
7552
+ let s = stripQuotes((p ?? "").trim()).replace(/\/+$/, "");
7553
+ if (s.startsWith("~/")) s = homedir2() + s.slice(1);
7554
+ if (s.length === 0 || !isAbsolute2(s)) return { resolved: null, problem: "relative" };
7555
+ const real = resolveRealpath(s);
7556
+ if (real === null) return { resolved: null, problem: "absent" };
7557
+ if (!isRepoRoot(real)) return { resolved: null, problem: "not_a_repo_root" };
7558
+ return { resolved: real, problem: null };
7559
+ }
7560
+ function findUnbindableRepos(repos) {
7561
+ const out = [];
7562
+ repos.forEach((repo, index) => {
7563
+ const { problem } = classifyRepoPath(repo);
7564
+ if (problem !== null) out.push({ repo, index, problem });
7565
+ });
7566
+ return out;
7567
+ }
7542
7568
  function normalizeRepoKey(p) {
7543
7569
  const full = normalizeRepoPath(p);
7544
7570
  return full === null ? null : basename3(full);
@@ -7560,10 +7586,16 @@ function inspectCommand(args) {
7560
7586
  }
7561
7587
  return { files: [...files], examinedDiff };
7562
7588
  }
7563
- function commandRepo(args, cwd) {
7589
+ function commandRepoWithProvenance(args, cwd) {
7590
+ const raw = commandRepoPath(args, cwd);
7591
+ return { key: normalizeRepoPath(raw), resolved: resolveRepoRoot(raw) !== null };
7592
+ }
7593
+ function commandRepoPath(args, cwd) {
7564
7594
  const cd = args.join(" ").match(/\bcd\s+("[^"]+"|'[^']+'|[^\s&]+)\s*&&/);
7565
- if (cd) return normalizeRepoPath(cd[1]);
7566
- return normalizeRepoPath(cwd);
7595
+ return cd?.[1] ?? cwd;
7596
+ }
7597
+ function commandRepo(args, cwd) {
7598
+ return normalizeRepoPath(commandRepoPath(args, cwd));
7567
7599
  }
7568
7600
  function commandFailed(exitCode) {
7569
7601
  return exitCode !== null && exitCode !== 0;
@@ -7585,6 +7617,9 @@ async function findReviewGaps(input) {
7585
7617
  if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
7586
7618
  const entries = await loadSessionEntries(input.paths, loadOpts);
7587
7619
  const reviews = [];
7620
+ const selfReports = [];
7621
+ let noRepos = 0;
7622
+ let unresolvableRepo = 0;
7588
7623
  const workUnits = /* @__PURE__ */ new Map();
7589
7624
  const unknownCommits = /* @__PURE__ */ new Map();
7590
7625
  for (const entry of entries) {
@@ -7596,6 +7631,28 @@ async function findReviewGaps(input) {
7596
7631
  for await (const ev of replayEvents(sessionDir, {
7597
7632
  onWarning: (w) => input.onWarning?.(w, entry.sessionId)
7598
7633
  })) {
7634
+ if (ev.type === "review_recorded") {
7635
+ const recordedAt = Date.parse(ev.occurred_at);
7636
+ const named = ev.repos_resolved !== void 0 && ev.repos_resolved.length > 0 ? ev.repos_resolved : ev.repos ?? [];
7637
+ const keys = named.map((r) => recordRepoKey(r));
7638
+ const repos2 = new Set(keys.filter((r) => r !== null));
7639
+ if (keys.some((k) => k === null) || repos2.size === 0 || Number.isNaN(recordedAt)) {
7640
+ if (named.length === 0) noRepos++;
7641
+ else unresolvableRepo++;
7642
+ continue;
7643
+ }
7644
+ selfReports.push({
7645
+ sessionId: entry.sessionId,
7646
+ eventId: ev.id,
7647
+ reviewer: ev.reviewer,
7648
+ target: ev.target,
7649
+ recordedAt: ev.occurred_at,
7650
+ commits: ev.commits ?? [],
7651
+ at: recordedAt,
7652
+ repos: repos2
7653
+ });
7654
+ continue;
7655
+ }
7599
7656
  if (ev.type !== "command_executed") continue;
7600
7657
  if (commandFailed(ev.exit_code)) continue;
7601
7658
  const at = Date.parse(ev.occurred_at);
@@ -7611,7 +7668,7 @@ async function findReviewGaps(input) {
7611
7668
  continue;
7612
7669
  }
7613
7670
  if (!ev.args.join(" ").includes("git commit")) continue;
7614
- const repo = commandRepo(ev.args, ev.cwd);
7671
+ const { key: repo, resolved: keyResolved } = commandRepoWithProvenance(ev.args, ev.cwd);
7615
7672
  if (repo === null || Number.isNaN(at)) {
7616
7673
  const list2 = unknownCommits.get(entry.sessionId) ?? [];
7617
7674
  list2.push(Number.isNaN(at) ? null : at);
@@ -7620,7 +7677,7 @@ async function findReviewGaps(input) {
7620
7677
  }
7621
7678
  const byRepo = workUnits.get(entry.sessionId) ?? /* @__PURE__ */ new Map();
7622
7679
  const list = byRepo.get(repo) ?? [];
7623
- list.push({ repo, at, files: commitFiles(ev.args) });
7680
+ list.push({ repo, at, files: commitFiles(ev.args), keyResolved });
7624
7681
  byRepo.set(repo, list);
7625
7682
  workUnits.set(entry.sessionId, byRepo);
7626
7683
  }
@@ -7635,19 +7692,33 @@ async function findReviewGaps(input) {
7635
7692
  const windowMs = windowHours * 3600 * 1e3;
7636
7693
  const units = [];
7637
7694
  let newestCommit = null;
7695
+ const attachedSelfReports = /* @__PURE__ */ new Set();
7696
+ const refusedForUnit = /* @__PURE__ */ new Set();
7697
+ let refusedPairings = 0;
7638
7698
  for (const [sessionId, byRepo] of workUnits) {
7639
7699
  for (const [repoPath, commits] of byRepo) {
7640
7700
  const label = basename3(repoPath);
7641
- if (scope !== null && !scope.includes(label)) continue;
7642
7701
  const times = commits.map((c) => c.at).sort((a, b) => a - b);
7643
7702
  const first = times[0] ?? null;
7644
7703
  const last = times[times.length - 1] ?? null;
7704
+ const earliest = first ?? last ?? 0;
7705
+ const latest = last ?? first ?? 0;
7706
+ const unitRepoIsHere = commits.every((c) => c.keyResolved);
7707
+ const inWindow = selfReports.filter(
7708
+ (r) => r.repos.has(repoPath) && r.at >= earliest - windowMs && r.at <= latest + windowMs
7709
+ );
7710
+ const selfBound = unitRepoIsHere ? inWindow : [];
7711
+ if (!unitRepoIsHere) {
7712
+ refusedPairings += inWindow.length;
7713
+ for (const r of inWindow) refusedForUnit.add(r.eventId);
7714
+ }
7715
+ for (const r of selfBound) attachedSelfReports.add(r.eventId);
7716
+ if (scope !== null && !scope.includes(label)) continue;
7645
7717
  if (last !== null) newestCommit = newestCommit === null ? last : Math.max(newestCommit, last);
7646
7718
  const changedFiles = new Set(commits.flatMap((c) => c.files));
7647
- const before = first ?? last ?? 0;
7648
7719
  const nearby = reviews.filter((r) => {
7649
7720
  if (!r.repos.has(repoPath) || r.endedAt === null) return false;
7650
- return r.endedAt <= before && r.endedAt >= before - windowMs;
7721
+ return r.endedAt <= earliest && r.endedAt >= earliest - windowMs;
7651
7722
  });
7652
7723
  const bound = nearby.filter((r) => {
7653
7724
  const touched = r.repos.get(repoPath);
@@ -7665,6 +7736,9 @@ async function findReviewGaps(input) {
7665
7736
  firstCommitAt: first === null ? null : new Date(first).toISOString(),
7666
7737
  lastCommitAt: last === null ? null : new Date(last).toISOString(),
7667
7738
  verdict,
7739
+ // Attached after the verdict is computed, and deliberately not an input
7740
+ // to it: a record must never move a unit out of `gaps`.
7741
+ selfReports: selfBound.map((r) => toSelfReportedReview(r, r.at > earliest)),
7668
7742
  reviews: cited.map((r) => ({
7669
7743
  sessionId: r.sessionId,
7670
7744
  examinedDiff: r.repos.get(repoPath)?.examinedDiff ?? false,
@@ -7674,34 +7748,41 @@ async function findReviewGaps(input) {
7674
7748
  });
7675
7749
  }
7676
7750
  }
7677
- if (scope === null) {
7678
- for (const [sessionId, times] of unknownCommits) {
7679
- const valid = times.filter((t) => t !== null).sort((a, b) => a - b);
7680
- const first = valid[0] ?? null;
7681
- const last = valid[valid.length - 1] ?? null;
7682
- if (last !== null) newestCommit = newestCommit === null ? last : Math.max(newestCommit, last);
7683
- units.push({
7684
- repo: "(unknown)",
7685
- sessionId,
7686
- commitCount: times.length,
7687
- firstCommitAt: first === null ? null : new Date(first).toISOString(),
7688
- lastCommitAt: last === null ? null : new Date(last).toISOString(),
7689
- verdict: "unknown",
7690
- reviews: []
7691
- });
7751
+ for (const [sessionId, times] of unknownCommits) {
7752
+ const valid = times.filter((t) => t !== null).sort((a, b) => a - b);
7753
+ const first = valid[0] ?? null;
7754
+ const last = valid[valid.length - 1] ?? null;
7755
+ if (last !== null && scope === null) {
7756
+ newestCommit = newestCommit === null ? last : Math.max(newestCommit, last);
7692
7757
  }
7758
+ units.push({
7759
+ repo: "(unknown)",
7760
+ sessionId,
7761
+ commitCount: times.length,
7762
+ firstCommitAt: first === null ? null : new Date(first).toISOString(),
7763
+ lastCommitAt: last === null ? null : new Date(last).toISOString(),
7764
+ verdict: "unknown",
7765
+ reviews: [],
7766
+ // No repo key, so nothing a record's `repos` could bind to.
7767
+ selfReports: []
7768
+ });
7693
7769
  }
7770
+ const missed = selfReports.filter((r) => !attachedSelfReports.has(r.eventId));
7771
+ const unverifiableUnit = missed.filter((r) => refusedForUnit.has(r.eventId)).length;
7772
+ const noMatchingUnit = missed.length - unverifiableUnit;
7694
7773
  const recentFirst = (a, b) => (Date.parse(b.lastCommitAt ?? "") || 0) - (Date.parse(a.lastCommitAt ?? "") || 0);
7695
- const repoKeys = [...new Set(units.map((u) => u.repo))].sort();
7774
+ const talliedUnits = scope === null ? units : units.filter((u) => u.verdict !== "unknown");
7775
+ const repoKeys = [...new Set(talliedUnits.map((u) => u.repo))].sort();
7696
7776
  const repos = repoKeys.map((repo) => {
7697
- const us = units.filter((u) => u.repo === repo);
7777
+ const us = talliedUnits.filter((u) => u.repo === repo);
7698
7778
  return {
7699
7779
  repo,
7700
7780
  units: us.length,
7701
7781
  omissionUnits: us.filter((u) => u.verdict === "omission").length,
7702
7782
  nearUnboundUnits: us.filter((u) => u.verdict === "near_unbound").length,
7703
7783
  candidateUnits: us.filter((u) => u.verdict === "candidate").length,
7704
- unknownUnits: us.filter((u) => u.verdict === "unknown").length
7784
+ unknownUnits: us.filter((u) => u.verdict === "unknown").length,
7785
+ selfReportedGapUnits: us.filter((u) => isGap(u) && u.selfReports.length > 0).length
7705
7786
  };
7706
7787
  });
7707
7788
  return {
@@ -7709,12 +7790,34 @@ async function findReviewGaps(input) {
7709
7790
  windowHours,
7710
7791
  scope,
7711
7792
  repos,
7712
- gaps: units.filter((u) => u.verdict === "omission" || u.verdict === "near_unbound").sort(recentFirst),
7793
+ gaps: units.filter(isGap).sort(recentFirst),
7713
7794
  candidates: units.filter((u) => u.verdict === "candidate").sort(recentFirst),
7714
7795
  unknowns: units.filter((u) => u.verdict === "unknown").sort(recentFirst),
7796
+ unattachedSelfReports: {
7797
+ total: noRepos + unresolvableRepo + noMatchingUnit + unverifiableUnit,
7798
+ noRepos,
7799
+ unresolvableRepo,
7800
+ noMatchingUnit,
7801
+ unverifiableUnit
7802
+ },
7803
+ refusedPairings,
7715
7804
  newestCommitAt: newestCommit === null ? null : new Date(newestCommit).toISOString()
7716
7805
  };
7717
7806
  }
7807
+ function isGap(u) {
7808
+ return u.verdict === "omission" || u.verdict === "near_unbound";
7809
+ }
7810
+ function toSelfReportedReview(r, recordedAfterCommit) {
7811
+ return {
7812
+ sessionId: r.sessionId,
7813
+ eventId: r.eventId,
7814
+ reviewer: r.reviewer,
7815
+ target: r.target,
7816
+ recordedAt: r.recordedAt,
7817
+ commits: r.commits,
7818
+ recordedAfterCommit
7819
+ };
7820
+ }
7718
7821
 
7719
7822
  // src/review/review-record.ts
7720
7823
  var VALID_VERDICTS = /* @__PURE__ */ new Set(["pass", "needs-attention", "fail"]);
@@ -7723,6 +7826,8 @@ var VALID_BLOCK_REASONS = /* @__PURE__ */ new Set(["spec-deviation", "design-rev
7723
7826
  var ALLOWED_KEYS = /* @__PURE__ */ new Set([
7724
7827
  "reviewer",
7725
7828
  "target",
7829
+ "repos",
7830
+ "commits",
7726
7831
  "verdict",
7727
7832
  "findings",
7728
7833
  "blocked"
@@ -7753,13 +7858,19 @@ function parseReviewRecordInput(raw) {
7753
7858
  for (const key of Object.keys(obj)) {
7754
7859
  if (!ALLOWED_KEYS.has(key)) {
7755
7860
  throw new Error(
7756
- `Unknown field '${key}'. Allowed: reviewer, target, verdict, findings, blocked.`
7861
+ `Unknown field '${key}'. Allowed: reviewer, target, repos, commits, verdict, findings, blocked.`
7757
7862
  );
7758
7863
  }
7759
7864
  }
7760
7865
  const reviewer = requireNonEmptyString(obj.reviewer, "reviewer");
7761
7866
  const target = requireNonEmptyString(obj.target, "target");
7762
7867
  const out = { reviewer, target };
7868
+ if (obj.repos !== void 0) {
7869
+ out.repos = parseStringArray(obj.repos, "repos");
7870
+ }
7871
+ if (obj.commits !== void 0) {
7872
+ out.commits = parseStringArray(obj.commits, "commits");
7873
+ }
7763
7874
  if (obj.verdict !== void 0) {
7764
7875
  if (typeof obj.verdict !== "string" || !VALID_VERDICTS.has(obj.verdict)) {
7765
7876
  throw new Error(`verdict must be one of pass, needs-attention, fail, got '${obj.verdict}'.`);
@@ -7774,6 +7885,12 @@ function parseReviewRecordInput(raw) {
7774
7885
  }
7775
7886
  return out;
7776
7887
  }
7888
+ function parseStringArray(value, field) {
7889
+ if (!Array.isArray(value)) {
7890
+ throw new Error(`${field} must be an array of strings.`);
7891
+ }
7892
+ return value.map((item, i) => requireNonEmptyString(item, `${field}[${i}]`));
7893
+ }
7777
7894
  function parseFindings(value) {
7778
7895
  if (!Array.isArray(value)) {
7779
7896
  throw new Error("findings must be an array of objects.");
@@ -7856,6 +7973,9 @@ function buildReviewRecordedEvent(input) {
7856
7973
  type: "review_recorded",
7857
7974
  reviewer: review.reviewer,
7858
7975
  target: review.target,
7976
+ ...review.repos !== void 0 ? { repos: review.repos } : {},
7977
+ ...input.reposResolved !== void 0 && input.reposResolved.length > 0 ? { repos_resolved: input.reposResolved } : {},
7978
+ ...review.commits !== void 0 ? { commits: review.commits } : {},
7859
7979
  ...review.verdict !== void 0 ? { verdict: review.verdict } : {},
7860
7980
  ...review.findings !== void 0 ? { findings: review.findings } : {},
7861
7981
  ...review.blocked !== void 0 ? { blocked: review.blocked } : {}
@@ -8866,6 +8986,7 @@ export {
8866
8986
  findBasouStopHookCommand,
8867
8987
  findErrorCode,
8868
8988
  findReviewGaps,
8989
+ findUnbindableRepos,
8869
8990
  formatDurationMs,
8870
8991
  genesisHash,
8871
8992
  getDiff,
@@ -8929,6 +9050,7 @@ export {
8929
9050
  resolveClaudeCodeCommand,
8930
9051
  resolveCodexCommand,
8931
9052
  resolveRepoContentLanguage,
9053
+ resolveRepoRoot,
8932
9054
  resolveRepositoryRoot,
8933
9055
  resolveSessionId,
8934
9056
  resolveTaskId,