Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ checksum:

release:
github:
owner: attentionfs
owner: Laaaaksh
name: writ
draft: false
prerelease: auto
Expand All @@ -43,10 +43,10 @@ brews:
- name: writ
directory: Formula
repository:
owner: attentionfs
owner: Laaaaksh
name: homebrew-writ
token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"
homepage: "https://github.com/attentionfs/writ"
homepage: "https://github.com/Laaaaksh/writ"
description: "Agreed scope of work, approved before any code exists"
license: "MIT"
install: |
Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
This file is the project's committed home for project-intrinsic agent knowledge: build, test, release, architecture, and sharp-edge notes that should travel with the code.

- Add durable project-specific notes here as they are discovered through real work.
- Module path: `github.com/attentionfs/writ`. Only dependencies allowed: `spf13/cobra` and `BurntSushi/toml` — do not add others without a decision.
- Module path: `github.com/Laaaaksh/writ`. Only dependencies allowed: `spf13/cobra` and `BurntSushi/toml` — do not add others without a decision.
- All internal packages (`writ`, `gate`, `render`, `evidence`, `drift`) are fully implemented; do not change exported signatures without a decision.
- The writ lifecycle is `propose` (agent drafts a complete writ, unapproved) → `approve` (human tightens it in `$EDITOR`, `--yes` to skip) → implement → `attest`/`unattest <criterion-id>` (claim or clear a criterion as met, `--human` for a human claim) → `status`/`merge`, with `discard` throwing away an unwanted open writ (rejected proposal, abandoned work, corrupt state file) so `propose` is unblocked. There is no `writ open`: a hand-authored writ from a blank file was the failure mode this replaced. `writ.Writ.Approved` (nil = proposed) and `Criterion.Attestation` gate `gate.Decide`'s auto-merge path; `render.Status` keeps agent claims visually distinct from human confirmation and from machine-verified evidence — never blur the three. Intake (`propose` and `approve`) validates via `writ.Writ.ValidateProposal`, which additionally refuses criteria arriving with `met`/`attestation` set — claims exist only after approval, so pre-assessed drafts cannot self-bless through `approve --yes`; plain `Validate` stays the validator for mid-flight state. Author-written TOML (stdin/`--file` drafts, `$EDITOR` saves) parses through exported `writ.Parse`, which refuses keys naming no Writ field so typos are reported instead of silently dropped; `writ.Load` deliberately stays lenient for Save-written `.writ/current.toml` reads so version skew cannot strand an open writ.
- `.writ/current.toml` is runtime output, never committed. Package-level constant `writPath` in `internal/writ/writ.go` is the source of truth for its location. Two defenses keep it that way: `propose` seeds `.writ/` into the repo-local `.git/info/exclude` (best-effort, skipped when an ignore rule already covers it; resolved via `--git-common-dir` because git reads info/exclude only from the common dir — seeding a linked worktree's own gitdir silently does nothing) so blanket `git add -A` cannot track the state file, and `decide()` (status/merge) refuses a git-indexed state file (`cmd/writ.writStateTracked`, staged counts too) with `git rm --cached` guidance — a tracked copy leaves stale state on base and blocks checkout with raw git errors.
- `cmd/writ` commands signal a specific process exit code (0 auto-mergeable, 1 needs human, 2 no writ open) by returning the unexported `exitCodeErr{code}` from `RunE`; `main()` unwraps it via `errors.As` before falling back to the default exit-1 path. Command tests that need a real repo `os.Chdir` into a `t.TempDir()` git repo, since `repoRoot()` shells out to `git rev-parse --show-toplevel` against the process cwd rather than taking a directory argument.
- The README's FAQ section states verifiable facts about the codebase ("no HTTP client anywhere in it or its two dependencies", "reads no API keys — only `EDITOR`/`VISUAL`/`NO_COLOR`/`WRIT_VERIFY_TIMEOUT`", "only child processes are `git` and POSIX `sh`"), and its `$ writ status` fence embeds byte-real output for the same writ the propose example creates. These rot silently when reality changes: adding a dependency, an env read, a subprocess, or changing `render.Status` output means re-verifying/updating them (`rg "net/http|Getenv|LookupEnv|exec.Command" --type go` covers the FAQ trio). Capture doc examples by running the real binary in a throwaway repo, never by hand-typing output.
- This repo is on the captain's **personal** GitHub account (`attentionfs`). The default `gh` login has changed between the personal and work profiles over time (as of 2026-08-22 the personal account is the default and `~/.config/gh-personal` no longer exists), so never assume either way: verify with `gh api user --jq .login` (must print `attentionfs`) before any GitHub CLI operation, and if it prints a work account, export `GH_CONFIG_DIR="$HOME/.config/gh-personal"` first — recreating that dir via `gh auth login` if needed. The default branch is `master`, not `main`.
- This repo is on the captain's **personal** GitHub account (`Laaaaksh`). The default `gh` login has changed between the personal and work profiles over time (as of 2026-08-22 the personal account is the default and `~/.config/gh-personal` no longer exists), so never assume either way: verify with `gh api user --jq .login` (must print `Laaaaksh`) before any GitHub CLI operation, and if it prints a work account, export `GH_CONFIG_DIR="$HOME/.config/gh-personal"` first — recreating that dir via `gh auth login` if needed. The default branch is `master`, not `main`.
- A repo commit-identity guard hook requires the git author email to match the personal class (`laksh.sadhwani07@gmail.com`) for this remote — set via local (not global) `git config user.email` if a commit is rejected.
- Release packaging: `.github/workflows/ci.yml` (ubuntu build/vet/gofmt/test plus a separate `Test (macOS)` job for the darwin release target; the macOS job must stay its own job, not a matrix on `test`, because branch protection requires the literal check name `Test` and matrix suffixes like `Test (ubuntu-latest)` would never satisfy it — the gofmt gate lives inside `Test` so unformatted code cannot land even though only that one check is required), `.github/workflows/release.yml` (goreleaser on `v*` tag push), and `.goreleaser.yml` (darwin/linux amd64/arm64, publishing a Homebrew formula to `attentionfs/homebrew-writ` via `brews:`) mirror the sibling `attentionfs/vessel` repo's setup. Releasing requires a `HOMEBREW_TAP_TOKEN` repo secret — without it goreleaser falls back to `GITHUB_TOKEN`, which cannot write to the separate tap repo and the release fails (this is what broke vessel's first release). The tap repo's README.md is hand-maintained (goreleaser writes only `Formula/`), so when writ's README install instructions change, mirror them there too — it kept serving a stale broken one-liner for releases after the main README was fixed (corrected 2026-08-22). `.github/dependabot.yml` runs weekly `gomod` + `github-actions` version-update PRs on top of the GitHub-side Dependabot alerts/security-fixes toggles (those live in repo settings, not files); expect its PRs against `master`. `.github/workflows/codeql.yml` runs CodeQL code scanning (languages go,actions) on PRs, pushes to `master`, and a weekly cron — its status check is named `CodeQL`; activity is proven by `/code-scanning/analyses` entries, not default-setup state. `.github/ISSUE_TEMPLATE/` holds GitHub issue-form YAML (`bug_report.yml`, `feature_request.yml`) plus `config.yml`; keep new templates in that schema rather than markdown bodies. `CHANGELOG.md` is the human-readable release record in Keep a Changelog format — GitHub release bodies are just goreleaser commit dumps — so land user-facing changes with an `[Unreleased]` entry as part of the change itself, and when cutting a tag move `[Unreleased]` into a dated section with compare links (backfilled 0.1.0–0.2.4 from per-tag `git log --no-merges <prev>..<tag>` ranges, since goreleaser's release notes misattribute commits across the tightly-spaced v0.2.x tags).
- Release packaging: `.github/workflows/ci.yml` (ubuntu build/vet/gofmt/test plus a separate `Test (macOS)` job for the darwin release target; the macOS job must stay its own job, not a matrix on `test`, because branch protection requires the literal check name `Test` and matrix suffixes like `Test (ubuntu-latest)` would never satisfy it — the gofmt gate lives inside `Test` so unformatted code cannot land even though only that one check is required), `.github/workflows/release.yml` (goreleaser on `v*` tag push), and `.goreleaser.yml` (darwin/linux amd64/arm64, publishing a Homebrew formula to `Laaaaksh/homebrew-writ` via `brews:`) mirror the sibling `Laaaaksh/vessel` repo's setup. Releasing requires a `HOMEBREW_TAP_TOKEN` repo secret — without it goreleaser falls back to `GITHUB_TOKEN`, which cannot write to the separate tap repo and the release fails (this is what broke vessel's first release). The tap repo's README.md is hand-maintained (goreleaser writes only `Formula/`), so when writ's README install instructions change, mirror them there too — it kept serving a stale broken one-liner for releases after the main README was fixed (corrected 2026-08-22). `.github/dependabot.yml` runs weekly `gomod` + `github-actions` version-update PRs on top of the GitHub-side Dependabot alerts/security-fixes toggles (those live in repo settings, not files); expect its PRs against `master`. `.github/workflows/codeql.yml` runs CodeQL code scanning (languages go,actions) on PRs, pushes to `master`, and a weekly cron — its status check is named `CodeQL`; activity is proven by `/code-scanning/analyses` entries, not default-setup state. `.github/ISSUE_TEMPLATE/` holds GitHub issue-form YAML (`bug_report.yml`, `feature_request.yml`) plus `config.yml`; keep new templates in that schema rather than markdown bodies. `CHANGELOG.md` is the human-readable release record in Keep a Changelog format — GitHub release bodies are just goreleaser commit dumps — so land user-facing changes with an `[Unreleased]` entry as part of the change itself, and when cutting a tag move `[Unreleased]` into a dated section with compare links (backfilled 0.1.0–0.2.4 from per-tag `git log --no-merges <prev>..<tag>` ranges, since goreleaser's release notes misattribute commits across the tightly-spaced v0.2.x tags).
- `go.mod`'s `go` directive is the single source of the Go toolchain for CI and releases: both workflows pass `go-version-file: go.mod` to actions/setup-go, so that line decides which compiler (and which stdlib security patches) ship inside released binaries — v0.2.x shipped go1.22.12, EOL since Feb 2025, before the directive was bumped to a supported line. Audit it against the two-current-versions support policy whenever cutting a release.
- `master` is branch-protected: required `Test` status check (strict), enforced for admins, conversation resolution required, no direct pushes — land every change through a PR and wait for its `Test` check (`gh pr merge` honors the rules); `CONTRIBUTING.md` documents the contributor-facing flow. Repo settings keep `delete_branch_on_merge` on: after any PR merges (including this run's own gnhf branch PRs), GitHub deletes that head branch from origin — re-push the local branch to recreate it; don't misread the vanished origin ref as data loss. Wiki/Projects tabs are deliberately disabled; don't re-enable them when auditing settings.
- `cmd/writ/main.go`'s `version`, `commit`, and `date` must stay package-level `var`s (with `default*` const placeholders), never `const` — goreleaser's `-X main.version=...` ldflags silently no-op against a `const` string, so every released binary would misreport itself. `versionString` is the single place that renders them; it omits the `(commit ..., built ...)` suffix when both are still at their defaults.
- `goreleaser check`/`release` (v2.16+) soft-deprecate the `brews:` key in favor of `homebrew_casks:`; kept `brews:` here to match vessel's existing convention and the tap's `bin.install`-style formula. `dist/` (goreleaser's output dir) is gitignored.
- `brews:` sets `directory: Formula` so the generated formula writes to `Formula/writ.rb` in `attentionfs/homebrew-writ`, not the tap root. Homebrew resolves a tap's formulae from `Formula/` first, then `HomebrewFormula/`, then the root — a formula anywhere in `Formula/` always wins over one at the root, so omitting `directory` risks silently serving a stale root formula the moment anything else creates a `Formula/` dir in the tap (this happened to vessel's tap). Confirm the key name (`directory` vs the older `folder`) against the installed goreleaser's own schema (`goreleaser jsonschema`) before touching this, not from memory.
- `brews:` sets `directory: Formula` so the generated formula writes to `Formula/writ.rb` in `Laaaaksh/homebrew-writ`, not the tap root. Homebrew resolves a tap's formulae from `Formula/` first, then `HomebrewFormula/`, then the root — a formula anywhere in `Formula/` always wins over one at the root, so omitting `directory` risks silently serving a stale root formula the moment anything else creates a `Formula/` dir in the tap (this happened to vessel's tap). Confirm the key name (`directory` vs the older `folder`) against the installed goreleaser's own schema (`goreleaser jsonschema`) before touching this, not from memory.
- The README's `## Demo` embeds `docs/assets/demo.gif` (960px, ~12fps, palette-quantized, must stay under 10 MB) and links `docs/assets/demo.mp4` (1280px H.264/yuv420p) for full quality — GitHub does not play a committed MP4 inline. Both are regenerated by `make demo` (`scripts/record-demo/record.sh`), which builds the real `writ` binary, seeds a throwaway repo, and drives it live through [VHS](https://github.com/charmbracelet/vhs) via `scripts/record-demo/demo.tape` — the tape is the actual recording script, not a mockup, so re-run it (never hand-edit the assets) whenever CLI output changes. See `scripts/record-demo/README.md` for the full mechanics.

## Maintaining this file
Expand Down
20 changes: 10 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.2.5] - 2026-11-01

### Changed
- Go module path is now `github.com/attentionfs/writ`, following the GitHub account rename. Update imports and `go install` commands. Homebrew users: `brew tap attentionfs/writ`.
- Go module path is now `github.com/Laaaaksh/writ`, following the GitHub account rename. Update imports and `go install` commands. Homebrew users: `brew tap Laaaaksh/writ`.

### Added

Expand Down Expand Up @@ -111,12 +111,12 @@ Public-release hardening of the core shipped in 0.1.x.
- Human attestation kept visually distinct from machine claims and from agent attestations.
- Cross-platform releases (darwin/linux × amd64/arm64) with Homebrew tap publishing.

[Unreleased]: https://github.com/attentionfs/writ/compare/v0.2.5...HEAD
[0.2.5]: https://github.com/attentionfs/writ/compare/v0.2.4...v0.2.5
[0.2.4]: https://github.com/attentionfs/writ/compare/v0.2.3...v0.2.4
[0.2.3]: https://github.com/attentionfs/writ/compare/v0.2.2...v0.2.3
[0.2.2]: https://github.com/attentionfs/writ/compare/v0.2.1...v0.2.2
[0.2.1]: https://github.com/attentionfs/writ/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/attentionfs/writ/compare/v0.1.1...v0.2.0
[0.1.1]: https://github.com/attentionfs/writ/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/attentionfs/writ/releases/tag/v0.1.0
[Unreleased]: https://github.com/Laaaaksh/writ/compare/v0.2.5...HEAD
[0.2.5]: https://github.com/Laaaaksh/writ/compare/v0.2.4...v0.2.5
[0.2.4]: https://github.com/Laaaaksh/writ/compare/v0.2.3...v0.2.4
[0.2.3]: https://github.com/Laaaaksh/writ/compare/v0.2.2...v0.2.3
[0.2.2]: https://github.com/Laaaaksh/writ/compare/v0.2.1...v0.2.2
[0.2.1]: https://github.com/Laaaaksh/writ/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/Laaaaksh/writ/compare/v0.1.1...v0.2.0
[0.1.1]: https://github.com/Laaaaksh/writ/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/Laaaaksh/writ/releases/tag/v0.1.0
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ CI runs the same build, vet, and test steps on every pull request and on pushes
`base`, and `merge` refuses a dirty working tree. Keep these guarantees intact in any
change to those commands.

- The module path is `github.com/attentionfs/writ`. Only `spf13/cobra` and `BurntSushi/toml`
- The module path is `github.com/Laaaaksh/writ`. Only `spf13/cobra` and `BurntSushi/toml`
are allowed as dependencies; do not add others without prior agreement.
- If you are a coding agent working in this repo, read [AGENTS.md](AGENTS.md) first. It
documents the lifecycle commands and invariants your changes must respect, including
Expand Down
Loading
Loading