session

package
v0.0.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 3, 2026 License: Apache-2.0 Imports: 33 Imported by: 0

Documentation

Index

Constants

View Source
const ADRSubdir = "docs/adr"
View Source
const (
	NavigatorWidth = 60
)

Variables

View Source
var BaseAllowedTools = []string{
	"Read",
	"Write",
	"Bash(ls:*)",
	"Bash(wc:*)",
	"Bash(find:*)",
	"Bash(echo:*)",
	"Bash(sort:*)",
	"Bash(grep:*)",
	"Bash(awk:*)",
	"Bash(sed:*)",
	"Bash(head:*)",
	"Bash(cat:*)",
}

Base tools (e.g. filesystem access, basic bash) that are generally useful and safe to enable by default for most workflows.

View Source
var ErrSpecNoImplementationSteps = errors.New("spec: no implementation steps after filtering")

ErrSpecNoImplementationSteps indicates that a wave had no implementation-oriented actions after filtering issue-management types, so no spec D-Mail was generated.

View Source
var GHAllowedTools = []string{
	"Bash(git:*)",
	"Bash(gh:*)",
	"WebFetch(domain:github.com)",
	"WebFetch(domain:raw.githubusercontent.com)",
}

GitHub CLI tools (git, gh) and GitHub WebFetch tools that sightjack commonly uses for GitHub-related tasks.

View Source
var LinearMCPAllowedTools = []string{
	"mcp__linear__list_issues",
	"mcp__linear__get_issue",
	"mcp__linear__create_issue",
	"mcp__linear__update_issue",
	"mcp__linear__list_issue_statuses",
	"mcp__linear__get_issue_status",
	"mcp__linear__list_issue_labels",
	"mcp__linear__create_issue_label",
	"mcp__linear__list_comments",
	"mcp__linear__create_comment",
	"mcp__linear__list_projects",
	"mcp__linear__get_project",
	"mcp__linear__save_project",
	"mcp__linear__list_project_labels",
	"mcp__linear__list_teams",
	"mcp__linear__get_team",
	"mcp__linear__list_users",
	"mcp__linear__get_user",
	"mcp__linear__list_cycles",

	"mcp__linear__search_documentation",
}

LinearMCPAllowedTools lists the official Linear MCP server tools that sightjack needs. Passing this via WithAllowedTools prevents context explosion from unrelated plugins loading hundreds of tool definitions (see anthropics/claude-code#25857).

View Source
var WithAllowedTools = port.WithAllowedTools

WithAllowedTools restricts the tools available to the Claude model.

View Source
var WithConfigBase = port.WithConfigBase

WithConfigBase sets the base directory for resolving stateDir settings.

View Source
var WithWorkDir = port.WithWorkDir

WithWorkDir sets the working directory for the Claude subprocess.

Functions

func ADRDir

func ADRDir(baseDir string) string

ADRDir returns the ADR directory path under baseDir.

func AllowedToolsForMode added in v0.0.11

func AllowedToolsForMode(mode domain.TrackingMode) []string

AllowedToolsForMode returns the appropriate tool list based on tracking mode. Wave mode: base + GitHub tools only (no Linear MCP). Linear mode: base + GitHub + Linear MCP tools.

func ArchitectDiscussFileName

func ArchitectDiscussFileName(wave domain.Wave) string

ArchitectDiscussFileName returns the output filename for an architect discussion.

func BuildApprover

func BuildApprover(cfg domain.ApproverConfig, input io.Reader, promptOut io.Writer) port.Approver

BuildApprover creates the appropriate Approver based on config. Priority: AutoApprove → CmdApprover → StdinApprover.

func BuildNextGenPrompt

func BuildNextGenPrompt(cfg *domain.Config, scanDir string, completedWave domain.Wave, cluster domain.ClusterScanResult, completedWaves []domain.Wave, existingADRs []domain.ExistingADR, rejectedActions []domain.WaveAction, strictness string, feedback []*DMail, reports []*DMail) (string, error)

BuildNextGenPrompt constructs the prompt for post-completion wave generation.

func BuildNotifier

func BuildNotifier(gate domain.GateConfig) port.Notifier

BuildNotifier creates the appropriate Notifier based on config. If NotifyCmd is set, uses CmdNotifier. Otherwise uses LocalNotifier (OS-native).

func BuildReviewFixPrompt

func BuildReviewFixPrompt(branch string, comments string) string

BuildReviewFixPrompt creates a focused prompt for fixing review comments.

func CanResume

func CanResume(baseDir string, state *domain.SessionState) bool

CanResume checks whether a saved session state supports resumption. It returns false when the cached ScanResult path is empty (e.g. v0.4 state files) or the file no longer exists on disk. baseDir is used to resolve relative ScanResultPaths stored in newer events.

func Center

func Center(s string, width int) string

func CheckConfig

func CheckConfig(configPath string) domain.DoctorCheck

CheckConfig validates that the config file exists and can be loaded.

func CheckContextBudget added in v0.0.5

func CheckContextBudget(streamJSON string, baseDir string) domain.DoctorCheck

CheckContextBudget parses stream-json output from a Claude CLI invocation and estimates context consumption by category.

func CheckEventStore added in v0.0.3

func CheckEventStore(baseDir string) domain.DoctorCheck

CheckEventStore validates the event store directory structure and JSONL integrity. Delegates to eventsource.ValidateStore for the actual file-level checks. Returns CheckSkip if the events directory does not exist yet.

func CheckSkills

func CheckSkills(baseDir string) domain.DoctorCheck

CheckSkills verifies that SKILL.md files exist under .siren/skills/ and that their frontmatter contains a dmail-schema-version field.

func CheckStateDir

func CheckStateDir(baseDir string, repair bool) domain.DoctorCheck

CheckStateDir verifies that the .siren/ state directory exists and is writable. When repair is true and the directory is missing, it creates the directory and returns CheckFixed.

func CheckTool

func CheckTool(ctx context.Context, name string) domain.DoctorCheck

CheckTool verifies that a CLI tool is installed and executable. It runs `<tool> --version` to confirm functionality.

func ClaudeSettingsExists added in v0.0.12

func ClaudeSettingsExists(baseDir string) bool

ClaudeSettingsExists reports whether .claude/settings.json exists.

func ClaudeSettingsPath added in v0.0.12

func ClaudeSettingsPath(baseDir string) string

ClaudeSettingsPath returns the path to the .claude/settings.json file under the tool state directory.

func ClearArchitectOutput

func ClearArchitectOutput(scanDir string, wave domain.Wave)

ClearArchitectOutput removes any existing architect output file to prevent stale results from a prior discuss round being parsed if Claude fails to write a new file.

func ClearNextgenOutput

func ClearNextgenOutput(scanDir string, wave domain.Wave)

ClearNextgenOutput removes any existing nextgen output file.

func ClearScribeOutput

func ClearScribeOutput(scanDir string, wave domain.Wave)

ClearScribeOutput removes any existing scribe output file to prevent stale results from a prior run being parsed if Claude fails to write a new file.

func ClusterLabel added in v0.0.11

func ClusterLabel(name string, issueCount int, completeness float64, maxWidth int) string

ClusterLabel builds the display label for a cluster row in the matrix navigator. Shows "[ok]" for complete clusters (completeness >= 0.99 and zero issues).

func CompletedWaves

func CompletedWaves(waves []domain.Wave) []domain.Wave

CompletedWaves filters waves to only those with "completed" status.

func ComposeDMail

func ComposeDMail(ctx context.Context, store port.OutboxStore, mail *DMail) error

ComposeDMail stages a d-mail via the transactional outbox store, then flushes it to archive/ and outbox/ using atomic file writes.

func ComposeFeedback

func ComposeFeedback(ctx context.Context, store port.OutboxStore, wave domain.Wave, result *domain.WaveApplyResult) error

ComposeFeedback stages a report D-Mail for amadeus consumption. Called after successful wave apply to complete the sightjack → amadeus feedback loop (O2). Uses DMailReport kind because sightjack's sendable contract only produces specification and report.

func ComposeReport

func ComposeReport(ctx context.Context, store port.OutboxStore, wave domain.Wave, result *domain.WaveApplyResult) error

ComposeReport creates and sends a report d-mail for a completed wave.

func ComposeSpecification

func ComposeSpecification(ctx context.Context, store port.OutboxStore, wave domain.Wave, mode ...domain.TrackingMode) error

ComposeSpecification creates and sends a specification d-mail for an approved wave. In wave mode, issue management actions are filtered out — only implementation-oriented actions (implement, fix, verify, etc.) are included as steps. If no implementation actions remain, the spec D-Mail is not generated.

func ComposeStallEscalation added in v0.0.11

func ComposeStallEscalation(ctx context.Context, store port.OutboxStore, wave domain.Wave, errors []string, reason string) error

ComposeStallEscalation stages a stall-escalation D-Mail in the outbox. Called when a wave is detected as stalled due to repeated structural errors.

func CountADRFiles

func CountADRFiles(adrDir string) int

CountADRFiles returns the number of files matching NNNN-*.md in adrDir. Returns 0 if the directory is empty or does not exist.

func DMailIdempotencyKey

func DMailIdempotencyKey(mail *DMail) string

DMailIdempotencyKey computes a SHA256 content-based idempotency key from the core fields of a DMail (name, kind, description, body).

func DMailName

func DMailName(prefix, waveKey string) string

DMailName generates a collision-safe d-mail name with tool prefix and UUID suffix. Format: sj-{kind}-{sanitized-key}_{uuid8} Example: DMailName("spec", "error-handling:w1") → "sj-spec-error-handling-w1_a3f2b7c4"

func DeleteArchiveFiles

func DeleteArchiveFiles(baseDir string, files []string) ([]string, error)

DeleteArchiveFiles deletes the named files from .siren/archive/. Files that no longer exist are silently skipped (ErrNotExist is not an error). Returns the list of filenames that were processed.

func DisplayADRConflicts

func DisplayADRConflicts(w io.Writer, conflicts []domain.ADRConflict)

DisplayADRConflicts shows potential conflicts between new and existing ADRs.

func DisplayArchitectResponse

func DisplayArchitectResponse(w io.Writer, resp *domain.ArchitectResponse)

DisplayArchitectResponse shows the architect's analysis and any wave modifications.

func DisplayCompletedWaveActions

func DisplayCompletedWaveActions(w io.Writer, wave domain.Wave)

DisplayCompletedWaveActions shows the actions that were applied in a completed wave.

func DisplayRippleEffects

func DisplayRippleEffects(w io.Writer, ripples []domain.Ripple)

DisplayRippleEffects shows cross-cluster effects after a wave is applied.

func DisplayScribeResponse

func DisplayScribeResponse(w io.Writer, resp *domain.ScribeResponse)

DisplayScribeResponse shows the scribe's ADR generation result.

func DisplayShibitoWarnings

func DisplayShibitoWarnings(w io.Writer, warnings []domain.ShibitoWarning)

DisplayShibitoWarnings shows shibito resurrection detection warnings.

func DisplayWaveCompletion

func DisplayWaveCompletion(w io.Writer, wave domain.Wave, ripples []domain.Ripple, overallCompleteness float64, newWavesAvailable int)

DisplayWaveCompletion shows a rich wave completion summary with grouped ripple effects.

func DisplayWidth

func DisplayWidth(s string) int

func EnsureMailDirs

func EnsureMailDirs(baseDir string) error

EnsureMailDirs creates inbox/, outbox/, archive/ under .siren/.

func EnsureRunDir added in v0.0.10

func EnsureRunDir(stateDir string) error

EnsureRunDir creates the .run/ directory under stateDir if it does not exist. Call once before opening stores that write to .run/ (idempotent).

func EnsureScanDir

func EnsureScanDir(baseDir, sessionID string) (string, error)

EnsureScanDir creates the scan directory for a session and returns its path. It also writes .siren/.gitignore as a best-effort side effect.

func EnterSession added in v0.0.12

func EnterSession(ctx context.Context, cfg EnterConfig) error

EnterSession launches the provider CLI in interactive mode with --resume. stdin/stdout/stderr are connected to the user's TTY. This function does NOT use ClaudeAdapter — it hands control to the user's terminal.

func EventStorePath

func EventStorePath(baseDir, sessionID string) string

EventStorePath returns the filesystem path for a session's event store.

func ExtractMeta added in v0.0.8

func ExtractMeta(filePath, stateDir, tool string) domain.IndexEntry

ExtractMeta populates a domain.IndexEntry from a markdown file, extracting operation type, issue ID, status, timestamp, and summary.

func ExtractStreamResult added in v0.0.5

func ExtractStreamResult(streamJSON string) string

ExtractStreamResult parses stream-json output and returns the "result" field from the result message. Used to reuse inference check output for inference validation.

func ExtractSummary added in v0.0.8

func ExtractSummary(filePath string) string

ExtractSummary returns the first markdown heading (# ...) found after skipping optional YAML frontmatter. If no heading is found, it returns the first non-empty line. The result is truncated to maxSummaryLen characters.

func FeedbackBody

func FeedbackBody(wave domain.Wave, result *domain.WaveApplyResult) string

FeedbackBody formats wave apply results as Markdown body for a feedback d-mail. Distinct from ReportBody: uses "Wave Feedback" heading to differentiate the sightjack → amadeus feedback loop (O2) from the standard report d-mail.

func FormatADRConflictSection added in v0.0.11

func FormatADRConflictSection(resp *domain.ScribeResponse) string

FormatADRConflictSection formats the conflicts from a ScribeResponse into a Markdown section suitable for appending to the ADR content. Returns an empty string if there are no conflicts.

func FormatFeedbackForPrompt

func FormatFeedbackForPrompt(feedback []*DMail) string

FormatFeedbackForPrompt formats feedback d-mails as a Markdown section suitable for injection into wave generation prompts. HIGH severity items are emphasized with a ### [HIGH] header. Returns "" for nil or empty input.

func FormatReportsForPrompt

func FormatReportsForPrompt(reports []*DMail) string

FormatReportsForPrompt formats report d-mails as a Markdown section suitable for injection into wave generation prompts. Reports come from other tools (e.g. amadeus check results) and provide cross-tool context. Returns "" for nil or empty input.

func GenerateClaudeSettings added in v0.0.12

func GenerateClaudeSettings(baseDir string, force bool) (string, error)

GenerateClaudeSettings creates a minimal .claude/settings.json that disables all plugins for Claude subprocess isolation.

func GenerateMCPConfig added in v0.0.12

func GenerateMCPConfig(baseDir string, mode domain.TrackingMode, force bool) (string, error)

GenerateMCPConfig creates a .mcp.json file. Linear mode includes Linear MCP server. Wave mode produces empty config. If a legacy .run/mcp-config.json exists and the new file does not, the legacy content is migrated forward to preserve custom MCP server entries.

func GenerateNextWaves

func GenerateNextWaves(ctx context.Context, cfg *domain.Config, scanDir string, completedWave domain.Wave, cluster domain.ClusterScanResult, completedWaves []domain.Wave, existingADRs []domain.ExistingADR, rejectedActions []domain.WaveAction, strictness string, feedback []*DMail, reports []*DMail, runner port.ClaudeRunner, logger domain.Logger) ([]domain.Wave, error)

GenerateNextWaves executes post-completion wave generation for a cluster.

func GenerateNextWavesDryRun

func GenerateNextWavesDryRun(cfg *domain.Config, scanDir string, completedWave domain.Wave, cluster domain.ClusterScanResult, completedWaves []domain.Wave, existingADRs []domain.ExistingADR, rejectedActions []domain.WaveAction, strictness string, feedback []*DMail, reports []*DMail, logger domain.Logger) error

GenerateNextWavesDryRun saves the nextgen prompt to a file instead of executing Claude.

func InstallSkills

func InstallSkills(baseDir string, skillsFS fs.FS, logger domain.Logger) error

InstallSkills copies embedded skill templates into baseDir/.siren/skills/. Existing files are overwritten when content differs (idempotent). Directories are created as needed. Logs to logger when a file is updated.

func ListDMail

func ListDMail(baseDir, sub string) ([]string, error)

ListDMail returns all .md filenames in the given mail subdirectory.

func ListExpiredArchive

func ListExpiredArchive(baseDir string, days int, logger domain.Logger) ([]string, error)

ListExpiredArchive returns filenames in .siren/archive/ whose mtime exceeds the given number of days. Only .md files are considered. Returns an empty slice (not error) when the archive directory does not exist.

func ListExpiredEventFiles

func ListExpiredEventFiles(ctx context.Context, baseDir string, days int) ([]string, error)

ListExpiredEventFiles returns event files older than the retention threshold.

func LoadAllEvents

func LoadAllEvents(ctx context.Context, baseDir string) ([]domain.Event, error)

LoadAllEvents aggregates events from all session stores.

func LoadConfig

func LoadConfig(path string) (*domain.Config, error)

LoadConfig reads a YAML config file and returns a Config with defaults applied for any fields not specified in the file.

func LoadLatestResumableState

func LoadLatestResumableState(ctx context.Context, baseDir string, match func(*domain.SessionState) bool) (*domain.SessionState, string, error)

LoadLatestResumableState loads the latest session state that matches the predicate.

func LoadLatestState

func LoadLatestState(ctx context.Context, baseDir string) (*domain.SessionState, string, error)

LoadLatestState loads the most recent session state from event data.

func LoadScanResult

func LoadScanResult(path string) (*domain.ScanResult, error)

LoadScanResult reads a cached ScanResult from a JSON file.

func MCPConfigExists added in v0.0.12

func MCPConfigExists(baseDir string) (bool, error)

MCPConfigExists reports whether the .mcp.json file exists and is valid JSON.

func MCPConfigPath added in v0.0.12

func MCPConfigPath(baseDir string) string

MCPConfigPath returns the path to the .mcp.json file.

func MarshalDMail

func MarshalDMail(mail *DMail) ([]byte, error)

MarshalDMail serializes a DMail to YAML frontmatter + Markdown body. Automatically injects an idempotency_key into metadata based on content hash.

func MergeScanResults

func MergeScanResults(clusters []domain.ClusterScanResult, shibitoWarnings []domain.ShibitoWarning, scanWarnings []string) domain.ScanResult

MergeScanResults combines per-cluster deep scan results into a single domain.ScanResult. shibitoWarnings are propagated from the Pass 1 classify result.

func MonitorInbox

func MonitorInbox(ctx context.Context, baseDir string, logger domain.Logger) (<-chan *DMail, error)

MonitorInbox starts monitoring the inbox directory for feedback and convergence d-mails. It first drains existing files (initial scan), then watches for new files via fsnotify. Each d-mail is received (archived + removed from inbox). Feedback, convergence, and report d-mails are sent to the returned channel. Consumer-side dedup is applied (MY-271). The channel is closed when the context is cancelled.

func NeedsMoreWaves

func NeedsMoreWaves(cluster domain.ClusterScanResult, waves []domain.Wave) bool

NeedsMoreWaves returns true when post-completion wave generation should run. Delegates to domain.NeedsMoreWaves.

func NewBranchResolver added in v0.0.11

func NewBranchResolver() port.BranchResolver

NewBranchResolver creates a BranchResolver.

func NewEventStore

func NewEventStore(stateDir string, logger domain.Logger) port.EventStore

NewEventStore creates an event store rooted at stateDir. eventsource is the event persistence adapter (AWS Event Sourcing pattern). cmd layer should use this instead of importing eventsource directly (ADR S0008).

func NewOnceRunner added in v0.0.12

func NewOnceRunner(cfg *domain.Config, baseDir string, logger domain.Logger) port.ClaudeRunner

NewOnceRunner creates a provider-tracked runner WITHOUT retry. Use this for operations with side-effects that must not be retried (e.g. wave apply, classify with label mutations). baseDir is used to resolve the session tracking database path.

func NewReviewExecutor added in v0.0.11

func NewReviewExecutor(dir string) port.ReviewExecutor

NewReviewExecutor creates a ReviewExecutor for the given working directory.

func NewReviewFixRunner added in v0.0.11

func NewReviewFixRunner(runner port.ClaudeRunner, branch port.BranchResolver, dir string, logger domain.Logger) port.ReviewFixRunner

NewReviewFixRunner creates a ReviewFixRunner.

func NewSessionRecorder

func NewSessionRecorder(stateDir, sessionID string, logger domain.Logger) (port.Recorder, error)

NewSessionRecorder creates a recorder for the given session.

func NewSpanEventStore

func NewSpanEventStore(inner port.EventStore) port.EventStore

NewSpanEventStore creates a span-instrumented EventStore wrapper.

func NewTrackedRunner added in v0.0.12

func NewTrackedRunner(cfg *domain.Config, baseDir string, logger domain.Logger) port.ClaudeRunner

NewTrackedRunner creates a provider-tracked runner with retry, session tracking, and circuit breaker protection. This is the standard way to create a runner for session-level operations that should be retried on transient failures. baseDir is used to resolve the session tracking database path.

func NextADRNumber

func NextADRNumber(adrDir string) (int, error)

NextADRNumber scans adrDir for files matching NNNN-*.md and returns max(NNNN)+1. Returns 1 if the directory is empty or does not exist.

func NextgenFileName

func NextgenFileName(wave domain.Wave) string

NextgenFileName returns the output filename for a nextgen wave generation run.

func NormalizeJSONFile

func NormalizeJSONFile(path string) error

NormalizeJSONFile reads a JSON file, re-encodes it with raw UTF-8 (no \uXXXX for non-ASCII characters), and writes it back. This normalizes Claude's output so that human-readable characters (e.g. Japanese) appear as raw UTF-8 instead of escape sequences.

It also handles non-JSON text wrapping that Claude may produce: markdown code blocks (```json ... ```) and natural language prefixes/suffixes around JSON content.

func NormalizeScribeResult

func NormalizeScribeResult(result *domain.ScribeResponse, adrID string, logger domain.Logger)

NormalizeScribeResult ensures the parsed ADRID matches the filesystem-derived adrID. Claude may return a mismatched or empty adr_id; the generated ID is authoritative because it is used to name the ADR file on disk.

func OverrideFindSkillsRefDir added in v0.0.7

func OverrideFindSkillsRefDir(fn func(string) string) func()

OverrideFindSkillsRefDir replaces the skills-ref directory finder for testing.

func OverrideGenerateSkills added in v0.0.7

func OverrideGenerateSkills(fn func(string, domain.Logger) error) func()

OverrideGenerateSkills replaces the skills generator for testing.

func OverrideInstallSkillsRef added in v0.0.7

func OverrideInstallSkillsRef(fn func() error) func()

OverrideInstallSkillsRef replaces the skills-ref installer for testing.

func OverrideLookPath added in v0.0.4

func OverrideLookPath(fn func(cmd string) (string, error)) func()

OverrideLookPath replaces the path lookup function for testing and returns a cleanup function.

func OverrideNewCmd

func OverrideNewCmd(fn func(ctx context.Context, name string, args ...string) *exec.Cmd) func()

OverrideNewCmd replaces the command constructor for testing and returns a cleanup function. Exported for cross-package test injection (root test suite).

func PadRight

func PadRight(s string, width int) string

func ParseArchitectResult

func ParseArchitectResult(path string) (*domain.ArchitectResponse, error)

ParseArchitectResult reads and parses an architect response JSON file.

func ParseClassifyResult

func ParseClassifyResult(path string) (*domain.ClassifyResult, error)

ParseClassifyResult reads and parses the classify.json output file.

func ParseClusterScanResult

func ParseClusterScanResult(path string) (*domain.ClusterScanResult, error)

ParseClusterScanResult reads and parses a cluster_{name}.json output file.

func ParseNextGenResult

func ParseNextGenResult(path string) (*domain.NextGenResult, error)

ParseNextGenResult reads and parses a nextgen wave generation result JSON file.

func ParseScribeResult

func ParseScribeResult(path string) (*domain.ScribeResponse, error)

ParseScribeResult reads and parses a scribe response JSON file.

func ParseWaveApplyResult

func ParseWaveApplyResult(path string) (*domain.WaveApplyResult, error)

ParseWaveApplyResult reads and parses an apply_{wave_id}.json output file.

func ParseWaveGenerateResult

func ParseWaveGenerateResult(path string) (*domain.WaveGenerateResult, error)

ParseWaveGenerateResult reads and parses a wave_{name}.json output file.

func PreflightCheck

func PreflightCheck(binaries ...string) error

PreflightCheck verifies that required binaries are available in PATH. Uses platform.LookPathShell which handles shell-aware lookups.

func PromptCompletedWaveSelection

func PromptCompletedWaveSelection(ctx context.Context, w io.Writer, s *bufio.Scanner, completed []domain.Wave) (domain.Wave, error)

PromptCompletedWaveSelection displays completed waves and reads the user's choice.

func PromptDiscussTopic

func PromptDiscussTopic(ctx context.Context, w io.Writer, s *bufio.Scanner) (string, error)

PromptDiscussTopic reads a free-text discussion topic from the user.

func PromptResume

func PromptResume(ctx context.Context, w io.Writer, s *bufio.Scanner, baseDir string, state *domain.SessionState) (domain.ResumeChoice, error)

PromptResume displays previous session info and asks the user to resume, start new, or re-scan.

func PromptSelectiveApproval

func PromptSelectiveApproval(ctx context.Context, w io.Writer, s *bufio.Scanner, wave domain.Wave) ([]domain.WaveAction, []domain.WaveAction, error)

PromptSelectiveApproval displays wave actions with toggle checkboxes. Returns approved and rejected action lists.

func PromptWaveApproval

func PromptWaveApproval(ctx context.Context, w io.Writer, s *bufio.Scanner, wave domain.Wave) (domain.ApprovalChoice, error)

PromptWaveApproval displays a wave proposal and reads approve/reject/discuss.

func PromptWaveSelection

func PromptWaveSelection(ctx context.Context, w io.Writer, s *bufio.Scanner, waves []domain.Wave) (domain.Wave, error)

PromptWaveSelection displays available waves and reads the user's choice.

func PruneArchive

func PruneArchive(baseDir string, days int, logger domain.Logger) ([]string, error)

PruneArchive deletes expired .md files from .siren/archive/ and returns the list of deleted filenames. Uses the same criteria as ListExpiredArchive. Returns an empty slice (not error) when the archive directory does not exist.

func PruneEventFiles

func PruneEventFiles(ctx context.Context, baseDir string, files []string) ([]string, error)

PruneEventFiles deletes the specified event files.

func PruneFlushedOutbox

func PruneFlushedOutbox(ctx context.Context, baseDir string) (int, error)

PruneFlushedOutbox opens the outbox DB, deletes flushed rows, runs incremental vacuum, and closes the store. Returns 0 if the DB does not exist.

func ReadCLAUDEMD added in v0.0.3

func ReadCLAUDEMD(scanDir string) string

ReadCLAUDEMD searches for CLAUDE.md starting from scanDir and walking up to 3 levels. Returns empty string if not found.

func ReadExistingADRs

func ReadExistingADRs(adrDir string) ([]domain.ExistingADR, error)

ReadExistingADRs reads all NNNN-*.md files from adrDir and returns their content. Returns empty slice if directory doesn't exist or is empty.

func ReadyIssueIDs

func ReadyIssueIDs(waves []domain.Wave) []string

ReadyIssueIDs returns issue IDs where ALL waves targeting them are completed. Delegates to domain.ReadyIssueIDs.

func RecordScanState

func RecordScanState(baseDir, sessionID string, result *domain.ScanResult, cfg *domain.Config, emitter port.SessionEventEmitter, scanTime time.Time, logger domain.Logger) string

RecordScanState caches the scan result and records session start + scan completed events via the SessionAggregate. This is the single authoritative path for scan state persistence — both the scan command and the run session converge here.

Returns the cached scan result path for downstream use (e.g. interactive loop).

func RenderADRFromDiscuss

func RenderADRFromDiscuss(dr domain.DiscussResult, adrNum int) string

RenderADRFromDiscuss generates an ADR Markdown document from a domain.DiscussResult. This is a pure transformer — no Claude invocation needed.

func RenderMatrixNavigator

func RenderMatrixNavigator(result *domain.ScanResult, projectName string, waves []domain.Wave, adrCount int, lastScanned *time.Time, strictnessLevel string, shibitoCount int) string

RenderMatrixNavigator renders the Link Navigator as a Pure ASCII matrix grid. Wave status symbols: [ ] available [x] locked [=] completed [?] unknown

func RenderNavigator

func RenderNavigator(result *domain.ScanResult, projectName string) string

RenderNavigator produces an ASCII Link Navigator display inspired by the PS2 game SIREN's sight-jack interface. It visualises cluster completeness in a fixed-width text matrix.

func RenderProgressBar

func RenderProgressBar(current float64, width int) string

RenderProgressBar produces an ASCII progress bar: [====....] NN%

func ReportBody

func ReportBody(wave domain.Wave, result *domain.WaveApplyResult) string

ReportBody formats wave apply results as Markdown body for a report d-mail.

func RescanCore added in v0.0.12

func RescanCore(ctx context.Context, cfg *domain.Config, baseDir, sessionID string,
	oldWaves []domain.Wave, oldCompleted map[string]bool,
	runner port.ClaudeRunner, onceRunner port.ClaudeRunner,
	emitter port.SessionEventEmitter, out io.Writer, logger domain.Logger,
) (scanDir, scanResultPath string, scanResult *domain.ScanResult,
	waves []domain.Wave, completed map[string]bool,
	adrCount int, scanTime time.Time, err error)

RescanCore performs the scan+wavegen+merge cycle without session setup (D-Mail monitoring, convergence gate, outbox). It is the reusable core shared by RunRescanSession (cmd-layer rescan) and the in-loop auto-rescan triggered by design-feedback D-Mail arrival.

func ResolveMCPConfigPath added in v0.0.12

func ResolveMCPConfigPath(baseDir string) string

ResolveMCPConfigPath returns the active MCP config path, preferring the new .mcp.json location and falling back to the legacy .run/mcp-config.json.

func ResumeScanDir

func ResumeScanDir(state *domain.SessionState, baseDir string) string

ResumeScanDir returns the scan directory for a resumed session. It derives the directory from state.ScanResultPath when available, preserving the original path even if the directory layout has changed (e.g. .siren/scans/ → .siren/.run/). Falls back to ScanDir() when ScanResultPath is empty.

func ResumeSession

func ResumeSession(baseDir string, state *domain.SessionState) (*domain.ScanResult, []domain.Wave, map[string]bool, int, error)

ResumeSession loads a previous session's state and cached scan result, restoring waves and completed map for the interactive loop.

func RunArchitectDiscuss

func RunArchitectDiscuss(ctx context.Context, cfg *domain.Config, scanDir string, wave domain.Wave, topic string, strictness string, out io.Writer, runner port.ClaudeRunner, logger domain.Logger) (*domain.ArchitectResponse, error)

RunArchitectDiscuss executes a single-turn architect discussion via Claude subprocess.

func RunArchitectDiscussDryRun

func RunArchitectDiscussDryRun(cfg *domain.Config, scanDir string, wave domain.Wave, topic string, strictness string, logger domain.Logger) error

RunArchitectDiscussDryRun saves the architect prompt to a file instead of executing Claude.

func RunAutoDiscuss added in v0.0.3

func RunAutoDiscuss(ctx context.Context, cfg *domain.Config, scanDir string,
	wave domain.Wave, feedback []*DMail, adrDir string, strictness string,
	out io.Writer, runner port.ClaudeRunner, logger domain.Logger) (*domain.AutoDiscussResult, error)

RunAutoDiscuss orchestrates the Devil's Advocate debate for auto-approve mode. Returns nil result (not error) when auto_discuss_rounds is 0.

func RunClaudeDryRun

func RunClaudeDryRun(cfg *domain.Config, prompt, outputPath string, name string, logger domain.Logger) error

RunClaudeDryRun saves the prompt to a file instead of executing the provider CLI, useful for previewing what would be sent. The name parameter makes each prompt file unique within the output directory (e.g. "classify", "wave_00_auth").

func RunConvergenceGate

func RunConvergenceGate(ctx context.Context, dmails []*DMail, notifier port.Notifier, approver port.Approver, logger domain.Logger) (bool, error)

RunConvergenceGate checks for convergence D-Mails and runs the notify + approve flow. Returns true if approved or no convergence found. Returns false if denied. Returns error on failure (fail-closed).

func RunDoctor

func RunDoctor(ctx context.Context, configPath string, baseDir string, logger domain.Logger, repair bool, mode domain.TrackingMode) []domain.DoctorCheck

RunDoctor executes all health checks and returns the results. The configPath is loaded to obtain tool configuration; if loading fails the config check reports failure but other checks continue where possible. baseDir is used to verify the .siren/ state directory is writable.

func RunParallel

func RunParallel[I, R any](
	ctx context.Context,
	items []I,
	concurrency int,
	work func(ctx context.Context, index int, item I) (R, error),
	itemName func(I) string,
	logger domain.Logger,
) ([]R, []string)

RunParallel executes work for each item with bounded concurrency using a pond worker pool. Failed items produce warnings and are skipped; successful results are returned in the original item order.

Panics inside work are recovered and converted to warnings (no deadlock).

func RunParallelDeepScan

func RunParallelDeepScan(ctx context.Context, cfg *domain.Config, scanDir string,
	clusters []domain.ClusterScanResult, scanFn DeepScanFunc, logger domain.Logger) ([]domain.ClusterScanResult, []string)

RunParallelDeepScan executes deep scan across clusters with bounded concurrency. Delegates to RunParallel for pond-based parallel orchestration. Failed clusters produce warnings and are skipped; successful results preserve order.

func RunReadyLabel

func RunReadyLabel(ctx context.Context, cfg *domain.Config, readyIssueIDs string, out io.Writer, runner port.ClaudeRunner, logger domain.Logger) error

RunReadyLabel applies the ready label to issues whose all waves have completed. This must only be called after a successful wave apply.

func RunRescanSession

func RunRescanSession(ctx context.Context, cfg *domain.Config, baseDir string, oldState *domain.SessionState, sessionID string, input io.Reader, out io.Writer, emitter port.SessionEventEmitter, logger domain.Logger) error

RunRescanSession performs a fresh scan then merges completed status from old state.

func RunResumeSession

func RunResumeSession(ctx context.Context, cfg *domain.Config, baseDir string, state *domain.SessionState, input io.Reader, out io.Writer, emitter port.SessionEventEmitter, logger domain.Logger) error

RunResumeSession resumes an existing session from saved state.

func RunReview

func RunReview(ctx context.Context, reviewCmd string, dir string) (*domain.ReviewResult, error)

RunReview executes the review command and parses the output.

func RunReviewGate

func RunReviewGate(ctx context.Context, gate domain.GateConfig, cfg *domain.Config, runner port.ClaudeRunner, dir string, logger domain.Logger, reviewGate ...port.ReviewGateRunner) (bool, error)

RunReviewGate runs the review-fix cycle with OTel spans. When a ReviewGateRunner is provided (variadic), delegates cycle control to it. Otherwise runs cycle control inline (backward compatibility for integration tests).

func RunScan

func RunScan(ctx context.Context, cfg *domain.Config, baseDir string, sessionID string, dryRun bool, out io.Writer, runner port.ClaudeRunner, onceRunner port.ClaudeRunner, logger domain.Logger) (*domain.ScanResult, error)

RunScan executes the full two-pass scan. Pass 1: Classify all issues into clusters. Pass 2: Deep scan each cluster in parallel.

func RunScribeADR

func RunScribeADR(ctx context.Context, cfg *domain.Config, scanDir string, wave domain.Wave, architectResp *domain.ArchitectResponse, adrDir string, strictness string, out io.Writer, runner port.ClaudeRunner, logger domain.Logger) (*domain.ScribeResponse, error)

RunScribeADR executes the Scribe Agent via Claude subprocess to generate an ADR.

func RunScribeADRDryRun

func RunScribeADRDryRun(cfg *domain.Config, scanDir string, wave domain.Wave, architectResp *domain.ArchitectResponse, adrDir string, strictness string, logger domain.Logger) error

RunScribeADRDryRun saves the scribe prompt to a file instead of executing Claude.

func RunSession

func RunSession(ctx context.Context, cfg *domain.Config, baseDir string, sessionID string, dryRun bool, input io.Reader, out io.Writer, emitter port.SessionEventEmitter, logger domain.Logger) error

RunSession runs the full session: Pass 1-3 (auto), then interactive wave loop.

func RunWaveApply

func RunWaveApply(ctx context.Context, cfg *domain.Config, scanDir string, wave domain.Wave, strictness string, out io.Writer, runner port.ClaudeRunner, logger domain.Logger) (*domain.WaveApplyResult, error)

RunWaveApply executes Pass 4: apply a single approved wave via Claude Code. It writes the apply result to a JSON file and returns the parsed result.

func RunWaveGenerate

func RunWaveGenerate(ctx context.Context, cfg *domain.Config, scanDir string, clusters []domain.ClusterScanResult, dryRun bool, runner port.ClaudeRunner, logger domain.Logger, extraPROpenIssues ...map[string]bool) ([]domain.Wave, []string, map[string]bool, error)

RunWaveGenerate executes Pass 3: generate waves for each cluster in parallel. Failed clusters are skipped with warnings (partial success), matching the fault-tolerance pattern of RunParallelDeepScan. Returns an error only when ALL clusters fail. RunWaveGenerate generates waves for all clusters. Optional extraPROpenIssues provides session-level knowledge of issues with spec D-Mails already sent (covers the race window before paintress applies the pr-open label).

func SanitizeADRTitle

func SanitizeADRTitle(title string) string

SanitizeADRTitle ensures an ADR title is safe for use in filenames. Prevents path traversal by stripping everything except [a-z0-9-_]. Returns "untitled" for empty input.

func ScanLine

func ScanLine(ctx context.Context, s *bufio.Scanner) (string, error)

ScanLine reads one line from s, returning early if ctx is cancelled. The goroutine blocked on s.Scan() may outlive the call when the context fires first; this is acceptable for a CLI tool that exits shortly after.

func ScribeFileName

func ScribeFileName(wave domain.Wave) string

ScribeFileName returns the output filename for a scribe run.

func SessionEventsDir

func SessionEventsDir(baseDir, sessionID string) string

SessionEventsDir returns the events directory for a specific session. Callers use this to compute the stateDir parameter for NewEventStore.

func SetCircuitBreaker added in v0.0.12

func SetCircuitBreaker(cb *platform.CircuitBreaker)

SetCircuitBreaker sets the process-wide circuit breaker for all provider calls. Call this once during startup before any provider invocations.

func ShellQuote

func ShellQuote(s string) string

ShellQuote quotes s for safe interpolation into shell commands. Uses single-quote escaping on Unix and double-quote escaping on Windows.

func ShellQuoteCmd

func ShellQuoteCmd(s string) string

ShellQuoteCmd wraps a string in double quotes with proper escaping for cmd.exe. Double quotes are escaped as "" and percent signs as %%.

func ShellQuoteUnix

func ShellQuoteUnix(s string) string

ShellQuoteUnix wraps a string in single quotes with proper escaping for sh. Single quotes within the string are escaped by ending the current quote, inserting an escaped single quote, and reopening the quote.

func SpecificationBody

func SpecificationBody(wave domain.Wave) string

SpecificationBody formats wave actions as Markdown body for a specification d-mail.

func Status

func Status(ctx context.Context, baseDir string) domain.StatusReport

Status collects current operational status from the event store and filesystem. baseDir is the repository root (e.g. the directory containing .siren/).

func SupersedeADR added in v0.0.11

func SupersedeADR(adrPath, supersededBy string) error

SupersedeADR patches the **Status:** line of the ADR file at adrPath, replacing it with "Superseded by [supersededBy]". Returns an error if the file cannot be read/written or if no Status line is found.

func ToApplyResult

func ToApplyResult(wave domain.Wave, internal *domain.WaveApplyResult) domain.ApplyResult

ToApplyResult converts the internal domain.WaveApplyResult to the pipe wire format domain.ApplyResult. Delegates to domain.ToApplyResult.

func ToDiscussResult

func ToDiscussResult(wave domain.Wave, resp *domain.ArchitectResponse, topic string) domain.DiscussResult

ToDiscussResult converts an domain.ArchitectResponse to the pipe wire format domain.DiscussResult. It compares original and modified wave actions to build the modifications list, detecting changed, added, and removed actions.

func Truncate

func Truncate(s string, maxWidth int) string

func TryLockDaemon added in v0.0.12

func TryLockDaemon(stateDir string) (func(), error)

TryLockDaemon acquires an exclusive advisory lock on daemon.lock in the given directory. If another process holds the lock, it returns an error immediately (LOCK_NB). The returned function releases the lock. The OS automatically releases the lock if the process crashes.

func UpdateConfig added in v0.0.3

func UpdateConfig(path string, key string, value string) error

UpdateConfig reads a config file, updates a single key, validates, and writes back. Supported keys: tracker.team, tracker.project, tracker.cycle, lang, strictness.default, scan.chunk_size, scan.max_concurrency, assistant.model, assistant.timeout_sec, gate.auto_approve, labels.enabled, labels.prefix, labels.ready_label.

func ValidateDMail

func ValidateDMail(mail *DMail) error

ValidateDMail checks required fields and kind validity.

func WaveApplyFileName

func WaveApplyFileName(wave domain.Wave) string

WaveApplyFileName returns the output filename for a wave apply result. Includes cluster name to avoid collisions when wave IDs are duplicated across clusters.

func WaveIssueIDs

func WaveIssueIDs(wave domain.Wave) []string

func WriteClaudeLog added in v0.0.12

func WriteClaudeLog(baseDir string, rawEvents []string) error

WriteClaudeLog persists raw stream-json events to .run/claude-logs/.

func WriteEstimatedStrictness added in v0.0.3

func WriteEstimatedStrictness(path string, estimated map[string]domain.StrictnessLevel) error

WriteEstimatedStrictness reads the config, replaces the estimated strictness map, and writes back. Called after scan to persist LLM-estimated values.

func WriteGitIgnore

func WriteGitIgnore(baseDir string) error

WriteGitIgnore ensures a .gitignore inside .siren/ excludes ephemeral files from version control. Uses append-only pattern: existing user entries are preserved.

func WriteScanResult

func WriteScanResult(path string, result *domain.ScanResult) error

WriteScanResult serializes a ScanResult to a JSON file for session resume caching.

func WriteShibitoInsights added in v0.0.4

func WriteShibitoInsights(w *InsightWriter, warnings []domain.ShibitoWarning, sessionID string, logger domain.Logger)

WriteShibitoInsights generates insight entries from ShibitoWarnings and appends them to the shibito.md insight file. Skips if there are no warnings. Errors are logged but do not propagate — insight writing is best-effort.

func WriteStrictnessInsights added in v0.0.4

func WriteStrictnessInsights(w *InsightWriter, clusters []domain.ClusterScanResult, sessionID string, logger domain.Logger)

WriteStrictnessInsights generates insight entries from cluster scan results that have EstimatedStrictness set. Appends to strictness.md insight file. Clusters without an estimate are skipped. Errors are best-effort.

Types

type ClaudeAdapter added in v0.0.5

type ClaudeAdapter struct {
	ClaudeCmd  string
	Model      string
	TimeoutSec int
	Logger     domain.Logger
	ToolName   string                      // CLI tool name for stream events (e.g. "sightjack")
	StreamBus  port.SessionStreamPublisher // optional: live session event streaming
	// NewCmd overrides command creation. If nil, platform.NewShellCmd is used.
	NewCmd func(ctx context.Context, name string, args ...string) *exec.Cmd
	// CancelFunc sets cmd.Cancel for graceful shutdown. If nil, default (process kill) is used.
	CancelFunc func(cmd *exec.Cmd) func() error
}

ClaudeAdapter implements port.ClaudeRunner by executing the Claude CLI as a subprocess with streaming (--output-format stream-json). It does NOT retry; wrap with RetryRunner for that.

func NewClaudeAdapter added in v0.0.5

func NewClaudeAdapter(cfg *domain.Config, logger domain.Logger) *ClaudeAdapter

NewClaudeAdapter creates a ClaudeAdapter implementing port.ClaudeRunner.

func (*ClaudeAdapter) Run added in v0.0.5

func (a *ClaudeAdapter) Run(ctx context.Context, prompt string, w io.Writer, opts ...port.RunOption) (string, error)

Run executes the Claude CLI once with streaming, returning only the result text.

func (*ClaudeAdapter) RunDetailed added in v0.0.12

func (a *ClaudeAdapter) RunDetailed(ctx context.Context, prompt string, w io.Writer, opts ...port.RunOption) (port.RunResult, error)

RunDetailed executes the Claude CLI once with streaming, returning the result text and provider session ID.

type CmdApprover

type CmdApprover struct {
	// contains filtered or unexported fields
}

CmdApprover runs an external command for approval. Exit 0 = approve, non-zero ExitError = deny, other error = fail.

func NewCmdApprover

func NewCmdApprover(cmdTemplate string) *CmdApprover

NewCmdApprover creates a CmdApprover from a shell command template.

func (*CmdApprover) RequestApproval

func (a *CmdApprover) RequestApproval(ctx context.Context, message string) (bool, error)

type CmdNotifier

type CmdNotifier struct {
	// contains filtered or unexported fields
}

CmdNotifier runs a user-provided shell command with {title} and {message} placeholders.

func NewCmdNotifier

func NewCmdNotifier(cmdTemplate string) *CmdNotifier

NewCmdNotifier creates a CmdNotifier from a shell command template.

func (*CmdNotifier) Notify

func (n *CmdNotifier) Notify(ctx context.Context, title, message string) error

type DMail

type DMail struct {
	Name          string                 `yaml:"name"`
	Kind          DMailKind              `yaml:"kind"`
	Description   string                 `yaml:"description"`
	SchemaVersion string                 `yaml:"dmail-schema-version,omitempty"`
	Issues        []string               `yaml:"issues,omitempty"`
	Severity      string                 `yaml:"severity,omitempty"`
	Action        string                 `yaml:"action,omitempty"`
	Priority      int                    `yaml:"priority,omitempty"`
	Wave          *domain.WaveReference  `yaml:"wave,omitempty"`
	Metadata      map[string]string      `yaml:"metadata,omitempty"`
	Context       *domain.InsightContext `yaml:"context,omitempty" json:"context,omitempty"`
	Body          string                 `yaml:"-"`
}

DMail represents a d-mail message: YAML frontmatter + Markdown body.

func DrainInboxFeedback

func DrainInboxFeedback(ch <-chan *DMail, logger domain.Logger) []*DMail

DrainInboxFeedback reads all currently buffered d-mails (feedback and convergence) from the monitor channel and logs them. Returns the drained messages for downstream use.

func FilterConvergence

func FilterConvergence(dmails []*DMail) []*DMail

FilterConvergence returns convergence-kind D-Mails from the slice.

func ParseDMail

func ParseDMail(data []byte) (*DMail, error)

ParseDMail parses YAML frontmatter + Markdown body from bytes.

func ReceiveDMail

func ReceiveDMail(baseDir, filename string) (*DMail, error)

ReceiveDMail reads a d-mail from inbox/, parses it, and moves it to archive/.

func RunConvergenceGateWithRedrain

func RunConvergenceGateWithRedrain(ctx context.Context, initial []*DMail, inboxCh <-chan *DMail,
	notifier port.Notifier, approver port.Approver, logger domain.Logger) (dmails []*DMail, approved bool, err error)

RunConvergenceGateWithRedrain runs the convergence gate in a loop, re-draining the inbox channel after each approval to catch late-arriving convergence D-Mails. Returns the accumulated D-Mails, approval status, and any error. The loop exits when no new convergence D-Mails arrived during the approval prompt, or when maxRedrainCycles is reached (fail-closed: approved=false).

func (*DMail) Filename

func (d *DMail) Filename() string

Filename returns the canonical filename: "<name>.md".

type DMailKind

type DMailKind string

DMailKind is the message type for d-mails.

const (
	DMailSpecification  DMailKind = "specification"
	DMailReport         DMailKind = "report"
	DMailDesignFeedback DMailKind = "design-feedback"
	DMailImplFeedback   DMailKind = "implementation-feedback"
	DMailConvergence    DMailKind = "convergence"
	DMailCIResult       DMailKind = "ci-result"
)
const DMailStallEscalation DMailKind = "stall-escalation"

DMailStallEscalation is the d-mail kind for stall escalation messages. Sent when a wave is detected as stalled due to repeated structural errors.

type DeepScanFunc

type DeepScanFunc func(ctx context.Context, cfg *domain.Config, scanDir string, index int, cluster domain.ClusterScanResult) (domain.ClusterScanResult, error)

DeepScanFunc is the function signature for scanning a single cluster. The index parameter identifies the cluster's position in the original slice, enabling safe lookup even when duplicate cluster names exist.

type DispatchingRecorder

type DispatchingRecorder struct {
	// contains filtered or unexported fields
}

DispatchingRecorder wraps a Recorder and dispatches events to an EventDispatcher. Record is delegated to inner first; then the event is dispatched best-effort.

func NewDispatchingRecorder

func NewDispatchingRecorder(inner port.Recorder, dispatcher port.EventDispatcher, logger domain.Logger) *DispatchingRecorder

NewDispatchingRecorder creates a DispatchingRecorder. If dispatcher is nil, Record simply delegates to inner.

func (*DispatchingRecorder) Record

func (r *DispatchingRecorder) Record(ev domain.Event) error

Record delegates to the inner Recorder, then dispatches the event best-effort.

type EnterConfig added in v0.0.12

type EnterConfig struct {
	ProviderCmd       string    // CLI command (from config.claude_cmd)
	ProviderSessionID string    // provider native session ID
	WorkDir           string    // working directory (from session record)
	ConfigBase        string    // base directory for resolving stateDir settings
	Stdin             io.Reader // injected by cmd layer (typically os.Stdin)
	Stdout            io.Writer // injected by cmd layer (typically os.Stdout)
	Stderr            io.Writer // injected by cmd layer (typically os.Stderr)
}

EnterConfig holds configuration for interactive session re-entry.

type FeedbackCollector

type FeedbackCollector struct {
	// contains filtered or unexported fields
}

FeedbackCollector accumulates feedback d-mails from both the initial drain and late-arriving items on the monitor channel. It replaces LogInboxFeedbackAsync by both displaying AND storing late arrivals, so they can be included in nextgen prompts. Convergence d-mails are tracked separately for journaling.

func CollectFeedback

func CollectFeedback(initial []*DMail, ch <-chan *DMail, notifier port.Notifier, logger domain.Logger) *FeedbackCollector

CollectFeedback creates a FeedbackCollector seeded with initial feedback and starts a background goroutine to accumulate late-arriving items from the channel. Convergence d-mails trigger a notification via notifier. Safe to call with nil initial, nil channel, or nil notifier.

func (*FeedbackCollector) All

func (c *FeedbackCollector) All() []*DMail

All returns a copy of all accumulated feedback (initial + late arrivals). Non-destructive: repeated calls return the same data plus any new arrivals.

func (*FeedbackCollector) ConvergenceNames

func (c *FeedbackCollector) ConvergenceNames() []string

ConvergenceNames returns a copy of convergence d-mail names received mid-session. Used for journaling/state persistence.

func (*FeedbackCollector) FeedbackOnly

func (c *FeedbackCollector) FeedbackOnly() []*DMail

FeedbackOnly returns a copy of accumulated d-mails filtered to feedback kind only (excludes convergence). Use this for nextgen prompt injection where only feedback d-mails are relevant.

func (*FeedbackCollector) NewSinceSnapshot added in v0.0.3

func (c *FeedbackCollector) NewSinceSnapshot() []*DMail

NewSinceSnapshot returns D-Mails received since the last Snapshot call.

func (*FeedbackCollector) NotifyCh added in v0.0.3

func (c *FeedbackCollector) NotifyCh() <-chan struct{}

NotifyCh returns a channel that receives a signal when new D-Mails arrive. Used by the waiting phase to wake up when inbox content changes.

func (*FeedbackCollector) ReportsOnly

func (c *FeedbackCollector) ReportsOnly() []*DMail

ReportsOnly returns a copy of accumulated d-mails filtered to report kind only (excludes feedback and convergence). Use this for nextgen prompt injection where cross-tool reports (e.g. amadeus check results) should inform wave planning.

func (*FeedbackCollector) Snapshot added in v0.0.3

func (c *FeedbackCollector) Snapshot()

Snapshot marks the current position in the collected items. Call before entering the waiting phase.

type FileHandoverWriter added in v0.0.7

type FileHandoverWriter struct{}

FileHandoverWriter writes a handover markdown document to the state directory.

func (*FileHandoverWriter) WriteHandover added in v0.0.7

func (w *FileHandoverWriter) WriteHandover(ctx context.Context, stateDir string, state domain.HandoverState) error

WriteHandover renders state to markdown and writes it to {stateDir}/handover.md. The file is always overwritten (latest only). Respects context cancellation.

type IndexWriter added in v0.0.8

type IndexWriter struct{}

IndexWriter writes domain.IndexEntry records to a JSONL index file.

func (*IndexWriter) Append added in v0.0.8

func (w *IndexWriter) Append(indexPath string, entries []domain.IndexEntry) error

Append appends entries to the index file using flock for concurrency safety.

func (*IndexWriter) Rebuild added in v0.0.8

func (w *IndexWriter) Rebuild(indexPath, stateDir, tool string) (int, error)

Rebuild scans known subdirectories for .md files, extracts metadata, and overwrites the index file atomically using a temp+rename strategy under flock.

type InitAdapter

type InitAdapter struct {
	Force bool // When true, overwrite existing config.yaml
}

InitAdapter implements port.InitRunner by orchestrating project initialization I/O.

func (*InitAdapter) InitProject

func (a *InitAdapter) InitProject(baseDir, team, project, lang, strictness string) ([]string, error)

InitProject creates .siren/config.yaml and supporting files. Returns warnings for non-fatal errors (skill install, mail dirs).

type InsightWriter added in v0.0.4

type InsightWriter struct {
	// contains filtered or unexported fields
}

InsightWriter provides atomic read/write access to insight ledger files. It uses flock-based locking for concurrent safety and temp-file-rename for atomicity. Title-based dedup ensures idempotent appends.

func NewInsightWriter added in v0.0.4

func NewInsightWriter(insightsDir, runDir string) *InsightWriter

NewInsightWriter creates an InsightWriter for the given directories.

func (*InsightWriter) Append added in v0.0.4

func (w *InsightWriter) Append(filename, kind, tool string, entry domain.InsightEntry) error

Append adds a new InsightEntry to the named file, creating it if needed. Uses flock + atomic rename for concurrent safety. Idempotent: skips if an entry with the same title already exists.

func (*InsightWriter) Read added in v0.0.4

func (w *InsightWriter) Read(filename string) (*domain.InsightFile, error)

Read parses an insight file. Safe without locking due to atomic rename writes.

type LocalNotifier

type LocalNotifier struct {
	// contains filtered or unexported fields
}

LocalNotifier sends desktop notifications using OS-native tools. darwin: osascript with Funk sound, linux: notify-send.

func (*LocalNotifier) Notify

func (n *LocalNotifier) Notify(ctx context.Context, title, message string) error

type LoggingRecorder

type LoggingRecorder struct {
	// contains filtered or unexported fields
}

LoggingRecorder wraps a Recorder and logs errors instead of propagating them. This ensures callers never need to handle Record errors at every call site.

func NewLoggingRecorder

func NewLoggingRecorder(inner port.Recorder, logger domain.Logger) *LoggingRecorder

NewLoggingRecorder creates a LoggingRecorder that wraps the given Recorder. If inner is nil, NopRecorder is used to prevent panics.

func (*LoggingRecorder) Record

func (r *LoggingRecorder) Record(ev domain.Event) error

Record delegates to the inner Recorder. On error, it logs a warning and returns nil.

type MCPConfig added in v0.0.12

type MCPConfig struct {
	MCPServers map[string]MCPServerEntry `json:"mcpServers"`
}

MCPConfig is the JSON structure for --mcp-config.

type MCPServerEntry added in v0.0.12

type MCPServerEntry struct {
	Command string   `json:"command"`
	Args    []string `json:"args"`
}

MCPServerEntry defines a single MCP server.

type RecorderFactoryAdapter

type RecorderFactoryAdapter struct{}

RecorderFactoryAdapter implements port.RecorderFactory by delegating to session package functions.

func NewRecorderFactoryAdapter

func NewRecorderFactoryAdapter() *RecorderFactoryAdapter

NewRecorderFactoryAdapter creates a new RecorderFactoryAdapter.

func (*RecorderFactoryAdapter) NewEventStore

func (f *RecorderFactoryAdapter) NewEventStore(stateDir string, logger domain.Logger) port.EventStore

func (*RecorderFactoryAdapter) NewSessionRecorder

func (f *RecorderFactoryAdapter) NewSessionRecorder(stateDir, sessionID string, logger domain.Logger) (port.Recorder, error)

func (*RecorderFactoryAdapter) SessionEventsDir

func (f *RecorderFactoryAdapter) SessionEventsDir(baseDir, sessionID string) string

type RetryRunner added in v0.0.5

type RetryRunner struct {
	Inner          port.ClaudeRunner
	MaxAttempts    int
	BaseDelay      time.Duration
	Timeout        time.Duration
	Logger         domain.Logger
	CircuitBreaker *platform.CircuitBreaker // optional: skip retries when circuit is open
}

RetryRunner wraps a provider runner (ClaudeRunner) with exponential backoff retry. Use the inner runner directly for non-idempotent operations. Timeout bounds the entire retry loop (not per-attempt).

func NewRetryRunner added in v0.0.5

func NewRetryRunner(inner port.ClaudeRunner, cfg *domain.Config, logger domain.Logger) *RetryRunner

NewRetryRunner creates a RetryRunner wrapping the given ClaudeRunner.

func (*RetryRunner) Run added in v0.0.5

func (r *RetryRunner) Run(ctx context.Context, prompt string, w io.Writer, opts ...port.RunOption) (string, error)

Run executes the inner runner with exponential backoff retry. The entire retry loop is bounded by Timeout.

func (*RetryRunner) RunDetailed added in v0.0.12

func (r *RetryRunner) RunDetailed(ctx context.Context, prompt string, w io.Writer, opts ...port.RunOption) (port.RunResult, error)

RunDetailed executes the inner runner with retry, returning the last RunResult.

type RunOption

type RunOption = port.RunOption

RunOption is an alias for port.RunOption for backward compatibility.

type SQLiteCodingSessionStore added in v0.0.12

type SQLiteCodingSessionStore struct {
	// contains filtered or unexported fields
}

SQLiteCodingSessionStore implements CodingSessionStore using SQLite.

func NewSQLiteCodingSessionStore added in v0.0.12

func NewSQLiteCodingSessionStore(dbPath string) (*SQLiteCodingSessionStore, error)

NewSQLiteCodingSessionStore opens or creates a SQLite database at dbPath.

func (*SQLiteCodingSessionStore) Close added in v0.0.12

func (s *SQLiteCodingSessionStore) Close() error

func (*SQLiteCodingSessionStore) FindByProviderSessionID added in v0.0.12

func (s *SQLiteCodingSessionStore) FindByProviderSessionID(ctx context.Context, provider domain.Provider, pid string) ([]domain.CodingSessionRecord, error)

func (*SQLiteCodingSessionStore) LatestByProviderSessionID added in v0.0.12

func (s *SQLiteCodingSessionStore) LatestByProviderSessionID(ctx context.Context, provider domain.Provider, pid string) (domain.CodingSessionRecord, error)

func (*SQLiteCodingSessionStore) List added in v0.0.12

func (*SQLiteCodingSessionStore) Load added in v0.0.12

func (*SQLiteCodingSessionStore) Save added in v0.0.12

func (*SQLiteCodingSessionStore) UpdateStatus added in v0.0.12

func (s *SQLiteCodingSessionStore) UpdateStatus(ctx context.Context, id string, status domain.SessionStatus, providerSessionID string, metadata map[string]string) error

type SQLiteOutboxStore

type SQLiteOutboxStore struct {
	// contains filtered or unexported fields
}

SQLiteOutboxStore implements OutboxStore using a SQLite database as the transactional write-ahead log. Staged D-Mails are flushed to archive/ and outbox/ using atomic file writes (temp file + rename).

func NewOutboxStoreForDir

func NewOutboxStoreForDir(baseDir string) (*SQLiteOutboxStore, error)

NewOutboxStoreForDir creates a SQLiteOutboxStore using conventional paths derived from baseDir: DB at .siren/.run/outbox.db, targets at .siren/archive/ and .siren/outbox/.

func NewSQLiteOutboxStore

func NewSQLiteOutboxStore(dbPath, archiveDir, outboxDir string) (*SQLiteOutboxStore, error)

NewSQLiteOutboxStore opens (or creates) a SQLite database at dbPath and initialises the schema. archiveDir and outboxDir are the target directories for flushed D-Mail files.

func (*SQLiteOutboxStore) Close

func (s *SQLiteOutboxStore) Close() error

Close closes the underlying database connection.

func (*SQLiteOutboxStore) Flush

func (s *SQLiteOutboxStore) Flush(ctx context.Context) (int, error)

Flush writes all unflushed D-Mails to archive/ and outbox/ using atomic file writes, then marks them as flushed in the database. The entire flush is wrapped in a BEGIN IMMEDIATE transaction so that concurrent CLI processes wait (up to busy_timeout) instead of deadlocking. A partial failure leaves items eligible for retry on the next Flush call.

func (*SQLiteOutboxStore) IncrementalVacuum

func (s *SQLiteOutboxStore) IncrementalVacuum() error

IncrementalVacuum reclaims free pages without acquiring an exclusive lock. Call after bulk deletes (e.g., archive-prune) to shrink the DB file. Requires PRAGMA auto_vacuum=INCREMENTAL set at DB open time.

func (*SQLiteOutboxStore) PruneFlushed

func (s *SQLiteOutboxStore) PruneFlushed(ctx context.Context) (int, error)

PruneFlushed deletes all flushed rows from the staging table and runs incremental vacuum to reclaim disk space. Returns the number of deleted rows.

func (*SQLiteOutboxStore) Stage

func (s *SQLiteOutboxStore) Stage(ctx context.Context, name string, data []byte) error

Stage inserts a D-Mail into the staging table. Idempotent: re-staging the same name updates the data and resets flushed/retry state, enabling re-delivery of D-Mails that have already been flushed (e.g. recurring conflict notifications for the same PR).

type ScanRunnerAdapter

type ScanRunnerAdapter struct{}

ScanRunnerAdapter implements port.ScanRunner by delegating to session package functions.

func NewScanRunnerAdapter

func NewScanRunnerAdapter() *ScanRunnerAdapter

NewScanRunnerAdapter creates a new ScanRunnerAdapter.

func (*ScanRunnerAdapter) RecordScanState

func (a *ScanRunnerAdapter) RecordScanState(baseDir, sessionID string, result *domain.ScanResult, cfg *domain.Config, emitter port.SessionEventEmitter, ts time.Time, logger domain.Logger)

func (*ScanRunnerAdapter) RunScan

func (a *ScanRunnerAdapter) RunScan(ctx context.Context, cfg *domain.Config, baseDir, sessionID string, dryRun bool, streamOut io.Writer, logger domain.Logger) (*domain.ScanResult, error)

type SessionRunnerAdapter

type SessionRunnerAdapter struct {
	// contains filtered or unexported fields
}

SessionRunnerAdapter implements port.SessionRunner by delegating to session package functions.

func NewSessionRunnerAdapter

func NewSessionRunnerAdapter() *SessionRunnerAdapter

NewSessionRunnerAdapter creates a new SessionRunnerAdapter.

func (*SessionRunnerAdapter) BuildNotifier

func (a *SessionRunnerAdapter) BuildNotifier(gate domain.GateConfig) port.Notifier

func (*SessionRunnerAdapter) NewDispatchingRecorder

func (a *SessionRunnerAdapter) NewDispatchingRecorder(inner port.Recorder, dispatcher port.EventDispatcher, logger domain.Logger) port.Recorder

func (*SessionRunnerAdapter) ReviewGateRunner added in v0.0.11

func (a *SessionRunnerAdapter) ReviewGateRunner() port.ReviewGateRunner

ReviewGateRunner returns the injected ReviewGateRunner (nil if not set).

func (*SessionRunnerAdapter) RunRescanSession

func (a *SessionRunnerAdapter) RunRescanSession(ctx context.Context, cfg *domain.Config, baseDir string, oldState *domain.SessionState, sessionID string, input io.Reader, out io.Writer, emitter port.SessionEventEmitter, logger domain.Logger) error

func (*SessionRunnerAdapter) RunResumeSession

func (a *SessionRunnerAdapter) RunResumeSession(ctx context.Context, cfg *domain.Config, baseDir string, state *domain.SessionState, input io.Reader, out io.Writer, emitter port.SessionEventEmitter, logger domain.Logger) error

func (*SessionRunnerAdapter) RunSession

func (a *SessionRunnerAdapter) RunSession(ctx context.Context, cfg *domain.Config, baseDir, sessionID string, dryRun bool, input io.Reader, out io.Writer, emitter port.SessionEventEmitter, logger domain.Logger) error

func (*SessionRunnerAdapter) SetReviewGateRunner added in v0.0.11

func (a *SessionRunnerAdapter) SetReviewGateRunner(runner port.ReviewGateRunner)

SetReviewGateRunner injects the review gate runner (usecase-layer logic).

type SessionTrackingAdapter added in v0.0.12

type SessionTrackingAdapter struct {
	// contains filtered or unexported fields
}

SessionTrackingAdapter wraps a DetailedRunner with provider-agnostic session persistence. It creates a CodingSessionRecord before each invocation, captures the provider's session ID from the result, updates the record in the store, and records circuit breaker state from stderr.

func NewSessionTrackingAdapter added in v0.0.12

func NewSessionTrackingAdapter(inner port.DetailedRunner, store port.CodingSessionStore, provider domain.Provider) *SessionTrackingAdapter

NewSessionTrackingAdapter creates a new adapter.

func (*SessionTrackingAdapter) Run added in v0.0.12

func (a *SessionTrackingAdapter) Run(ctx context.Context, prompt string, w io.Writer, opts ...port.RunOption) (string, error)

Run implements the provider runner interface, enabling drop-in replacement of plain adapters.

func (*SessionTrackingAdapter) RunSession added in v0.0.12

RunSession executes the inner runner and persists session metadata.

type SpanEventStore

type SpanEventStore struct {
	// contains filtered or unexported fields
}

SpanEventStore wraps a port.EventStore with OTEL span instrumentation. Each operation is wrapped in a span with event count and stats attributes.

func (*SpanEventStore) Append

func (s *SpanEventStore) Append(events ...domain.Event) (domain.AppendResult, error)

func (*SpanEventStore) LoadAll

func (s *SpanEventStore) LoadAll() ([]domain.Event, domain.LoadResult, error)

func (*SpanEventStore) LoadSince

func (s *SpanEventStore) LoadSince(after time.Time) ([]domain.Event, domain.LoadResult, error)

type StateLoaderAdapter

type StateLoaderAdapter struct{}

StateLoaderAdapter implements port.StateLoader by delegating to session package functions.

func NewStateLoaderAdapter

func NewStateLoaderAdapter() *StateLoaderAdapter

NewStateLoaderAdapter creates a new StateLoaderAdapter.

func (*StateLoaderAdapter) CanResume

func (l *StateLoaderAdapter) CanResume(baseDir string, state *domain.SessionState) bool

func (*StateLoaderAdapter) LoadLatestResumableState

func (l *StateLoaderAdapter) LoadLatestResumableState(ctx context.Context, baseDir string, match func(*domain.SessionState) bool) (*domain.SessionState, string, error)

func (*StateLoaderAdapter) LoadLatestState

func (l *StateLoaderAdapter) LoadLatestState(ctx context.Context, baseDir string) (*domain.SessionState, string, error)

type StdinApprover

type StdinApprover struct {
	// contains filtered or unexported fields
}

StdinApprover prompts the user on a terminal and reads y/n. Uses goroutine + channel for context cancellation support. Safe default: empty or non-y input = deny.

func NewStdinApprover

func NewStdinApprover(r io.Reader, w io.Writer) *StdinApprover

NewStdinApprover creates a StdinApprover with the given reader and writer.

func (*StdinApprover) RequestApproval

func (a *StdinApprover) RequestApproval(ctx context.Context, message string) (bool, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL