Refactor test cases for improved readability and consistency
- Updated test_dns.py to enhance mock function definitions and improve spacing for better readability. - Modified test_filters.py to streamline assertions and ensure consistent formatting across test cases. - Cleaned up test_import_export.py by organizing imports and ensuring consistent formatting in CSV and JSON tests. - Improved test_main.py by refining mock setups and ensuring consistent error handling in assertions.
This commit is contained in:
parent
0710d10fac
commit
7872991e0b
42 changed files with 2376 additions and 2735 deletions
7
docs/adr/0001-privileged-mode-gates-writes.md
Normal file
7
docs/adr/0001-privileged-mode-gates-writes.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Privileged mode gates system-file writes
|
||||
|
||||
The application starts read-only and permits changes to `/etc/hosts` only after
|
||||
sudo access has been validated and a timestamped backup has been created. All
|
||||
privileged writes go through `HostsManager` during that mode. This makes the
|
||||
authorization boundary visible to the user and guarantees a pre-edit safety
|
||||
snapshot; the temporary backup is not a user-facing recovery system.
|
||||
7
docs/adr/0002-normalize-hosts-serialization.md
Normal file
7
docs/adr/0002-normalize-hosts-serialization.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Normalize hosts-file serialization
|
||||
|
||||
The parser models Host Entries plus header and footer comments rather than the
|
||||
file's exact byte layout. Serialization deliberately aligns fields, groups
|
||||
comments, omits original blank-line placement, and ensures the management header
|
||||
is present. This trades lossless round trips for a stable, readable output; code
|
||||
and documentation must not promise preservation of original formatting.
|
||||
36
docs/agents/domain.md
Normal file
36
docs/agents/domain.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# Domain Docs
|
||||
|
||||
How engineering skills should consume this repository's domain documentation
|
||||
when exploring the codebase.
|
||||
|
||||
## Before exploring, read these
|
||||
|
||||
- **`CONTEXT.md`** at the repository root.
|
||||
- **`docs/adr/`** — read ADRs that affect the area about to be changed.
|
||||
|
||||
If either location does not exist, proceed silently. The `/domain-modeling`
|
||||
skill creates domain documentation when terminology or decisions are resolved.
|
||||
|
||||
## File structure
|
||||
|
||||
This repository uses a single-context layout:
|
||||
|
||||
/
|
||||
├── CONTEXT.md
|
||||
├── docs/
|
||||
│ └── adr/
|
||||
└── src/
|
||||
|
||||
## Use the glossary's vocabulary
|
||||
|
||||
When output names a domain concept—for example in an issue title, refactor
|
||||
proposal, hypothesis, or test name—use the term defined in `CONTEXT.md`. Avoid
|
||||
synonyms that the glossary explicitly rejects.
|
||||
|
||||
If a required concept is missing, reconsider whether the proposed language
|
||||
belongs to the project or note the gap for `/domain-modeling`.
|
||||
|
||||
## Flag ADR conflicts
|
||||
|
||||
Explicitly surface output that contradicts an existing ADR instead of silently
|
||||
overriding it.
|
||||
52
docs/agents/issue-tracker.md
Normal file
52
docs/agents/issue-tracker.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Issue tracker: Forgejo
|
||||
|
||||
Issues and PRDs for this repository live as Forgejo issues in
|
||||
`git.s1q.dev/phg/hosts`. Use `fgj` for all operations.
|
||||
|
||||
Every repository-scoped command must use the hostname `git.s1q.dev` and
|
||||
repository `phg/hosts`.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Create an issue**:
|
||||
`fgj --hostname git.s1q.dev issue create -R phg/hosts --title "..." --body "..."`
|
||||
- **Read an issue**:
|
||||
`fgj --hostname git.s1q.dev issue view <number> -R phg/hosts`
|
||||
- **Read an issue as JSON**:
|
||||
`fgj --hostname git.s1q.dev issue view <number> -R phg/hosts --json`
|
||||
- **List issues**:
|
||||
`fgj --hostname git.s1q.dev issue list -R phg/hosts --state open --json`
|
||||
- **Comment on an issue**:
|
||||
`fgj --hostname git.s1q.dev issue comment <number> -R phg/hosts --body "..."`
|
||||
- **Apply or remove labels**:
|
||||
`fgj --hostname git.s1q.dev issue edit <number> -R phg/hosts --add-label "..."`
|
||||
or `--remove-label "..."`
|
||||
- **Close an issue**:
|
||||
`fgj --hostname git.s1q.dev issue close <number> -R phg/hosts --comment "..."`
|
||||
- **List repository labels**:
|
||||
`fgj --hostname git.s1q.dev label list -R phg/hosts --json`
|
||||
|
||||
## When a skill says "publish to the issue tracker"
|
||||
|
||||
Create a Forgejo issue with `fgj issue create`.
|
||||
|
||||
## When a skill says "fetch the relevant ticket"
|
||||
|
||||
Run `fgj issue view <number>` with the configured hostname and repository.
|
||||
|
||||
## Wayfinding operations
|
||||
|
||||
The map is a Forgejo issue with one child issue per ticket.
|
||||
|
||||
- **Map**: an issue labelled `wayfinder:map` containing Notes,
|
||||
Decisions-so-far, and Fog sections.
|
||||
- **Child ticket**: an issue whose body starts with `Part of #<map>` and whose
|
||||
type is recorded with a `wayfinder:<type>` label (`research`, `prototype`,
|
||||
`grilling`, or `task`).
|
||||
- **Blocking**: put `Blocked by: #<number>, #<number>` at the top of the child
|
||||
issue body. A ticket is unblocked when every listed issue is closed.
|
||||
- **Frontier**: inspect the map's open child issues and choose the first issue
|
||||
that has no open blocker and has not been claimed.
|
||||
- **Claim**: apply the `wayfinder:claimed` label before beginning work.
|
||||
- **Resolve**: comment with the answer, close the child issue, then append a
|
||||
context pointer to the map's Decisions-so-far section.
|
||||
15
docs/agents/triage-labels.md
Normal file
15
docs/agents/triage-labels.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Triage Labels
|
||||
|
||||
The skills speak in terms of five canonical triage roles. This file maps those
|
||||
roles to the label strings used in this repository's Forgejo issue tracker.
|
||||
|
||||
| Label in mattpocock/skills | Label in our tracker | Meaning |
|
||||
| -------------------------- | -------------------- | ----------------------------------------- |
|
||||
| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue |
|
||||
| `needs-info` | `needs-info` | Waiting on reporter for more information |
|
||||
| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent |
|
||||
| `ready-for-human` | `ready-for-human` | Requires human implementation |
|
||||
| `wontfix` | `wontfix` | Will not be actioned |
|
||||
|
||||
When a skill mentions a canonical role, use the corresponding tracker label
|
||||
from this table.
|
||||
253
docs/user-guide.md
Normal file
253
docs/user-guide.md
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
# User guide
|
||||
|
||||
`hosts` is a keyboard-first terminal application for inspecting and editing the
|
||||
system Hosts File. It starts in Read-only Mode and requires explicit sudo
|
||||
authorization before it can change `/etc/hosts`.
|
||||
|
||||
> [!WARNING]
|
||||
> `hosts` is alpha software. Read [Persistence and recovery](#persistence-and-recovery)
|
||||
> before entering Privileged Mode. In particular, undo, redo, sorting, and save
|
||||
> failures can currently leave the interface and `/etc/hosts` out of sync.
|
||||
|
||||
## Before you begin
|
||||
|
||||
You need macOS or Linux, Python 3.13 or newer, `sudo`, and
|
||||
[uv](https://docs.astral.sh/uv/). The normal user entry point runs the current
|
||||
revision from the project's default branch:
|
||||
|
||||
```bash
|
||||
uvx git+https://git.s1q.dev/phg/hosts.git
|
||||
```
|
||||
|
||||
The project does not yet publish versioned releases. The `uvx` command can
|
||||
therefore run newer code on a later invocation.
|
||||
|
||||
## Inspect the Hosts File safely
|
||||
|
||||
The application opens in Read-only Mode. In this mode you can:
|
||||
|
||||
- select a Host Entry and inspect all of its hostnames, comment, state, and DNS
|
||||
details;
|
||||
- search by hostname, IP address, or comment;
|
||||
- sort by IP address or Canonical Hostname;
|
||||
- show or hide Default Entries from the configuration screen; and
|
||||
- reload `/etc/hosts` from disk.
|
||||
|
||||
The footer shows `Read-only` while system-file mutations are blocked. Press `?`
|
||||
for in-application help.
|
||||
|
||||
### A warning about sorting
|
||||
|
||||
Sorting is intended as a view operation, but currently reorders the in-memory
|
||||
Hosts File. If you later enter Privileged Mode and save or perform an auto-saved
|
||||
mutation, that sorted order can be written to `/etc/hosts`. Reload with `Ctrl+R`
|
||||
before entering Privileged Mode if you sorted only for inspection.
|
||||
|
||||
## Enter Privileged Mode
|
||||
|
||||
Press `Ctrl+E`. The application first checks for existing sudo authorization.
|
||||
If authorization is not already available, it asks for your password. Before
|
||||
enabling mutations it verifies write access and creates a Pre-edit Backup.
|
||||
|
||||
If authorization, permission validation, or backup creation fails, the
|
||||
application remains in Read-only Mode.
|
||||
|
||||
The footer shows `Edit` while Privileged Mode is active. This label refers to
|
||||
Privileged Mode; it is separate from the Entry Editor used to change one Host
|
||||
Entry.
|
||||
|
||||
## Change a Host Entry
|
||||
|
||||
In Privileged Mode:
|
||||
|
||||
- Press `n` to add a Host Entry.
|
||||
- Select a non-default Host Entry and press `e` to open the Entry Editor.
|
||||
- Press `d` and confirm to delete the selected Host Entry.
|
||||
- Press `Space` to activate or deactivate the selected Host Entry.
|
||||
- Press `Shift+Up` or `Shift+Down` to change its file order.
|
||||
|
||||
A Host Entry requires a valid IPv4 or IPv6 address, at least one valid hostname,
|
||||
and may include a comment. The first hostname is its Canonical Hostname; any
|
||||
additional hostnames are Aliases.
|
||||
|
||||
Default Entries represent protected baseline localhost or broadcasthost
|
||||
mappings. They cannot be changed, deleted, activated, deactivated, or moved.
|
||||
|
||||
Most successful mutations are saved to `/etc/hosts` immediately. Read the
|
||||
status message after every action. A message that reports a save failure means
|
||||
the interface may have changed while the system file did not.
|
||||
|
||||
## Use a DNS Entry
|
||||
|
||||
Choose the DNS Name Entry type when adding or editing an entry, then provide:
|
||||
|
||||
- the DNS name whose address should be resolved; and
|
||||
- one or more local hostnames that should map to that address.
|
||||
|
||||
The application resolves the DNS name in the background. A successful result
|
||||
replaces the entry's stored IP address, records DNS metadata in the serialized
|
||||
comment, activates a newly created DNS Entry, and saves the Hosts File. DNS
|
||||
refresh is manual rather than continuous:
|
||||
|
||||
- Press `r` to refresh the selected DNS Entry.
|
||||
- Press `Shift+R` to refresh all DNS Entries.
|
||||
|
||||
Both actions require Privileged Mode because a successful result changes
|
||||
`/etc/hosts`. A failed lookup reports the failure and keeps the previously
|
||||
stored mapping.
|
||||
|
||||
## Verify what is on disk
|
||||
|
||||
The details shown by the application normally match the last successful save,
|
||||
except for the known persistence cases below. To discard in-memory state and
|
||||
read `/etc/hosts` again, press `Ctrl+R`.
|
||||
|
||||
You can also inspect the file from another terminal:
|
||||
|
||||
```bash
|
||||
sed -n '1,240p' /etc/hosts
|
||||
```
|
||||
|
||||
Remember that saving is normalized rather than byte-preserving. The serializer
|
||||
aligns fields, groups comments, removes original blank-line placement, and adds
|
||||
a management header while retaining the modeled Host Entries and comments.
|
||||
|
||||
## Persistence and recovery
|
||||
|
||||
### What saves immediately
|
||||
|
||||
Adding, editing, deleting, moving, activating, deactivating, and successfully
|
||||
refreshing DNS Entries normally save immediately. `Ctrl+S` explicitly saves the
|
||||
entire current in-memory Hosts File.
|
||||
|
||||
### What does not save immediately
|
||||
|
||||
Undo and redo currently update the in-memory state without saving it. After
|
||||
`Ctrl+Z` or `Ctrl+Y`, use `Ctrl+S` if you want `/etc/hosts` to match the display.
|
||||
Reloading or quitting discards that unsaved in-memory result. Leaving
|
||||
Privileged Mode does not save it: the result remains displayed, but its
|
||||
undo/redo history is cleared and `/etc/hosts` still differs from the display.
|
||||
|
||||
A save failure can also leave the interface changed while the file on disk is
|
||||
unchanged. Reload with `Ctrl+R` to return the interface to the on-disk state
|
||||
before attempting another change.
|
||||
|
||||
### Locate a Pre-edit Backup
|
||||
|
||||
Entering Privileged Mode creates one timestamped Pre-edit Backup below the
|
||||
operating system's temporary directory. The application does not currently show
|
||||
this path. Ask Python for the directory used on your system:
|
||||
|
||||
```bash
|
||||
python3 -c 'import tempfile; print(tempfile.gettempdir() + "/hosts-manager-backups")'
|
||||
```
|
||||
|
||||
List the directory printed by that command. Backup files are named
|
||||
`hosts.backup.<timestamp>`:
|
||||
|
||||
```bash
|
||||
ls -lt /path/printed/by/the/previous/command
|
||||
```
|
||||
|
||||
The application does not remove old Pre-edit Backups, apply retention rules, or
|
||||
record which file belongs to a later session. Do not choose a backup solely
|
||||
because it is the newest; inspect its timestamp and contents first.
|
||||
|
||||
### Restore manually
|
||||
|
||||
Restoration is not available through the TUI. To restore manually:
|
||||
|
||||
1. Leave the application or return it to Read-only Mode.
|
||||
2. Locate and inspect the intended Pre-edit Backup.
|
||||
3. Copy that explicit file over `/etc/hosts` with sudo.
|
||||
4. Relaunch the application or press `Ctrl+R` to verify the restored contents.
|
||||
|
||||
For example, after replacing the placeholder with the exact file you inspected:
|
||||
|
||||
```bash
|
||||
sudo cp /exact/path/to/hosts.backup.TIMESTAMP /etc/hosts
|
||||
```
|
||||
|
||||
This replaces the current Hosts File. Never paste a guessed path or automate
|
||||
selection of the newest backup without inspecting it.
|
||||
|
||||
## Leave Privileged Mode
|
||||
|
||||
Press `Ctrl+E` again. The application clears its undo/redo history and forgets
|
||||
which Pre-edit Backup belongs to the session. The backup file remains in the
|
||||
temporary directory.
|
||||
|
||||
Leaving Privileged Mode currently runs `sudo -k`, which invalidates your cached
|
||||
sudo timestamp for other terminal sessions as well. This behavior is
|
||||
[tracked for removal](https://git.s1q.dev/phg/hosts/issues/4).
|
||||
|
||||
## Configuration
|
||||
|
||||
Press `c` to open configuration. The supported screen controls whether Default
|
||||
Entries are visible; they are hidden by default. The setting is stored at
|
||||
`~/.config/hosts-manager/config.json`.
|
||||
|
||||
Other values may exist in that file but are internal until their behavior is
|
||||
reachable through the TUI. Editing them manually is not a supported workflow.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Privileged Mode asks for a password
|
||||
|
||||
This is expected when no cached sudo authorization is available. Canceling the
|
||||
password prompt leaves the application in Read-only Mode.
|
||||
|
||||
### Privileged Mode cannot be enabled
|
||||
|
||||
The application requires all three safety gates: valid sudo authorization,
|
||||
write access to `/etc/hosts`, and successful creation of a Pre-edit Backup. A
|
||||
failure in any gate leaves mutations disabled. Read the status message for the
|
||||
specific failure.
|
||||
|
||||
### The display differs from `/etc/hosts`
|
||||
|
||||
This can occur after undo, redo, a failed save, or sorting followed by other
|
||||
actions. Press `Ctrl+R` to discard in-memory state and reload the file. If an
|
||||
unwanted change reached disk, follow the manual restoration procedure above.
|
||||
|
||||
### DNS resolution fails
|
||||
|
||||
DNS resolution uses the operating system resolver and a per-query timeout. A
|
||||
failure can result from an invalid DNS name, resolver failure, timeout, or no
|
||||
address being returned. The application reports the failure rather than
|
||||
continually retrying it.
|
||||
|
||||
## Key reference
|
||||
|
||||
### General
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| `Up` / `Down` | Select a Host Entry |
|
||||
| `Home` / `End` | Select the first or last visible Host Entry |
|
||||
| `Page Up` / `Page Down` | Move by one page |
|
||||
| `i` | Sort by IP address |
|
||||
| `h` | Sort by Canonical Hostname |
|
||||
| `Ctrl+R` | Reload `/etc/hosts` |
|
||||
| `c` | Open configuration |
|
||||
| `?` | Show help |
|
||||
| `q` or `Ctrl+C` | Quit |
|
||||
| `Ctrl+E` | Enter or leave Privileged Mode |
|
||||
|
||||
### Privileged Mode
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| `n` | Add a Host Entry |
|
||||
| `e` | Open the selected Host Entry in the Entry Editor |
|
||||
| `d` | Delete the selected Host Entry |
|
||||
| `Space` | Activate or deactivate the selected Host Entry |
|
||||
| `Shift+Up` / `Shift+Down` | Move the selected Host Entry |
|
||||
| `r` | Refresh the selected DNS Entry |
|
||||
| `Shift+R` | Refresh all DNS Entries |
|
||||
| `Ctrl+Z` / `Ctrl+Y` | Undo or redo in memory |
|
||||
| `Ctrl+S` | Save the current in-memory state |
|
||||
|
||||
Within the Entry Editor, use `Tab` and `Shift+Tab` to move between fields and
|
||||
`Escape` to leave the form. If values changed, the application asks whether to
|
||||
save, discard, or continue editing.
|
||||
Loading…
Add table
Add a link
Reference in a new issue