From a798aa8741d545f0c93c2d763ff13fb910364782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E5=B1=B1=E8=B2=B4=E8=87=B3?= Date: Thu, 4 Jun 2026 23:18:32 +0900 Subject: [PATCH] feat(core): emit JSON Schema artifacts for the on-disk .basou/ formats `@basou/core` now ships JSON Schema artifacts for every on-disk `.basou/` document, generated from the canonical Zod schemas (zod v4's built-in `z.toJSONSchema`) so they cannot drift from validation. Non-JavaScript / cross-language tooling and editors can now validate `.basou/` files directly, without reimplementing the format. One schema per document: manifest, session, event (a `oneOf` over the event type), task, approval, status, task-index, and session-import. Each is a draft 2020-12 document with a stable `$id` (`https://basou.dev/schemas/0.1.0/.schema.json`), published under the package's `./schemas/*` export (e.g. `@basou/core/schemas/session.schema.json`) and listed in `files`. The artifacts describe what a consumer AUTHORS on disk: generated in `io: "input"` mode so a field with a `.default()` (e.g. `events_log`) stays optional rather than required, and a non-strict object allows additive fields; only the `.strict()` event variants (e.g. `adapter_output`) keep `additionalProperties: false`. Prefixed-id fields carry a faithful ULID `pattern` via schema metadata on the shared id factory (validation behavior is unchanged; the existing `.refine` still gates acceptance). Other refinement-only constraints are not expressible in JSON Schema and are omitted. Generation and guards: - `pnpm --filter @basou/core gen:schemas` builds, then regenerates the committed `packages/core/schemas/*.json` from `buildJsonSchemas()`. - A drift-guard test asserts every committed file still matches the Zod source byte-for-byte, so a schema change without regeneration fails CI. - An ajv (draft 2020-12) round-trip test validates a representative document per schema, plus the input-mode cases (defaulted fields may be omitted; a non-strict event accepts an additive field) and that the ULID pattern rejects a malformed id. - The generated `schemas/` dir is excluded from biome (a generated artifact, like `dist`), so the formatter does not fight the generator's output. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 13 + biome.json | 3 +- packages/core/package.json | 14 +- packages/core/schemas/approval.schema.json | 130 ++ packages/core/schemas/event.schema.json | 1188 ++++++++++++++ packages/core/schemas/manifest.schema.json | 150 ++ .../core/schemas/session-import.schema.json | 1415 +++++++++++++++++ packages/core/schemas/session.schema.json | 228 +++ packages/core/schemas/status.schema.json | 85 + packages/core/schemas/task-index.schema.json | 61 + packages/core/schemas/task.schema.json | 82 + packages/core/scripts/gen-schemas.mjs | 22 + packages/core/src/index.ts | 4 + packages/core/src/schemas/index.ts | 2 + packages/core/src/schemas/json-schema.test.ts | 240 +++ packages/core/src/schemas/json-schema.ts | 137 ++ packages/core/src/schemas/shared.schema.ts | 17 +- pnpm-lock.yaml | 50 + 18 files changed, 3836 insertions(+), 5 deletions(-) create mode 100644 packages/core/schemas/approval.schema.json create mode 100644 packages/core/schemas/event.schema.json create mode 100644 packages/core/schemas/manifest.schema.json create mode 100644 packages/core/schemas/session-import.schema.json create mode 100644 packages/core/schemas/session.schema.json create mode 100644 packages/core/schemas/status.schema.json create mode 100644 packages/core/schemas/task-index.schema.json create mode 100644 packages/core/schemas/task.schema.json create mode 100644 packages/core/scripts/gen-schemas.mjs create mode 100644 packages/core/src/schemas/json-schema.test.ts create mode 100644 packages/core/src/schemas/json-schema.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 627f42f2..941851eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ All notable changes to **basou** are recorded here. The project follows ### Added +- `@basou/core` now ships JSON Schema artifacts for the on-disk `.basou/` + document formats, generated from the canonical Zod schemas (so they cannot + drift from validation). One schema per document — `manifest`, `session`, + `event` (a `oneOf` over the event type), `task`, `approval`, `status`, + `task-index`, and `session-import` — each a draft 2020-12 document with a + stable `$id` (`https://basou.dev/schemas/0.1.0/.schema.json`). They let + non-JavaScript / cross-language tooling and editors validate `.basou/` files + directly. Published under the package's `./schemas/*` export (e.g. + `@basou/core/schemas/session.schema.json`). Prefixed-id fields carry a faithful + ULID `pattern`; other refinement-only constraints are not expressible in JSON + Schema and are omitted. Regenerate with `pnpm --filter @basou/core + gen:schemas`; a drift-guard test fails CI if the committed files fall behind + the Zod source. - `@basou/sdk` v0.2 — the package gains a runtime, read-only programmatic API for reading a workspace's provenance (it was types-only before). It is a thin, ergonomic, semver-stable facade over `@basou/core`'s readers, so diff --git a/biome.json b/biome.json index cb3e3a29..e7d7e619 100644 --- a/biome.json +++ b/biome.json @@ -12,7 +12,8 @@ "!**/node_modules", "!**/coverage", "!**/*.tsbuildinfo", - "!**/pnpm-lock.yaml" + "!**/pnpm-lock.yaml", + "!packages/core/schemas" ] }, "assist": { "actions": { "source": { "organizeImports": "on" } } }, diff --git a/packages/core/package.json b/packages/core/package.json index d933ddd2..fbf79569 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -12,7 +12,8 @@ "audit", "provenance", "git", - "claude-code" + "claude-code", + "json-schema" ], "repository": { "type": "git", @@ -35,10 +36,12 @@ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" - } + }, + "./schemas/*": "./schemas/*" }, "files": [ - "dist" + "dist", + "schemas" ], "engines": { "node": ">=20.10.0" @@ -46,6 +49,7 @@ "scripts": { "build": "tsup", "dev": "tsup --watch", + "gen:schemas": "pnpm build && node scripts/gen-schemas.mjs", "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest", @@ -57,5 +61,9 @@ "ulid": "^3.0.2", "yaml": "^2.9.0", "zod": "^4.4.3" + }, + "devDependencies": { + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1" } } diff --git a/packages/core/schemas/approval.schema.json b/packages/core/schemas/approval.schema.json new file mode 100644 index 00000000..a7e2d983 --- /dev/null +++ b/packages/core/schemas/approval.schema.json @@ -0,0 +1,130 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://basou.dev/schemas/0.1.0/approval.schema.json", + "title": "Basou Approval", + "description": "A `.basou/approvals/{pending,resolved}/.yaml` approval record.", + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^appr_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou appr id: `appr_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "created_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "approved", + "rejected", + "expired" + ] + }, + "risk_level": { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "critical" + ] + }, + "action": { + "type": "object", + "properties": { + "kind": { + "type": "string" + } + }, + "required": [ + "kind" + ], + "additionalProperties": {} + }, + "reason": { + "type": "string" + }, + "expires_at": { + "default": null, + "anyOf": [ + { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + { + "type": "null" + } + ] + }, + "resolver": { + "default": null, + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "resolved_at": { + "default": null, + "anyOf": [ + { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + { + "type": "null" + } + ] + }, + "note": { + "default": null, + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "rejection_reason": { + "default": null, + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "created_at", + "status", + "risk_level", + "action", + "reason" + ] +} diff --git a/packages/core/schemas/event.schema.json b/packages/core/schemas/event.schema.json new file mode 100644 index 00000000..908f518b --- /dev/null +++ b/packages/core/schemas/event.schema.json @@ -0,0 +1,1188 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://basou.dev/schemas/0.1.0/event.schema.json", + "title": "Basou Event", + "description": "One line of a `.basou/sessions//events.jsonl` stream (a discriminated union over the event `type`).", + "oneOf": [ + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "session_started" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "session_ended" + }, + "exit_code": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "session_status_changed" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "from", + "to" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "approval_requested" + }, + "approval_id": { + "type": "string", + "pattern": "^appr_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou appr id: `appr_` followed by a 26-character Crockford Base32 ULID." + }, + "expires_at": { + "default": null, + "anyOf": [ + { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + { + "type": "null" + } + ] + }, + "risk_level": { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "critical" + ] + }, + "action": { + "type": "object", + "properties": { + "kind": { + "type": "string" + } + }, + "required": [ + "kind" + ], + "additionalProperties": {} + }, + "reason": { + "type": "string" + }, + "status": { + "type": "string", + "const": "pending" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "approval_id", + "risk_level", + "action", + "reason", + "status" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "approval_approved" + }, + "approval_id": { + "type": "string", + "pattern": "^appr_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou appr id: `appr_` followed by a 26-character Crockford Base32 ULID." + }, + "resolver": { + "type": "string" + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "approval_id" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "approval_rejected" + }, + "approval_id": { + "type": "string", + "pattern": "^appr_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou appr id: `appr_` followed by a 26-character Crockford Base32 ULID." + }, + "resolver": { + "type": "string" + }, + "reason": { + "type": "string" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "approval_id", + "reason" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "approval_expired" + }, + "approval_id": { + "type": "string", + "pattern": "^appr_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou appr id: `appr_` followed by a 26-character Crockford Base32 ULID." + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "approval_id" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "command_executed" + }, + "command": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "cwd": { + "type": "string" + }, + "exit_code": { + "anyOf": [ + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + { + "type": "null" + } + ] + }, + "signal": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "received_signal": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "duration_ms": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "command", + "args", + "cwd", + "exit_code", + "duration_ms" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "git_snapshot" + }, + "head": { + "type": "string" + }, + "branch": { + "type": "string" + }, + "dirty": { + "type": "boolean" + }, + "staged": { + "type": "array", + "items": { + "type": "string" + } + }, + "unstaged": { + "type": "array", + "items": { + "type": "string" + } + }, + "untracked": { + "type": "array", + "items": { + "type": "string" + } + }, + "ahead": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "behind": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "head", + "branch", + "dirty", + "staged", + "unstaged", + "untracked" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "file_changed" + }, + "path": { + "type": "string" + }, + "change_type": { + "type": "string", + "enum": [ + "added", + "modified", + "deleted", + "renamed" + ] + }, + "old_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "path", + "change_type" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "decision_recorded" + }, + "decision_id": { + "type": "string", + "pattern": "^decision_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou decision id: `decision_` followed by a 26-character Crockford Base32 ULID." + }, + "title": { + "type": "string" + }, + "rationale": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "alternatives": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "rejected_reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "linked_events": { + "type": "array", + "items": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + } + }, + "linked_files": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + } + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "decision_id", + "title" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "task_created" + }, + "task_id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "title": { + "type": "string" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "task_id", + "title" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "task_status_changed" + }, + "task_id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "task_id", + "from", + "to" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "task_reconciled" + }, + "task_id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "removed_created_in_session": { + "default": null, + "anyOf": [ + { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + { + "type": "null" + } + ] + }, + "created_in_session_replacement": { + "default": null, + "anyOf": [ + { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + { + "type": "null" + } + ] + }, + "removed_linked_sessions": { + "default": [], + "type": "array", + "items": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + } + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "task_id" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "task_linkage_refreshed" + }, + "task_id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "added_linked_sessions": { + "default": [], + "type": "array", + "items": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + } + }, + "removed_linked_sessions": { + "default": [], + "type": "array", + "items": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + } + }, + "final_count": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "task_id" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "task_deleted" + }, + "task_id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "title": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "task_id", + "title" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "task_archived" + }, + "task_id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "title": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "task_id", + "title" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "note_added" + }, + "body": { + "type": "string" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "body" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "adapter_output" + }, + "stream": { + "type": "string", + "enum": [ + "stdout", + "stderr" + ] + }, + "summary": { + "type": "string" + }, + "raw_ref": { + "type": "string" + }, + "redacted": { + "type": "boolean" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "stream", + "summary", + "raw_ref" + ], + "additionalProperties": false + } + ] +} diff --git a/packages/core/schemas/manifest.schema.json b/packages/core/schemas/manifest.schema.json new file mode 100644 index 00000000..aaf0a613 --- /dev/null +++ b/packages/core/schemas/manifest.schema.json @@ -0,0 +1,150 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://basou.dev/schemas/0.1.0/manifest.schema.json", + "title": "Basou Manifest", + "description": "The `.basou/manifest.yaml` workspace manifest.", + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "basou_version": { + "type": "string", + "const": "0.1.0" + }, + "workspace": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^ws_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ws id: `ws_` followed by a 26-character Crockford Base32 ULID." + }, + "name": { + "type": "string", + "minLength": 1 + }, + "created_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + } + }, + "required": [ + "id", + "name", + "created_at", + "updated_at" + ] + }, + "project": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "repository_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + } + }, + "capabilities": { + "type": "object", + "properties": { + "enabled": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "enabled" + ] + }, + "approval": { + "type": "object", + "properties": { + "required_for": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_risk_level": { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "critical" + ] + } + }, + "required": [ + "default_risk_level" + ] + }, + "adapters": { + "type": "object", + "properties": { + "claude-code": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "config_path": { + "type": "string" + } + }, + "required": [ + "enabled" + ] + } + }, + "required": [ + "claude-code" + ] + }, + "git": { + "type": "object", + "properties": { + "events_log": { + "default": "ignore", + "type": "string", + "enum": [ + "ignore", + "commit" + ] + } + } + } + }, + "required": [ + "schema_version", + "basou_version", + "workspace", + "project", + "capabilities", + "approval", + "adapters", + "git" + ] +} diff --git a/packages/core/schemas/session-import.schema.json b/packages/core/schemas/session-import.schema.json new file mode 100644 index 00000000..f0cf93ae --- /dev/null +++ b/packages/core/schemas/session-import.schema.json @@ -0,0 +1,1415 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://basou.dev/schemas/0.1.0/session-import.schema.json", + "title": "Basou Session Import Payload", + "description": "The portable session payload consumed by `basou session import` (and produced by `basou session export`).", + "type": "object", + "properties": { + "schema_version": { + "type": "string" + }, + "session": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "label": { + "type": "string" + }, + "task_id": { + "anyOf": [ + { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string", + "pattern": "^ws_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ws id: `ws_` followed by a 26-character Crockford Base32 ULID." + }, + "source": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "claude-code-adapter", + "claude-code-import", + "codex-import", + "human", + "import", + "terminal" + ] + }, + "version": { + "type": "string", + "const": "0.1.0" + }, + "external_id": { + "type": "string" + } + }, + "required": [ + "kind", + "version" + ] + }, + "started_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "ended_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "status": { + "type": "string", + "enum": [ + "initialized", + "running", + "waiting_approval", + "completed", + "failed", + "interrupted", + "imported", + "archived" + ] + }, + "working_directory": { + "type": "string", + "minLength": 1 + }, + "invocation": { + "type": "object", + "properties": { + "command": { + "type": "string", + "minLength": 1 + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "exit_code": { + "anyOf": [ + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "command", + "args", + "exit_code" + ] + }, + "related_files": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "events_log": { + "type": "string" + }, + "summary": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "metrics": { + "type": "object", + "properties": { + "output_tokens": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "input_tokens": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "cached_input_tokens": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "reasoning_output_tokens": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "active_time_ms": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "active_intervals": { + "type": "array", + "items": { + "type": "object", + "properties": { + "start": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "end": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + } + }, + "required": [ + "start", + "end" + ] + } + }, + "active_gap_cap_ms": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "active_time_method": { + "type": "string" + }, + "machine_active_time_ms": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + } + } + } + }, + "required": [ + "workspace_id", + "source", + "started_at", + "status", + "working_directory", + "invocation" + ], + "additionalProperties": false + }, + "events": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "session_started" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "session_ended" + }, + "exit_code": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "session_status_changed" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "from", + "to" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "approval_requested" + }, + "approval_id": { + "type": "string", + "pattern": "^appr_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou appr id: `appr_` followed by a 26-character Crockford Base32 ULID." + }, + "expires_at": { + "default": null, + "anyOf": [ + { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + { + "type": "null" + } + ] + }, + "risk_level": { + "type": "string", + "enum": [ + "low", + "medium", + "high", + "critical" + ] + }, + "action": { + "type": "object", + "properties": { + "kind": { + "type": "string" + } + }, + "required": [ + "kind" + ], + "additionalProperties": {} + }, + "reason": { + "type": "string" + }, + "status": { + "type": "string", + "const": "pending" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "approval_id", + "risk_level", + "action", + "reason", + "status" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "approval_approved" + }, + "approval_id": { + "type": "string", + "pattern": "^appr_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou appr id: `appr_` followed by a 26-character Crockford Base32 ULID." + }, + "resolver": { + "type": "string" + }, + "note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "approval_id" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "approval_rejected" + }, + "approval_id": { + "type": "string", + "pattern": "^appr_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou appr id: `appr_` followed by a 26-character Crockford Base32 ULID." + }, + "resolver": { + "type": "string" + }, + "reason": { + "type": "string" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "approval_id", + "reason" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "approval_expired" + }, + "approval_id": { + "type": "string", + "pattern": "^appr_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou appr id: `appr_` followed by a 26-character Crockford Base32 ULID." + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "approval_id" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "command_executed" + }, + "command": { + "type": "string" + }, + "args": { + "type": "array", + "items": { + "type": "string" + } + }, + "cwd": { + "type": "string" + }, + "exit_code": { + "anyOf": [ + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + { + "type": "null" + } + ] + }, + "signal": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "received_signal": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "duration_ms": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "command", + "args", + "cwd", + "exit_code", + "duration_ms" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "git_snapshot" + }, + "head": { + "type": "string" + }, + "branch": { + "type": "string" + }, + "dirty": { + "type": "boolean" + }, + "staged": { + "type": "array", + "items": { + "type": "string" + } + }, + "unstaged": { + "type": "array", + "items": { + "type": "string" + } + }, + "untracked": { + "type": "array", + "items": { + "type": "string" + } + }, + "ahead": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "behind": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "head", + "branch", + "dirty", + "staged", + "unstaged", + "untracked" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "file_changed" + }, + "path": { + "type": "string" + }, + "change_type": { + "type": "string", + "enum": [ + "added", + "modified", + "deleted", + "renamed" + ] + }, + "old_path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "path", + "change_type" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "decision_recorded" + }, + "decision_id": { + "type": "string", + "pattern": "^decision_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou decision id: `decision_` followed by a 26-character Crockford Base32 ULID." + }, + "title": { + "type": "string" + }, + "rationale": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "alternatives": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "rejected_reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "linked_events": { + "type": "array", + "items": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + } + }, + "linked_files": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + } + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "decision_id", + "title" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "task_created" + }, + "task_id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "title": { + "type": "string" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "task_id", + "title" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "task_status_changed" + }, + "task_id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "task_id", + "from", + "to" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "task_reconciled" + }, + "task_id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "removed_created_in_session": { + "default": null, + "anyOf": [ + { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + { + "type": "null" + } + ] + }, + "created_in_session_replacement": { + "default": null, + "anyOf": [ + { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + { + "type": "null" + } + ] + }, + "removed_linked_sessions": { + "default": [], + "type": "array", + "items": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + } + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "task_id" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "task_linkage_refreshed" + }, + "task_id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "added_linked_sessions": { + "default": [], + "type": "array", + "items": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + } + }, + "removed_linked_sessions": { + "default": [], + "type": "array", + "items": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + } + }, + "final_count": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "task_id" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "task_deleted" + }, + "task_id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "title": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "task_id", + "title" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "task_archived" + }, + "task_id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "title": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "task_id", + "title" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "note_added" + }, + "body": { + "type": "string" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "body" + ] + }, + { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "id": { + "type": "string", + "pattern": "^evt_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou evt id: `evt_` followed by a 26-character Crockford Base32 ULID." + }, + "session_id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "const": "adapter_output" + }, + "stream": { + "type": "string", + "enum": [ + "stdout", + "stderr" + ] + }, + "summary": { + "type": "string" + }, + "raw_ref": { + "type": "string" + }, + "redacted": { + "type": "boolean" + } + }, + "required": [ + "schema_version", + "id", + "session_id", + "occurred_at", + "source", + "type", + "stream", + "summary", + "raw_ref" + ], + "additionalProperties": false + } + ] + } + } + }, + "required": [ + "schema_version", + "session", + "events" + ], + "additionalProperties": false +} diff --git a/packages/core/schemas/session.schema.json b/packages/core/schemas/session.schema.json new file mode 100644 index 00000000..7c66e459 --- /dev/null +++ b/packages/core/schemas/session.schema.json @@ -0,0 +1,228 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://basou.dev/schemas/0.1.0/session.schema.json", + "title": "Basou Session", + "description": "A `.basou/sessions//session.yaml` session record.", + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "session": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "label": { + "type": "string" + }, + "task_id": { + "anyOf": [ + { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + { + "type": "null" + } + ] + }, + "workspace_id": { + "type": "string", + "pattern": "^ws_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ws id: `ws_` followed by a 26-character Crockford Base32 ULID." + }, + "source": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "claude-code-adapter", + "claude-code-import", + "codex-import", + "human", + "import", + "terminal" + ] + }, + "version": { + "type": "string", + "const": "0.1.0" + }, + "external_id": { + "type": "string" + } + }, + "required": [ + "kind", + "version" + ] + }, + "started_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "ended_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "status": { + "type": "string", + "enum": [ + "initialized", + "running", + "waiting_approval", + "completed", + "failed", + "interrupted", + "imported", + "archived" + ] + }, + "working_directory": { + "type": "string", + "minLength": 1 + }, + "invocation": { + "type": "object", + "properties": { + "command": { + "type": "string", + "minLength": 1 + }, + "args": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "exit_code": { + "anyOf": [ + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "command", + "exit_code" + ] + }, + "related_files": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "events_log": { + "default": "events.jsonl", + "type": "string" + }, + "summary": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "metrics": { + "type": "object", + "properties": { + "output_tokens": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "input_tokens": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "cached_input_tokens": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "reasoning_output_tokens": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "active_time_ms": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "active_intervals": { + "type": "array", + "items": { + "type": "object", + "properties": { + "start": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "end": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + } + }, + "required": [ + "start", + "end" + ] + } + }, + "active_gap_cap_ms": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "active_time_method": { + "type": "string" + }, + "machine_active_time_ms": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + } + } + } + }, + "required": [ + "id", + "workspace_id", + "source", + "started_at", + "status", + "working_directory", + "invocation" + ] + } + }, + "required": [ + "schema_version", + "session" + ] +} diff --git a/packages/core/schemas/status.schema.json b/packages/core/schemas/status.schema.json new file mode 100644 index 00000000..a63f0d26 --- /dev/null +++ b/packages/core/schemas/status.schema.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://basou.dev/schemas/0.1.0/status.schema.json", + "title": "Basou Status", + "description": "The `.basou/status.json` workspace status snapshot.", + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "generated_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "workspace": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^ws_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ws id: `ws_` followed by a 26-character Crockford Base32 ULID." + }, + "name": { + "type": "string", + "minLength": 1 + }, + "basou_version": { + "type": "string", + "const": "0.1.0" + } + }, + "required": [ + "id", + "name", + "basou_version" + ], + "additionalProperties": false + }, + "directories_present": { + "type": "object", + "properties": { + "sessions": { + "type": "boolean" + }, + "tasks": { + "type": "boolean" + }, + "approvals_pending": { + "type": "boolean" + }, + "approvals_resolved": { + "type": "boolean" + }, + "logs": { + "type": "boolean" + }, + "raw": { + "type": "boolean" + }, + "tmp": { + "type": "boolean" + } + }, + "required": [ + "sessions", + "tasks", + "approvals_pending", + "approvals_resolved", + "logs", + "raw", + "tmp" + ], + "additionalProperties": false + } + }, + "required": [ + "schema_version", + "generated_at", + "workspace", + "directories_present" + ], + "additionalProperties": false +} diff --git a/packages/core/schemas/task-index.schema.json b/packages/core/schemas/task-index.schema.json new file mode 100644 index 00000000..ad40f7f5 --- /dev/null +++ b/packages/core/schemas/task-index.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://basou.dev/schemas/0.1.0/task-index.schema.json", + "title": "Basou Task Index", + "description": "The `.basou/tasks/index.json` task lookup index.", + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "tasks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "status": { + "type": "string", + "enum": [ + "planned", + "in_progress", + "done", + "cancelled" + ] + }, + "label": { + "type": "string", + "minLength": 1 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + } + }, + "required": [ + "id", + "status", + "updated_at" + ], + "additionalProperties": false + } + }, + "last_rebuilt_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + } + }, + "required": [ + "schema_version", + "tasks", + "last_rebuilt_at" + ], + "additionalProperties": false +} diff --git a/packages/core/schemas/task.schema.json b/packages/core/schemas/task.schema.json new file mode 100644 index 00000000..88f55d58 --- /dev/null +++ b/packages/core/schemas/task.schema.json @@ -0,0 +1,82 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://basou.dev/schemas/0.1.0/task.schema.json", + "title": "Basou Task", + "description": "The YAML front matter of a `.basou/tasks/.md` task document.", + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "const": "0.1.0" + }, + "task": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^task_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou task id: `task_` followed by a 26-character Crockford Base32 ULID." + }, + "title": { + "type": "string", + "minLength": 1 + }, + "label": { + "type": "string", + "minLength": 1 + }, + "status": { + "type": "string", + "enum": [ + "planned", + "in_progress", + "done", + "cancelled" + ] + }, + "created_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + }, + "workspace_id": { + "type": "string", + "pattern": "^ws_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ws id: `ws_` followed by a 26-character Crockford Base32 ULID." + }, + "created_in_session": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + }, + "linked_sessions": { + "default": [], + "type": "array", + "items": { + "type": "string", + "pattern": "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + "description": "Basou ses id: `ses_` followed by a 26-character Crockford Base32 ULID." + } + } + }, + "required": [ + "id", + "title", + "status", + "created_at", + "updated_at", + "workspace_id", + "created_in_session" + ] + } + }, + "required": [ + "schema_version", + "task" + ] +} diff --git a/packages/core/scripts/gen-schemas.mjs b/packages/core/scripts/gen-schemas.mjs new file mode 100644 index 00000000..d4b6803f --- /dev/null +++ b/packages/core/scripts/gen-schemas.mjs @@ -0,0 +1,22 @@ +#!/usr/bin/env node +// Regenerate the committed JSON Schema artifacts in packages/core/schemas/ from +// the canonical Zod schemas. Run via `pnpm --filter @basou/core gen:schemas` +// (build first: it imports the built dist). The drift-guard test +// (`json-schema.test.ts`) fails CI if the committed files fall out of sync, so +// this is the one command to re-run after changing a schema. +import { mkdir, writeFile } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { buildJsonSchemas, serializeJsonSchema } from "../dist/index.js"; + +const here = dirname(fileURLToPath(import.meta.url)); +const outDir = join(here, "..", "schemas"); + +await mkdir(outDir, { recursive: true }); +const artifacts = buildJsonSchemas(); +for (const { name, schema } of artifacts) { + const file = join(outDir, `${name}.schema.json`); + await writeFile(file, serializeJsonSchema(schema)); + console.log(`wrote schemas/${name}.schema.json`); +} +console.log(`generated ${artifacts.length} JSON Schema artifact(s)`); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2e3a834a..39842e03 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -63,6 +63,7 @@ export type { Event, FileChangedEvent, GitSnapshotEvent, + JsonSchemaArtifact, Manifest, NoteAddedEvent, RiskLevel, @@ -89,11 +90,13 @@ export { ApprovalIdSchema, ApprovalSchema, ApprovalStatusSchema, + buildJsonSchemas, DecisionIdSchema, EventIdSchema, EventSchema, EventSourceSchema, IsoTimestampSchema, + JSON_SCHEMA_VERSION, ManifestSchema, RiskLevelSchema, SchemaVersionSchema, @@ -105,6 +108,7 @@ export { SessionSourceKindSchema, SessionStatusSchema, StatusSchema, + serializeJsonSchema, TaskIdSchema, TaskSchema, TaskStatusSchema, diff --git a/packages/core/src/schemas/index.ts b/packages/core/src/schemas/index.ts index d96066ab..2bc91d28 100644 --- a/packages/core/src/schemas/index.ts +++ b/packages/core/src/schemas/index.ts @@ -26,6 +26,8 @@ export type { TaskStatusChangedEvent, } from "./event.schema.js"; export { EventSchema } from "./event.schema.js"; +export type { JsonSchemaArtifact } from "./json-schema.js"; +export { buildJsonSchemas, JSON_SCHEMA_VERSION, serializeJsonSchema } from "./json-schema.js"; export type { Manifest } from "./manifest.schema.js"; export { ManifestSchema } from "./manifest.schema.js"; export type { diff --git a/packages/core/src/schemas/json-schema.test.ts b/packages/core/src/schemas/json-schema.test.ts new file mode 100644 index 00000000..7eee5281 --- /dev/null +++ b/packages/core/src/schemas/json-schema.test.ts @@ -0,0 +1,240 @@ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { Ajv2020 } from "ajv/dist/2020.js"; +// ajv-formats is CJS (`module.exports = fn`); under NodeNext the callable lives +// on `.default`. Its `FormatsPlugin` type is mis-resolved as non-callable here, +// so cast to the (verified-at-runtime) call signature rather than loosen tsc. +import * as ajvFormats from "ajv-formats"; + +const addFormats = ajvFormats.default as unknown as (ajv: Ajv2020) => void; + +import { describe, expect, it } from "vitest"; +import { buildJsonSchemas, JSON_SCHEMA_VERSION, serializeJsonSchema } from "./json-schema.js"; + +const schemasDir = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "schemas"); +const artifacts = buildJsonSchemas(); + +/** Find an event `oneOf` variant by its `type` const. */ +function eventVariant(type: string): Record { + const event = artifacts.find((a) => a.name === "event")?.schema as { + oneOf: Array<{ properties?: { type?: { const?: string } } } & Record>; + }; + const found = event.oneOf.find((v) => v.properties?.type?.const === type); + if (found === undefined) throw new Error(`no event variant ${type}`); + return found; +} + +describe("buildJsonSchemas", () => { + it("emits one artifact per on-disk document", () => { + expect(artifacts.map((a) => a.name).sort()).toEqual([ + "approval", + "event", + "manifest", + "session", + "session-import", + "status", + "task", + "task-index", + ]); + }); + + it("heads every artifact with the draft dialect, a versioned $id, title, and description", () => { + for (const { name, schema } of artifacts) { + expect(schema.$schema).toBe("https://json-schema.org/draft/2020-12/schema"); + expect(schema.$id).toBe( + `https://basou.dev/schemas/${JSON_SCHEMA_VERSION}/${name}.schema.json`, + ); + expect(typeof schema.title).toBe("string"); + expect(typeof schema.description).toBe("string"); + } + }); + + it("carries the ULID pattern on prefixed-id fields (metadata fidelity)", () => { + const session = artifacts.find((a) => a.name === "session")?.schema as { + properties: { session: { properties: { id: { pattern?: string } } } }; + }; + expect(session.properties.session.properties.id.pattern).toBe( + "^ses_[0-7][0-9A-HJKMNP-TV-Z]{25}$", + ); + }); + + it("emits the event document as a discriminated union (oneOf)", () => { + const event = artifacts.find((a) => a.name === "event")?.schema as { oneOf?: unknown[] }; + expect(Array.isArray(event.oneOf)).toBe(true); + expect(event.oneOf?.length ?? 0).toBeGreaterThan(1); + }); + + it("locks unknown keys only on strict event variants (input-mode semantics)", () => { + // adapter_output is `.strict()` (bars raw bodies); session_started is not, + // so it must allow additive fields rather than rejecting them. + expect(eventVariant("adapter_output").additionalProperties).toBe(false); + expect(eventVariant("session_started").additionalProperties).toBeUndefined(); + }); +}); + +describe("committed JSON Schema artifacts", () => { + // Drift guard: the committed schemas/*.json must match what the canonical Zod + // schemas generate today. If this fails, a schema changed without + // regenerating — run `pnpm --filter @basou/core gen:schemas`. + for (const { name, schema } of artifacts) { + it(`schemas/${name}.schema.json is in sync with the Zod source`, () => { + const committed = readFileSync(join(schemasDir, `${name}.schema.json`), "utf8"); + expect(committed).toBe(serializeJsonSchema(schema)); + }); + } +}); + +describe("emitted schemas validate real documents (ajv draft 2020-12)", () => { + const WS = "ws_01HXABCDEF1234567890ABCDEF"; + const SES = "ses_01HXABCDEF1234567890ABCDEF"; + const EVT = "evt_01HXABCDEF1234567890ABCDEF"; + const TASK = "task_01HXABCDEF1234567890ABCDEF"; + const APPR = "appr_01HXABCDEF1234567890ABCDEF"; + const ISO = "2026-05-10T00:00:00.000Z"; + + // One representative VALID document per emitted schema. + const samples: Record = { + manifest: { + schema_version: "0.1.0", + basou_version: "0.1.0", + workspace: { id: WS, name: "w", created_at: ISO, updated_at: ISO }, + project: {}, + capabilities: { enabled: [] }, + approval: { default_risk_level: "low" }, + adapters: { "claude-code": { enabled: false } }, + git: { events_log: "ignore" }, + }, + session: { + schema_version: "0.1.0", + session: { + id: SES, + workspace_id: WS, + source: { kind: "codex-import", version: "0.1.0" }, + started_at: ISO, + ended_at: ISO, + status: "completed", + working_directory: "/tmp", + invocation: { command: "codex", args: [], exit_code: null }, + related_files: [], + events_log: "events.jsonl", + }, + }, + event: { + schema_version: "0.1.0", + id: EVT, + session_id: SES, + occurred_at: ISO, + source: "codex-import", + type: "session_started", + }, + task: { + schema_version: "0.1.0", + task: { + id: TASK, + title: "t", + status: "planned", + created_at: ISO, + updated_at: ISO, + workspace_id: WS, + created_in_session: SES, + linked_sessions: [SES], + }, + }, + approval: { + schema_version: "0.1.0", + id: APPR, + session_id: SES, + created_at: ISO, + status: "pending", + risk_level: "low", + action: { kind: "command" }, + reason: "r", + }, + status: { + schema_version: "0.1.0", + generated_at: ISO, + workspace: { id: WS, name: "w", basou_version: "0.1.0" }, + directories_present: { + sessions: true, + tasks: true, + approvals_pending: true, + approvals_resolved: true, + logs: true, + raw: true, + tmp: true, + }, + }, + "task-index": { + schema_version: "0.1.0", + tasks: [{ id: TASK, status: "planned", updated_at: ISO }], + last_rebuilt_at: ISO, + }, + "session-import": { + schema_version: "0.1.0", + session: { + id: SES, + workspace_id: WS, + source: { kind: "claude-code-adapter", version: "0.1.0" }, + started_at: ISO, + status: "completed", + working_directory: "/tmp", + invocation: { command: "claude", args: [], exit_code: 0 }, + related_files: [], + }, + events: [ + { + schema_version: "0.1.0", + type: "session_started", + id: EVT, + session_id: SES, + occurred_at: ISO, + source: "claude-code-adapter", + }, + ], + }, + }; + + // One ajv instance; each schema registers under its unique $id. + const ajv = new Ajv2020({ strict: false }); + addFormats(ajv); + const validators = new Map(artifacts.map((a) => [a.name, ajv.compile(a.schema)])); + const validate = (name: string, doc: unknown): boolean => { + const fn = validators.get(name); + if (fn === undefined) throw new Error(`no validator for ${name}`); + const ok = fn(doc); + if (!ok) console.error(`${name} validation errors:`, fn.errors); + return ok; + }; + + for (const { name } of artifacts) { + it(`${name}.schema.json compiles and accepts a representative document`, () => { + expect(validate(name, samples[name])).toBe(true); + }); + } + + it("accepts a manifest that omits the defaulted events_log (input-mode optional)", () => { + const m = structuredClone(samples.manifest) as { git: { events_log?: string } }; + delete m.git.events_log; + expect(validate("manifest", m)).toBe(true); + }); + + it("accepts a session that omits defaulted events_log / related_files", () => { + const s = structuredClone(samples.session) as { + session: { events_log?: string; related_files?: unknown }; + }; + delete s.session.events_log; + delete s.session.related_files; + expect(validate("session", s)).toBe(true); + }); + + it("accepts an additive field on a non-strict event variant", () => { + expect(validate("event", { ...(samples.event as object), extra_field: true })).toBe(true); + }); + + it("rejects a malformed session id (the ULID pattern has teeth)", () => { + const s = structuredClone(samples.session) as { session: { id: string } }; + s.session.id = "ses_not-a-valid-ulid"; + expect(validate("session", s)).toBe(false); + }); +}); diff --git a/packages/core/src/schemas/json-schema.ts b/packages/core/src/schemas/json-schema.ts new file mode 100644 index 00000000..565e251d --- /dev/null +++ b/packages/core/src/schemas/json-schema.ts @@ -0,0 +1,137 @@ +import { z } from "zod"; +import { ApprovalSchema } from "./approval.schema.js"; +import { EventSchema } from "./event.schema.js"; +import { ManifestSchema } from "./manifest.schema.js"; +import { SessionSchema } from "./session.schema.js"; +import { SessionImportPayloadSchema } from "./session-import.schema.js"; +import { StatusSchema } from "./status.schema.js"; +import { TaskSchema } from "./task.schema.js"; +import { TaskIndexSchema } from "./task-index.schema.js"; + +/** + * Schema version of the on-disk Basou v0.1 formats these JSON Schemas describe. + * It tracks {@link SchemaVersionSchema} (the `schema_version` field), NOT the + * npm package version, so the `$id` URLs stay stable while the package moves. + */ +export const JSON_SCHEMA_VERSION = "0.1.0"; + +/** Base of every emitted schema's `$id`. The URL is a stable identifier; it + * need not resolve (serving the schemas on basou.dev is a separate concern). */ +const ID_BASE = `https://basou.dev/schemas/${JSON_SCHEMA_VERSION}`; + +/** JSON Schema draft the artifacts target (what `z.toJSONSchema` emits). */ +const JSON_SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema"; + +/** + * The on-disk Basou documents that get a published JSON Schema, keyed by the + * artifact basename (`.schema.json`). Each entry maps a `.basou/` file + * format to the Zod schema that is its single source of truth. + */ +const DOCUMENTS: ReadonlyArray<{ + name: string; + schema: z.ZodType; + title: string; + description: string; +}> = [ + { + name: "manifest", + schema: ManifestSchema, + title: "Basou Manifest", + description: "The `.basou/manifest.yaml` workspace manifest.", + }, + { + name: "session", + schema: SessionSchema, + title: "Basou Session", + description: "A `.basou/sessions//session.yaml` session record.", + }, + { + name: "event", + schema: EventSchema, + title: "Basou Event", + description: + "One line of a `.basou/sessions//events.jsonl` stream (a discriminated union over the event `type`).", + }, + { + name: "task", + schema: TaskSchema, + title: "Basou Task", + description: "The YAML front matter of a `.basou/tasks/.md` task document.", + }, + { + name: "approval", + schema: ApprovalSchema, + title: "Basou Approval", + description: "A `.basou/approvals/{pending,resolved}/.yaml` approval record.", + }, + { + name: "status", + schema: StatusSchema, + title: "Basou Status", + description: "The `.basou/status.json` workspace status snapshot.", + }, + { + name: "task-index", + schema: TaskIndexSchema, + title: "Basou Task Index", + description: "The `.basou/tasks/index.json` task lookup index.", + }, + { + name: "session-import", + schema: SessionImportPayloadSchema, + title: "Basou Session Import Payload", + description: + "The portable session payload consumed by `basou session import` (and produced by `basou session export`).", + }, +]; + +/** One emitted JSON Schema artifact. */ +export type JsonSchemaArtifact = { + /** Artifact basename without extension (e.g. `session`). */ + name: string; + /** The JSON Schema document (draft 2020-12). */ + schema: Record; +}; + +/** + * Build the published JSON Schema artifacts from the canonical Zod schemas. + * + * Pure: no disk or environment access. Each artifact is `z.toJSONSchema` of the + * document schema, re-headed with a stable `$id` / `title` / `description` (the + * draft `$schema` from zod is preserved). This is the single generator used by + * both the `gen:schemas` script (which writes the committed files) and the + * drift-guard test (which asserts the committed files still match), so the two + * can never disagree. + * + * Generated in `io: "input"` mode so the artifacts describe what a consumer + * AUTHORS on disk, not zod's parsed output: a field with a `.default()` (e.g. + * `events_log`) stays optional rather than `required`, and a non-strict object + * omits `additionalProperties: false` so additive fields are allowed. Only the + * `.strict()` event variants (e.g. `adapter_output`) keep + * `additionalProperties: false`, preserving their reject-unknown contract. + * + * Note: prefixed-id fields carry a representable `pattern` (see + * `createPrefixedIdSchema`); other refinement-only constraints are not + * expressible in JSON Schema and are intentionally omitted. + */ +export function buildJsonSchemas(): JsonSchemaArtifact[] { + return DOCUMENTS.map((doc) => { + const generated = z.toJSONSchema(doc.schema, { io: "input" }) as Record; + const { $schema, ...rest } = generated; + const schema: Record = { + $schema: typeof $schema === "string" ? $schema : JSON_SCHEMA_DIALECT, + $id: `${ID_BASE}/${doc.name}.schema.json`, + title: doc.title, + description: doc.description, + ...rest, + }; + return { name: doc.name, schema }; + }); +} + +/** Serialize an artifact's schema exactly as the committed file stores it + * (2-space indent, trailing newline) so the generator and the drift-guard test + * compare byte-for-byte. */ +export function serializeJsonSchema(schema: Record): string { + return `${JSON.stringify(schema, null, 2)}\n`; +} diff --git a/packages/core/src/schemas/shared.schema.ts b/packages/core/src/schemas/shared.schema.ts index 417d4371..a4e993c3 100644 --- a/packages/core/src/schemas/shared.schema.ts +++ b/packages/core/src/schemas/shared.schema.ts @@ -17,10 +17,25 @@ export const IsoTimestampSchema = z.string().datetime({ offset: true }); // Internal factory shared by every prefixed-ID schema. Not exported because // the public API surface should only expose the six fully-typed ID schemas. +// +// The `.refine` carries the real (ULID-aware) validation but is opaque to JSON +// Schema generation, so the `.meta` mirrors the prefix + ULID-body shape as a +// representable `pattern` (and a description). This is METADATA ONLY: it does +// not affect parsing — `isValidPrefixedId` still gates acceptance — it just +// lets `z.toJSONSchema` emit a faithful pattern for the published artifact. The +// pattern mirrors `ULID_BODY_REGEX` (leading 0-7, then 25 Crockford symbols +// excluding I/L/O/U); it is intentionally slightly looser than the library +// `isValid` check, matching the documented id shape. const createPrefixedIdSchema =

(prefix: P) => { const refiner = (value: string): value is PrefixedId

=> isValidPrefixedId(value) && value.startsWith(`${prefix}_`); - return z.string().refine(refiner, { message: `Expected ${prefix}_` }); + return z + .string() + .refine(refiner, { message: `Expected ${prefix}_` }) + .meta({ + pattern: `^${prefix}_[0-7][0-9A-HJKMNP-TV-Z]{25}$`, + description: `Basou ${prefix} id: \`${prefix}_\` followed by a 26-character Crockford Base32 ULID.`, + }); }; /** Workspace ID schema: validates `ws_<26-char ULID>`. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74e62a60..05612ebd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,13 @@ importers: zod: specifier: ^4.4.3 version: 4.4.3 + devDependencies: + ajv: + specifier: ^8.17.1 + version: 8.20.0 + ajv-formats: + specifier: ^3.0.1 + version: 3.0.1(ajv@8.20.0) packages/sdk: dependencies: @@ -599,6 +606,17 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -676,6 +694,12 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -697,6 +721,9 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -782,6 +809,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -1295,6 +1326,17 @@ snapshots: acorn@8.16.0: {} + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + any-promise@1.3.0: {} assertion-error@2.0.1: {} @@ -1397,6 +1439,10 @@ snapshots: expect-type@1.3.0: {} + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.2: {} + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -1412,6 +1458,8 @@ snapshots: joycon@3.1.1: {} + json-schema-traverse@1.0.0: {} + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -1476,6 +1524,8 @@ snapshots: readdirp@4.1.2: {} + require-from-string@2.0.2: {} + resolve-from@5.0.0: {} rollup@4.60.2: