norn-cli 0.7.2__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- norn_cli-0.8.0/PKG-INFO +293 -0
- norn_cli-0.8.0/README.md +268 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/pyproject.toml +2 -1
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/__init__.py +1 -1
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/cli.py +56 -2
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/config.py +176 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/models.py +3 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/pipeline.py +621 -50
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/base.py +2 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/claude.py +2 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/codex.py +5 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/grok.py +18 -1
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/reporting.py +7 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/resources/default.yaml +28 -3
- norn_cli-0.8.0/src/norf_harness/resources/e2e_forward_command.py +162 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/resources/skills/norn/SKILL.md +28 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/resources/skills/norn/references/cli.md +80 -1
- norn_cli-0.8.0/src/norf_harness/tcp_forward.py +323 -0
- norn_cli-0.8.0/src/norf_harness/worktree.py +236 -0
- norn_cli-0.8.0/src/norn_cli.egg-info/PKG-INFO +293 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norn_cli.egg-info/SOURCES.txt +3 -0
- norn_cli-0.8.0/tests/test_cli.py +280 -0
- norn_cli-0.8.0/tests/test_config.py +283 -0
- norn_cli-0.8.0/tests/test_pipeline.py +925 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/tests/test_providers.py +92 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/tests/test_skill_install.py +24 -0
- norn_cli-0.8.0/tests/test_tcp_forward.py +86 -0
- norn_cli-0.8.0/tests/test_worktree.py +169 -0
- norn_cli-0.7.2/PKG-INFO +0 -160
- norn_cli-0.7.2/README.md +0 -135
- norn_cli-0.7.2/src/norf_harness/worktree.py +0 -109
- norn_cli-0.7.2/src/norn_cli.egg-info/PKG-INFO +0 -160
- norn_cli-0.7.2/tests/test_cli.py +0 -143
- norn_cli-0.7.2/tests/test_config.py +0 -117
- norn_cli-0.7.2/tests/test_pipeline.py +0 -423
- norn_cli-0.7.2/tests/test_worktree.py +0 -75
- {norn_cli-0.7.2 → norn_cli-0.8.0}/LICENSE +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/setup.cfg +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/doctor.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/e2e_evidence.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/gate.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/project_gates.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/__init__.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/command.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/registry.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/providers/router.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/resources/skills/norn/agents/openai.yaml +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/run_store.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norf_harness/skill_install.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norn_cli.egg-info/dependency_links.txt +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norn_cli.egg-info/entry_points.txt +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norn_cli.egg-info/requires.txt +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/src/norn_cli.egg-info/top_level.txt +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/tests/test_e2e_evidence.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/tests/test_gate.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/tests/test_project_gates.py +0 -0
- {norn_cli-0.7.2 → norn_cli-0.8.0}/tests/test_run_store.py +0 -0
norn_cli-0.8.0/PKG-INFO
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: norn-cli
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Norn: a provider-neutral multi-agent development harness
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Project-URL: Repository, https://github.com/uruca-kk/norn
|
|
7
|
+
Project-URL: Issues, https://github.com/uruca-kk/norn/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/uruca-kk/norn/blob/main/CHANGELOG.md
|
|
9
|
+
Keywords: agents,cli,codex,claude,testing
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: PyYAML>=6.0
|
|
20
|
+
Requires-Dist: jsonschema>=4.10
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: build<2,>=1.2; extra == "dev"
|
|
23
|
+
Requires-Dist: ruff<0.16,>=0.15; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# Norn
|
|
27
|
+
|
|
28
|
+
Provider-neutral multi-agent development harness for local subscription CLIs. The name evokes
|
|
29
|
+
the Norns who shape fate: the harness coordinates independent agents toward a bounded outcome.
|
|
30
|
+
|
|
31
|
+
> Status: beta. The `norn-cli` distribution is published to PyPI; the source
|
|
32
|
+
> repository remains private during the beta period.
|
|
33
|
+
|
|
34
|
+
Norn provides configuration, CLI, run-state, event journal, recovery,
|
|
35
|
+
human-readable escalation reports, provider-neutral adapters, and an executable
|
|
36
|
+
multi-role delivery pipeline for Codex, Claude Code, Grok Build, and future
|
|
37
|
+
command-based subscription CLIs.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
norn init
|
|
41
|
+
norn --version
|
|
42
|
+
norn doctor
|
|
43
|
+
norn validate-config
|
|
44
|
+
norn providers
|
|
45
|
+
norn plan "add an audit log"
|
|
46
|
+
norn run "add an audit log"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Install the bundled explicit-invocation skill for both Codex and Claude Code:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
norn install-skill --target all --scope project --project /path/to/repository
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This writes `.agents/skills/norn` for Codex and `.claude/skills/norn` for Claude Code. Use
|
|
56
|
+
`$norn` in Codex or `/norn` in Claude Code. Install a single user-level copy with
|
|
57
|
+
`--target codex|claude --scope user`; use `--force` only to replace an existing
|
|
58
|
+
copy intentionally. Loading the skill never starts a model-consuming lifecycle
|
|
59
|
+
unless the user explicitly requests `plan` or `run`.
|
|
60
|
+
|
|
61
|
+
See [Installation](docs/installation.md) for isolated `uv tool`, `pipx`, and
|
|
62
|
+
virtual-environment installs from PyPI.
|
|
63
|
+
|
|
64
|
+
`plan` runs orchestration, planning, and plan review. `run` continues through
|
|
65
|
+
implementation, language-aware local tests/lint, tester roles, triage, and final
|
|
66
|
+
gate. UI changes require validated E2E execution. A repository-owned managed
|
|
67
|
+
runner is the recommended path; the legacy policy can instead require each tester
|
|
68
|
+
to execute E2E independently. Every required result includes the executed argv,
|
|
69
|
+
exit code, a concise result summary, and at least one non-empty artifact in its
|
|
70
|
+
assigned evidence directory. The harness rejects missing, stale, symlinked, or
|
|
71
|
+
out-of-directory evidence and archives accepted files under
|
|
72
|
+
`.norf/runs/<run-id>/artifacts/e2e/`.
|
|
73
|
+
|
|
74
|
+
To implement a previously approved plan without invoking orchestration, planning,
|
|
75
|
+
or plan review again, pass the successful `plan` run ID with the identical request:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
norn plan "add an audit log"
|
|
79
|
+
norn run "add an audit log" --from-plan <plan-run-id>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Norn verifies the source run mode and status, project root, request text, and saved
|
|
83
|
+
artifact schemas before creating the implementation worktree.
|
|
84
|
+
|
|
85
|
+
For Codex tester roles, the adapter keeps `workspace-write` filesystem isolation
|
|
86
|
+
and enables command networking through a loopback-only proxy allowlist
|
|
87
|
+
(`localhost` and `127.0.0.1`). Planning and implementation roles retain their
|
|
88
|
+
normal network-off policy.
|
|
89
|
+
|
|
90
|
+
Docker-backed tests additionally need access to the host Docker API socket. Grant
|
|
91
|
+
that access explicitly for one run:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
norn run "task" --allow-tester-host-services
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The flag resolves the local Docker Unix socket from `DOCKER_HOST`, the active
|
|
98
|
+
Docker context, or a detected rootless, Docker Desktop, or system-default socket,
|
|
99
|
+
then adds only that socket as a writable root to Codex TEST invocations. Remote
|
|
100
|
+
TCP Docker endpoints are not added as filesystem roots. The flag does not remove
|
|
101
|
+
the workspace sandbox or promise that raw TCP clients can reach services bound
|
|
102
|
+
to the host's loopback interface. Docker socket access effectively delegates host
|
|
103
|
+
control, so use the flag only with a trusted repository; the resolved path and
|
|
104
|
+
opt-in are recorded in the run manifest and reports.
|
|
105
|
+
|
|
106
|
+
Configure one shared loopback URL for parallel browser testers. For a project-local
|
|
107
|
+
development server, also provide its shell-free command:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
e2e:
|
|
111
|
+
base_url: http://127.0.0.1:4173
|
|
112
|
+
server:
|
|
113
|
+
command: [pnpm, --filter, "@trato/marchant-admin", dev, --host, "127.0.0.1", --port, "4173", --strictPort]
|
|
114
|
+
startup_timeout_seconds: 30
|
|
115
|
+
tcp_forwards:
|
|
116
|
+
- {listen: 4173, to: "127.0.0.1:4173"}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Norn starts the command after implementation, waits for the URL, directs all tester
|
|
120
|
+
roles to reuse it without starting Vite, and always stops it afterward. Omit
|
|
121
|
+
`server.command` to reuse an already-running Docker or external Web server; external
|
|
122
|
+
servers are checked once and are not stopped by Norn. `tcp_forwards` is an explicit
|
|
123
|
+
allowlist for services bound to the Norn host's loopback interface. For every E2E
|
|
124
|
+
command, Norn supplies a Python argv wrapper that listens only inside that command's
|
|
125
|
+
sandbox and exchanges byte streams through a run-scoped file spool with the host
|
|
126
|
+
relay. The host accepts only strictly named, regular-file requests for declared
|
|
127
|
+
ports; duplicate listen ports, non-loopback targets, and undeclared host ports are
|
|
128
|
+
rejected. Runtime files are contained in a mode `0700` temporary directory and are
|
|
129
|
+
removed when testing ends.
|
|
130
|
+
|
|
131
|
+
For distributable projects, prefer a reviewed, repository-owned E2E command over
|
|
132
|
+
granting a model browser or host permissions:
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
e2e:
|
|
136
|
+
require_all_testers: false
|
|
137
|
+
base_url: http://127.0.0.1:4173
|
|
138
|
+
server:
|
|
139
|
+
command: [pnpm, dev, --host, "127.0.0.1", --port, "4173", --strictPort]
|
|
140
|
+
runner:
|
|
141
|
+
command: [pnpm, test:e2e]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Norn runs the four static tester roles in parallel, starts or checks the one shared
|
|
145
|
+
server, then executes `runner.command` exactly once as a shell-free argv list.
|
|
146
|
+
The implementer may add or update E2E specs but is explicitly told not to launch a
|
|
147
|
+
browser, start the server, or execute this runner itself.
|
|
148
|
+
The command receives `NORN_E2E_BASE_URL`, `NORN_E2E_EVIDENCE_DIR`, `NORN_RUN_ID`,
|
|
149
|
+
and `NORN_IMPLEMENTATION_ROUND`. It must write at least one non-empty log,
|
|
150
|
+
screenshot, trace, or result file below `NORN_E2E_EVIDENCE_DIR`; Norn's own stdout
|
|
151
|
+
and stderr logs do not count as evidence. Norn owns the authoritative command,
|
|
152
|
+
timeout, exit code, and evidence validation, then gives those results to
|
|
153
|
+
`e2e_tester` for a read-only review with no command tools. The reviewer cannot
|
|
154
|
+
change a failed run into a pass. Managed runners cannot be combined with
|
|
155
|
+
`e2e.tcp_forwards` or `--allow-e2e-danger-full-access` because execution occurs in
|
|
156
|
+
Norn's process, outside a tester provider sandbox.
|
|
157
|
+
|
|
158
|
+
Heavy repositories receive bounded but generous defaults: model roles and project
|
|
159
|
+
test commands each have 60 minutes, while managed or delegated E2E has 40 minutes.
|
|
160
|
+
Override them per project when needed:
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
limits:
|
|
164
|
+
timeouts:
|
|
165
|
+
role_seconds: 3600
|
|
166
|
+
test_seconds: 3600
|
|
167
|
+
e2e_seconds: 2400
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
These are timeout ceilings, not delays. Existing project configurations that
|
|
171
|
+
explicitly contain older values keep those values until updated.
|
|
172
|
+
|
|
173
|
+
The default `require_all_testers: true` keeps independent browser evidence from
|
|
174
|
+
spec, clean, security, and chaos testers. Projects where parallel quality commands
|
|
175
|
+
interfere with a watched development server can opt into one dedicated browser role:
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
e2e:
|
|
179
|
+
require_all_testers: false
|
|
180
|
+
roles:
|
|
181
|
+
e2e_tester: {profile: e2e-browser}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Norn first runs the four non-browser tester roles in parallel, then starts or checks
|
|
185
|
+
the shared server and runs `e2e_tester` alone. The dedicated role is still required
|
|
186
|
+
to produce valid browser evidence. Its profile is independently configurable, so a
|
|
187
|
+
non-Codex provider can be selected when Chromium is incompatible with the Codex TEST
|
|
188
|
+
sandbox. In this mode the four static roles are explicitly prohibited from starting a
|
|
189
|
+
browser or development server, connecting to the E2E URL, or rerunning shared project-wide
|
|
190
|
+
lint, test, coverage, and build commands already owned by Norn's deterministic gate.
|
|
191
|
+
|
|
192
|
+
The shipped `e2e-browser` profile uses Sonnet at medium effort, with Grok 4.5 at
|
|
193
|
+
medium effort as its fallback. It intentionally has no Codex or Fable candidate.
|
|
194
|
+
When no managed runner is configured and the provider's non-interactive permission
|
|
195
|
+
policy still blocks browser or loopback access, a trusted repository may opt in at run time with
|
|
196
|
+
`--allow-e2e-danger-full-access`. This bypass applies only to the dedicated
|
|
197
|
+
`e2e_tester`; the four static testers keep their normal TEST sandbox.
|
|
198
|
+
|
|
199
|
+
Profiles keep a stable logical model name while optionally mapping it to a
|
|
200
|
+
provider-specific identifier. For example, Codex Sol is configured as:
|
|
201
|
+
|
|
202
|
+
```yaml
|
|
203
|
+
candidates:
|
|
204
|
+
- provider: codex-cli
|
|
205
|
+
model: sol
|
|
206
|
+
model_id: gpt-5.6-sol
|
|
207
|
+
effort: high
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Round limits are configured under `limits` or overridden per invocation:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
norn run "task" --max-rounds 5 --max-total-rounds 12 \
|
|
214
|
+
--max-planner-retries 2 --max-final-replans 2
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Known Python, Node, Rust, and Go gates are auto-detected. Projects can define
|
|
218
|
+
additional commands as shell-free argv lists:
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
gates:
|
|
222
|
+
enabled: true
|
|
223
|
+
auto_detect: true
|
|
224
|
+
commands:
|
|
225
|
+
- name: integration
|
|
226
|
+
kind: test
|
|
227
|
+
command: [python3, -m, pytest, tests/integration]
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Each run records role responses, model selections, attempts, findings, test
|
|
231
|
+
results, and round history below `.norf/runs/<run-id>/`. Any non-successful stop
|
|
232
|
+
also produces `escalation-report.md` and `escalation.json` for human judgment.
|
|
233
|
+
|
|
234
|
+
By default, `run` requires a clean Git repository and implements inside a
|
|
235
|
+
detached `.norf/worktrees/<run-id>/` worktree. Set `git.worktree: false` only when
|
|
236
|
+
direct changes to the selected project directory are intentional.
|
|
237
|
+
|
|
238
|
+
Fresh Git worktrees contain tracked files only. Projects whose tests require
|
|
239
|
+
ignored dependencies, local environment files, or a prepared database can
|
|
240
|
+
provision the worktree before any provider role starts:
|
|
241
|
+
|
|
242
|
+
```yaml
|
|
243
|
+
git:
|
|
244
|
+
worktree: true
|
|
245
|
+
worktree_setup:
|
|
246
|
+
- name: php-dependencies
|
|
247
|
+
copy: laravel/vendor
|
|
248
|
+
- name: node-dependencies
|
|
249
|
+
copy: node_modules
|
|
250
|
+
optional: true
|
|
251
|
+
- name: test-database
|
|
252
|
+
command: [scripts/setup-norn-database, --template]
|
|
253
|
+
timeout_seconds: 900
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Each step has exactly one action. `copy` materializes a project-relative path
|
|
257
|
+
from the source checkout; `command` is a shell-free argv list executed in the
|
|
258
|
+
new worktree. Commands receive `NORN_SOURCE_ROOT`, `NORN_WORKTREE`, and
|
|
259
|
+
`NORN_RUN_ID`. A failed required step stops the run before model invocation.
|
|
260
|
+
|
|
261
|
+
## Development and packaging
|
|
262
|
+
|
|
263
|
+
Install the local development tools and run the same checks as CI:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
python3 -m pip install --editable ".[dev]"
|
|
267
|
+
ruff format --check .
|
|
268
|
+
ruff check .
|
|
269
|
+
python3 -m unittest discover -v
|
|
270
|
+
python3 -m build
|
|
271
|
+
python3 scripts/package_smoke.py dist/norn_cli-0.7.3-py3-none-any.whl
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
CI runs the quality suite on Python 3.12, 3.13, and 3.14, then installs the
|
|
275
|
+
built wheel in a clean virtual environment and verifies both Codex and Claude
|
|
276
|
+
skill copies. It uploads the distributions as a short-lived workflow artifact.
|
|
277
|
+
Publishing a GitHub Release runs the same deterministic gates before sending
|
|
278
|
+
the verified artifact to PyPI through OIDC Trusted Publishing; neither workflow
|
|
279
|
+
invokes subscription models.
|
|
280
|
+
|
|
281
|
+
Release acceptance is intentionally split in two: CI performs deterministic
|
|
282
|
+
package checks, while local acceptance invokes the authenticated subscription
|
|
283
|
+
CLIs. Follow [Release acceptance](docs/acceptance.md) before promoting a release.
|
|
284
|
+
See [CHANGELOG](CHANGELOG.md) for version history and [Security](SECURITY.md) for
|
|
285
|
+
private vulnerability reporting.
|
|
286
|
+
|
|
287
|
+
The canonical command is `norn`. The legacy `norf-harness` executable remains an alias during
|
|
288
|
+
the pre-1.0 migration; new automation should use `norn`.
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
Copyright 2026 uruca-kk. Licensed under the
|
|
293
|
+
[Apache License 2.0](LICENSE).
|
norn_cli-0.8.0/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Norn
|
|
2
|
+
|
|
3
|
+
Provider-neutral multi-agent development harness for local subscription CLIs. The name evokes
|
|
4
|
+
the Norns who shape fate: the harness coordinates independent agents toward a bounded outcome.
|
|
5
|
+
|
|
6
|
+
> Status: beta. The `norn-cli` distribution is published to PyPI; the source
|
|
7
|
+
> repository remains private during the beta period.
|
|
8
|
+
|
|
9
|
+
Norn provides configuration, CLI, run-state, event journal, recovery,
|
|
10
|
+
human-readable escalation reports, provider-neutral adapters, and an executable
|
|
11
|
+
multi-role delivery pipeline for Codex, Claude Code, Grok Build, and future
|
|
12
|
+
command-based subscription CLIs.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
norn init
|
|
16
|
+
norn --version
|
|
17
|
+
norn doctor
|
|
18
|
+
norn validate-config
|
|
19
|
+
norn providers
|
|
20
|
+
norn plan "add an audit log"
|
|
21
|
+
norn run "add an audit log"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Install the bundled explicit-invocation skill for both Codex and Claude Code:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
norn install-skill --target all --scope project --project /path/to/repository
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This writes `.agents/skills/norn` for Codex and `.claude/skills/norn` for Claude Code. Use
|
|
31
|
+
`$norn` in Codex or `/norn` in Claude Code. Install a single user-level copy with
|
|
32
|
+
`--target codex|claude --scope user`; use `--force` only to replace an existing
|
|
33
|
+
copy intentionally. Loading the skill never starts a model-consuming lifecycle
|
|
34
|
+
unless the user explicitly requests `plan` or `run`.
|
|
35
|
+
|
|
36
|
+
See [Installation](docs/installation.md) for isolated `uv tool`, `pipx`, and
|
|
37
|
+
virtual-environment installs from PyPI.
|
|
38
|
+
|
|
39
|
+
`plan` runs orchestration, planning, and plan review. `run` continues through
|
|
40
|
+
implementation, language-aware local tests/lint, tester roles, triage, and final
|
|
41
|
+
gate. UI changes require validated E2E execution. A repository-owned managed
|
|
42
|
+
runner is the recommended path; the legacy policy can instead require each tester
|
|
43
|
+
to execute E2E independently. Every required result includes the executed argv,
|
|
44
|
+
exit code, a concise result summary, and at least one non-empty artifact in its
|
|
45
|
+
assigned evidence directory. The harness rejects missing, stale, symlinked, or
|
|
46
|
+
out-of-directory evidence and archives accepted files under
|
|
47
|
+
`.norf/runs/<run-id>/artifacts/e2e/`.
|
|
48
|
+
|
|
49
|
+
To implement a previously approved plan without invoking orchestration, planning,
|
|
50
|
+
or plan review again, pass the successful `plan` run ID with the identical request:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
norn plan "add an audit log"
|
|
54
|
+
norn run "add an audit log" --from-plan <plan-run-id>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Norn verifies the source run mode and status, project root, request text, and saved
|
|
58
|
+
artifact schemas before creating the implementation worktree.
|
|
59
|
+
|
|
60
|
+
For Codex tester roles, the adapter keeps `workspace-write` filesystem isolation
|
|
61
|
+
and enables command networking through a loopback-only proxy allowlist
|
|
62
|
+
(`localhost` and `127.0.0.1`). Planning and implementation roles retain their
|
|
63
|
+
normal network-off policy.
|
|
64
|
+
|
|
65
|
+
Docker-backed tests additionally need access to the host Docker API socket. Grant
|
|
66
|
+
that access explicitly for one run:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
norn run "task" --allow-tester-host-services
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The flag resolves the local Docker Unix socket from `DOCKER_HOST`, the active
|
|
73
|
+
Docker context, or a detected rootless, Docker Desktop, or system-default socket,
|
|
74
|
+
then adds only that socket as a writable root to Codex TEST invocations. Remote
|
|
75
|
+
TCP Docker endpoints are not added as filesystem roots. The flag does not remove
|
|
76
|
+
the workspace sandbox or promise that raw TCP clients can reach services bound
|
|
77
|
+
to the host's loopback interface. Docker socket access effectively delegates host
|
|
78
|
+
control, so use the flag only with a trusted repository; the resolved path and
|
|
79
|
+
opt-in are recorded in the run manifest and reports.
|
|
80
|
+
|
|
81
|
+
Configure one shared loopback URL for parallel browser testers. For a project-local
|
|
82
|
+
development server, also provide its shell-free command:
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
e2e:
|
|
86
|
+
base_url: http://127.0.0.1:4173
|
|
87
|
+
server:
|
|
88
|
+
command: [pnpm, --filter, "@trato/marchant-admin", dev, --host, "127.0.0.1", --port, "4173", --strictPort]
|
|
89
|
+
startup_timeout_seconds: 30
|
|
90
|
+
tcp_forwards:
|
|
91
|
+
- {listen: 4173, to: "127.0.0.1:4173"}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Norn starts the command after implementation, waits for the URL, directs all tester
|
|
95
|
+
roles to reuse it without starting Vite, and always stops it afterward. Omit
|
|
96
|
+
`server.command` to reuse an already-running Docker or external Web server; external
|
|
97
|
+
servers are checked once and are not stopped by Norn. `tcp_forwards` is an explicit
|
|
98
|
+
allowlist for services bound to the Norn host's loopback interface. For every E2E
|
|
99
|
+
command, Norn supplies a Python argv wrapper that listens only inside that command's
|
|
100
|
+
sandbox and exchanges byte streams through a run-scoped file spool with the host
|
|
101
|
+
relay. The host accepts only strictly named, regular-file requests for declared
|
|
102
|
+
ports; duplicate listen ports, non-loopback targets, and undeclared host ports are
|
|
103
|
+
rejected. Runtime files are contained in a mode `0700` temporary directory and are
|
|
104
|
+
removed when testing ends.
|
|
105
|
+
|
|
106
|
+
For distributable projects, prefer a reviewed, repository-owned E2E command over
|
|
107
|
+
granting a model browser or host permissions:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
e2e:
|
|
111
|
+
require_all_testers: false
|
|
112
|
+
base_url: http://127.0.0.1:4173
|
|
113
|
+
server:
|
|
114
|
+
command: [pnpm, dev, --host, "127.0.0.1", --port, "4173", --strictPort]
|
|
115
|
+
runner:
|
|
116
|
+
command: [pnpm, test:e2e]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Norn runs the four static tester roles in parallel, starts or checks the one shared
|
|
120
|
+
server, then executes `runner.command` exactly once as a shell-free argv list.
|
|
121
|
+
The implementer may add or update E2E specs but is explicitly told not to launch a
|
|
122
|
+
browser, start the server, or execute this runner itself.
|
|
123
|
+
The command receives `NORN_E2E_BASE_URL`, `NORN_E2E_EVIDENCE_DIR`, `NORN_RUN_ID`,
|
|
124
|
+
and `NORN_IMPLEMENTATION_ROUND`. It must write at least one non-empty log,
|
|
125
|
+
screenshot, trace, or result file below `NORN_E2E_EVIDENCE_DIR`; Norn's own stdout
|
|
126
|
+
and stderr logs do not count as evidence. Norn owns the authoritative command,
|
|
127
|
+
timeout, exit code, and evidence validation, then gives those results to
|
|
128
|
+
`e2e_tester` for a read-only review with no command tools. The reviewer cannot
|
|
129
|
+
change a failed run into a pass. Managed runners cannot be combined with
|
|
130
|
+
`e2e.tcp_forwards` or `--allow-e2e-danger-full-access` because execution occurs in
|
|
131
|
+
Norn's process, outside a tester provider sandbox.
|
|
132
|
+
|
|
133
|
+
Heavy repositories receive bounded but generous defaults: model roles and project
|
|
134
|
+
test commands each have 60 minutes, while managed or delegated E2E has 40 minutes.
|
|
135
|
+
Override them per project when needed:
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
limits:
|
|
139
|
+
timeouts:
|
|
140
|
+
role_seconds: 3600
|
|
141
|
+
test_seconds: 3600
|
|
142
|
+
e2e_seconds: 2400
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
These are timeout ceilings, not delays. Existing project configurations that
|
|
146
|
+
explicitly contain older values keep those values until updated.
|
|
147
|
+
|
|
148
|
+
The default `require_all_testers: true` keeps independent browser evidence from
|
|
149
|
+
spec, clean, security, and chaos testers. Projects where parallel quality commands
|
|
150
|
+
interfere with a watched development server can opt into one dedicated browser role:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
e2e:
|
|
154
|
+
require_all_testers: false
|
|
155
|
+
roles:
|
|
156
|
+
e2e_tester: {profile: e2e-browser}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Norn first runs the four non-browser tester roles in parallel, then starts or checks
|
|
160
|
+
the shared server and runs `e2e_tester` alone. The dedicated role is still required
|
|
161
|
+
to produce valid browser evidence. Its profile is independently configurable, so a
|
|
162
|
+
non-Codex provider can be selected when Chromium is incompatible with the Codex TEST
|
|
163
|
+
sandbox. In this mode the four static roles are explicitly prohibited from starting a
|
|
164
|
+
browser or development server, connecting to the E2E URL, or rerunning shared project-wide
|
|
165
|
+
lint, test, coverage, and build commands already owned by Norn's deterministic gate.
|
|
166
|
+
|
|
167
|
+
The shipped `e2e-browser` profile uses Sonnet at medium effort, with Grok 4.5 at
|
|
168
|
+
medium effort as its fallback. It intentionally has no Codex or Fable candidate.
|
|
169
|
+
When no managed runner is configured and the provider's non-interactive permission
|
|
170
|
+
policy still blocks browser or loopback access, a trusted repository may opt in at run time with
|
|
171
|
+
`--allow-e2e-danger-full-access`. This bypass applies only to the dedicated
|
|
172
|
+
`e2e_tester`; the four static testers keep their normal TEST sandbox.
|
|
173
|
+
|
|
174
|
+
Profiles keep a stable logical model name while optionally mapping it to a
|
|
175
|
+
provider-specific identifier. For example, Codex Sol is configured as:
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
candidates:
|
|
179
|
+
- provider: codex-cli
|
|
180
|
+
model: sol
|
|
181
|
+
model_id: gpt-5.6-sol
|
|
182
|
+
effort: high
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Round limits are configured under `limits` or overridden per invocation:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
norn run "task" --max-rounds 5 --max-total-rounds 12 \
|
|
189
|
+
--max-planner-retries 2 --max-final-replans 2
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Known Python, Node, Rust, and Go gates are auto-detected. Projects can define
|
|
193
|
+
additional commands as shell-free argv lists:
|
|
194
|
+
|
|
195
|
+
```yaml
|
|
196
|
+
gates:
|
|
197
|
+
enabled: true
|
|
198
|
+
auto_detect: true
|
|
199
|
+
commands:
|
|
200
|
+
- name: integration
|
|
201
|
+
kind: test
|
|
202
|
+
command: [python3, -m, pytest, tests/integration]
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Each run records role responses, model selections, attempts, findings, test
|
|
206
|
+
results, and round history below `.norf/runs/<run-id>/`. Any non-successful stop
|
|
207
|
+
also produces `escalation-report.md` and `escalation.json` for human judgment.
|
|
208
|
+
|
|
209
|
+
By default, `run` requires a clean Git repository and implements inside a
|
|
210
|
+
detached `.norf/worktrees/<run-id>/` worktree. Set `git.worktree: false` only when
|
|
211
|
+
direct changes to the selected project directory are intentional.
|
|
212
|
+
|
|
213
|
+
Fresh Git worktrees contain tracked files only. Projects whose tests require
|
|
214
|
+
ignored dependencies, local environment files, or a prepared database can
|
|
215
|
+
provision the worktree before any provider role starts:
|
|
216
|
+
|
|
217
|
+
```yaml
|
|
218
|
+
git:
|
|
219
|
+
worktree: true
|
|
220
|
+
worktree_setup:
|
|
221
|
+
- name: php-dependencies
|
|
222
|
+
copy: laravel/vendor
|
|
223
|
+
- name: node-dependencies
|
|
224
|
+
copy: node_modules
|
|
225
|
+
optional: true
|
|
226
|
+
- name: test-database
|
|
227
|
+
command: [scripts/setup-norn-database, --template]
|
|
228
|
+
timeout_seconds: 900
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Each step has exactly one action. `copy` materializes a project-relative path
|
|
232
|
+
from the source checkout; `command` is a shell-free argv list executed in the
|
|
233
|
+
new worktree. Commands receive `NORN_SOURCE_ROOT`, `NORN_WORKTREE`, and
|
|
234
|
+
`NORN_RUN_ID`. A failed required step stops the run before model invocation.
|
|
235
|
+
|
|
236
|
+
## Development and packaging
|
|
237
|
+
|
|
238
|
+
Install the local development tools and run the same checks as CI:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
python3 -m pip install --editable ".[dev]"
|
|
242
|
+
ruff format --check .
|
|
243
|
+
ruff check .
|
|
244
|
+
python3 -m unittest discover -v
|
|
245
|
+
python3 -m build
|
|
246
|
+
python3 scripts/package_smoke.py dist/norn_cli-0.7.3-py3-none-any.whl
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
CI runs the quality suite on Python 3.12, 3.13, and 3.14, then installs the
|
|
250
|
+
built wheel in a clean virtual environment and verifies both Codex and Claude
|
|
251
|
+
skill copies. It uploads the distributions as a short-lived workflow artifact.
|
|
252
|
+
Publishing a GitHub Release runs the same deterministic gates before sending
|
|
253
|
+
the verified artifact to PyPI through OIDC Trusted Publishing; neither workflow
|
|
254
|
+
invokes subscription models.
|
|
255
|
+
|
|
256
|
+
Release acceptance is intentionally split in two: CI performs deterministic
|
|
257
|
+
package checks, while local acceptance invokes the authenticated subscription
|
|
258
|
+
CLIs. Follow [Release acceptance](docs/acceptance.md) before promoting a release.
|
|
259
|
+
See [CHANGELOG](CHANGELOG.md) for version history and [Security](SECURITY.md) for
|
|
260
|
+
private vulnerability reporting.
|
|
261
|
+
|
|
262
|
+
The canonical command is `norn`. The legacy `norf-harness` executable remains an alias during
|
|
263
|
+
the pre-1.0 migration; new automation should use `norn`.
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
Copyright 2026 uruca-kk. Licensed under the
|
|
268
|
+
[Apache License 2.0](LICENSE).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "norn-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.0"
|
|
8
8
|
description = "Norn: a provider-neutral multi-agent development harness"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -49,6 +49,7 @@ where = ["src"]
|
|
|
49
49
|
[tool.setuptools.package-data]
|
|
50
50
|
norf_harness = [
|
|
51
51
|
"resources/*.yaml",
|
|
52
|
+
"resources/*.py",
|
|
52
53
|
"resources/skills/norn/*.md",
|
|
53
54
|
"resources/skills/norn/agents/*.yaml",
|
|
54
55
|
"resources/skills/norn/references/*.md",
|
|
@@ -66,6 +66,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
66
66
|
metavar="RUN_ID",
|
|
67
67
|
help="reuse the approved plan from a successful plan run",
|
|
68
68
|
)
|
|
69
|
+
run_parser.add_argument(
|
|
70
|
+
"--allow-tester-host-services",
|
|
71
|
+
action="store_true",
|
|
72
|
+
help="allow tester roles to use supported host test services such as Docker",
|
|
73
|
+
)
|
|
74
|
+
run_parser.add_argument(
|
|
75
|
+
"--allow-e2e-danger-full-access",
|
|
76
|
+
action="store_true",
|
|
77
|
+
help="run only the dedicated e2e_tester without provider sandbox restrictions",
|
|
78
|
+
)
|
|
69
79
|
_common_options(run_parser)
|
|
70
80
|
_limit_options(run_parser)
|
|
71
81
|
|
|
@@ -121,6 +131,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
121
131
|
args.request,
|
|
122
132
|
RunMode(args.command),
|
|
123
133
|
plan_run_id=getattr(args, "plan_run_id", None),
|
|
134
|
+
allow_tester_host_services=getattr(args, "allow_tester_host_services", False),
|
|
135
|
+
allow_e2e_danger_full_access=getattr(args, "allow_e2e_danger_full_access", False),
|
|
124
136
|
)
|
|
125
137
|
if args.command == "status":
|
|
126
138
|
return _status(config, args.run_id, args.json_output)
|
|
@@ -242,8 +254,50 @@ def _providers(config, json_output: bool) -> int:
|
|
|
242
254
|
return 1 if failed else 0
|
|
243
255
|
|
|
244
256
|
|
|
245
|
-
def _execution(
|
|
246
|
-
|
|
257
|
+
def _execution(
|
|
258
|
+
config,
|
|
259
|
+
request: str,
|
|
260
|
+
mode: RunMode,
|
|
261
|
+
*,
|
|
262
|
+
plan_run_id: str | None = None,
|
|
263
|
+
allow_tester_host_services: bool = False,
|
|
264
|
+
allow_e2e_danger_full_access: bool = False,
|
|
265
|
+
) -> int:
|
|
266
|
+
if allow_e2e_danger_full_access and config.e2e_require_all_testers:
|
|
267
|
+
print(
|
|
268
|
+
"CONFIG ERROR: --allow-e2e-danger-full-access requires e2e.require_all_testers: false",
|
|
269
|
+
file=sys.stderr,
|
|
270
|
+
)
|
|
271
|
+
return int(ExitCode.CONFIG_ERROR)
|
|
272
|
+
if allow_e2e_danger_full_access and config.e2e_runner_command:
|
|
273
|
+
print(
|
|
274
|
+
"CONFIG ERROR: --allow-e2e-danger-full-access cannot be used with "
|
|
275
|
+
"e2e.runner.command; the managed runner does not delegate execution to a model",
|
|
276
|
+
file=sys.stderr,
|
|
277
|
+
)
|
|
278
|
+
return int(ExitCode.CONFIG_ERROR)
|
|
279
|
+
if allow_tester_host_services:
|
|
280
|
+
print(
|
|
281
|
+
"WARNING: tester roles may access the host Docker API; "
|
|
282
|
+
"only use this with trusted repositories.",
|
|
283
|
+
file=sys.stderr,
|
|
284
|
+
)
|
|
285
|
+
if allow_e2e_danger_full_access:
|
|
286
|
+
print(
|
|
287
|
+
"WARNING: the dedicated e2e_tester runs with provider permission checks and "
|
|
288
|
+
"sandbox restrictions bypassed; only use this with a trusted repository.",
|
|
289
|
+
file=sys.stderr,
|
|
290
|
+
)
|
|
291
|
+
outcome = asyncio.run(
|
|
292
|
+
execute_pipeline(
|
|
293
|
+
config,
|
|
294
|
+
request,
|
|
295
|
+
mode,
|
|
296
|
+
plan_run_id=plan_run_id,
|
|
297
|
+
allow_tester_host_services=allow_tester_host_services,
|
|
298
|
+
allow_e2e_danger_full_access=allow_e2e_danger_full_access,
|
|
299
|
+
)
|
|
300
|
+
)
|
|
247
301
|
manifest = outcome.manifest
|
|
248
302
|
if manifest.exit_code == 0:
|
|
249
303
|
print(f"Harness succeeded: {outcome.paths.final_report}")
|