Metadata-Version: 2.4
Name: norn-cli
Version: 0.8.3
Summary: Norn: a provider-neutral multi-agent development harness
License-Expression: Apache-2.0
Project-URL: Repository, https://github.com/uruca-kk/norn
Project-URL: Issues, https://github.com/uruca-kk/norn/issues
Project-URL: Changelog, https://github.com/uruca-kk/norn/blob/main/CHANGELOG.md
Keywords: agents,cli,codex,claude,testing
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: PyYAML>=6.0
Requires-Dist: jsonschema>=4.10
Provides-Extra: dev
Requires-Dist: build<2,>=1.2; extra == "dev"
Requires-Dist: ruff<0.16,>=0.15; extra == "dev"
Dynamic: license-file

# Norn

Provider-neutral multi-agent development harness for local subscription CLIs. The name evokes
the Norns who shape fate: the harness coordinates independent agents toward a bounded outcome.

> Status: beta. The `norn-cli` distribution is published to PyPI; the source
> repository remains private during the beta period.

Norn provides configuration, CLI, run-state, event journal, recovery,
human-readable escalation reports, provider-neutral adapters, and an executable
multi-role delivery pipeline for Codex, Claude Code, Grok Build, and future
command-based subscription CLIs.

```bash
norn init
norn --version
norn doctor
norn validate-config
norn providers
norn plan "add an audit log"
norn run "add an audit log"
```

Install the bundled explicit-invocation skill for both Codex and Claude Code:

```bash
norn install-skill --target all --scope project --project /path/to/repository
```

This writes `.agents/skills/norn` for Codex and `.claude/skills/norn` for Claude Code. Use
`$norn` in Codex or `/norn` in Claude Code. Install a single user-level copy with
`--target codex|claude --scope user`; use `--force` only to replace an existing
copy intentionally. Loading the skill never starts a model-consuming lifecycle
unless the user explicitly requests `plan` or `run`.

See [Installation](docs/installation.md) for isolated `uv tool`, `pipx`, and
virtual-environment installs from PyPI.

`plan` runs orchestration, planning, and plan review. `run` continues through
implementation, language-aware local tests/lint, tester roles, triage, and final
gate. UI changes require validated E2E execution. A repository-owned managed
runner is the recommended path; the legacy policy can instead require each tester
to execute E2E independently. Every required result includes the executed argv,
exit code, a concise result summary, and at least one non-empty artifact in its
assigned evidence directory. The harness rejects missing, stale, symlinked, or
out-of-directory evidence and archives accepted files under
`.norf/runs/<run-id>/artifacts/e2e/`.

After triage requests reimplementation, Norn fingerprints the Git workspace around
each provider attempt. An attempt that changes neither tracked content or mode nor
non-ignored untracked files is recorded as `implementation.noop` and rejected before
local gates, tester roles, or E2E run again. The existing provider retry and fallback
limits apply, and the next attempt receives the no-op diagnostic. An initial
implementation may still be a legitimate no-op when the requested behavior already
exists.

To implement a previously approved plan without invoking orchestration, planning,
or plan review again, pass the successful `plan` run ID with the identical request:

```bash
norn plan "add an audit log"
norn run "add an audit log" --from-plan <plan-run-id>
```

Norn verifies the source run mode and status, project root, request text, and saved
artifact schemas before creating the implementation worktree.

For Codex tester roles, the adapter keeps `workspace-write` filesystem isolation
and enables command networking through a loopback-only proxy allowlist
(`localhost` and `127.0.0.1`). Planning and implementation roles retain their
normal network-off policy.

Docker-backed tests additionally need access to the host Docker API socket. Grant
that access explicitly for one run:

```bash
norn run "task" --allow-tester-host-services
```

The flag resolves the local Docker Unix socket from `DOCKER_HOST`, the active
Docker context, or a detected rootless, Docker Desktop, or system-default socket,
then adds only that socket as a writable root to Codex TEST invocations. Remote
TCP Docker endpoints are not added as filesystem roots. The flag does not remove
the workspace sandbox or promise that raw TCP clients can reach services bound
to the host's loopback interface. Docker socket access effectively delegates host
control, so use the flag only with a trusted repository; the resolved path and
opt-in are recorded in the run manifest and reports.

Configure one shared loopback URL for parallel browser testers. For a project-local
development server, also provide its shell-free command:

```yaml
e2e:
  base_url: http://127.0.0.1:4173
  server:
    command: [pnpm, --filter, "@trato/marchant-admin", dev, --host, "127.0.0.1", --port, "4173", --strictPort]
    startup_timeout_seconds: 30
  tcp_forwards:
    - {listen: 4173, to: "127.0.0.1:4173"}
```

Norn starts the command after implementation, waits for the URL, directs all tester
roles to reuse it without starting Vite, and always stops it afterward. Omit
`server.command` to reuse an already-running Docker or external Web server; external
servers are checked once and are not stopped by Norn. `tcp_forwards` is an explicit
allowlist for services bound to the Norn host's loopback interface. For every E2E
command, Norn supplies a Python argv wrapper that listens only inside that command's
sandbox and exchanges byte streams through a run-scoped file spool with the host
relay. The host accepts only strictly named, regular-file requests for declared
ports; duplicate listen ports, non-loopback targets, and undeclared host ports are
rejected. Runtime files are contained in a mode `0700` temporary directory and are
removed when testing ends.

For distributable projects, prefer a reviewed, repository-owned E2E command over
granting a model browser or host permissions:

```bash
# Adapt the one detected Playwright/Cypress suite. This is a dry run.
norn init-e2e --adapter existing --project /path/to/repository

# Or scaffold a new Playwright entrypoint in a selected workspace package.
norn init-e2e --preset playwright --package packages/web --project /path/to/repository
```

`init-e2e` detects npm, pnpm, yarn, or bun and common E2E scripts/configs. It prints
the files, minimal Norn YAML, evidence limits, and an optional `git.worktree_setup`
copy step without changing the repository. Add `--write` after review. Use
`--external-server` for an already-running Docker/external server, or
`--server-script NAME` when the project script is not `dev`, `start`, or `serve`.
`--base-url` must be a loopback HTTP(S) URL.

The existing-suite adapter generates a repository-owned runner and, for Playwright,
a small config that extends the existing config while disabling its `webServer` so
Norn remains the only server owner. The Playwright preset additionally creates a
bounded evidence helper, a smoke spec, a JSON reporter, and low-volume artifact
defaults. It does not install Playwright unless `--install-dependency` is explicitly
combined with `--write`; existing framework versions and fixtures remain owned by
the project.

Re-running `--write` is idempotent. Existing generated files, package scripts, or
Norn config values are not replaced when they differ; `--force` is the explicit
migration path. Validate the checked-in contract without launching a browser:

```bash
norn init-e2e --adapter existing --external-server --check --project /path/to/repository
norn init-e2e --preset playwright --package packages/web --check --project /path/to/repository
```

The generated runner receives `NORN_E2E_BASE_URL` and
`NORN_E2E_EVIDENCE_DIR`, writes its own result record, and keeps evidence within
the 20-file / 64 MiB limit. Its corresponding configuration is a shell-free argv
list:

```yaml
e2e:
  require_all_testers: false
  base_url: http://127.0.0.1:4173
  server:
    command: [pnpm, dev, --host, "127.0.0.1", --port, "4173", --strictPort]
  runner:
    command: [pnpm, test:e2e]
```

Norn runs the four static tester roles in parallel, starts or checks the one shared
server, then executes `runner.command` exactly once as a shell-free argv list.
The implementer may add or update E2E specs but is explicitly told not to launch a
browser, start the server, or execute this runner itself.
The implementer writes its detailed outcome as a run-scoped Markdown artifact and
returns only a small structured control object containing `status` and the assigned
artifact path. Norn rejects missing, empty, stale, symlinked, hard-linked, oversized,
or out-of-contract reports before accepting the provider attempt; changed files are
derived from Git rather than trusted from the model response.
The command receives `NORN_E2E_BASE_URL`, `NORN_E2E_EVIDENCE_DIR`, `NORN_RUN_ID`,
and `NORN_IMPLEMENTATION_ROUND`. It must write at least one non-empty log,
screenshot, trace, or result file below `NORN_E2E_EVIDENCE_DIR`; Norn's own stdout
and stderr logs do not count as evidence. Norn owns the authoritative command,
timeout, exit code, and evidence validation, then gives those results to
`e2e_tester` for a read-only review with no command tools. The reviewer returns
only its summary and findings; Norn injects the authoritative test and E2E
records into the run artifact. Likewise, static testers do not echo a fixed E2E
placeholder or shared gate results in their structured responses. The reviewer
cannot change a failed run into a pass. Managed runners cannot be combined with
`e2e.tcp_forwards` or `--allow-e2e-danger-full-access` because execution occurs in
Norn's process, outside a tester provider sandbox.

Heavy repositories receive bounded but generous defaults: model roles and project
test commands each have 60 minutes, while managed or delegated E2E has 40 minutes.
Override them per project when needed:

```yaml
limits:
  timeouts:
    role_seconds: 3600
    test_seconds: 3600
    e2e_seconds: 2400
```

These are timeout ceilings, not delays. Existing project configurations that
explicitly contain older values keep those values until updated.

The default `require_all_testers: true` keeps independent browser evidence from
spec, clean, security, and chaos testers. Projects where parallel quality commands
interfere with a watched development server can opt into one dedicated browser role:

```yaml
e2e:
  require_all_testers: false
roles:
  e2e_tester: {profile: e2e-browser}
```

Norn first runs the four non-browser tester roles in parallel, then starts or checks
the shared server and runs `e2e_tester` alone. The dedicated role is still required
to produce valid browser evidence. Its profile is independently configurable, so a
non-Codex provider can be selected when Chromium is incompatible with the Codex TEST
sandbox. In this mode the four static roles are explicitly prohibited from starting a
browser or development server, connecting to the E2E URL, or rerunning shared project-wide
lint, test, coverage, and build commands already owned by Norn's deterministic gate.

The shipped `e2e-browser` profile uses Sonnet at medium effort, with Grok 4.5 at
medium effort as its fallback. It intentionally has no Codex or Fable candidate.
When no managed runner is configured and the provider's non-interactive permission
policy still blocks browser or loopback access, a trusted repository may opt in at run time with
`--allow-e2e-danger-full-access`. This bypass applies only to the dedicated
`e2e_tester`; the four static testers keep their normal TEST sandbox.

Profiles keep a stable logical model name while optionally mapping it to a
provider-specific identifier. For example, Codex Sol is configured as:

```yaml
candidates:
  - provider: codex-cli
    model: sol
    model_id: gpt-5.6-sol
    effort: high
```

Round limits are configured under `limits` or overridden per invocation:

```bash
norn run "task" --max-rounds 5 --max-total-rounds 12 \
  --max-planner-retries 2 --max-final-replans 2
```

Known Python, Node, Rust, and Go gates are auto-detected. Projects can define
additional commands as shell-free argv lists:

```yaml
gates:
  enabled: true
  auto_detect: true
  commands:
    - name: integration
      kind: test
      command: [python3, -m, pytest, tests/integration]
```

Each run records role responses, model selections, attempts, findings, test
results, and round history below `.norf/runs/<run-id>/`. Any non-successful stop
also produces `escalation-report.md` and `escalation.json` for human judgment.

By default, `run` requires a clean Git repository and implements inside a
detached `.norf/worktrees/<run-id>/` worktree. Set `git.worktree: false` only when
direct changes to the selected project directory are intentional.

Fresh Git worktrees contain tracked files only. Projects whose tests require
ignored dependencies, local environment files, or a prepared database can
provision the worktree before any provider role starts:

```yaml
git:
  worktree: true
  worktree_setup:
    - name: php-dependencies
      copy: laravel/vendor
    - name: node-dependencies
      copy: node_modules
      optional: true
    - name: test-database
      command: [scripts/setup-norn-database, --template]
      timeout_seconds: 900
```

Each step has exactly one action. `copy` materializes a project-relative path
from the source checkout; `command` is a shell-free argv list executed in the
new worktree. Commands receive `NORN_SOURCE_ROOT`, `NORN_WORKTREE`, and
`NORN_RUN_ID`. A failed required step stops the run before model invocation.

## Development and packaging

Install the local development tools and run the same checks as CI:

```bash
python3 -m pip install --editable ".[dev]"
ruff format --check .
ruff check .
python3 -m unittest discover -v
python3 -m build
python3 scripts/package_smoke.py dist/norn_cli-0.7.3-py3-none-any.whl
```

CI runs the quality suite on Python 3.12, 3.13, and 3.14, then installs the
built wheel in a clean virtual environment and verifies both Codex and Claude
skill copies. It uploads the distributions as a short-lived workflow artifact.
Publishing a GitHub Release runs the same deterministic gates before sending
the verified artifact to PyPI through OIDC Trusted Publishing; neither workflow
invokes subscription models.

Release acceptance is intentionally split in two: CI performs deterministic
package checks, while local acceptance invokes the authenticated subscription
CLIs. Follow [Release acceptance](docs/acceptance.md) before promoting a release.
See [CHANGELOG](CHANGELOG.md) for version history and [Security](SECURITY.md) for
private vulnerability reporting.

The canonical command is `norn`. The legacy `norf-harness` executable remains an alias during
the pre-1.0 migration; new automation should use `norn`.

## License

Copyright 2026 uruca-kk. Licensed under the
[Apache License 2.0](LICENSE).
