import { Effect, Schema } from "effect" const NonEmptyString = Schema.String.pipe(Schema.check(Schema.isMinLength(1))) const EnvironmentName = NonEmptyString.pipe( Schema.check(Schema.isPattern(/^[A-Za-z_][A-Za-z0-9_]*$/)), ) const Description = Schema.optional(NonEmptyString) const IdPattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/ export const EntityId = NonEmptyString.pipe( Schema.check(Schema.isPattern(IdPattern)), ) export const RepositoryAlias = NonEmptyString.pipe( Schema.check(Schema.isPattern(IdPattern)), ) // Portable declarations must be usable after cloning the workbase elsewhere. // Local paths, file URLs, and credential-bearing HTTP URLs are intentionally // excluded; SSH usernames are identities and remain supported. export const RepositoryRemote = NonEmptyString.pipe( Schema.check( Schema.isPattern( /^(?!-)(?![a-zA-Z]:[\\/])(?!https?:\/\/[^/@\s]+@)(?![a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/@\s]*:[^/@\s]*@)(?:(?:https?|ssh|git):\/\/[^\s?#]+|(?![^\s]*::)(?:[^@\s/:]+@)?[a-zA-Z0-9_.][^@\s/:]*:(?!\/\/)[^\s?#]+)$/, ), ), ) export const RepositoryDeclaration = Schema.Struct({ remote: RepositoryRemote, postCheckoutCommand: Schema.optional(Schema.NonEmptyArray(NonEmptyString)), }) export const RepositoryReference = Schema.Struct({ repo: RepositoryAlias, ref: NonEmptyString, }) export const WorkStatus = Schema.Literals([ "open", "working", "delegated", "done", "dropped", ]) const IsoTimestamp = NonEmptyString.pipe( Schema.check( Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/), ), Schema.check( Schema.makeFilter( (value) => { const timestamp = Date.parse(value) return ( Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value ) }, { message: "Expected a canonical ISO-8601 timestamp" }, ), ), ) const GitCommit = Schema.String.pipe( Schema.check(Schema.isPattern(/^[a-f0-9]{40}$/)), ) export const DocumentRevision = Schema.String.pipe( Schema.check(Schema.isPattern(/^[a-f0-9]{64}$/)), ) const Url = NonEmptyString.pipe( Schema.check(Schema.isPattern(/^[a-zA-Z][a-zA-Z0-9+.-]*:/)), ) const GitHubPullRequestUrl = NonEmptyString.pipe( Schema.check( Schema.isPattern(/^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+\/?$/), ), ) export const PullRequestRecord = Schema.Struct({ provider: EntityId, repository: NonEmptyString, headRepository: Schema.optional(NonEmptyString), headBranch: Schema.optional(NonEmptyString), baseRepository: Schema.optional(NonEmptyString), baseBranch: Schema.optional(NonEmptyString), identifier: NonEmptyString, url: Url, state: Schema.Literals(["open", "closed", "merged"]), draft: Schema.Boolean, merged: Schema.Boolean, mergeable: Schema.optional(Schema.NullOr(Schema.Boolean)), }) export const CompletionRecord = Schema.Struct({ mode: Schema.Literal("non-pr"), completedAt: IsoTimestamp, summary: NonEmptyString, evidenceUrl: Schema.optional(Url), }) const DeliveryProvider = Schema.Struct({ provider: EntityId, remote: Schema.optional(NonEmptyString), createCommand: Schema.NonEmptyArray(NonEmptyString), queryCommand: Schema.NonEmptyArray(NonEmptyString), environment: Schema.optional(Schema.Record(EnvironmentName, Schema.String)), }) /** A user triage prompt, inline or in a file relative to its config file. */ const TriagePrompt = Schema.Struct({ prompt: Schema.optional(NonEmptyString), promptFile: Schema.optional(NonEmptyString), }) export const WorkbaseConfig = Schema.Struct({ version: Schema.Literal(2), vcs: Schema.optional(Schema.Literal("git")), autoArchive: Schema.optional(Schema.Boolean), repositories: Schema.optional( Schema.Record(RepositoryAlias, RepositoryDeclaration), ), chooserCommand: Schema.optional(Schema.NonEmptyArray(NonEmptyString)), branchNameCommand: Schema.optional(Schema.NonEmptyArray(NonEmptyString)), worktreeCreateCommand: Schema.optional(Schema.NonEmptyArray(NonEmptyString)), worktreeRemoveCommand: Schema.optional(Schema.NonEmptyArray(NonEmptyString)), agents: Schema.optional( Schema.Record( EntityId, Schema.Struct({ command: Schema.NonEmptyArray(NonEmptyString), autoCommand: Schema.optional(Schema.NonEmptyArray(NonEmptyString)), resumeCommand: Schema.optional(Schema.NonEmptyArray(NonEmptyString)), autoResumeCommand: Schema.optional( Schema.NonEmptyArray(NonEmptyString), ), environment: Schema.optional( Schema.Record(EnvironmentName, Schema.String), ), }), ), ), delivery: Schema.optional(DeliveryProvider), triage: Schema.optional(TriagePrompt), tickets: Schema.optional( Schema.Struct({ jira: Schema.optional( Schema.Struct({ /** Status name for suggested completion transitions (default: Done). */ doneStatus: Schema.optional(NonEmptyString), /** Status name for suggested reopen transitions (default: In Progress). */ reopenStatus: Schema.optional(NonEmptyString), }), ), }), ), }) export const LegacyWorkbaseRegistry = Schema.Struct({ version: Schema.Literal(1), workbases: Schema.Array(NonEmptyString), }) export const WorkbaseRegistration = Schema.Struct({ id: EntityId, name: Schema.optional(EntityId), path: NonEmptyString, }) export const WorkbaseRegistry = Schema.Struct({ version: Schema.Literal(2), workbases: Schema.Array(WorkbaseRegistration), defaultId: Schema.optional(EntityId), }) export const GlobalConfig = Schema.Struct({ agent: Schema.optional( Schema.Literals(["opencode2", "opencode", "pi", "claude"]), ), triage: Schema.optional( Schema.Struct({ ...TriagePrompt.fields, /** The OpenCode model for triage runs, as provider/model[#variant]. */ model: Schema.optional(NonEmptyString), }), ), }) export const Dependency = Schema.Struct({ id: EntityId, dependsOn: Schema.optional(Schema.Array(EntityId)), }) const ExecutionUnit = { repo: RepositoryAlias, repos: Schema.optional(Schema.Array(RepositoryReference)), branch: NonEmptyString, base: NonEmptyString, pr: Schema.NullOr(Schema.Union([GitHubPullRequestUrl, PullRequestRecord])), status: WorkStatus.pipe( Schema.withDecodingDefaultType(Effect.succeed("open" as const)), ), completion: Schema.optional(CompletionRecord), } export const TaskPurpose = Schema.Literals(["investigation", "implementation"]) export const TaskHandoffSource = Schema.Union([ Schema.Struct({ kind: Schema.Literal("task"), taskId: EntityId, }), Schema.Struct({ kind: Schema.Literal("phase"), taskId: EntityId, phaseId: EntityId, }), ]) export const TaskHandoff = Schema.Struct({ source: TaskHandoffSource, sourceRevision: DocumentRevision, }) const TaskMetadata = { purpose: Schema.optional(TaskPurpose), handoff: Schema.optional(TaskHandoff), } export const EpicFrontmatter = Schema.Struct({ ticketUrl: Url, description: Description, repos: Schema.NonEmptyArray(RepositoryReference), tasks: Schema.Array(Dependency), }) const SinglePhaseTaskFrontmatter = Schema.Struct({ ticketUrl: Schema.NullOr(Url), description: Description, epic: Schema.optional(EntityId), ...TaskMetadata, ...ExecutionUnit, }) const MultiPhaseTaskFrontmatter = Schema.Struct({ ticketUrl: Schema.NullOr(Url), description: Description, epic: Schema.optional(EntityId), ...TaskMetadata, phases: Schema.Array(Dependency), }) export const ReviewPullRequestSource = Schema.Struct({ kind: Schema.Literal("pull-request"), provider: Schema.Literal("github"), repository: NonEmptyString, identifier: NonEmptyString.pipe(Schema.check(Schema.isPattern(/^\d+$/))), url: GitHubPullRequestUrl, fetchRef: NonEmptyString, }).check( Schema.makeFilter( (source) => source.url === `https://github.com/${source.repository}/pull/${source.identifier}` && source.fetchRef === `refs/pull/${source.identifier}/head`, { message: "Pull request review source URL, repository, identifier, and fetch ref must agree", }, ), ) export const ReviewBranchSource = Schema.Struct({ kind: Schema.Literal("branch"), ref: NonEmptyString.pipe( Schema.check( Schema.isPattern( /^refs\/heads\/(?!HEAD$)(?!.*(?:\.\.|@\{|[ ~^:?*\[\\\]]))(?!.*\/\/)(?!.*(?:^|\/)\.)(?!.*\/$)(?!.*\.lock(?:\/|$))[A-Za-z0-9._\/-]+$/, ), ), ), }) export const ReviewSource = Schema.Union([ ReviewPullRequestSource, ReviewBranchSource, ]) export const ReviewRecord = Schema.Struct({ repo: RepositoryAlias, source: ReviewSource, commit: GitCommit, refreshedAt: IsoTimestamp, }) const ReviewTaskFrontmatter = Schema.Struct({ ticketUrl: Schema.NullOr(Url), description: Description, epic: Schema.optional(EntityId), ...TaskMetadata, review: ReviewRecord, status: WorkStatus.pipe( Schema.withDecodingDefaultType(Effect.succeed("open" as const)), ), completion: Schema.optional(CompletionRecord), }) export const TaskFrontmatter = Schema.Union([ SinglePhaseTaskFrontmatter, MultiPhaseTaskFrontmatter, ReviewTaskFrontmatter, ]) export const PhaseFrontmatter = Schema.Struct({ description: Description, ...ExecutionUnit, }) export type WorkbaseConfig = Schema.Schema.Type export type WorkbaseRegistry = Schema.Schema.Type export type WorkbaseRegistration = Schema.Schema.Type< typeof WorkbaseRegistration > export type Dependency = Schema.Schema.Type export type RepositoryReference = Schema.Schema.Type export type RepositoryDeclaration = Schema.Schema.Type< typeof RepositoryDeclaration > export type WorkStatus = Schema.Schema.Type export type PullRequestRecord = Schema.Schema.Type export type ReviewSource = Schema.Schema.Type export type ReviewRecord = Schema.Schema.Type export type CompletionRecord = Schema.Schema.Type export type TaskPurpose = Schema.Schema.Type export type TaskHandoffSource = Schema.Schema.Type export type TaskHandoff = Schema.Schema.Type export type EpicFrontmatter = Schema.Schema.Type export type TaskFrontmatter = Schema.Schema.Type export type PhaseFrontmatter = Schema.Schema.Type