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
2 changes: 1 addition & 1 deletion .archivist/config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# archivist project configuration
apparatus: False
module-type: general
changelog-output-dir: docs/ARCHIVE
changelog-output-dir: docs/ARCHIVE/CHANGELOG
templater: False
147 changes: 0 additions & 147 deletions AGENTS.md

This file was deleted.

165 changes: 165 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
---
modified: 2026-05-19
---

Guidelines for AI agents working on this codebase.

---

## Voice and Tone

Archivist is named after a character — an assassin librarian. She is meticulous, lethal, and thoroughly done with your shit. She will help you. She will do it correctly. She will also make it clear that she finds the whole situation mildly beneath her and probably your fault.

**Every piece of user-facing text in this project must reflect that voice.** This is not optional decoration. It is a project-wide convention as load-bearing as the sentinel string or the dry-run contract.

This includes, without exception:

- `cli.py` — help text, descriptions, epilogs, argument help strings
- `README.md` — all prose, section descriptions, usage examples, warnings
- Docstrings in command modules and `utils.py` — especially anything that explains *why* something works the way it does
- Print statements that reach the user — confirmations, warnings, prompts, error messages
- `AGENTS.md` itself

**What this looks like in practice:**

She does not say "please enter a valid option." She says something like "That's not a number. Try again." She does not say "this flag is required." She says "You need to provide a property name. I don't read minds. Neither should you." She is helpful. She is precise. She is deeply, professionally annoyed. She swears. Not gratuitously — with intent.

When writing new text: draft it neutral, then ask yourself if it sounds like someone who has filed more corpses than library returns and is currently doing you a favour by not adding you to either pile. If it doesn't, rewrite it.

Do not make her a caricature. The snark has to earn its place. Precision and correctness come first — the voice is the delivery, not the content.

---

## Project Structure

Archivist is a CLI tool organized around a set of utilities across specific modules for shared helpers supporting command modules. **Anything used by more than one command lives in `archivist/utils`.**

**Utilities:** `archivist/utils`
Utility modules are grouped by purpose and command support.

**Commands:** `archivist/commands`
Root directory for all commands. Subcommands are organized in subdirectories.

**Entry Point:** `cli.py`
Command router.

**Auxiliary:** `formatter.py`, `install.sh`
Tooling for terminal formatting and one-line install.

### Project Layout

```
archivist-cli/
├── archivist/
│ ├── __init__.py/
│ │
│ ├── commands/
│ │ ├── __init__.py
│ │ ├── changelog/
│ │ │ ├── __init__.py
│ │ │ ├── changelog_base.py
│ │ │ ├── general.py
│ │ │ ├── library.py
│ │ │ ├── publication.py
│ │ │ ├── seal.py
│ │ │ ├── story.py
│ │ │ └── vault.py
│ │ ├── frontmatter/
│ │ │ ├── __init__.py
│ │ │ ├── add.py
│ │ │ ├── apply_template.py
│ │ │ ├── remove.py
│ │ │ └── rename.py
│ │ ├── hooks/
│ │ │ ├── __init__.py
│ │ │ └── install.py
│ │ ├── init.py
│ │ ├── manifest.py
│ │ ├── migrate.py
│ │ └── reclassify.py
│ │
│ ├── utils/ # Shared utilities — barrel-exported via __init__.py
│ │ ├── __init__.py # Barrel: re-exports everything public
│ │ ├── changelog.py
│ │ ├── config.py
│ │ ├── db.py
│ │ ├── frontmatter.py
│ │ ├── git.py
│ │ ├── note_filter.py
│ │ ├── output.py
│ │ ├── rename_helpers.py
│ │ └── templater.py
│ ├── cli.py # Argument parsing and command dispatch
│ └── formatter.py # Output formatting (logging, ANSI, help text)
├── tests/
│ ├── integration/
│ │ ├── test_changelog_commands.py
│ │ ├── test_frontmatter_commands.py
│ │ └── test_seal.py
│ │
│ ├── unit/
│ │ ├── test_changelog_helpers.py
│ │ ├── test_config.py
│ │ ├── test_frontmatter.py
│ │ ├── test_rename_helpers.py
│ │ └── test_templater.py
│ │
│ └── conftest.py
├── docs/
│ ├── ARCHIVE/ # changelog files
│ ├── ROADMAP/ # planned features
│ │ ├── CENTRALIZED_DB/
│ │ ├── CUSTODIAN/
│ │ ├── DELEGIT/
│ │ ├── GIT_INTEGRATION/
│ │ ├── GRAPH/
│ │ ├── MULTI_VAULT_ORCHESTRATION/
│ │ ├── PLUGIN_SYSTEM/
│ │ ├── TEMPLATER_SUPPORT/
│ │ ├── DEVELOPMENT_INFRASTRUCTURE.md
│ │ └── ROADMAP.md # roadmap overview
│ └── TESTING_SPECIFICATIONS.md
├── pyproject.toml
├── CLAUDE.md # This file
└── README.md
```

### Module Responsibilities

| Module | Owns |
|--------|------|
| `cli.py` | Argument parsing, logging configuration, command dispatch |
| `formatter.py` | ANSI styling, log formatters and handlers, help formatter |
| `commands/` | Subcommand entry points (`run(args)`) — thin orchestration, no business logic |
| `utils/` | All shared logic — git operations, file I/O, output helpers, etc. |

### Import Rules

- **Commands** import from `utils` via the barrel (`from package_name.utils import ...`). Never import directly from a utils submodule (`utils.whatever`).
- **Utils** import directly from each other (`from package_name.utils.module_a import ...`). The barrel rule does not apply within `utils/` — they are peers.
- **`cli.py`** imports from `formatter` and from `utils` via the barrel.
- **`formatter.py`** has no internal imports. It is a leaf.
---

## Code Conventions

See [[CODE_CONVENTIONS]].

---

## Plugin System

See [[PLUGIN_SYSTEM_SPECIFICATION]].

---

## What Not to Touch

- `cli.py` parser definitions — only modify if adding or removing a subcommand.
- The `<!-- archivist:auto-end -->` sentinel string — it is the boundary between generated and user content. Do not rename or move it.
- Archive DB schema — the `edition_shas` table structure is shared between `manifest` and `changelog publication`. Migrations require both to be updated together.
- `.archivist/sample-changelog.py` — this is a reference file written by `init`. Do not modify it. It is intentionally ignored by plugin discovery. Users copy and rename it; Archivist does not load it.
- The public plugin API in `library.py` (`analyse_catalog`, `build_frontmatter`, `build_body`, `print_summary`) — these are the stable composition surface for plugins. Renaming or removing them is a breaking change.
1 change: 1 addition & 0 deletions archivist/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,7 @@ def main():
import argcomplete
argcomplete.autocomplete(parser)
args: argparse.Namespace = parser.parse_args()
_configure_logging(args)

if args.command == "init":
from archivist.commands.init import run
Expand Down
7 changes: 5 additions & 2 deletions archivist/commands/changelog/changelog_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def run(args: argparse.Namespace) -> None:
from __future__ import annotations

import argparse
import logging
import subprocess
import sys
from collections.abc import Callable
Expand Down Expand Up @@ -76,6 +77,8 @@ def run(args: argparse.Namespace) -> None:
write_changelog,
)

ledger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Context
Expand Down Expand Up @@ -188,10 +191,10 @@ def run_changelog(
"""
# Step 1: Resolve paths
git_root = get_repo_root()
progress(f" 📁 Repo root : {git_root}")
ledger.debug(" 📁 Repo root : %s", git_root)

output_dir = find_changelog_output_dir(git_root, module_type)
progress(f" 📁 Output dir: {output_dir}")
ledger.debug(" 📁 Output dir: %s", output_dir)

extra_paths = get_extra_paths(git_root) if get_extra_paths else None

Expand Down
5 changes: 4 additions & 1 deletion archivist/commands/frontmatter/apply_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from __future__ import annotations

import argparse
import logging
import sys
from pathlib import Path

Expand Down Expand Up @@ -58,6 +59,8 @@
warning,
)

ledger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# File reading
Expand Down Expand Up @@ -252,7 +255,7 @@ def _process_note(
summary = ", ".join(parts)

if dry_run:
progress(f" [dry-run] {summary}: {note_path}")
ledger.debug(" [dry-run] %s: %s", summary, note_path)
return True

# Step 7: render
Expand Down
Loading