@basou/sdk 0.44.0 → 0.45.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
@@ -1,6 +1,34 @@
1
1
  import { Manifest, StatusSnapshot, SessionEntry, Event, TaskDocument, LoadedApproval, WorkStatsResult } from '@basou/core';
2
2
  export { ActiveTimeBasis, Approval, ApprovalStatus, CommandExecutedEvent, DayWorkStats, DecisionRecordedEvent, Event, FileChangedEvent, LoadedApproval, Manifest, MeasureAvailability, NoteAddedEvent, RiskLevel, Session, SessionEndedEvent, SessionEntry, SessionMetrics, SessionSourceKind, SessionStartedEvent, SessionStatus, SessionStatusChangedEvent, SessionWorkStats, SourceWorkStats, StatusCount, StatusSnapshot, SuspectReason, Task, TaskDocument, TaskStatus, TokenTotals, WorkStatsResult, WorkStatsTotals, readObservedDuration } from '@basou/core';
3
3
 
4
+ /** What a build knows about itself. `commit` is `"unknown"` outside a checkout. */
5
+ type BuildStamp = {
6
+ readonly version: string;
7
+ readonly commit: string;
8
+ readonly committedAt: string;
9
+ };
10
+ /**
11
+ * Parse an injected stamp. Separate from the constant below so it is reachable
12
+ * from a test: under vitest the module loads from SOURCE, where the injected
13
+ * identifier does not exist, so every line of the parse would otherwise be
14
+ * unreachable -- a guarantee with no test behind it, which is the shape of
15
+ * omission this whole feature exists to correct.
16
+ *
17
+ * Anything unparseable yields `undefined` rather than throwing: a malformed
18
+ * stamp must not stop the CLI from starting.
19
+ */
20
+ declare function parseBuildStamp(raw: string | undefined): BuildStamp | undefined;
21
+ /**
22
+ * The SDK's own build identity.
23
+ *
24
+ * The SDK is a semver-guaranteed surface, and a consumer embedding it has no
25
+ * `basou --version` to fall back on: this is the only way for them to say
26
+ * which build they are running. It is stamped separately from `@basou/core`
27
+ * for the same reason the CLI is -- they are distinct artifacts, and a partial
28
+ * build can leave them at different commits.
29
+ */
30
+ declare const BASOU_SDK_BUILD: BuildStamp | undefined;
31
+
4
32
  /**
5
33
  * Base class for every error the SDK throws on its own behalf. Errors that
6
34
  * originate in `@basou/core` (e.g. a malformed `session.yaml`) propagate as-is;
@@ -181,4 +209,4 @@ declare function openWorkspace(repoRoot: string, options?: WorkspaceOptions): Pr
181
209
  */
182
210
  declare const BASOU_SDK_VERSION = "0.4.0";
183
211
 
184
- export { AmbiguousIdError, BASOU_SDK_VERSION, BasouSdkError, type ReportOptions, type StatsOptions, type Workspace, type WorkspaceDiagnostic, WorkspaceNotFoundError, type WorkspaceOptions, openWorkspace, resolveWorkspaceRoot };
212
+ export { AmbiguousIdError, BASOU_SDK_BUILD, BASOU_SDK_VERSION, BasouSdkError, type BuildStamp, type ReportOptions, type StatsOptions, type Workspace, type WorkspaceDiagnostic, WorkspaceNotFoundError, type WorkspaceOptions, openWorkspace, parseBuildStamp, resolveWorkspaceRoot };
package/dist/index.js CHANGED
@@ -1,3 +1,20 @@
1
+ // src/build-stamp.ts
2
+ function parseBuildStamp(raw) {
3
+ if (typeof raw !== "string") return void 0;
4
+ try {
5
+ const parsed = JSON.parse(raw);
6
+ if (typeof parsed.version !== "string" || typeof parsed.commit !== "string" || typeof parsed.committedAt !== "string") {
7
+ return void 0;
8
+ }
9
+ return { version: parsed.version, commit: parsed.commit, committedAt: parsed.committedAt };
10
+ } catch {
11
+ return void 0;
12
+ }
13
+ }
14
+ var BASOU_SDK_BUILD = parseBuildStamp(
15
+ true ? '{"version":"0.45.0","commit":"6b04208","committedAt":"2026-09-19T01:07:28+09:00"}' : void 0
16
+ );
17
+
1
18
  // src/index.ts
2
19
  import { readObservedDuration } from "@basou/core";
3
20
 
@@ -181,10 +198,12 @@ async function resolveOrNull(resolver, input) {
181
198
  var BASOU_SDK_VERSION = "0.4.0";
182
199
  export {
183
200
  AmbiguousIdError,
201
+ BASOU_SDK_BUILD,
184
202
  BASOU_SDK_VERSION,
185
203
  BasouSdkError,
186
204
  WorkspaceNotFoundError,
187
205
  openWorkspace,
206
+ parseBuildStamp,
188
207
  readObservedDuration,
189
208
  resolveWorkspaceRoot
190
209
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/workspace.ts"],"sourcesContent":["/**\n * `@basou/sdk` — the stable, read-only programmatic API for reading a Basou\n * workspace's provenance (`.basou/`). It is a thin, ergonomic facade over\n * `@basou/core`'s readers: open a workspace once and query sessions, events,\n * tasks, approvals, status, stats, and the rendered handoff / decisions. No\n * writers are exposed — third-party tooling can read provenance without any\n * risk of mutating it.\n *\n * @example\n * ```ts\n * import { openWorkspace, resolveWorkspaceRoot } from \"@basou/sdk\";\n *\n * const root = await resolveWorkspaceRoot(process.cwd()); // or pass a known root\n * const ws = await openWorkspace(root);\n * for (const { session, suspect } of await ws.listSessions()) {\n * console.log(session.session.id, session.session.status, suspect);\n * }\n * const stats = await ws.stats();\n * console.log(stats.totals.billableActiveTimeMs);\n * ```\n */\n\n/**\n * SDK API version, tracking the Basou SDK surface (not the npm package\n * version, which moves in lockstep with the monorepo). `0.2.0` was the first\n * release with a runtime read API; `0.3.0` adds `Workspace.renderReport`;\n * `0.4.0` re-exports `readObservedDuration` and carries the `duration_ms`\n * nullability through the re-exported `CommandExecutedEvent`; `0.1.0` was\n * types-only.\n */\nexport const BASOU_SDK_VERSION = \"0.4.0\";\n\n// Read types re-exported from @basou/core so consumers can type the values the\n// SDK returns without depending on @basou/core directly. These track the\n// on-disk provenance schema.\nexport type {\n ActiveTimeBasis,\n Approval,\n ApprovalStatus,\n CommandExecutedEvent,\n DayWorkStats,\n DecisionRecordedEvent,\n Event,\n FileChangedEvent,\n LoadedApproval,\n Manifest,\n MeasureAvailability,\n NoteAddedEvent,\n RiskLevel,\n Session,\n SessionEndedEvent,\n SessionEntry,\n SessionMetrics,\n SessionSourceKind,\n SessionStartedEvent,\n SessionStatus,\n SessionStatusChangedEvent,\n SessionWorkStats,\n SourceWorkStats,\n StatusCount,\n StatusSnapshot,\n SuspectReason,\n Task,\n TaskDocument,\n TaskStatus,\n TokenTotals,\n WorkStatsResult,\n WorkStatsTotals,\n} from \"@basou/core\";\n/**\n * The read rule for `command_executed.duration_ms`, re-exported from\n * `@basou/core` so a consumer of this facade can apply it without depending on\n * core directly. The field is `number | null` and a stored `0` also means \"not\n * observed\", so reading it off the event is wrong on both counts; this returns\n * the duration that was actually observed, or null.\n */\nexport { readObservedDuration } from \"@basou/core\";\nexport { AmbiguousIdError, BasouSdkError, WorkspaceNotFoundError } from \"./errors.js\";\nexport {\n openWorkspace,\n type ReportOptions,\n resolveWorkspaceRoot,\n type StatsOptions,\n type Workspace,\n type WorkspaceDiagnostic,\n type WorkspaceOptions,\n} from \"./workspace.js\";\n","/**\n * Base class for every error the SDK throws on its own behalf. Errors that\n * originate in `@basou/core` (e.g. a malformed `session.yaml`) propagate as-is;\n * only the SDK's own preconditions are wrapped, so `instanceof BasouSdkError`\n * identifies \"the SDK rejected this call\" rather than \"the data was bad\".\n */\nexport class BasouSdkError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\n/**\n * `openWorkspace` was pointed at a path that is not a usable Basou workspace:\n * the `.basou/` directory is missing, is a symlink, or is otherwise not a\n * directory. The offending repository root is on {@link root}.\n */\nexport class WorkspaceNotFoundError extends BasouSdkError {\n readonly root: string;\n constructor(root: string, options?: { cause?: unknown }) {\n super(\n `No Basou workspace at ${root}: expected a '.basou/' directory (run 'basou init' there first).`,\n options,\n );\n this.root = root;\n }\n}\n\n/**\n * A session / task id prefix matched more than one record. The {@link input}\n * is the prefix as given; the caller should retry with a longer one. (A prefix\n * that matches nothing is NOT an error — the lookup returns `null` instead.)\n */\nexport class AmbiguousIdError extends BasouSdkError {\n readonly input: string;\n constructor(input: string, options?: { cause?: unknown }) {\n super(`Ambiguous id '${input}': matched more than one record; use a longer prefix.`, options);\n this.input = input;\n }\n}\n","import { join, resolve } from \"node:path\";\nimport {\n assertBasouRootSafe,\n basouPaths,\n buildStatusSnapshot,\n computeWorkStats,\n type Event,\n enumerateApprovals,\n type LoadedApproval,\n loadApproval,\n loadSessionEntries,\n loadTaskEntries,\n type Manifest,\n readAllEvents,\n readManifest,\n readTaskFileWithArchiveFallback,\n renderDecisions,\n renderHandoff,\n renderReport,\n replayEvents,\n resolveRepositoryRoot,\n resolveSessionId,\n resolveTaskId,\n type SessionEntry,\n type StatusSnapshot,\n type TaskDocument,\n type WorkStatsResult,\n} from \"@basou/core\";\nimport { AmbiguousIdError, WorkspaceNotFoundError } from \"./errors.js\";\n\n/**\n * A degradation the SDK noticed while reading provenance: a malformed event\n * line, or a session / task that could not be loaded. Best-effort reads skip\n * these and keep going; pass `onDiagnostic` to {@link openWorkspace} to observe\n * them. `message` is a human-readable summary (it folds in the core\n * `ReplayWarning.kind` or skip-reason); structured fields are intentionally not\n * part of this stable shape.\n */\nexport type WorkspaceDiagnostic = {\n /** Human-readable summary of the malformed line / skipped record. */\n message: string;\n /** Session or task id the diagnostic relates to, when known. */\n id?: string;\n};\n\n/** Options for {@link openWorkspace}; all optional. */\nexport type WorkspaceOptions = {\n /**\n * Clock used for time-sensitive reads (session \"suspect\" classification,\n * stats span-to-now, status / approval expiry). Injectable for deterministic\n * callers and tests. Defaults to `() => new Date()`, evaluated per call.\n */\n now?: () => Date;\n /**\n * Observe a malformed event line or a skipped session / task instead of it\n * being silently dropped. Reads are still best-effort: a diagnostic does not\n * fail the call.\n */\n onDiagnostic?: (diagnostic: WorkspaceDiagnostic) => void;\n};\n\n/** Options for {@link Workspace.stats}. */\nexport type StatsOptions = {\n /**\n * IANA timezone used to bucket the per-day breakdown (native logs are UTC).\n * Defaults to the host's local zone.\n */\n timeZone?: string;\n};\n\n/**\n * A read-only handle on one Basou workspace (`<root>/.basou/`). Every method\n * reads provenance from disk; the SDK exposes no writers. Obtain one with\n * {@link openWorkspace}.\n *\n * Session / task lookups (`getSession`, `getTask`, `readEvents`,\n * `streamEvents`) accept a full id or a unique prefix: a prefix matching\n * nothing yields `null` (or an empty stream), a prefix matching more than one\n * record throws {@link AmbiguousIdError}. `getApproval` takes an exact id only.\n */\nexport interface Workspace {\n /** Absolute repository root this workspace was opened at. */\n readonly root: string;\n\n /** Parsed `manifest.yaml`. */\n manifest(): Promise<Manifest>;\n /** A freshly computed workspace status snapshot (directory presence + manifest). */\n status(): Promise<StatusSnapshot>;\n\n /** Every session, ULID-ascending, each with its `suspect` classification. */\n listSessions(): Promise<SessionEntry[]>;\n /** One session by id / unique prefix, or `null` if no session matches. */\n getSession(idOrPrefix: string): Promise<SessionEntry | null>;\n /** All events of a session, eagerly, ordered as written. Empty if no match. */\n readEvents(idOrPrefix: string): Promise<Event[]>;\n /** All events of a session as a lazy stream (for large logs). */\n streamEvents(idOrPrefix: string): AsyncIterable<Event>;\n\n /** Every task (active + lazily-indexed), created-at ascending. */\n listTasks(): Promise<TaskDocument[]>;\n /** One task by id / unique prefix (archived included), or `null`. */\n getTask(idOrPrefix: string): Promise<TaskDocument | null>;\n\n /** Pending + resolved approvals, fully loaded. */\n listApprovals(): Promise<{ pending: LoadedApproval[]; resolved: LoadedApproval[] }>;\n /** One approval by exact id (resolved checked first), or `null`. */\n getApproval(id: string): Promise<LoadedApproval | null>;\n\n /** Aggregated work / time / token stats across the workspace's sessions. */\n stats(options?: StatsOptions): Promise<WorkStatsResult>;\n\n /** The rendered `handoff.md` body (recomputed, without generated markers). */\n renderHandoff(): Promise<string>;\n /** The rendered `decisions.md` body (recomputed, without generated markers). */\n renderDecisions(): Promise<string>;\n /**\n * A rendered work report — a point-in-time markdown export explaining the\n * work captured in this workspace (volume, decisions, approvals, tasks,\n * changed files, and the local provenance integrity verdicts). Read-only,\n * markerless. The CLI's `--json` structured shape is a CLI concern; the SDK\n * facade returns the markdown body, mirroring `renderHandoff` / `renderDecisions`.\n */\n renderReport(options?: ReportOptions): Promise<string>;\n}\n\n/** Options for {@link Workspace.renderReport}. */\nexport type ReportOptions = {\n /** Subject line shown in the report header. */\n title?: string;\n /**\n * IANA timezone used to label the report's time figures. Defaults to the\n * host's local zone (matching {@link StatsOptions.timeZone}).\n */\n timeZone?: string;\n};\n\n/**\n * Resolve the Basou workspace root for a working directory by finding the\n * enclosing git repository root (`.basou/` lives at the repo root). A\n * convenience for the common \"I'm somewhere in the repo\" case; requires git\n * and a repository. Pass the returned path to {@link openWorkspace}. When you\n * already know the root (CI checkout, a copied `.basou/`), skip this and call\n * {@link openWorkspace} directly — it needs no git.\n */\nexport function resolveWorkspaceRoot(cwd: string): Promise<string> {\n return resolveRepositoryRoot(cwd);\n}\n\n/**\n * Open a read-only handle on the Basou workspace rooted at `repoRoot` (the\n * directory that contains `.basou/`). Validates that `.basou/` exists and is a\n * real directory; throws {@link WorkspaceNotFoundError} otherwise. No git is\n * required — point it at any directory holding a `.basou/`.\n */\nexport async function openWorkspace(\n repoRoot: string,\n options: WorkspaceOptions = {},\n): Promise<Workspace> {\n // Normalize to an absolute path up front so `root` honors its documented\n // absolute-path contract even when the caller passes a relative directory.\n const root = resolve(repoRoot);\n const paths = basouPaths(root);\n try {\n await assertBasouRootSafe(paths.root);\n } catch (cause) {\n throw new WorkspaceNotFoundError(root, { cause });\n }\n const now = options.now ?? (() => new Date());\n const emit = options.onDiagnostic;\n const onWarning = (warning: { kind: string; line?: number }, id?: string): void =>\n emit?.({\n message: `event ${warning.kind}${warning.line ? ` (line ${warning.line})` : \"\"}`,\n ...(id !== undefined ? { id } : {}),\n });\n const onSkip = (id: string, reason: string): void =>\n emit?.({ message: `skipped: ${reason}`, id });\n\n /** Resolve a session prefix to a full id, or null when nothing matches. */\n const resolveSession = (input: string): Promise<string | null> =>\n resolveOrNull(() => resolveSessionId(paths, input), input);\n const resolveTask = (input: string): Promise<string | null> =>\n resolveOrNull(() => resolveTaskId(paths, input, { includeArchived: true }), input);\n\n return {\n root,\n\n manifest: () => readManifest(paths),\n\n status: async () =>\n buildStatusSnapshot({ manifest: await readManifest(paths), paths, now: now() }),\n\n listSessions: () =>\n loadSessionEntries(paths, {\n now: now(),\n onWarning: (w, sid) => onWarning(w, sid),\n onSkip,\n }),\n\n getSession: async (idOrPrefix) => {\n const id = await resolveSession(idOrPrefix);\n if (id === null) return null;\n const entries = await loadSessionEntries(paths, {\n now: now(),\n onWarning: (w, sid) => onWarning(w, sid),\n onSkip,\n });\n return entries.find((e) => e.sessionId === id) ?? null;\n },\n\n readEvents: async (idOrPrefix) => {\n const id = await resolveSession(idOrPrefix);\n if (id === null) return [];\n return readAllEvents(join(paths.sessions, id), { onWarning: (w) => onWarning(w, id) });\n },\n\n streamEvents: (idOrPrefix): AsyncIterable<Event> => {\n async function* iterate(): AsyncGenerator<Event> {\n const id = await resolveSession(idOrPrefix);\n if (id === null) return;\n yield* replayEvents(join(paths.sessions, id), { onWarning: (w) => onWarning(w, id) });\n }\n return iterate();\n },\n\n listTasks: () => loadTaskEntries(paths, { onSkip }),\n\n getTask: async (idOrPrefix) => {\n const id = await resolveTask(idOrPrefix);\n if (id === null) return null;\n const { doc } = await readTaskFileWithArchiveFallback(paths, id);\n return doc;\n },\n\n listApprovals: async () => {\n const ids = await enumerateApprovals(paths);\n // `loadApproval` searches resolved/ before pending/, so an id present in\n // BOTH (a stale pending file left after resolution) would otherwise load\n // the resolved record into the pending list too. Drop those from pending\n // so a resolved approval is reported once, under `resolved`.\n const resolvedSet = new Set(ids.resolved);\n const pendingIds = ids.pending.filter((id) => !resolvedSet.has(id));\n const load = async (id: string): Promise<LoadedApproval | null> => loadApproval(paths, id);\n const [pending, resolved] = await Promise.all([\n Promise.all(pendingIds.map(load)),\n Promise.all(ids.resolved.map(load)),\n ]);\n return {\n pending: pending.filter((a): a is LoadedApproval => a !== null),\n resolved: resolved.filter((a): a is LoadedApproval => a !== null),\n };\n },\n\n getApproval: (id) => loadApproval(paths, id),\n\n stats: (statsOptions) =>\n computeWorkStats({\n paths,\n now: now(),\n ...(statsOptions?.timeZone !== undefined ? { timeZone: statsOptions.timeZone } : {}),\n onWarning: (w, sid) => onWarning(w, sid),\n onSessionSkip: onSkip,\n }),\n\n renderHandoff: async () => {\n const result = await renderHandoff({\n paths,\n nowIso: now().toISOString(),\n onWarning: (w, sid) => onWarning(w, sid),\n onSessionSkip: onSkip,\n onTaskSkip: onSkip,\n });\n return result.body;\n },\n\n renderDecisions: async () => {\n const result = await renderDecisions({\n paths,\n nowIso: now().toISOString(),\n onWarning: (w, sid) => onWarning(w, sid),\n onSessionSkip: onSkip,\n });\n return result.body;\n },\n\n renderReport: async (reportOptions) => {\n const result = await renderReport({\n paths,\n nowIso: now().toISOString(),\n ...(reportOptions?.title !== undefined ? { title: reportOptions.title } : {}),\n ...(reportOptions?.timeZone !== undefined ? { timeZone: reportOptions.timeZone } : {}),\n onWarning: (w, sid) => onWarning(w, sid),\n onSessionSkip: onSkip,\n onTaskSkip: onSkip,\n });\n return result.body;\n },\n };\n}\n\n/**\n * Run a core id-resolver and normalize its outcome: a successful resolution\n * returns the id; the \"not found\" / \"empty input\" contract errors map to\n * `null` (no such record); the \"ambiguous\" contract error maps to\n * {@link AmbiguousIdError}. Any other error propagates unchanged.\n */\nasync function resolveOrNull(\n resolver: () => Promise<string>,\n input: string,\n): Promise<string | null> {\n try {\n return await resolver();\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n // Match the core resolver's exact contract strings (id-resolver.ts), not a\n // loose substring, so an unrelated error that merely contains \"not found\"\n // is never silently swallowed to null.\n if (/^Ambiguous (session|task) id /.test(message)) {\n throw new AmbiguousIdError(input, { cause: error });\n }\n if (\n /^(Session|Task) not found: /.test(message) ||\n /^(Session|Task) id is empty$/.test(message)\n ) {\n return null;\n }\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/build-stamp.ts","../src/index.ts","../src/errors.ts","../src/workspace.ts"],"sourcesContent":["/**\n * The identity of the build that is RUNNING, frozen into the bundle by\n * `tsup.config.ts` at build time. `undefined` when this module is loaded from\n * source (tests, `tsx`), where there is no build to be stale.\n *\n * `typeof` guards an identifier esbuild only declares in a built bundle.\n */\ndeclare const __BASOU_BUILD_STAMP__: string | undefined;\n\n/** What a build knows about itself. `commit` is `\"unknown\"` outside a checkout. */\nexport type BuildStamp = {\n readonly version: string;\n readonly commit: string;\n readonly committedAt: string;\n};\n\n/**\n * Parse an injected stamp. Separate from the constant below so it is reachable\n * from a test: under vitest the module loads from SOURCE, where the injected\n * identifier does not exist, so every line of the parse would otherwise be\n * unreachable -- a guarantee with no test behind it, which is the shape of\n * omission this whole feature exists to correct.\n *\n * Anything unparseable yields `undefined` rather than throwing: a malformed\n * stamp must not stop the CLI from starting.\n */\nexport function parseBuildStamp(raw: string | undefined): BuildStamp | undefined {\n if (typeof raw !== \"string\") return undefined;\n try {\n const parsed = JSON.parse(raw) as Partial<BuildStamp>;\n if (\n typeof parsed.version !== \"string\" ||\n typeof parsed.commit !== \"string\" ||\n typeof parsed.committedAt !== \"string\"\n ) {\n return undefined;\n }\n return { version: parsed.version, commit: parsed.commit, committedAt: parsed.committedAt };\n } catch {\n return undefined;\n }\n}\n\n/**\n * The SDK's own build identity.\n *\n * The SDK is a semver-guaranteed surface, and a consumer embedding it has no\n * `basou --version` to fall back on: this is the only way for them to say\n * which build they are running. It is stamped separately from `@basou/core`\n * for the same reason the CLI is -- they are distinct artifacts, and a partial\n * build can leave them at different commits.\n */\nexport const BASOU_SDK_BUILD: BuildStamp | undefined = parseBuildStamp(\n typeof __BASOU_BUILD_STAMP__ === \"string\" ? __BASOU_BUILD_STAMP__ : undefined,\n);\n","export { BASOU_SDK_BUILD, type BuildStamp, parseBuildStamp } from \"./build-stamp.js\";\n/**\n * `@basou/sdk` — the stable, read-only programmatic API for reading a Basou\n * workspace's provenance (`.basou/`). It is a thin, ergonomic facade over\n * `@basou/core`'s readers: open a workspace once and query sessions, events,\n * tasks, approvals, status, stats, and the rendered handoff / decisions. No\n * writers are exposed — third-party tooling can read provenance without any\n * risk of mutating it.\n *\n * @example\n * ```ts\n * import { openWorkspace, resolveWorkspaceRoot } from \"@basou/sdk\";\n *\n * const root = await resolveWorkspaceRoot(process.cwd()); // or pass a known root\n * const ws = await openWorkspace(root);\n * for (const { session, suspect } of await ws.listSessions()) {\n * console.log(session.session.id, session.session.status, suspect);\n * }\n * const stats = await ws.stats();\n * console.log(stats.totals.billableActiveTimeMs);\n * ```\n */\n\n/**\n * SDK API version, tracking the Basou SDK surface (not the npm package\n * version, which moves in lockstep with the monorepo). `0.2.0` was the first\n * release with a runtime read API; `0.3.0` adds `Workspace.renderReport`;\n * `0.4.0` re-exports `readObservedDuration` and carries the `duration_ms`\n * nullability through the re-exported `CommandExecutedEvent`; `0.1.0` was\n * types-only.\n */\nexport const BASOU_SDK_VERSION = \"0.4.0\";\n\n// Read types re-exported from @basou/core so consumers can type the values the\n// SDK returns without depending on @basou/core directly. These track the\n// on-disk provenance schema.\nexport type {\n ActiveTimeBasis,\n Approval,\n ApprovalStatus,\n CommandExecutedEvent,\n DayWorkStats,\n DecisionRecordedEvent,\n Event,\n FileChangedEvent,\n LoadedApproval,\n Manifest,\n MeasureAvailability,\n NoteAddedEvent,\n RiskLevel,\n Session,\n SessionEndedEvent,\n SessionEntry,\n SessionMetrics,\n SessionSourceKind,\n SessionStartedEvent,\n SessionStatus,\n SessionStatusChangedEvent,\n SessionWorkStats,\n SourceWorkStats,\n StatusCount,\n StatusSnapshot,\n SuspectReason,\n Task,\n TaskDocument,\n TaskStatus,\n TokenTotals,\n WorkStatsResult,\n WorkStatsTotals,\n} from \"@basou/core\";\n/**\n * The read rule for `command_executed.duration_ms`, re-exported from\n * `@basou/core` so a consumer of this facade can apply it without depending on\n * core directly. The field is `number | null` and a stored `0` also means \"not\n * observed\", so reading it off the event is wrong on both counts; this returns\n * the duration that was actually observed, or null.\n */\nexport { readObservedDuration } from \"@basou/core\";\nexport { AmbiguousIdError, BasouSdkError, WorkspaceNotFoundError } from \"./errors.js\";\nexport {\n openWorkspace,\n type ReportOptions,\n resolveWorkspaceRoot,\n type StatsOptions,\n type Workspace,\n type WorkspaceDiagnostic,\n type WorkspaceOptions,\n} from \"./workspace.js\";\n","/**\n * Base class for every error the SDK throws on its own behalf. Errors that\n * originate in `@basou/core` (e.g. a malformed `session.yaml`) propagate as-is;\n * only the SDK's own preconditions are wrapped, so `instanceof BasouSdkError`\n * identifies \"the SDK rejected this call\" rather than \"the data was bad\".\n */\nexport class BasouSdkError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\n/**\n * `openWorkspace` was pointed at a path that is not a usable Basou workspace:\n * the `.basou/` directory is missing, is a symlink, or is otherwise not a\n * directory. The offending repository root is on {@link root}.\n */\nexport class WorkspaceNotFoundError extends BasouSdkError {\n readonly root: string;\n constructor(root: string, options?: { cause?: unknown }) {\n super(\n `No Basou workspace at ${root}: expected a '.basou/' directory (run 'basou init' there first).`,\n options,\n );\n this.root = root;\n }\n}\n\n/**\n * A session / task id prefix matched more than one record. The {@link input}\n * is the prefix as given; the caller should retry with a longer one. (A prefix\n * that matches nothing is NOT an error — the lookup returns `null` instead.)\n */\nexport class AmbiguousIdError extends BasouSdkError {\n readonly input: string;\n constructor(input: string, options?: { cause?: unknown }) {\n super(`Ambiguous id '${input}': matched more than one record; use a longer prefix.`, options);\n this.input = input;\n }\n}\n","import { join, resolve } from \"node:path\";\nimport {\n assertBasouRootSafe,\n basouPaths,\n buildStatusSnapshot,\n computeWorkStats,\n type Event,\n enumerateApprovals,\n type LoadedApproval,\n loadApproval,\n loadSessionEntries,\n loadTaskEntries,\n type Manifest,\n readAllEvents,\n readManifest,\n readTaskFileWithArchiveFallback,\n renderDecisions,\n renderHandoff,\n renderReport,\n replayEvents,\n resolveRepositoryRoot,\n resolveSessionId,\n resolveTaskId,\n type SessionEntry,\n type StatusSnapshot,\n type TaskDocument,\n type WorkStatsResult,\n} from \"@basou/core\";\nimport { AmbiguousIdError, WorkspaceNotFoundError } from \"./errors.js\";\n\n/**\n * A degradation the SDK noticed while reading provenance: a malformed event\n * line, or a session / task that could not be loaded. Best-effort reads skip\n * these and keep going; pass `onDiagnostic` to {@link openWorkspace} to observe\n * them. `message` is a human-readable summary (it folds in the core\n * `ReplayWarning.kind` or skip-reason); structured fields are intentionally not\n * part of this stable shape.\n */\nexport type WorkspaceDiagnostic = {\n /** Human-readable summary of the malformed line / skipped record. */\n message: string;\n /** Session or task id the diagnostic relates to, when known. */\n id?: string;\n};\n\n/** Options for {@link openWorkspace}; all optional. */\nexport type WorkspaceOptions = {\n /**\n * Clock used for time-sensitive reads (session \"suspect\" classification,\n * stats span-to-now, status / approval expiry). Injectable for deterministic\n * callers and tests. Defaults to `() => new Date()`, evaluated per call.\n */\n now?: () => Date;\n /**\n * Observe a malformed event line or a skipped session / task instead of it\n * being silently dropped. Reads are still best-effort: a diagnostic does not\n * fail the call.\n */\n onDiagnostic?: (diagnostic: WorkspaceDiagnostic) => void;\n};\n\n/** Options for {@link Workspace.stats}. */\nexport type StatsOptions = {\n /**\n * IANA timezone used to bucket the per-day breakdown (native logs are UTC).\n * Defaults to the host's local zone.\n */\n timeZone?: string;\n};\n\n/**\n * A read-only handle on one Basou workspace (`<root>/.basou/`). Every method\n * reads provenance from disk; the SDK exposes no writers. Obtain one with\n * {@link openWorkspace}.\n *\n * Session / task lookups (`getSession`, `getTask`, `readEvents`,\n * `streamEvents`) accept a full id or a unique prefix: a prefix matching\n * nothing yields `null` (or an empty stream), a prefix matching more than one\n * record throws {@link AmbiguousIdError}. `getApproval` takes an exact id only.\n */\nexport interface Workspace {\n /** Absolute repository root this workspace was opened at. */\n readonly root: string;\n\n /** Parsed `manifest.yaml`. */\n manifest(): Promise<Manifest>;\n /** A freshly computed workspace status snapshot (directory presence + manifest). */\n status(): Promise<StatusSnapshot>;\n\n /** Every session, ULID-ascending, each with its `suspect` classification. */\n listSessions(): Promise<SessionEntry[]>;\n /** One session by id / unique prefix, or `null` if no session matches. */\n getSession(idOrPrefix: string): Promise<SessionEntry | null>;\n /** All events of a session, eagerly, ordered as written. Empty if no match. */\n readEvents(idOrPrefix: string): Promise<Event[]>;\n /** All events of a session as a lazy stream (for large logs). */\n streamEvents(idOrPrefix: string): AsyncIterable<Event>;\n\n /** Every task (active + lazily-indexed), created-at ascending. */\n listTasks(): Promise<TaskDocument[]>;\n /** One task by id / unique prefix (archived included), or `null`. */\n getTask(idOrPrefix: string): Promise<TaskDocument | null>;\n\n /** Pending + resolved approvals, fully loaded. */\n listApprovals(): Promise<{ pending: LoadedApproval[]; resolved: LoadedApproval[] }>;\n /** One approval by exact id (resolved checked first), or `null`. */\n getApproval(id: string): Promise<LoadedApproval | null>;\n\n /** Aggregated work / time / token stats across the workspace's sessions. */\n stats(options?: StatsOptions): Promise<WorkStatsResult>;\n\n /** The rendered `handoff.md` body (recomputed, without generated markers). */\n renderHandoff(): Promise<string>;\n /** The rendered `decisions.md` body (recomputed, without generated markers). */\n renderDecisions(): Promise<string>;\n /**\n * A rendered work report — a point-in-time markdown export explaining the\n * work captured in this workspace (volume, decisions, approvals, tasks,\n * changed files, and the local provenance integrity verdicts). Read-only,\n * markerless. The CLI's `--json` structured shape is a CLI concern; the SDK\n * facade returns the markdown body, mirroring `renderHandoff` / `renderDecisions`.\n */\n renderReport(options?: ReportOptions): Promise<string>;\n}\n\n/** Options for {@link Workspace.renderReport}. */\nexport type ReportOptions = {\n /** Subject line shown in the report header. */\n title?: string;\n /**\n * IANA timezone used to label the report's time figures. Defaults to the\n * host's local zone (matching {@link StatsOptions.timeZone}).\n */\n timeZone?: string;\n};\n\n/**\n * Resolve the Basou workspace root for a working directory by finding the\n * enclosing git repository root (`.basou/` lives at the repo root). A\n * convenience for the common \"I'm somewhere in the repo\" case; requires git\n * and a repository. Pass the returned path to {@link openWorkspace}. When you\n * already know the root (CI checkout, a copied `.basou/`), skip this and call\n * {@link openWorkspace} directly — it needs no git.\n */\nexport function resolveWorkspaceRoot(cwd: string): Promise<string> {\n return resolveRepositoryRoot(cwd);\n}\n\n/**\n * Open a read-only handle on the Basou workspace rooted at `repoRoot` (the\n * directory that contains `.basou/`). Validates that `.basou/` exists and is a\n * real directory; throws {@link WorkspaceNotFoundError} otherwise. No git is\n * required — point it at any directory holding a `.basou/`.\n */\nexport async function openWorkspace(\n repoRoot: string,\n options: WorkspaceOptions = {},\n): Promise<Workspace> {\n // Normalize to an absolute path up front so `root` honors its documented\n // absolute-path contract even when the caller passes a relative directory.\n const root = resolve(repoRoot);\n const paths = basouPaths(root);\n try {\n await assertBasouRootSafe(paths.root);\n } catch (cause) {\n throw new WorkspaceNotFoundError(root, { cause });\n }\n const now = options.now ?? (() => new Date());\n const emit = options.onDiagnostic;\n const onWarning = (warning: { kind: string; line?: number }, id?: string): void =>\n emit?.({\n message: `event ${warning.kind}${warning.line ? ` (line ${warning.line})` : \"\"}`,\n ...(id !== undefined ? { id } : {}),\n });\n const onSkip = (id: string, reason: string): void =>\n emit?.({ message: `skipped: ${reason}`, id });\n\n /** Resolve a session prefix to a full id, or null when nothing matches. */\n const resolveSession = (input: string): Promise<string | null> =>\n resolveOrNull(() => resolveSessionId(paths, input), input);\n const resolveTask = (input: string): Promise<string | null> =>\n resolveOrNull(() => resolveTaskId(paths, input, { includeArchived: true }), input);\n\n return {\n root,\n\n manifest: () => readManifest(paths),\n\n status: async () =>\n buildStatusSnapshot({ manifest: await readManifest(paths), paths, now: now() }),\n\n listSessions: () =>\n loadSessionEntries(paths, {\n now: now(),\n onWarning: (w, sid) => onWarning(w, sid),\n onSkip,\n }),\n\n getSession: async (idOrPrefix) => {\n const id = await resolveSession(idOrPrefix);\n if (id === null) return null;\n const entries = await loadSessionEntries(paths, {\n now: now(),\n onWarning: (w, sid) => onWarning(w, sid),\n onSkip,\n });\n return entries.find((e) => e.sessionId === id) ?? null;\n },\n\n readEvents: async (idOrPrefix) => {\n const id = await resolveSession(idOrPrefix);\n if (id === null) return [];\n return readAllEvents(join(paths.sessions, id), { onWarning: (w) => onWarning(w, id) });\n },\n\n streamEvents: (idOrPrefix): AsyncIterable<Event> => {\n async function* iterate(): AsyncGenerator<Event> {\n const id = await resolveSession(idOrPrefix);\n if (id === null) return;\n yield* replayEvents(join(paths.sessions, id), { onWarning: (w) => onWarning(w, id) });\n }\n return iterate();\n },\n\n listTasks: () => loadTaskEntries(paths, { onSkip }),\n\n getTask: async (idOrPrefix) => {\n const id = await resolveTask(idOrPrefix);\n if (id === null) return null;\n const { doc } = await readTaskFileWithArchiveFallback(paths, id);\n return doc;\n },\n\n listApprovals: async () => {\n const ids = await enumerateApprovals(paths);\n // `loadApproval` searches resolved/ before pending/, so an id present in\n // BOTH (a stale pending file left after resolution) would otherwise load\n // the resolved record into the pending list too. Drop those from pending\n // so a resolved approval is reported once, under `resolved`.\n const resolvedSet = new Set(ids.resolved);\n const pendingIds = ids.pending.filter((id) => !resolvedSet.has(id));\n const load = async (id: string): Promise<LoadedApproval | null> => loadApproval(paths, id);\n const [pending, resolved] = await Promise.all([\n Promise.all(pendingIds.map(load)),\n Promise.all(ids.resolved.map(load)),\n ]);\n return {\n pending: pending.filter((a): a is LoadedApproval => a !== null),\n resolved: resolved.filter((a): a is LoadedApproval => a !== null),\n };\n },\n\n getApproval: (id) => loadApproval(paths, id),\n\n stats: (statsOptions) =>\n computeWorkStats({\n paths,\n now: now(),\n ...(statsOptions?.timeZone !== undefined ? { timeZone: statsOptions.timeZone } : {}),\n onWarning: (w, sid) => onWarning(w, sid),\n onSessionSkip: onSkip,\n }),\n\n renderHandoff: async () => {\n const result = await renderHandoff({\n paths,\n nowIso: now().toISOString(),\n onWarning: (w, sid) => onWarning(w, sid),\n onSessionSkip: onSkip,\n onTaskSkip: onSkip,\n });\n return result.body;\n },\n\n renderDecisions: async () => {\n const result = await renderDecisions({\n paths,\n nowIso: now().toISOString(),\n onWarning: (w, sid) => onWarning(w, sid),\n onSessionSkip: onSkip,\n });\n return result.body;\n },\n\n renderReport: async (reportOptions) => {\n const result = await renderReport({\n paths,\n nowIso: now().toISOString(),\n ...(reportOptions?.title !== undefined ? { title: reportOptions.title } : {}),\n ...(reportOptions?.timeZone !== undefined ? { timeZone: reportOptions.timeZone } : {}),\n onWarning: (w, sid) => onWarning(w, sid),\n onSessionSkip: onSkip,\n onTaskSkip: onSkip,\n });\n return result.body;\n },\n };\n}\n\n/**\n * Run a core id-resolver and normalize its outcome: a successful resolution\n * returns the id; the \"not found\" / \"empty input\" contract errors map to\n * `null` (no such record); the \"ambiguous\" contract error maps to\n * {@link AmbiguousIdError}. Any other error propagates unchanged.\n */\nasync function resolveOrNull(\n resolver: () => Promise<string>,\n input: string,\n): Promise<string | null> {\n try {\n return await resolver();\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n // Match the core resolver's exact contract strings (id-resolver.ts), not a\n // loose substring, so an unrelated error that merely contains \"not found\"\n // is never silently swallowed to null.\n if (/^Ambiguous (session|task) id /.test(message)) {\n throw new AmbiguousIdError(input, { cause: error });\n }\n if (\n /^(Session|Task) not found: /.test(message) ||\n /^(Session|Task) id is empty$/.test(message)\n ) {\n return null;\n }\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basou/sdk",
3
- "version": "0.44.0",
3
+ "version": "0.45.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "Read-only SDK for Basou: a stable, ergonomic API to read a workspace's provenance (sessions, events, tasks, approvals, stats).",
@@ -42,7 +42,7 @@
42
42
  "node": ">=20.10.0"
43
43
  },
44
44
  "dependencies": {
45
- "@basou/core": "0.44.0"
45
+ "@basou/core": "0.45.0"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "tsup",