no-human 0.1.1__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.
- no_human-0.1.1/.gitignore +63 -0
- no_human-0.1.1/LICENSE +21 -0
- no_human-0.1.1/PKG-INFO +191 -0
- no_human-0.1.1/README.md +170 -0
- no_human-0.1.1/hatch_build.py +213 -0
- no_human-0.1.1/migrations/0001_init.sql +91 -0
- no_human-0.1.1/migrations/0002_project_profiles.sql +13 -0
- no_human-0.1.1/migrations/0003_task_kind.sql +4 -0
- no_human-0.1.1/migrations/0004_projects.sql +11 -0
- no_human-0.1.1/migrations/0005_task_events.sql +10 -0
- no_human-0.1.1/migrations/0006_events_fts.sql +28 -0
- no_human-0.1.1/migrations/0007_playbooks.sql +19 -0
- no_human-0.1.1/migrations/0008_pr_edges.sql +13 -0
- no_human-0.1.1/migrations/0009_fts_ci_gate_event_kind.sql +22 -0
- no_human-0.1.1/migrations/0010_pr_outcomes.sql +50 -0
- no_human-0.1.1/migrations/0011_verification_receipts.sql +44 -0
- no_human-0.1.1/migrations/0012_memory_usage_ledger.sql +41 -0
- no_human-0.1.1/migrations/0013_fix_pairs.sql +34 -0
- no_human-0.1.1/pyproject.toml +186 -0
- no_human-0.1.1/src/no_human/__init__.py +7 -0
- no_human-0.1.1/src/no_human/agent/__init__.py +0 -0
- no_human-0.1.1/src/no_human/agent/advisory.py +68 -0
- no_human-0.1.1/src/no_human/agent/backend.py +403 -0
- no_human-0.1.1/src/no_human/agent/backend_check.py +272 -0
- no_human-0.1.1/src/no_human/agent/claude_backend.py +1302 -0
- no_human-0.1.1/src/no_human/agent/codex_backend.py +722 -0
- no_human-0.1.1/src/no_human/agent/guard.py +1577 -0
- no_human-0.1.1/src/no_human/agent/lint_hook.py +95 -0
- no_human-0.1.1/src/no_human/agent/scope_guard.py +340 -0
- no_human-0.1.1/src/no_human/agent/supervisor.py +528 -0
- no_human-0.1.1/src/no_human/agent/tool_result_cap.py +191 -0
- no_human-0.1.1/src/no_human/agent/venv_install_guard.py +552 -0
- no_human-0.1.1/src/no_human/agent/verification_receipts.py +993 -0
- no_human-0.1.1/src/no_human/agent/worker_context.py +80 -0
- no_human-0.1.1/src/no_human/api/__init__.py +3 -0
- no_human-0.1.1/src/no_human/api/app.py +4571 -0
- no_human-0.1.1/src/no_human/api/models.py +729 -0
- no_human-0.1.1/src/no_human/blockers/__init__.py +65 -0
- no_human-0.1.1/src/no_human/blockers/actions.py +217 -0
- no_human-0.1.1/src/no_human/blockers/answers.py +139 -0
- no_human-0.1.1/src/no_human/blockers/challenge.py +114 -0
- no_human-0.1.1/src/no_human/blockers/landed_override.py +242 -0
- no_human-0.1.1/src/no_human/blockers/pr_closeout.py +103 -0
- no_human-0.1.1/src/no_human/blockers/report.py +239 -0
- no_human-0.1.1/src/no_human/blockers/shipped.py +277 -0
- no_human-0.1.1/src/no_human/blockers/taxonomy.py +371 -0
- no_human-0.1.1/src/no_human/blockers/wake.py +1917 -0
- no_human-0.1.1/src/no_human/brain/__init__.py +170 -0
- no_human-0.1.1/src/no_human/brain/cli.py +351 -0
- no_human-0.1.1/src/no_human/brain/client.py +280 -0
- no_human-0.1.1/src/no_human/brain/credentials.py +288 -0
- no_human-0.1.1/src/no_human/brain/keys.py +61 -0
- no_human-0.1.1/src/no_human/brain/render.py +114 -0
- no_human-0.1.1/src/no_human/brain/screen.py +237 -0
- no_human-0.1.1/src/no_human/brain/settings.py +72 -0
- no_human-0.1.1/src/no_human/brain/store.py +444 -0
- no_human-0.1.1/src/no_human/brain/sync.py +578 -0
- no_human-0.1.1/src/no_human/brain/verify.py +233 -0
- no_human-0.1.1/src/no_human/ci/__init__.py +232 -0
- no_human-0.1.1/src/no_human/ci/base.py +180 -0
- no_human-0.1.1/src/no_human/ci/circleci.py +214 -0
- no_human-0.1.1/src/no_human/ci/ghe_checkruns.py +394 -0
- no_human-0.1.1/src/no_human/ci/github_actions.py +144 -0
- no_human-0.1.1/src/no_human/ci/gitlab.py +567 -0
- no_human-0.1.1/src/no_human/ci/jenkins.py +422 -0
- no_human-0.1.1/src/no_human/ci/jenkins_session.py +152 -0
- no_human-0.1.1/src/no_human/ci/parser.py +49 -0
- no_human-0.1.1/src/no_human/cli/__init__.py +62 -0
- no_human-0.1.1/src/no_human/cli/api_client.py +332 -0
- no_human-0.1.1/src/no_human/cli/commands.py +7178 -0
- no_human-0.1.1/src/no_human/cli/init_cmd.py +660 -0
- no_human-0.1.1/src/no_human/cli/shell.py +557 -0
- no_human-0.1.1/src/no_human/cli/shell_input.py +216 -0
- no_human-0.1.1/src/no_human/cli/shell_lanes.py +246 -0
- no_human-0.1.1/src/no_human/cli/tui.py +139 -0
- no_human-0.1.1/src/no_human/config.py +2124 -0
- no_human-0.1.1/src/no_human/context/__init__.py +36 -0
- no_human-0.1.1/src/no_human/context/base.py +98 -0
- no_human-0.1.1/src/no_human/context/codebase.py +130 -0
- no_human-0.1.1/src/no_human/context/gatherer.py +41 -0
- no_human-0.1.1/src/no_human/context/outlook.py +35 -0
- no_human-0.1.1/src/no_human/context/repo_map.py +134 -0
- no_human-0.1.1/src/no_human/context/sessions.py +137 -0
- no_human-0.1.1/src/no_human/context/teams.py +126 -0
- no_human-0.1.1/src/no_human/core/__init__.py +0 -0
- no_human-0.1.1/src/no_human/core/autonomy.py +365 -0
- no_human-0.1.1/src/no_human/core/bounds.py +339 -0
- no_human-0.1.1/src/no_human/core/build_info.py +199 -0
- no_human-0.1.1/src/no_human/core/complexity.py +261 -0
- no_human-0.1.1/src/no_human/core/db.py +3674 -0
- no_human-0.1.1/src/no_human/core/events.py +85 -0
- no_human-0.1.1/src/no_human/core/health.py +158 -0
- no_human-0.1.1/src/no_human/core/humanize.py +12 -0
- no_human-0.1.1/src/no_human/core/infra_breaker.py +96 -0
- no_human-0.1.1/src/no_human/core/jsonparse.py +75 -0
- no_human-0.1.1/src/no_human/core/lanes.py +110 -0
- no_human-0.1.1/src/no_human/core/metrics.py +369 -0
- no_human-0.1.1/src/no_human/core/multi_repo.py +151 -0
- no_human-0.1.1/src/no_human/core/orchestrator.py +15549 -0
- no_human-0.1.1/src/no_human/core/ordinal.py +12 -0
- no_human-0.1.1/src/no_human/core/plan_gate.py +231 -0
- no_human-0.1.1/src/no_human/core/pr_evidence.py +142 -0
- no_human-0.1.1/src/no_human/core/pricing.py +379 -0
- no_human-0.1.1/src/no_human/core/prompt_blocks.py +928 -0
- no_human-0.1.1/src/no_human/core/report_quality.py +69 -0
- no_human-0.1.1/src/no_human/core/review_routing.py +303 -0
- no_human-0.1.1/src/no_human/core/runtime.py +52 -0
- no_human-0.1.1/src/no_human/core/scheduler.py +1456 -0
- no_human-0.1.1/src/no_human/core/task.py +448 -0
- no_human-0.1.1/src/no_human/core/worktree.py +321 -0
- no_human-0.1.1/src/no_human/docs_gen.py +242 -0
- no_human-0.1.1/src/no_human/doctor.py +577 -0
- no_human-0.1.1/src/no_human/eval/__init__.py +25 -0
- no_human-0.1.1/src/no_human/eval/bench_compare.py +529 -0
- no_human-0.1.1/src/no_human/eval/bench_task.py +613 -0
- no_human-0.1.1/src/no_human/eval/funnel_corpus.py +124 -0
- no_human-0.1.1/src/no_human/eval/funnel_criteria.py +123 -0
- no_human-0.1.1/src/no_human/eval/funnel_eval.py +587 -0
- no_human-0.1.1/src/no_human/eval/golden.py +104 -0
- no_human-0.1.1/src/no_human/eval/harness.py +175 -0
- no_human-0.1.1/src/no_human/eval/harvest.py +122 -0
- no_human-0.1.1/src/no_human/eval/judge.py +361 -0
- no_human-0.1.1/src/no_human/eval/northstar.py +1079 -0
- no_human-0.1.1/src/no_human/eval/northstar_card.py +1459 -0
- no_human-0.1.1/src/no_human/eval/replay.py +302 -0
- no_human-0.1.1/src/no_human/eval/sandbox_selftest.py +112 -0
- no_human-0.1.1/src/no_human/eval/scorecard.py +201 -0
- no_human-0.1.1/src/no_human/eval/startup.py +539 -0
- no_human-0.1.1/src/no_human/eval/vendor_terms.py +442 -0
- no_human-0.1.1/src/no_human/history/__init__.py +10 -0
- no_human-0.1.1/src/no_human/history/analyzer.py +414 -0
- no_human-0.1.1/src/no_human/history/claude_code.py +250 -0
- no_human-0.1.1/src/no_human/history/extractor.py +322 -0
- no_human-0.1.1/src/no_human/history/ingester.py +233 -0
- no_human-0.1.1/src/no_human/history/machinery.py +123 -0
- no_human-0.1.1/src/no_human/history/skills.py +166 -0
- no_human-0.1.1/src/no_human/history/topic.py +202 -0
- no_human-0.1.1/src/no_human/intake/__init__.py +60 -0
- no_human-0.1.1/src/no_human/intake/base.py +115 -0
- no_human-0.1.1/src/no_human/intake/classify.py +480 -0
- no_human-0.1.1/src/no_human/intake/evaluator.py +879 -0
- no_human-0.1.1/src/no_human/intake/github_issues.py +70 -0
- no_human-0.1.1/src/no_human/intake/gitlab_issues.py +68 -0
- no_human-0.1.1/src/no_human/intake/grill.py +258 -0
- no_human-0.1.1/src/no_human/intake/jira.py +254 -0
- no_human-0.1.1/src/no_human/intake/jira_poll.py +247 -0
- no_human-0.1.1/src/no_human/intake/linear.py +700 -0
- no_human-0.1.1/src/no_human/intake/linear_poll.py +284 -0
- no_human-0.1.1/src/no_human/intake/mcp_bridge.py +112 -0
- no_human-0.1.1/src/no_human/intake/monday.py +707 -0
- no_human-0.1.1/src/no_human/intake/monday_poll.py +303 -0
- no_human-0.1.1/src/no_human/intake/split_proposal.py +147 -0
- no_human-0.1.1/src/no_human/intake/surface_advisory.py +84 -0
- no_human-0.1.1/src/no_human/intake/unavailable_inputs.py +177 -0
- no_human-0.1.1/src/no_human/integrations/__init__.py +1396 -0
- no_human-0.1.1/src/no_human/integrations/slack/__init__.py +11 -0
- no_human-0.1.1/src/no_human/integrations/slack/intake.py +198 -0
- no_human-0.1.1/src/no_human/integrations/slack/worker.py +131 -0
- no_human-0.1.1/src/no_human/learning/__init__.py +35 -0
- no_human-0.1.1/src/no_human/learning/corrections.py +268 -0
- no_human-0.1.1/src/no_human/learning/curator.py +163 -0
- no_human-0.1.1/src/no_human/learning/pii.py +299 -0
- no_human-0.1.1/src/no_human/learning/provenance.py +312 -0
- no_human-0.1.1/src/no_human/learning/queue.py +1067 -0
- no_human-0.1.1/src/no_human/learning/ranking.py +210 -0
- no_human-0.1.1/src/no_human/learning/retire.py +208 -0
- no_human-0.1.1/src/no_human/learning/scope.py +157 -0
- no_human-0.1.1/src/no_human/learning/triggers.py +91 -0
- no_human-0.1.1/src/no_human/learning/vocab.py +210 -0
- no_human-0.1.1/src/no_human/notify/__init__.py +136 -0
- no_human-0.1.1/src/no_human/notify/slack.py +58 -0
- no_human-0.1.1/src/no_human/notify/teams.py +217 -0
- no_human-0.1.1/src/no_human/onboard.py +548 -0
- no_human-0.1.1/src/no_human/profile.py +199 -0
- no_human-0.1.1/src/no_human/project_config.py +159 -0
- no_human-0.1.1/src/no_human/project_model.py +68 -0
- no_human-0.1.1/src/no_human/repo_discovery.py +581 -0
- no_human-0.1.1/src/no_human/review/__init__.py +4 -0
- no_human-0.1.1/src/no_human/review/lint_evidence.py +378 -0
- no_human-0.1.1/src/no_human/review/reviewer.py +2494 -0
- no_human-0.1.1/src/no_human/review/selfcheck.py +59 -0
- no_human-0.1.1/src/no_human/review/tamper_adjudication.py +456 -0
- no_human-0.1.1/src/no_human/review/wiring_evidence.py +166 -0
- no_human-0.1.1/src/no_human/telemetry.py +274 -0
- no_human-0.1.1/src/no_human/testing/__init__.py +0 -0
- no_human-0.1.1/src/no_human/testing/plan_runner.py +362 -0
- no_human-0.1.1/src/no_human/testing/pytest_isolated_home.py +180 -0
- no_human-0.1.1/src/no_human/testing/repro_gate.py +448 -0
- no_human-0.1.1/src/no_human/testing/runner.py +1241 -0
- no_human-0.1.1/src/no_human/testing/tamper_guard.py +442 -0
- no_human-0.1.1/src/no_human/testing/test_layers.py +163 -0
- no_human-0.1.1/src/no_human/updates.py +286 -0
- no_human-0.1.1/src/no_human/vcs/__init__.py +106 -0
- no_human-0.1.1/src/no_human/vcs/approve_merge.py +632 -0
- no_human-0.1.1/src/no_human/vcs/comment_poster.py +326 -0
- no_human-0.1.1/src/no_human/vcs/derived_conflict.py +391 -0
- no_human-0.1.1/src/no_human/vcs/git.py +943 -0
- no_human-0.1.1/src/no_human/vcs/github.py +93 -0
- no_human-0.1.1/src/no_human/vcs/gitlab.py +39 -0
- no_human-0.1.1/src/no_human/vcs/manifest_repair.py +283 -0
- no_human-0.1.1/src/no_human/vcs/merge_order.py +58 -0
- no_human-0.1.1/src/no_human/vcs/outbound_scrub.py +63 -0
- no_human-0.1.1/src/no_human/vcs/pr_outcome.py +503 -0
- no_human-0.1.1/src/no_human/vcs/pr_refs.py +60 -0
- no_human-0.1.1/src/no_human/vcs/pr_watcher.py +1524 -0
- no_human-0.1.1/src/no_human/vcs/push_hook.py +278 -0
- no_human-0.1.1/src/no_human/vcs/receipts.py +106 -0
- no_human-0.1.1/src/no_human/vcs/task_pr.py +160 -0
- no_human-0.1.1/web/dist/assets/-F63fjptAgt5VM-kVkqdyU8n1i8q131nj-o-BJoXLJYV.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F63fjptAgt5VM-kVkqdyU8n1iAq131nj-otFQ-DKn25-tQ.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F63fjptAgt5VM-kVkqdyU8n1iEq131nj-otFQ-C05TWSE2.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F63fjptAgt5VM-kVkqdyU8n1iIq131nj-otFQ-BKehAWor.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F63fjptAgt5VM-kVkqdyU8n1isq131nj-otFQ-DkeVBss5.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6pfjptAgt5VM-kVkqdyU8n1ioa0XdgregdFOFh-DBCFrjSV.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6pfjptAgt5VM-kVkqdyU8n1ioa1XdgregdFA-BkxdLi3-.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6pfjptAgt5VM-kVkqdyU8n1ioa23dgregdFOFh-DjXFaAjD.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6pfjptAgt5VM-kVkqdyU8n1ioa2HdgregdFOFh-BgiqLiQn.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6pfjptAgt5VM-kVkqdyU8n1ioa2ndgregdFOFh-D3ijpaJE.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6qfjptAgt5VM-kVkqdyU8n3twJwl1FgsAXHNlYzg-CU9Da17h.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6qfjptAgt5VM-kVkqdyU8n3twJwl5FgsAXHNlYzg-BQJS6Ovj.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6qfjptAgt5VM-kVkqdyU8n3twJwl9FgsAXHNlYzg-B5e70VyC.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6qfjptAgt5VM-kVkqdyU8n3twJwlBFgsAXHNk-C820gu2e.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6qfjptAgt5VM-kVkqdyU8n3twJwlRFgsAXHNlYzg-dnJBCtls.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6qfjptAgt5VM-kVkqdyU8n3vAOwl1FgsAXHNlYzg-hCF3fsXQ.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6qfjptAgt5VM-kVkqdyU8n3vAOwl5FgsAXHNlYzg-BRMVj9uZ.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6qfjptAgt5VM-kVkqdyU8n3vAOwl9FgsAXHNlYzg-Dky8cY56.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6qfjptAgt5VM-kVkqdyU8n3vAOwlBFgsAXHNk-DpGnXj3s.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/-F6qfjptAgt5VM-kVkqdyU8n3vAOwlRFgsAXHNlYzg-DiqaCFl3.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/index-D4wFtDnQ.js +96 -0
- no_human-0.1.1/web/dist/assets/index-wqze04F6.css +1 -0
- no_human-0.1.1/web/dist/assets/module-BBzDlm4e.js +3 -0
- no_human-0.1.1/web/dist/assets/rP2Hp2ywxg089UriCZ2IHTWEBlwu8Q-BH18Pfgv.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/rP2Hp2ywxg089UriCZOIHTWEBlw-BNY05QUC.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/rP2Wp2ywxg089UriCZaSExdy3sGt9zz86GPwyKK58UfivUw4aw-CTzf7qy0.woff2 +0 -0
- no_human-0.1.1/web/dist/assets/rP2Wp2ywxg089UriCZaSExdy3sGt9zz86GPwyKy58UfivUw-C5VcRjlN.woff2 +0 -0
- no_human-0.1.1/web/dist/index.html +16 -0
- no_human-0.1.1/web/dist/nh-mark-512.png +0 -0
- no_human-0.1.1/web/dist/nh-mark-64.png +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
# desktop/build is electron-builder's buildResources. Neither icon.icns nor
|
|
6
|
+
# icon.ico is tracked: both are build OUTPUTS, derived at package time from
|
|
7
|
+
# web/public/nh-mark-512.png by packaging/derive-icons.mjs — see that file's
|
|
8
|
+
# header comment for why the binaries stopped being committed.
|
|
9
|
+
!desktop/build/
|
|
10
|
+
desktop/build/*
|
|
11
|
+
dist/
|
|
12
|
+
wheels/
|
|
13
|
+
*.egg-info
|
|
14
|
+
|
|
15
|
+
# Virtual environments
|
|
16
|
+
.venv
|
|
17
|
+
|
|
18
|
+
# Secrets & local state — never commit
|
|
19
|
+
.env
|
|
20
|
+
*.key
|
|
21
|
+
*.pem
|
|
22
|
+
.no_human/
|
|
23
|
+
secrets/
|
|
24
|
+
*.db
|
|
25
|
+
*.db-wal
|
|
26
|
+
*.db-shm
|
|
27
|
+
# ...except the memory-triage runbook's committed test fixture: a tracked
|
|
28
|
+
# INPUT (synthetic, neutral content -- see EXPORT_CLASSIFICATION.txt).
|
|
29
|
+
!testdata/memory_triage_fixture.db
|
|
30
|
+
|
|
31
|
+
# Test scratch
|
|
32
|
+
/tmp_e2e/
|
|
33
|
+
# pytest's node-id cache stores collected test names verbatim, so it carries
|
|
34
|
+
# whatever the test bodies spell. It was NOT ignored — only `__pycache__/` and
|
|
35
|
+
# `*.py[oc]` were — so a `git add -A` after a test run could have tracked it.
|
|
36
|
+
.pytest_cache/
|
|
37
|
+
|
|
38
|
+
# IDE-generated (skills, local settings)
|
|
39
|
+
.claude/
|
|
40
|
+
.windsurf/
|
|
41
|
+
|
|
42
|
+
# web build
|
|
43
|
+
web/node_modules/
|
|
44
|
+
|
|
45
|
+
# north-star bench: the raw built corpus is verbatim operator conversation
|
|
46
|
+
# content (personal + enterprise) — never committed. Curated specs live one
|
|
47
|
+
# level up and ARE tracked.
|
|
48
|
+
eval/northstar_tasks/generated/
|
|
49
|
+
# Machine-local translation from vendor-neutral spec paths to real checkouts.
|
|
50
|
+
# Holds real repo names by definition — never committed.
|
|
51
|
+
eval/repo_map.yaml
|
|
52
|
+
eval/results/northstar/
|
|
53
|
+
|
|
54
|
+
# desktop shell
|
|
55
|
+
desktop/node_modules/
|
|
56
|
+
# also match a SYMLINK: a trailing slash only matches a directory, so a
|
|
57
|
+
# symlinked node_modules slipped past and got committed as an absolute path.
|
|
58
|
+
desktop/node_modules
|
|
59
|
+
web/node_modules
|
|
60
|
+
desktop/dist/
|
|
61
|
+
|
|
62
|
+
# The adoption harness writes its report here on every run.
|
|
63
|
+
e2e/adoption/out/
|
no_human-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eyal Golan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
no_human-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: no-human
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Autonomous AI software-delivery orchestrator driving the Claude Agent SDK on your own Claude credentials
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: aiosqlite>=0.22.1
|
|
9
|
+
Requires-Dist: claude-agent-sdk>=0.2.120
|
|
10
|
+
Requires-Dist: click>=8.4.1
|
|
11
|
+
Requires-Dist: fastapi>=0.138.0
|
|
12
|
+
Requires-Dist: httpx>=0.28.1
|
|
13
|
+
Requires-Dist: mcp>=1.28.0
|
|
14
|
+
Requires-Dist: psutil>=7.0.0
|
|
15
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
16
|
+
Requires-Dist: rich>=15.0.0
|
|
17
|
+
Requires-Dist: slack-sdk>=3.43.0
|
|
18
|
+
Requires-Dist: textual>=8.2.7
|
|
19
|
+
Requires-Dist: uvicorn[standard]>=0.49.0
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
<div align="center">
|
|
23
|
+
|
|
24
|
+
<img src="docs/assets/nh-mark.png" alt="" width="140" height="140">
|
|
25
|
+
|
|
26
|
+
# no_human
|
|
27
|
+
|
|
28
|
+
**From ticket to reviewed pull request.**<br>***Free and open-source, on your machine.***
|
|
29
|
+
|
|
30
|
+
[](https://github.com/no-human-ai/no_human/releases/latest) [](https://github.com/no-human-ai/no_human/actions/workflows/ci.yml) [](https://www.python.org/) [](LICENSE)
|
|
31
|
+
|
|
32
|
+
[getnohuman.com](https://getnohuman.com) · [Quickstart](docs/quickstart.md) · [Docs](docs/README.md) · [Watch it work a sprint](https://getnohuman.com/demo)
|
|
33
|
+
|
|
34
|
+
[](https://github.com/no-human-ai/no_human/releases/latest) [](https://github.com/no-human-ai/no_human/releases/latest) [](https://github.com/no-human-ai/no_human/releases/latest)
|
|
35
|
+
|
|
36
|
+
<a href="https://getnohuman.com/"><img src="docs/assets/hero-loop-poster.jpg" alt="The no_human board: one task waiting on a question in Needs answer, four tasks working in parallel, one pull request ready for review." width="880"></a>
|
|
37
|
+
|
|
38
|
+
<sub>▶ <a href="https://getnohuman.com/">Watch the loop</a> — a ticket in, a reviewed pull request out; the whole loop in 57 seconds.</sub>
|
|
39
|
+
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
The AI coding factory you <ins>**can trust**</ins>:
|
|
43
|
+
|
|
44
|
+
- **A plan before any code**, from the ticket plus what it finds in your repo.
|
|
45
|
+
- **An adversarial review.** A different model, fresh context, read-only tools,
|
|
46
|
+
told to refute "done". You get a pass/fail checklist citing file and line —
|
|
47
|
+
never a numeric self-score.
|
|
48
|
+
- **A tamper guard.** Deleted tests, new skips, an assertion turned into a
|
|
49
|
+
tautology — blocked before a reviewer token is spent.
|
|
50
|
+
- **Proof the fix fixed the bug.** For a bug fix, the tests offered as evidence
|
|
51
|
+
must fail at the merge base and pass on the new tree — the reproduction gate
|
|
52
|
+
enforces that, and you can require it for every change.
|
|
53
|
+
- **Your tests run**, locally and optionally through your CI.
|
|
54
|
+
- **An honest stop.** When it cannot finish, it parks with one specific question
|
|
55
|
+
instead of inventing a plausible diff.
|
|
56
|
+
|
|
57
|
+
## Install
|
|
58
|
+
|
|
59
|
+
Whichever way you install, you need a **Claude credential**: an OAuth token
|
|
60
|
+
from `claude setup-token` (personal subscription or enterprise), so install the
|
|
61
|
+
Claude Code CLI first — `npm install -g @anthropic-ai/claude-code`, or
|
|
62
|
+
`curl -fsSL https://claude.ai/install.sh | bash`. The desktop app also calls
|
|
63
|
+
that CLI for every task. To pay Anthropic directly instead, set
|
|
64
|
+
`llm.auth_mode: "api_key"` and put your `ANTHROPIC_API_KEY` in
|
|
65
|
+
`~/.no_human/.env`.
|
|
66
|
+
|
|
67
|
+
### Desktop app
|
|
68
|
+
|
|
69
|
+
[](https://github.com/no-human-ai/no_human/releases/latest) [](https://github.com/no-human-ai/no_human/releases/latest) [](https://github.com/no-human-ai/no_human/releases/latest)
|
|
70
|
+
|
|
71
|
+
Each release ships a SHA-256 alongside the artifact. Platform notes and the
|
|
72
|
+
first-run walk-through: [docs/quickstart.md](docs/quickstart.md).
|
|
73
|
+
|
|
74
|
+
### From source
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git clone https://github.com/no-human-ai/no_human.git && cd no_human
|
|
78
|
+
uv sync # installs the `nh` entry point into .venv
|
|
79
|
+
(cd web && npm install && npm run build) # builds the board (cold first install can take minutes)
|
|
80
|
+
uv run nh init # token, config, first repo (about 2 minutes)
|
|
81
|
+
uv run nh doctor # verify the install is real before relying on it
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The `web` build is not optional if you want the board: a source checkout ships
|
|
85
|
+
no `web/dist`, so without it `nh start` serves the API only and renders no UI.
|
|
86
|
+
Needs Python 3.12+, [uv](https://github.com/astral-sh/uv), git, and Node with
|
|
87
|
+
npm for the board build.
|
|
88
|
+
|
|
89
|
+
## Run one task
|
|
90
|
+
|
|
91
|
+
Run `nh` with no arguments for the shell: your lanes, a live event tail, and an
|
|
92
|
+
intake you describe a task to in plain English. Every command below still works.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
nh # the shell
|
|
96
|
+
nh start # board + worker on 127.0.0.1:8420
|
|
97
|
+
nh task add https://github.com/org/repo/issues/42 --repo ~/git/repo
|
|
98
|
+
nh status # needs-you / working / waiting / done
|
|
99
|
+
nh review <id> # the reviewer's evidence checklist
|
|
100
|
+
nh diff <id> # the diff it wants to ship
|
|
101
|
+
nh approve <id> # your approval squash-lands the PR (git.approve_identity)
|
|
102
|
+
nh reject <id> --reason "..." # send it back with feedback
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Integrations
|
|
106
|
+
|
|
107
|
+
Point no_human at the tracker you already use and it pulls the tickets to your
|
|
108
|
+
board — a tracker's filter lives in your config, never in a task's own text,
|
|
109
|
+
and a transport error logs and retries on the next tick instead of crashing
|
|
110
|
+
the pool.
|
|
111
|
+
|
|
112
|
+
| Tracker | How tickets arrive | Filter you configure |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| **Jira Cloud** | Polled via REST `search/jql` (HTTP Basic `email:token`) | `integrations.jira.jql` |
|
|
115
|
+
| **Linear** | Polled via the GraphQL API | `integrations.linear.team_key` + `state_types` + `label` |
|
|
116
|
+
| **monday.com** | Polled via GraphQL v2 | `integrations.monday.board_id` + `status_column` + `todo_labels` |
|
|
117
|
+
|
|
118
|
+
With write-back on (`write_back`, off by default), the ticket moves with the
|
|
119
|
+
task — matched by status category, type, or the label you name, never a
|
|
120
|
+
hard-coded transition id — and gets the PR link; a task that needs a human is commented on, never
|
|
121
|
+
transitioned. GitHub and
|
|
122
|
+
GitLab issues import as tasks by URL, and PRs or MRs open on your own host;
|
|
123
|
+
Slack and Teams get a message when a task needs you; Jenkins and CircleCI can
|
|
124
|
+
run your test layers and gate the loop. Setup for each:
|
|
125
|
+
[docs/adapters.md](docs/adapters.md).
|
|
126
|
+
|
|
127
|
+
**Watch the Jira flow end to end** — tickets synced from a Jira board, scoped,
|
|
128
|
+
implemented, and delivered as a review-passed pull request (click for the full
|
|
129
|
+
video with every step):
|
|
130
|
+
|
|
131
|
+
[](https://getnohuman.com/assets/demo-jira.mp4)
|
|
132
|
+
|
|
133
|
+
<p align="center">▶️ <strong><a href="https://getnohuman.com/assets/demo-jira.mp4">Play the full demo</a></strong> — 1:33, from Jira board to review-passed PR</p>
|
|
134
|
+
|
|
135
|
+
## MCP server — hand it work from the agent you are already in
|
|
136
|
+
|
|
137
|
+
no_human ships an **MCP (Model Context Protocol) server**: a stdio bridge, built
|
|
138
|
+
on the official Python MCP SDK, that lets Claude Code, Cursor or any MCP client
|
|
139
|
+
file work with your local no_human and check on it.
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
nh mcp-serve # the MCP server, over stdio
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Two tools, and no more:
|
|
146
|
+
|
|
147
|
+
| Tool | What it does |
|
|
148
|
+
|---|---|
|
|
149
|
+
| `task_add(title, description, repo_path)` | Files a task. no_human then plans it, writes the change, runs your tests, has a second model review it, and opens the pull request. |
|
|
150
|
+
| `task_status(task_id_or_external_id)` | Returns that task's current state — status, attempts, the PR link once there is one. |
|
|
151
|
+
|
|
152
|
+
It talks to your own no_human at `http://127.0.0.1:8420` and nothing else: no
|
|
153
|
+
auth, because that address is localhost, and no service of ours in between. For
|
|
154
|
+
Claude Code, the same server ships as a plugin — point it at
|
|
155
|
+
[`plugins/no-human/`](plugins/no-human/) and the two tools appear in your
|
|
156
|
+
session.
|
|
157
|
+
|
|
158
|
+
```jsonc
|
|
159
|
+
// .mcp.json
|
|
160
|
+
{ "mcpServers": { "no_human": { "command": "nh", "args": ["mcp-serve"] } } }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Docs
|
|
164
|
+
|
|
165
|
+
| | |
|
|
166
|
+
|---|---|
|
|
167
|
+
| [quickstart.md](docs/quickstart.md) | Zero to first task, per platform |
|
|
168
|
+
| [configuration.md](docs/configuration.md) | Every setting and default |
|
|
169
|
+
| [verification.md](docs/verification.md) | The gates, the bounded loop, the limits |
|
|
170
|
+
| [security.md](docs/security.md) | Auth boundary, the never-merge rule, guards |
|
|
171
|
+
| [blockers.md](docs/blockers.md) | Escalation, wake watcher, `nh reply` |
|
|
172
|
+
| [adapters.md](docs/adapters.md) | Intake, context, VCS and CI backends |
|
|
173
|
+
| [eval.md](docs/eval.md) | Golden set, replay scoring, shadow mode |
|
|
174
|
+
| [CHANGELOG.md](CHANGELOG.md) | What changed, per release |
|
|
175
|
+
|
|
176
|
+
## Development
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
uv sync
|
|
180
|
+
uv run pytest -q
|
|
181
|
+
uv run nh --help
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Issues and pull requests welcome; run `uv run pytest -q` before submitting.
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT — see [LICENSE](LICENSE). The licence covers the code, not the name:
|
|
189
|
+
[TRADEMARK.md](TRADEMARK.md) is the policy on using "no_human" and the logo.
|
|
190
|
+
Packaging a binary carries obligations the source tree does not, listed in
|
|
191
|
+
[THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).
|
no_human-0.1.1/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="docs/assets/nh-mark.png" alt="" width="140" height="140">
|
|
4
|
+
|
|
5
|
+
# no_human
|
|
6
|
+
|
|
7
|
+
**From ticket to reviewed pull request.**<br>***Free and open-source, on your machine.***
|
|
8
|
+
|
|
9
|
+
[](https://github.com/no-human-ai/no_human/releases/latest) [](https://github.com/no-human-ai/no_human/actions/workflows/ci.yml) [](https://www.python.org/) [](LICENSE)
|
|
10
|
+
|
|
11
|
+
[getnohuman.com](https://getnohuman.com) · [Quickstart](docs/quickstart.md) · [Docs](docs/README.md) · [Watch it work a sprint](https://getnohuman.com/demo)
|
|
12
|
+
|
|
13
|
+
[](https://github.com/no-human-ai/no_human/releases/latest) [](https://github.com/no-human-ai/no_human/releases/latest) [](https://github.com/no-human-ai/no_human/releases/latest)
|
|
14
|
+
|
|
15
|
+
<a href="https://getnohuman.com/"><img src="docs/assets/hero-loop-poster.jpg" alt="The no_human board: one task waiting on a question in Needs answer, four tasks working in parallel, one pull request ready for review." width="880"></a>
|
|
16
|
+
|
|
17
|
+
<sub>▶ <a href="https://getnohuman.com/">Watch the loop</a> — a ticket in, a reviewed pull request out; the whole loop in 57 seconds.</sub>
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
The AI coding factory you <ins>**can trust**</ins>:
|
|
22
|
+
|
|
23
|
+
- **A plan before any code**, from the ticket plus what it finds in your repo.
|
|
24
|
+
- **An adversarial review.** A different model, fresh context, read-only tools,
|
|
25
|
+
told to refute "done". You get a pass/fail checklist citing file and line —
|
|
26
|
+
never a numeric self-score.
|
|
27
|
+
- **A tamper guard.** Deleted tests, new skips, an assertion turned into a
|
|
28
|
+
tautology — blocked before a reviewer token is spent.
|
|
29
|
+
- **Proof the fix fixed the bug.** For a bug fix, the tests offered as evidence
|
|
30
|
+
must fail at the merge base and pass on the new tree — the reproduction gate
|
|
31
|
+
enforces that, and you can require it for every change.
|
|
32
|
+
- **Your tests run**, locally and optionally through your CI.
|
|
33
|
+
- **An honest stop.** When it cannot finish, it parks with one specific question
|
|
34
|
+
instead of inventing a plausible diff.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
Whichever way you install, you need a **Claude credential**: an OAuth token
|
|
39
|
+
from `claude setup-token` (personal subscription or enterprise), so install the
|
|
40
|
+
Claude Code CLI first — `npm install -g @anthropic-ai/claude-code`, or
|
|
41
|
+
`curl -fsSL https://claude.ai/install.sh | bash`. The desktop app also calls
|
|
42
|
+
that CLI for every task. To pay Anthropic directly instead, set
|
|
43
|
+
`llm.auth_mode: "api_key"` and put your `ANTHROPIC_API_KEY` in
|
|
44
|
+
`~/.no_human/.env`.
|
|
45
|
+
|
|
46
|
+
### Desktop app
|
|
47
|
+
|
|
48
|
+
[](https://github.com/no-human-ai/no_human/releases/latest) [](https://github.com/no-human-ai/no_human/releases/latest) [](https://github.com/no-human-ai/no_human/releases/latest)
|
|
49
|
+
|
|
50
|
+
Each release ships a SHA-256 alongside the artifact. Platform notes and the
|
|
51
|
+
first-run walk-through: [docs/quickstart.md](docs/quickstart.md).
|
|
52
|
+
|
|
53
|
+
### From source
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/no-human-ai/no_human.git && cd no_human
|
|
57
|
+
uv sync # installs the `nh` entry point into .venv
|
|
58
|
+
(cd web && npm install && npm run build) # builds the board (cold first install can take minutes)
|
|
59
|
+
uv run nh init # token, config, first repo (about 2 minutes)
|
|
60
|
+
uv run nh doctor # verify the install is real before relying on it
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The `web` build is not optional if you want the board: a source checkout ships
|
|
64
|
+
no `web/dist`, so without it `nh start` serves the API only and renders no UI.
|
|
65
|
+
Needs Python 3.12+, [uv](https://github.com/astral-sh/uv), git, and Node with
|
|
66
|
+
npm for the board build.
|
|
67
|
+
|
|
68
|
+
## Run one task
|
|
69
|
+
|
|
70
|
+
Run `nh` with no arguments for the shell: your lanes, a live event tail, and an
|
|
71
|
+
intake you describe a task to in plain English. Every command below still works.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
nh # the shell
|
|
75
|
+
nh start # board + worker on 127.0.0.1:8420
|
|
76
|
+
nh task add https://github.com/org/repo/issues/42 --repo ~/git/repo
|
|
77
|
+
nh status # needs-you / working / waiting / done
|
|
78
|
+
nh review <id> # the reviewer's evidence checklist
|
|
79
|
+
nh diff <id> # the diff it wants to ship
|
|
80
|
+
nh approve <id> # your approval squash-lands the PR (git.approve_identity)
|
|
81
|
+
nh reject <id> --reason "..." # send it back with feedback
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Integrations
|
|
85
|
+
|
|
86
|
+
Point no_human at the tracker you already use and it pulls the tickets to your
|
|
87
|
+
board — a tracker's filter lives in your config, never in a task's own text,
|
|
88
|
+
and a transport error logs and retries on the next tick instead of crashing
|
|
89
|
+
the pool.
|
|
90
|
+
|
|
91
|
+
| Tracker | How tickets arrive | Filter you configure |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| **Jira Cloud** | Polled via REST `search/jql` (HTTP Basic `email:token`) | `integrations.jira.jql` |
|
|
94
|
+
| **Linear** | Polled via the GraphQL API | `integrations.linear.team_key` + `state_types` + `label` |
|
|
95
|
+
| **monday.com** | Polled via GraphQL v2 | `integrations.monday.board_id` + `status_column` + `todo_labels` |
|
|
96
|
+
|
|
97
|
+
With write-back on (`write_back`, off by default), the ticket moves with the
|
|
98
|
+
task — matched by status category, type, or the label you name, never a
|
|
99
|
+
hard-coded transition id — and gets the PR link; a task that needs a human is commented on, never
|
|
100
|
+
transitioned. GitHub and
|
|
101
|
+
GitLab issues import as tasks by URL, and PRs or MRs open on your own host;
|
|
102
|
+
Slack and Teams get a message when a task needs you; Jenkins and CircleCI can
|
|
103
|
+
run your test layers and gate the loop. Setup for each:
|
|
104
|
+
[docs/adapters.md](docs/adapters.md).
|
|
105
|
+
|
|
106
|
+
**Watch the Jira flow end to end** — tickets synced from a Jira board, scoped,
|
|
107
|
+
implemented, and delivered as a review-passed pull request (click for the full
|
|
108
|
+
video with every step):
|
|
109
|
+
|
|
110
|
+
[](https://getnohuman.com/assets/demo-jira.mp4)
|
|
111
|
+
|
|
112
|
+
<p align="center">▶️ <strong><a href="https://getnohuman.com/assets/demo-jira.mp4">Play the full demo</a></strong> — 1:33, from Jira board to review-passed PR</p>
|
|
113
|
+
|
|
114
|
+
## MCP server — hand it work from the agent you are already in
|
|
115
|
+
|
|
116
|
+
no_human ships an **MCP (Model Context Protocol) server**: a stdio bridge, built
|
|
117
|
+
on the official Python MCP SDK, that lets Claude Code, Cursor or any MCP client
|
|
118
|
+
file work with your local no_human and check on it.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
nh mcp-serve # the MCP server, over stdio
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Two tools, and no more:
|
|
125
|
+
|
|
126
|
+
| Tool | What it does |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `task_add(title, description, repo_path)` | Files a task. no_human then plans it, writes the change, runs your tests, has a second model review it, and opens the pull request. |
|
|
129
|
+
| `task_status(task_id_or_external_id)` | Returns that task's current state — status, attempts, the PR link once there is one. |
|
|
130
|
+
|
|
131
|
+
It talks to your own no_human at `http://127.0.0.1:8420` and nothing else: no
|
|
132
|
+
auth, because that address is localhost, and no service of ours in between. For
|
|
133
|
+
Claude Code, the same server ships as a plugin — point it at
|
|
134
|
+
[`plugins/no-human/`](plugins/no-human/) and the two tools appear in your
|
|
135
|
+
session.
|
|
136
|
+
|
|
137
|
+
```jsonc
|
|
138
|
+
// .mcp.json
|
|
139
|
+
{ "mcpServers": { "no_human": { "command": "nh", "args": ["mcp-serve"] } } }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Docs
|
|
143
|
+
|
|
144
|
+
| | |
|
|
145
|
+
|---|---|
|
|
146
|
+
| [quickstart.md](docs/quickstart.md) | Zero to first task, per platform |
|
|
147
|
+
| [configuration.md](docs/configuration.md) | Every setting and default |
|
|
148
|
+
| [verification.md](docs/verification.md) | The gates, the bounded loop, the limits |
|
|
149
|
+
| [security.md](docs/security.md) | Auth boundary, the never-merge rule, guards |
|
|
150
|
+
| [blockers.md](docs/blockers.md) | Escalation, wake watcher, `nh reply` |
|
|
151
|
+
| [adapters.md](docs/adapters.md) | Intake, context, VCS and CI backends |
|
|
152
|
+
| [eval.md](docs/eval.md) | Golden set, replay scoring, shadow mode |
|
|
153
|
+
| [CHANGELOG.md](CHANGELOG.md) | What changed, per release |
|
|
154
|
+
|
|
155
|
+
## Development
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
uv sync
|
|
159
|
+
uv run pytest -q
|
|
160
|
+
uv run nh --help
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Issues and pull requests welcome; run `uv run pytest -q` before submitting.
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT — see [LICENSE](LICENSE). The licence covers the code, not the name:
|
|
168
|
+
[TRADEMARK.md](TRADEMARK.md) is the policy on using "no_human" and the logo.
|
|
169
|
+
Packaging a binary carries obligations the source tree does not, listed in
|
|
170
|
+
[THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""The board is REQUIRED for a distributable build and OPTIONAL for a dev one.
|
|
2
|
+
|
|
3
|
+
`web/dist` is the built React board. `nh start` — the entrypoint README calls
|
|
4
|
+
primary — serves it, so a wheel or sdist built without it installs a CLI whose
|
|
5
|
+
main command renders nothing. That shipped once, and the fix was to declare the
|
|
6
|
+
board with hatchling's `force-include`, which raises `Forced include not found`
|
|
7
|
+
when the source is absent: a release cut without `npm run build` FAILS instead
|
|
8
|
+
of quietly shipping the broken product.
|
|
9
|
+
|
|
10
|
+
That guarantee was right; the mechanism was too wide. `force-include` is static
|
|
11
|
+
configuration, so it is evaluated for EVERY build target and version — including
|
|
12
|
+
the editable wheel that `uv sync` and `pip install -e .` build. `web/dist` is a
|
|
13
|
+
gitignored build artifact, so a clean clone of this repo does not have one, and
|
|
14
|
+
the release-time guard fired on the developer's first command instead:
|
|
15
|
+
|
|
16
|
+
$ git clone … && cd no_human && uv sync
|
|
17
|
+
FileNotFoundError: Forced include not found: <repo>/web/dist
|
|
18
|
+
Call to `hatchling.build.build_editable` failed (exit status: 1)
|
|
19
|
+
|
|
20
|
+
A clean clone could not be installed, developed, or tested — before collection,
|
|
21
|
+
with a message that reads like a broken repository rather than a missing npm
|
|
22
|
+
build. (The comment this replaces claimed "nothing in CI builds a wheel, so this
|
|
23
|
+
only ever fires on a release build". That was false: `uv sync` reaches the same
|
|
24
|
+
code path through `build_editable`.)
|
|
25
|
+
|
|
26
|
+
So the declaration moves here, where the build VERSION is visible. Hatchling
|
|
27
|
+
calls `initialize(version, build_data)` with `version == "editable"` for an
|
|
28
|
+
editable install and `"standard"` for everything else, and honours anything a
|
|
29
|
+
hook adds to `build_data["force_include"]` (merged in `BuilderConfig.
|
|
30
|
+
set_build_data`, read by both `build_standard` and `build_editable_detection`).
|
|
31
|
+
That is the whole seam:
|
|
32
|
+
|
|
33
|
+
* editable + no board -> build it automatically when `npm` is on PATH ("cd
|
|
34
|
+
web && npm install && npm run build"). If npm is absent, the build fails,
|
|
35
|
+
or it exits 0 without producing `index.html`, fall back to warning and
|
|
36
|
+
include nothing — the install still succeeds either way.
|
|
37
|
+
* editable + board -> include it, exactly as before.
|
|
38
|
+
* standard + no board -> FAIL, with a message that names `npm run build` and
|
|
39
|
+
the directory. The release-time guarantee is unchanged; only its blast
|
|
40
|
+
radius is.
|
|
41
|
+
* standard + board -> include it, exactly as before.
|
|
42
|
+
|
|
43
|
+
Presence is judged by `index.html`, not by the directory. `npm run build` writes
|
|
44
|
+
INTO an existing directory, so an interrupted or cleaned build leaves the husk
|
|
45
|
+
behind — and the old directory-existence check would have force-included that
|
|
46
|
+
husk into a release wheel, which is the boardless-CLI bug wearing a directory.
|
|
47
|
+
`api/app.py::_resolve_web_dist` already refuses a husk for the same reason.
|
|
48
|
+
|
|
49
|
+
`migrations/` stays a static `force-include` in `pyproject.toml`: it is TRACKED,
|
|
50
|
+
so it is present in every clone and worktree, and a static declaration is the
|
|
51
|
+
simpler thing that works.
|
|
52
|
+
|
|
53
|
+
The hatchling import is guarded so this module can be imported as a plain script
|
|
54
|
+
by `tests/test_clean_clone_installable.py`. Hatchling is a BUILD dependency — it
|
|
55
|
+
exists only inside the isolated build environment PEP 517 creates, never in this
|
|
56
|
+
repo's own venv — so an unguarded import would make the decision logic below
|
|
57
|
+
untestable anywhere except inside a real build, which is the slowest and least
|
|
58
|
+
specific place to test it.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
from __future__ import annotations
|
|
62
|
+
|
|
63
|
+
import shutil
|
|
64
|
+
import subprocess
|
|
65
|
+
from collections.abc import Callable
|
|
66
|
+
from pathlib import Path
|
|
67
|
+
|
|
68
|
+
try: # pragma: no cover - the real build environment always has hatchling
|
|
69
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
70
|
+
except ModuleNotFoundError: # pragma: no cover - importing this file as a module
|
|
71
|
+
BuildHookInterface = object # type: ignore[assignment,misc]
|
|
72
|
+
|
|
73
|
+
# The version string hatchling passes for `pip install -e .` / `uv sync`.
|
|
74
|
+
EDITABLE = "editable"
|
|
75
|
+
|
|
76
|
+
DEFAULT_SOURCE = "web/dist"
|
|
77
|
+
|
|
78
|
+
NPM = "npm"
|
|
79
|
+
|
|
80
|
+
# `npm install` on a cold `node_modules` can take a while; this bounds an
|
|
81
|
+
# editable install rather than hanging it forever.
|
|
82
|
+
NPM_TIMEOUT = 900
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class BoardNotBuiltError(RuntimeError):
|
|
86
|
+
"""A distributable artifact was built without a board to put in it."""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _build_it() -> str:
|
|
90
|
+
return "cd web && npm install && npm run build"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _npm_available() -> bool:
|
|
94
|
+
return shutil.which(NPM) is not None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def try_build_board(
|
|
98
|
+
root: str | Path,
|
|
99
|
+
source: str,
|
|
100
|
+
runner: Callable[..., subprocess.CompletedProcess] = subprocess.run,
|
|
101
|
+
) -> bool:
|
|
102
|
+
"""Attempt the editable-install board build named by `_build_it()`.
|
|
103
|
+
|
|
104
|
+
Runs `npm install` then `npm run build` in `<root>/<web dir>` (the parent
|
|
105
|
+
of `source`) and returns whether `<root>/<source>/index.html` exists
|
|
106
|
+
afterwards. Never raises: an absent `npm`, a missing `web/package.json`
|
|
107
|
+
(not a real checkout — e.g. a test's bare `tmp_path`), a non-zero exit, a
|
|
108
|
+
crash, or a timeout all just return `False` so the caller can fall back to
|
|
109
|
+
`board_missing_warning`. A 0 exit with no `index.html` is also `False` —
|
|
110
|
+
npm can exit 0 having produced an incomplete build.
|
|
111
|
+
"""
|
|
112
|
+
web_dir = Path(root) / Path(source).parent
|
|
113
|
+
if not (web_dir / "package.json").is_file():
|
|
114
|
+
return False
|
|
115
|
+
if not _npm_available():
|
|
116
|
+
return False
|
|
117
|
+
try:
|
|
118
|
+
for argv in (["npm", "install"], ["npm", "run", "build"]):
|
|
119
|
+
result = runner(
|
|
120
|
+
argv, cwd=web_dir, capture_output=True, text=True,
|
|
121
|
+
check=False, timeout=NPM_TIMEOUT,
|
|
122
|
+
)
|
|
123
|
+
if result.returncode != 0:
|
|
124
|
+
return False
|
|
125
|
+
except (OSError, subprocess.SubprocessError):
|
|
126
|
+
return False
|
|
127
|
+
return (Path(root) / source / "index.html").is_file()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def board_missing_warning(index: Path) -> str:
|
|
131
|
+
return (
|
|
132
|
+
f"\nno_human: the web board is not built — {index} is missing.\n"
|
|
133
|
+
f" This dev install will work, but `nh start` will serve a notice\n"
|
|
134
|
+
f" instead of the board until you run:\n"
|
|
135
|
+
f" {_build_it()}\n"
|
|
136
|
+
f" (The board is a gitignored build artifact, so a fresh clone never\n"
|
|
137
|
+
f" has one. The CLI — `nh task`, `nh status` — is unaffected.)\n"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def board_missing_error(index: Path, target_name: str) -> str:
|
|
142
|
+
return (
|
|
143
|
+
f"\nno_human: refusing to build a {target_name} without the web board.\n"
|
|
144
|
+
f"\n"
|
|
145
|
+
f" missing: {index}\n"
|
|
146
|
+
f"\n"
|
|
147
|
+
f" `nh start` is the documented entrypoint and it serves this board, so\n"
|
|
148
|
+
f" a {target_name} built without it installs a CLI whose main command\n"
|
|
149
|
+
f" renders nothing. That has shipped once already, which is why this is\n"
|
|
150
|
+
f" a build failure and not a warning.\n"
|
|
151
|
+
f"\n"
|
|
152
|
+
f" Build the board first:\n"
|
|
153
|
+
f" {_build_it()}\n"
|
|
154
|
+
f"\n"
|
|
155
|
+
f" A dev install does NOT need it: `uv sync` and `pip install -e .`\n"
|
|
156
|
+
f" warn and continue.\n"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def plan_board_inclusion(
|
|
161
|
+
root: str | Path,
|
|
162
|
+
source: str,
|
|
163
|
+
target: str,
|
|
164
|
+
version: str,
|
|
165
|
+
target_name: str = "wheel",
|
|
166
|
+
*,
|
|
167
|
+
builder: Callable[[str | Path, str], bool] | None = None,
|
|
168
|
+
) -> tuple[dict[str, str], str | None]:
|
|
169
|
+
"""Decide what a build of `version` does about the board.
|
|
170
|
+
|
|
171
|
+
Returns `(force_include_additions, warning_or_None)` and raises
|
|
172
|
+
`BoardNotBuiltError` when a distributable build has no board to ship. Split
|
|
173
|
+
out of the hook class so it can be tested without hatchling and without
|
|
174
|
+
running a build — the branch that matters (editable vs standard) is one
|
|
175
|
+
string comparison, and it should be provable in milliseconds.
|
|
176
|
+
|
|
177
|
+
`builder` is test-injectable and only ever consulted on the editable path
|
|
178
|
+
(default `try_build_board`); the standard/distributable path never calls
|
|
179
|
+
it and always fails closed when the board is absent.
|
|
180
|
+
"""
|
|
181
|
+
index = Path(root) / source / "index.html"
|
|
182
|
+
if index.is_file():
|
|
183
|
+
return {source: target}, None
|
|
184
|
+
if version == EDITABLE:
|
|
185
|
+
build = builder or try_build_board
|
|
186
|
+
if build(root, source) and index.is_file():
|
|
187
|
+
return {source: target}, None
|
|
188
|
+
return {}, board_missing_warning(index)
|
|
189
|
+
raise BoardNotBuiltError(board_missing_error(index, target_name))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class BoardBuildHook(BuildHookInterface): # type: ignore[misc,valid-type]
|
|
193
|
+
"""Injects the board's `force-include` entry, or explains its absence."""
|
|
194
|
+
|
|
195
|
+
PLUGIN_NAME = "no-human-board"
|
|
196
|
+
|
|
197
|
+
def initialize(self, version: str, build_data: dict) -> None:
|
|
198
|
+
source = self.config.get("source", DEFAULT_SOURCE)
|
|
199
|
+
target = self.config.get("target")
|
|
200
|
+
if not isinstance(target, str) or not target:
|
|
201
|
+
msg = (
|
|
202
|
+
"hatch_build.py: the hook needs a `target` in "
|
|
203
|
+
f"[tool.hatch.build.targets.{self.target_name}.hooks.custom] — "
|
|
204
|
+
"the path the board takes inside the artifact."
|
|
205
|
+
)
|
|
206
|
+
raise ValueError(msg)
|
|
207
|
+
|
|
208
|
+
additions, warning = plan_board_inclusion(
|
|
209
|
+
self.root, source, target, version, self.target_name
|
|
210
|
+
)
|
|
211
|
+
if warning:
|
|
212
|
+
self.app.display_warning(warning)
|
|
213
|
+
build_data["force_include"].update(additions)
|