Initialize narratio Go module and CLI scaffold

This commit is contained in:
2026-05-02 10:37:38 -05:00
parent 48c51e1c77
commit 902e6bc994
42 changed files with 1486 additions and 1 deletions

View File

@@ -0,0 +1,20 @@
// Package analyzer declares the adapter contract for artifact analysis generation.
package analyzer
import "context"
// Runner is a placeholder adapter interface for session artifact generation.
type Runner interface {
Run(ctx context.Context, req AnalysisRequest) (AnalysisResult, error)
}
// AnalysisRequest is a placeholder analyzer input.
type AnalysisRequest struct {
ProcessedTranscriptPath string
OutputDir string
}
// AnalysisResult is a placeholder analyzer output.
type AnalysisResult struct {
ArtifactPaths []string
}

View File

@@ -0,0 +1,20 @@
// Package audita declares the adapter contract for transcript polishing.
package audita
import "context"
// Runner is a placeholder adapter interface for audita invocation.
type Runner interface {
Run(ctx context.Context, req PolishRequest) (PolishResult, error)
}
// PolishRequest is a placeholder polish input.
type PolishRequest struct {
MergedTranscriptPath string
OutputPath string
}
// PolishResult is a placeholder polish output.
type PolishResult struct {
ProcessedPath string
}

View File

@@ -0,0 +1,15 @@
// Package notify declares the adapter contract for run notifications.
package notify
import "context"
// Sender is a placeholder adapter interface for notifications.
type Sender interface {
Send(ctx context.Context, msg Message) error
}
// Message is a placeholder notification payload.
type Message struct {
Subject string
Body string
}

View File

@@ -0,0 +1,20 @@
// Package seriatim declares the adapter contract for transcript merge execution.
package seriatim
import "context"
// Runner is a placeholder adapter interface for seriatim invocation.
type Runner interface {
Run(ctx context.Context, req MergeRequest) (MergeResult, error)
}
// MergeRequest is a placeholder merge input.
type MergeRequest struct {
TranscriptPaths []string
OutputPath string
}
// MergeResult is a placeholder merge output.
type MergeResult struct {
MergedPath string
}

View File

@@ -0,0 +1,20 @@
// Package whisperx declares the adapter contract for WhisperX transcription.
package whisperx
import "context"
// Client is a placeholder adapter interface for WhisperX interactions.
type Client interface {
Transcribe(ctx context.Context, req TranscriptionRequest) (TranscriptionResult, error)
}
// TranscriptionRequest is a placeholder transcription input.
type TranscriptionRequest struct {
Speaker string
AudioPath string
}
// TranscriptionResult is a placeholder transcription output.
type TranscriptionResult struct {
TranscriptPath string
}

26
internal/app/app.go Normal file
View File

@@ -0,0 +1,26 @@
package app
import (
"log/slog"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/analyzer"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/audita"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/notify"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/seriatim"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/whisperx"
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
"gitea.maximumdirect.net/eric/narratio/internal/config"
)
// Env is the shared dependency container passed to orchestrator components.
type Env struct {
Config *config.Config
ArtifactStore artifacts.Store
Logger *slog.Logger
WhisperX whisperx.Client
Seriatim seriatim.Runner
Audita audita.Runner
Analyzer analyzer.Runner
Notifier notify.Sender
}

56
internal/app/commands.go Normal file
View File

@@ -0,0 +1,56 @@
package app
import (
"context"
"fmt"
"io"
"strings"
)
var supportedCommands = []string{"run", "plan", "status", "resume", "run-stage"}
// Execute dispatches CLI commands and returns a process exit code.
func Execute(args []string, stdout, stderr io.Writer) int {
if len(args) == 0 {
printUsage(stderr)
return 1
}
ctx := context.Background()
cmd := args[0]
cmdArgs := args[1:]
var err error
switch cmd {
case "run":
err = Run(ctx, cmdArgs, stdout)
case "plan":
err = Plan(ctx, cmdArgs, stdout)
case "status":
err = Status(ctx, cmdArgs, stdout)
case "resume":
err = Resume(ctx, cmdArgs, stdout)
case "run-stage":
err = RunStage(ctx, cmdArgs, stdout)
default:
fmt.Fprintf(stderr, "unknown command: %q\n\n", cmd)
printUsage(stderr)
return 1
}
if err != nil {
fmt.Fprintf(stderr, "%v\n", err)
return 1
}
return 0
}
func printUsage(w io.Writer) {
fmt.Fprintf(w, "Usage: narratio <%s>\n", strings.Join(supportedCommands, "|"))
}
func placeholder(out io.Writer, command string) error {
_, err := fmt.Fprintf(out, "narratio %s: not yet implemented\n", command)
return err
}

View File

@@ -0,0 +1,75 @@
package app
import (
"bytes"
"strings"
"testing"
)
func TestExecuteValidCommands(t *testing.T) {
cases := []struct {
name string
args []string
wantOut string
}{
{name: "run", args: []string{"run"}, wantOut: "narratio run: not yet implemented"},
{name: "plan", args: []string{"plan"}, wantOut: "narratio plan: not yet implemented"},
{name: "status", args: []string{"status"}, wantOut: "narratio status: not yet implemented"},
{name: "resume", args: []string{"resume"}, wantOut: "narratio resume: not yet implemented"},
{name: "run-stage", args: []string{"run-stage", "polish"}, wantOut: "narratio run-stage: not yet implemented"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
code := Execute(tc.args, &stdout, &stderr)
if code != 0 {
t.Fatalf("exit code = %d, want 0", code)
}
if stderr.Len() != 0 {
t.Fatalf("stderr = %q, want empty", stderr.String())
}
if !strings.Contains(stdout.String(), tc.wantOut) {
t.Fatalf("stdout = %q, want to contain %q", stdout.String(), tc.wantOut)
}
})
}
}
func TestExecuteInvalidCommand(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
code := Execute([]string{"bogus"}, &stdout, &stderr)
if code == 0 {
t.Fatalf("exit code = 0, want non-zero")
}
if stdout.Len() != 0 {
t.Fatalf("stdout = %q, want empty", stdout.String())
}
out := stderr.String()
if !strings.Contains(out, "unknown command") {
t.Fatalf("stderr = %q, want unknown command message", out)
}
if !strings.Contains(out, "Usage: narratio") {
t.Fatalf("stderr = %q, want usage message", out)
}
}
func TestExecuteMissingCommand(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
code := Execute(nil, &stdout, &stderr)
if code == 0 {
t.Fatalf("exit code = 0, want non-zero")
}
if stdout.Len() != 0 {
t.Fatalf("stdout = %q, want empty", stdout.String())
}
if !strings.Contains(stderr.String(), "Usage: narratio") {
t.Fatalf("stderr = %q, want usage message", stderr.String())
}
}

3
internal/app/doc.go Normal file
View File

@@ -0,0 +1,3 @@
// Package app contains CLI command wiring and top-level application orchestration
// primitives for narratio.
package app

11
internal/app/plan.go Normal file
View File

@@ -0,0 +1,11 @@
package app
import (
"context"
"io"
)
// Plan is a placeholder for future stage planning behavior.
func Plan(_ context.Context, _ []string, out io.Writer) error {
return placeholder(out, "plan")
}

11
internal/app/resume.go Normal file
View File

@@ -0,0 +1,11 @@
package app
import (
"context"
"io"
)
// Resume is a placeholder for future resume-from-manifest behavior.
func Resume(_ context.Context, _ []string, out io.Writer) error {
return placeholder(out, "resume")
}

11
internal/app/run.go Normal file
View File

@@ -0,0 +1,11 @@
package app
import (
"context"
"io"
)
// Run is a placeholder for the future end-to-end pipeline execution command.
func Run(_ context.Context, _ []string, out io.Writer) error {
return placeholder(out, "run")
}

11
internal/app/run_stage.go Normal file
View File

@@ -0,0 +1,11 @@
package app
import (
"context"
"io"
)
// RunStage is a placeholder for future single-stage execution behavior.
func RunStage(_ context.Context, _ []string, out io.Writer) error {
return placeholder(out, "run-stage")
}

11
internal/app/status.go Normal file
View File

@@ -0,0 +1,11 @@
package app
import (
"context"
"io"
)
// Status is a placeholder for future manifest status inspection behavior.
func Status(_ context.Context, _ []string, out io.Writer) error {
return placeholder(out, "status")
}

View File

@@ -0,0 +1,24 @@
package artifacts
import (
"crypto/sha256"
"encoding/hex"
"io"
"os"
)
// SHA256File returns the SHA-256 hex digest of a file.
func SHA256File(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}

View File

@@ -0,0 +1,2 @@
// Package artifacts defines artifact references and storage contracts.
package artifacts

View File

@@ -0,0 +1,31 @@
package artifacts
import (
"context"
"fmt"
)
// LocalStore is a placeholder local filesystem artifact store.
type LocalStore struct {
RootDir string
}
// WriteLocal returns a not-yet-implemented error in the scaffold.
func (s *LocalStore) WriteLocal(_ context.Context, _ Ref, _ []byte) error {
return fmt.Errorf("artifacts local write: not yet implemented")
}
// ReadLocal returns a not-yet-implemented error in the scaffold.
func (s *LocalStore) ReadLocal(_ context.Context, _ Ref) ([]byte, error) {
return nil, fmt.Errorf("artifacts local read: not yet implemented")
}
// ExistsLocal returns a not-yet-implemented error in the scaffold.
func (s *LocalStore) ExistsLocal(_ context.Context, _ Ref) (bool, error) {
return false, fmt.Errorf("artifacts local exists: not yet implemented")
}
// Upload returns a not-yet-implemented error in the scaffold.
func (s *LocalStore) Upload(_ context.Context, _ Ref) (string, error) {
return "", fmt.Errorf("artifacts local upload: not yet implemented")
}

View File

@@ -0,0 +1,8 @@
package artifacts
import "path/filepath"
// SessionWorkDir returns the work directory for one session.
func SessionWorkDir(rootDir, sessionID string) string {
return filepath.Join(rootDir, "work", sessionID)
}

32
internal/artifacts/s3.go Normal file
View File

@@ -0,0 +1,32 @@
package artifacts
import (
"context"
"fmt"
)
// S3Store is a placeholder S3-compatible artifact store.
type S3Store struct {
Bucket string
Prefix string
}
// WriteLocal returns a not-yet-implemented error in the scaffold.
func (s *S3Store) WriteLocal(_ context.Context, _ Ref, _ []byte) error {
return fmt.Errorf("artifacts s3 write local: not yet implemented")
}
// ReadLocal returns a not-yet-implemented error in the scaffold.
func (s *S3Store) ReadLocal(_ context.Context, _ Ref) ([]byte, error) {
return nil, fmt.Errorf("artifacts s3 read local: not yet implemented")
}
// ExistsLocal returns a not-yet-implemented error in the scaffold.
func (s *S3Store) ExistsLocal(_ context.Context, _ Ref) (bool, error) {
return false, fmt.Errorf("artifacts s3 exists local: not yet implemented")
}
// Upload returns a not-yet-implemented error in the scaffold.
func (s *S3Store) Upload(_ context.Context, _ Ref) (string, error) {
return "", fmt.Errorf("artifacts s3 upload: not yet implemented")
}

View File

@@ -0,0 +1,19 @@
package artifacts
import "context"
// Ref identifies a pipeline artifact and optional remote location.
type Ref struct {
Kind string
LocalPath string
RemoteKey string
Checksum string
}
// Store is a placeholder artifact storage abstraction.
type Store interface {
WriteLocal(ctx context.Context, ref Ref, data []byte) error
ReadLocal(ctx context.Context, ref Ref) ([]byte, error)
ExistsLocal(ctx context.Context, ref Ref) (bool, error)
Upload(ctx context.Context, ref Ref) (string, error)
}

15
internal/config/config.go Normal file
View File

@@ -0,0 +1,15 @@
package config
// Config contains loaded pipeline and session configuration.
type Config struct {
Pipeline *Pipeline
Session *Session
}
// Pipeline represents durable pipeline-level settings.
type Pipeline struct{}
// Session represents per-session inputs and metadata.
type Session struct {
SessionID string
}

2
internal/config/doc.go Normal file
View File

@@ -0,0 +1,2 @@
// Package config contains pipeline and session configuration contracts.
package config

8
internal/config/load.go Normal file
View File

@@ -0,0 +1,8 @@
package config
import "fmt"
// Load is a placeholder for strict pipeline/session config decoding.
func Load(_ string, _ string) (*Config, error) {
return nil, fmt.Errorf("config load: not yet implemented")
}

View File

@@ -0,0 +1,8 @@
package config
import "fmt"
// Validate is a placeholder for configuration validation logic.
func Validate(_ *Config) error {
return fmt.Errorf("config validation: not yet implemented")
}

View File

@@ -0,0 +1,7 @@
package contracts
// ArtifactResult is a placeholder generated artifact contract.
type ArtifactResult struct {
Schema string `json:"schema"`
Path string `json:"path"`
}

View File

@@ -0,0 +1,2 @@
// Package contracts defines shared high-level artifact contracts.
package contracts

View File

@@ -0,0 +1,7 @@
package contracts
// SessionManifest is a placeholder durable session-run contract.
type SessionManifest struct {
Schema string `json:"schema"`
SessionID string `json:"session_id"`
}

View File

@@ -0,0 +1,19 @@
package contracts
// SpeakerTranscript is a placeholder transcription artifact contract.
type SpeakerTranscript struct {
Schema string `json:"schema"`
SessionID string `json:"session_id"`
}
// CanonicalTranscript is a placeholder merged transcript contract.
type CanonicalTranscript struct {
Schema string `json:"schema"`
SessionID string `json:"session_id"`
}
// ProcessedTranscript is a placeholder polished transcript contract.
type ProcessedTranscript struct {
Schema string `json:"schema"`
SessionID string `json:"session_id"`
}

2
internal/logging/doc.go Normal file
View File

@@ -0,0 +1,2 @@
// Package logging provides structured logger construction for narratio.
package logging

View File

@@ -0,0 +1,17 @@
package logging
import (
"io"
"log/slog"
"os"
)
// NewLogger creates a text slog logger.
func NewLogger(out io.Writer, level slog.Level) *slog.Logger {
if out == nil {
out = os.Stderr
}
handler := slog.NewTextHandler(out, &slog.HandlerOptions{Level: level})
return slog.New(handler)
}

View File

@@ -0,0 +1,31 @@
package logging
import (
"bytes"
"log/slog"
"strings"
"testing"
)
func TestNewLoggerWritesOutput(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger(&buf, slog.LevelInfo)
if logger == nil {
t.Fatal("logger is nil")
}
logger.Info("hello", "component", "test")
out := buf.String()
if !strings.Contains(out, "hello") {
t.Fatalf("output = %q, want to contain message", out)
}
}
func TestNewLoggerNilWriter(t *testing.T) {
logger := NewLogger(nil, slog.LevelInfo)
if logger == nil {
t.Fatal("logger is nil")
}
logger.Info("should not panic")
}

2
internal/manifest/doc.go Normal file
View File

@@ -0,0 +1,2 @@
// Package manifest defines durable run-state tracking for narratio sessions.
package manifest

View File

@@ -0,0 +1,18 @@
package manifest
import "time"
// StageState tracks status and metadata for one stage execution.
type StageState struct {
Status StageStatus
UpdatedAt time.Time
Error string
}
// Manifest is the durable state record for a session run.
type Manifest struct {
SessionID string
CreatedAt time.Time
UpdatedAt time.Time
Stages map[string]StageState
}

View File

@@ -0,0 +1,14 @@
package manifest
// StageStatus is the lifecycle state of a pipeline stage.
type StageStatus string
const (
StatusPending StageStatus = "pending"
StatusRunning StageStatus = "running"
StatusSucceeded StageStatus = "succeeded"
StatusFailed StageStatus = "failed"
StatusSkipped StageStatus = "skipped"
StatusStale StageStatus = "stale"
StatusInterrupted StageStatus = "interrupted"
)

View File

@@ -0,0 +1,27 @@
package manifest
import (
"context"
"fmt"
)
// Store is a placeholder interface for manifest persistence.
type Store interface {
Load(ctx context.Context, sessionID string) (*Manifest, error)
Save(ctx context.Context, m *Manifest) error
}
// LocalStore is a placeholder local-filesystem manifest store.
type LocalStore struct {
RootDir string
}
// Load returns a not-yet-implemented error in the scaffold.
func (s *LocalStore) Load(_ context.Context, _ string) (*Manifest, error) {
return nil, fmt.Errorf("manifest local load: not yet implemented")
}
// Save returns a not-yet-implemented error in the scaffold.
func (s *LocalStore) Save(_ context.Context, _ *Manifest) error {
return fmt.Errorf("manifest local save: not yet implemented")
}

2
internal/stage/doc.go Normal file
View File

@@ -0,0 +1,2 @@
// Package stage defines pipeline stage contracts and placeholder stage types.
package stage

View File

@@ -0,0 +1,63 @@
package stage
import (
"context"
"fmt"
"gitea.maximumdirect.net/eric/narratio/internal/app"
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
)
func notImplemented(name string) error {
return fmt.Errorf("stage %q: not yet implemented", name)
}
type Prepare struct{}
type Transcribe struct{}
type Normalize struct{}
type Merge struct{}
type Polish struct{}
type Analyze struct{}
type Archive struct{}
type Notify struct{}
func (Prepare) Name() string { return "prepare" }
func (Transcribe) Name() string { return "transcribe" }
func (Normalize) Name() string { return "normalize" }
func (Merge) Name() string { return "merge" }
func (Polish) Name() string { return "polish" }
func (Analyze) Name() string { return "analyze" }
func (Archive) Name() string { return "archive" }
func (Notify) Name() string { return "notify" }
func (s Prepare) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
}
func (s Transcribe) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
}
func (s Normalize) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
}
func (s Merge) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
}
func (s Polish) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
}
func (s Analyze) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
}
func (s Archive) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
}
func (s Notify) Run(_ context.Context, _ *app.Env, _ *manifest.Manifest) (*Result, error) {
return nil, notImplemented(s.Name())
}

21
internal/stage/stage.go Normal file
View File

@@ -0,0 +1,21 @@
package stage
import (
"context"
"gitea.maximumdirect.net/eric/narratio/internal/app"
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
)
// Stage is the pipeline unit contract.
type Stage interface {
Name() string
Run(ctx context.Context, env *app.Env, m *manifest.Manifest) (*Result, error)
}
// Result is the declared output of a stage execution.
type Result struct {
Outputs []artifacts.Ref
Metadata map[string]any
}