Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions cmd/nightshift/commands/commit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package commands

import (
"fmt"
"io"
"os"

"github.com/marcus/nightshift/internal/commits"
"github.com/spf13/cobra"
)

var commitCmd = &cobra.Command{
Use: "commit",
Short: "Conventional Commits helpers",
Long: `Tools for working with Conventional Commits messages.

Use "commit normalize" to validate and reformat a commit message so it
follows the project's rules (type prefix, lowercase type, subject length,
and wrapped body).`,
}

var commitNormalizeCmd = &cobra.Command{
Use: "normalize [MESSAGE]",
Short: "Normalize a commit message to Conventional Commits format",
Long: `Validate and rewrite a commit message into canonical Conventional
Commits form.

The message is read from a positional argument, from a file passed via
--file (typically .git/COMMIT_EDITMSG by a commit-msg hook), or from stdin
when no argument and no --file are given.

nightshift commit normalize "feat: add login"
nightshift commit normalize --file .git/COMMIT_EDITMSG
git log -1 --pretty=%B | nightshift commit normalize

Use --check to only validate without rewriting; the exit code is non-zero
when the message does not conform.`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
check, _ := cmd.Flags().GetBool("check")
file, _ := cmd.Flags().GetString("file")

raw, err := readCommitMessage(args, file)
if err != nil {
return err
}

normalized, err := commits.Normalize(raw)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
return err
}

if check {
fmt.Fprintln(os.Stdout, normalized)
return nil
}
fmt.Fprintln(os.Stdout, normalized)
return nil
},
}

func init() {
commitNormalizeCmd.Flags().BoolP("check", "c", false, "Only validate; do not rewrite")
commitNormalizeCmd.Flags().StringP("file", "f", "", "Read the message from this file (use by the commit-msg hook)")
commitCmd.AddCommand(commitNormalizeCmd)
rootCmd.AddCommand(commitCmd)
}

// readCommitMessage resolves the message source in order: positional arg,
// --file, then stdin.
func readCommitMessage(args []string, file string) (string, error) {
if len(args) == 1 {
return args[0], nil
}
if file != "" {
b, err := os.ReadFile(file)
if err != nil {
return "", fmt.Errorf("read %s: %w", file, err)
}
return string(b), nil
}
b, err := io.ReadAll(os.Stdin)
if err != nil {
return "", fmt.Errorf("read stdin: %w", err)
}
return string(b), nil
}
47 changes: 47 additions & 0 deletions docs/commit-messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Commit Messages

Nightshift uses [Conventional Commits](https://www.conventionalcommits.org/)
for all commit messages. This keeps the history readable and lets tooling
derive changelogs automatically.

## Format

```
<type>(<scope>): <subject>

<body>
```

- **type** — one of `feat`, `fix`, `docs`, `style`, `refactor`, `test`,
`chore`, `perf`, `build`, `ci`.
- **scope** — optional, e.g. `fix(api): ...`.
- **subject** — lowercase, imperative mood, no trailing period, max 72 chars.
- **body** — optional, wrapped at 72 columns, separated from the subject by a
blank line.

## The `commit normalize` command

Validate and reformat a message:

```sh
nightshift commit normalize "feat: add login screen"
nightshift commit normalize --file .git/COMMIT_EDITMSG
git log -1 --pretty=%B | nightshift commit normalize
```

Add `--check` to validate only. The command exits non-zero when a message
cannot be normalized (missing/unknown type, capitalized or overlong subject).

## commit-msg hook

To enforce the rules locally, install the hook:

```sh
make install-hooks
# or manually:
ln -sf ../../scripts/commit-msg.sh .git/hooks/commit-msg
```

The hook normalizes your message file in place before the commit is created and
rejects messages that cannot be fixed automatically. Bypass it with
`git commit --no-verify`.
2 changes: 0 additions & 2 deletions internal/agents/agent.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// Package agents provides interfaces and implementations for spawning AI agents.
// Unlike providers (which track usage), agents execute tasks autonomously.
package agents

import (
Expand Down
22 changes: 22 additions & 0 deletions internal/agents/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Package agents provides interfaces and implementations for spawning and
// driving AI coding agents.
//
// Unlike the [providers] package, which is concerned with tracking token usage
// and cost, agents are responsible for autonomously executing a task to
// completion: running a prompt against a CLI, handling timeouts, and returning
// structured output. The orchestrator uses agents to perform the implement and
// review phases of the plan-implement-review loop.
//
// The core abstraction is the [Agent] interface, implemented per backend:
//
// - [ClaudeAgent] wraps the Claude Code CLI (claude --print).
// - CodexAgent wraps the OpenAI Codex CLI.
// - CopilotAgent wraps the GitHub Copilot CLI.
//
// Agents are constructed with functional options (e.g. [WithBinaryPath],
// [WithDangerouslySkipPermissions]) and report availability via Available so
// the orchestrator can skip backends that are not installed. Execution honours
// [DefaultTimeout] unless overridden by the caller's context.
//
// [providers]: github.com/marcus/nightshift/internal/providers
package agents
2 changes: 0 additions & 2 deletions internal/budget/budget.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// Package budget implements token budget calculation and allocation for nightshift.
// Supports daily and weekly modes with reserve and aggressive end-of-week options.
package budget

import (
Expand Down
24 changes: 24 additions & 0 deletions internal/budget/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Package budget implements token budget calculation and allocation for
// nightshift runs.
//
// Given a provider and its recent usage, the package computes how many tokens
// may be safely spent on the next run. It supports two modes:
//
// - daily: a fixed per-day allowance.
// - weekly: a rolling weekly budget with reserve holding and an aggressive
// end-of-week multiplier so leftover budget is spent down before reset.
//
// The primary result type is [AllowanceResult], which carries the final token
// allowance together with the metadata used to derive it (used percentage and
// its source, predicted remaining daytime usage, reserve amount, mode, and the
// confidence/sample count of the underlying budget estimate).
//
// Usage data is supplied through provider-specific interfaces
// ([ClaudeUsageProvider], [CodexUsageProvider], [CopilotUsageProvider]), all
// extending the common [UsageProvider]. Budget estimates may come from config,
// the API, or the [calibrator] via the [BudgetSource] interface, which returns
// a [BudgetEstimate] annotated with source, confidence, sample count, and
// variance.
//
// [calibrator]: github.com/marcus/nightshift/internal/calibrator
package budget
1 change: 0 additions & 1 deletion internal/calibrator/calibrator.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// Package calibrator tunes task budgets and scheduling based on historical usage data.
package calibrator

import (
Expand Down
22 changes: 22 additions & 0 deletions internal/calibrator/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Package calibrator infers provider subscription budgets from historical
// usage and feeds them back into budget planning.
//
// Rather than requiring the user to configure an exact weekly token budget,
// the calibrator mines the snapshot history collected by the [snapshots]
// package to estimate the effective subscription limit for each provider. It
// implements the [budget.BudgetSource] interface so its estimates flow
// transparently into the budget manager.
//
// [Calibrate] returns a [CalibrationResult] holding the inferred weekly
// budget plus a confidence level (none/low/medium/high), the sample count it
// was derived from, the variance across samples, and a human-readable source
// label. [GetBudget] wraps that result as a [budget.BudgetEstimate] for direct
// consumption by the budget package.
//
// A [Calibrator] is constructed with [New], taking the [db.DB] it reads
// snapshots from and the project [config.Config].
//
// [snapshots]: github.com/marcus/nightshift/internal/snapshots
// [db]: github.com/marcus/nightshift/internal/db
// [config]: github.com/marcus/nightshift/internal/config
package calibrator
Loading