create-egregore 0.21.6 → 0.22.0
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.
- package/assets/egregore-launcher.js +58 -6
- package/assets/egregore-launcher.sh +15 -2
- package/lib/claude-runtime.js +2 -0
- package/lib/codex-runtime.js +2 -0
- package/lib/setup.js +20 -3
- package/package.json +2 -2
- package/runtime/claude/.claude/context/commit-format.md +7 -4
- package/runtime/claude/.claude/context/handoff-claiming.md +32 -0
- package/runtime/claude/.claude/context/notification-consent.md +12 -9
- package/runtime/claude/.claude/hooks/boundary-check.sh +14 -331
- package/runtime/claude/.claude/hooks/memory-read-guard.sh +9 -1
- package/runtime/claude/.claude/rules/voice-bedrock.md +6 -2
- package/runtime/claude/.claude/settings.json +1 -1
- package/runtime/claude/.claude/skills/commit/SKILL.md +5 -3
- package/runtime/claude/.claude/skills/dark-mode/SKILL.md +48 -369
- package/runtime/claude/.claude/skills/records/SKILL.md +158 -0
- package/runtime/claude/.claude/skills/reflect/SKILL.md +50 -21
- package/runtime/claude/.claude/skills/review-pr/SKILL.md +4 -2
- package/runtime/claude/.claude/skills/save/SKILL.md +9 -3
- package/runtime/claude/.claude/skills/summon/SKILL.md +138 -207
- package/runtime/claude/.claude/skills/tui-design/SKILL.md +1 -1
- package/runtime/claude/.claude/skills/update/SKILL.md +1 -1
- package/runtime/claude/.claude/skills/view/SKILL.md +52 -16
- package/runtime/claude/.claude/skills/view/compose-scaffold.html +19 -13
- package/runtime/claude/CLAUDE.md +59 -95
- package/runtime/claude/bin/agent.sh +231 -7
- package/runtime/claude/bin/api-call.sh +44 -23
- package/runtime/claude/bin/artifact-register.sh +7 -1
- package/runtime/claude/bin/artifact-writeback.sh +1 -1
- package/runtime/claude/bin/artifacts.sh +66 -265
- package/runtime/claude/bin/attendant.sh +4 -1
- package/runtime/claude/bin/boundary-guard.cjs +1848 -0
- package/runtime/claude/bin/boundary-hook.sh +41 -0
- package/runtime/claude/bin/boundary.sh +15 -155
- package/runtime/claude/bin/capture-run.sh +32 -2
- package/runtime/claude/bin/codex-render-spec.mjs +24 -46
- package/runtime/claude/bin/codex-session-start.sh +19 -0
- package/runtime/claude/bin/codex-skill-render.mjs +18 -0
- package/runtime/claude/bin/config-get.sh +10 -1
- package/runtime/claude/bin/connect-refresh.sh +16 -0
- package/runtime/claude/bin/corpus_map.py +5 -0
- package/runtime/claude/bin/data/capabilities/claude.json +1480 -0
- package/runtime/claude/bin/genui.sh +19 -0
- package/runtime/claude/bin/graph-batch.sh +1 -1
- package/runtime/claude/bin/graph-op.sh +14 -0
- package/runtime/claude/bin/graph.sh +1 -1
- package/runtime/claude/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/claude/bin/handoff-run.sh +11 -6
- package/runtime/claude/bin/ingest-item.sh +1 -1
- package/runtime/claude/bin/ingest-surface.html +22 -4
- package/runtime/claude/bin/issue.sh +1 -1
- package/runtime/claude/bin/knowledge.sh +1 -1
- package/runtime/claude/bin/lib/card-delivery.sh +329 -0
- package/runtime/claude/bin/lib/config.sh +148 -3
- package/runtime/claude/bin/lib/context.sh +38 -67
- package/runtime/claude/bin/lib/genui-entry.py +36 -0
- package/runtime/claude/bin/lib/git-message.sh +25 -6
- package/runtime/claude/bin/lib/git-sync.sh +12 -4
- package/runtime/claude/bin/lib/greeting.sh +255 -28
- package/runtime/claude/bin/lib/handoff_meta.py +294 -0
- package/runtime/claude/bin/lib/health-footer.sh +83 -2
- package/runtime/claude/bin/lib/identity.sh +31 -10
- package/runtime/claude/bin/lib/instance-registry.sh +71 -0
- package/runtime/claude/bin/lib/local_ci_receipt.py +249 -0
- package/runtime/claude/bin/lib/metrics.sh +121 -8
- package/runtime/claude/bin/lib/notices.sh +36 -0
- package/runtime/claude/bin/lib/record_outcomes.py +729 -0
- package/runtime/claude/bin/lib/reflexes.py +267 -0
- package/runtime/claude/bin/lib/retrieval_scorecard.py +624 -0
- package/runtime/claude/bin/lib/session-record-prompt.txt +18 -0
- package/runtime/claude/bin/lib/session_record.py +3747 -0
- package/runtime/claude/bin/lib/session_trail.py +1983 -0
- package/runtime/claude/bin/lib/settings-drift.sh +8 -5
- package/runtime/claude/bin/lib/startup-restart.sh +41 -0
- package/runtime/claude/bin/lib/worktree-links.sh +92 -0
- package/runtime/claude/bin/memory-push.sh +6 -0
- package/runtime/claude/bin/merge-gate.sh +498 -0
- package/runtime/claude/bin/notify.sh +5 -2
- package/runtime/claude/bin/page-access.sh +103 -0
- package/runtime/claude/bin/person.sh +13 -2
- package/runtime/claude/bin/pi-render-spec.mjs +0 -1
- package/runtime/claude/bin/pi-spirit-discovery.mjs +34 -0
- package/runtime/claude/bin/post-update.sh +181 -0
- package/runtime/claude/bin/prime-render-spec.mjs +0 -1
- package/runtime/claude/bin/publish-artifact.sh +89 -42
- package/runtime/claude/bin/qa-inventory.sh +2 -2
- package/runtime/claude/bin/quest.sh +1 -1
- package/runtime/claude/bin/records.sh +11 -0
- package/runtime/claude/bin/reflexes/05-vitals.sh +9 -0
- package/runtime/claude/bin/reflexes/10-open-items.sh +12 -0
- package/runtime/claude/bin/reflexes/15-retrieval.sh +8 -0
- package/runtime/claude/bin/reflexes/20-nightly.sh +100 -0
- package/runtime/claude/bin/reflexes/20-sandbox.sh +85 -0
- package/runtime/claude/bin/reflexes/30-cost.py +116 -0
- package/runtime/claude/bin/reflexes/40-shells.py +51 -0
- package/runtime/claude/bin/reflexes/README.md +61 -0
- package/runtime/claude/bin/reflexes.sh +7 -0
- package/runtime/claude/bin/runtime-identity.sh +140 -42
- package/runtime/claude/bin/screenshot.sh +77 -0
- package/runtime/claude/bin/scroll.sh +1 -1
- package/runtime/claude/bin/search.sh +31 -5
- package/runtime/claude/bin/session-autosave.sh +6 -3
- package/runtime/claude/bin/session-id.sh +147 -0
- package/runtime/claude/bin/session-log.sh +19 -0
- package/runtime/claude/bin/session-record.sh +26 -0
- package/runtime/claude/bin/session-start.sh +121 -151
- package/runtime/claude/bin/settings.sh +13 -8
- package/runtime/claude/bin/spirit-discovery-claude.sh +4 -0
- package/runtime/claude/bin/spirit-discovery-codex.sh +4 -0
- package/runtime/claude/bin/spirit-discovery-pi.sh +4 -0
- package/runtime/claude/bin/spirit-discovery-prime.sh +4 -0
- package/runtime/claude/bin/spirit-discovery.sh +4 -0
- package/runtime/claude/bin/startup-check.sh +1 -1
- package/runtime/claude/bin/telemetry.sh +11 -4
- package/runtime/claude/bin/test-baseline.sh +33 -5
- package/runtime/claude/bin/todo.sh +1 -1
- package/runtime/claude/bin/worktree-create.sh +15 -45
- package/runtime/claude/bin/worktree.sh +6 -0
- package/runtime/claude/docs/spirit-discovery.md +96 -0
- package/runtime/claude/egregore_runtime/__init__.py +18 -12
- package/runtime/claude/egregore_runtime/access.py +608 -0
- package/runtime/claude/egregore_runtime/access_cli.py +190 -0
- package/runtime/claude/egregore_runtime/adapters/invitation.py +5 -3
- package/runtime/claude/egregore_runtime/adapters/qmd.py +521 -47
- package/runtime/claude/egregore_runtime/admin_entry.py +56 -0
- package/runtime/claude/egregore_runtime/artifacts.py +222 -16
- package/runtime/claude/egregore_runtime/contracts.py +27 -0
- package/runtime/claude/egregore_runtime/eligibility.py +5 -12
- package/runtime/claude/egregore_runtime/episode_binding.py +16 -5
- package/runtime/claude/egregore_runtime/github_issues.py +2 -2
- package/runtime/claude/egregore_runtime/handoff_lookup.py +9 -20
- package/runtime/claude/egregore_runtime/harness_cli.py +674 -149
- package/runtime/claude/egregore_runtime/held.py +2436 -0
- package/runtime/claude/egregore_runtime/hosted_api.py +137 -0
- package/runtime/claude/egregore_runtime/identity.py +196 -65
- package/runtime/claude/egregore_runtime/identity_transfer.py +6 -2
- package/runtime/claude/egregore_runtime/ingest_cli.py +2 -2
- package/runtime/claude/egregore_runtime/ingest_index_cli.py +4 -16
- package/runtime/claude/egregore_runtime/installation.py +119 -0
- package/runtime/claude/egregore_runtime/investigation.py +64 -41
- package/runtime/claude/egregore_runtime/investigation_guidance.py +16 -11
- package/runtime/claude/egregore_runtime/issue_cli.py +51 -32
- package/runtime/claude/egregore_runtime/issue_reporting.py +4 -3
- package/runtime/claude/egregore_runtime/issue_triage_service.py +2 -2
- package/runtime/claude/egregore_runtime/issues.py +12 -6
- package/runtime/claude/egregore_runtime/knowledge.py +331 -5
- package/runtime/claude/egregore_runtime/knowledge_cli.py +57 -17
- package/runtime/claude/egregore_runtime/lifecycle.py +88 -22
- package/runtime/claude/egregore_runtime/lifecycle_cli.py +21 -13
- package/runtime/claude/egregore_runtime/line_ids.py +10 -0
- package/runtime/claude/egregore_runtime/memory_push.py +349 -0
- package/runtime/claude/egregore_runtime/observe.py +64 -25
- package/runtime/claude/egregore_runtime/observers.py +19 -0
- package/runtime/claude/egregore_runtime/optional_modules.py +13 -0
- package/runtime/claude/egregore_runtime/outputs_cli.py +321 -0
- package/runtime/claude/egregore_runtime/policy.py +651 -24
- package/runtime/claude/egregore_runtime/private_result.py +2 -1
- package/runtime/claude/egregore_runtime/projection.py +946 -0
- package/runtime/claude/egregore_runtime/quest_cli.py +20 -23
- package/runtime/claude/egregore_runtime/question_cli.py +15 -11
- package/runtime/claude/egregore_runtime/questions.py +10 -10
- package/runtime/claude/egregore_runtime/quests.py +10 -10
- package/runtime/claude/egregore_runtime/record_publish.py +548 -0
- package/runtime/claude/egregore_runtime/record_subject.py +564 -0
- package/runtime/claude/egregore_runtime/research_ingest.py +2 -4
- package/runtime/claude/egregore_runtime/runtime.py +169 -60
- package/runtime/claude/egregore_runtime/sandbox.py +194 -0
- package/runtime/claude/egregore_runtime/scroll_cli.py +22 -32
- package/runtime/claude/egregore_runtime/scrolls.py +23 -15
- package/runtime/claude/egregore_runtime/shadow.py +262 -0
- package/runtime/claude/egregore_runtime/spirit_discovery.py +487 -0
- package/runtime/claude/egregore_runtime/spirit_discovery_cli.py +25 -0
- package/runtime/claude/egregore_runtime/spirit_discovery_transport.py +18 -0
- package/runtime/claude/egregore_runtime/status.py +101 -44
- package/runtime/claude/egregore_runtime/status_cli.py +13 -5
- package/runtime/claude/egregore_runtime/sync.py +39 -21
- package/runtime/claude/egregore_runtime/taste.py +934 -0
- package/runtime/claude/egregore_runtime/taste_cli.py +242 -0
- package/runtime/claude/egregore_runtime/taste_rules.py +91 -0
- package/runtime/claude/egregore_runtime/telemetry.py +26 -3
- package/runtime/claude/egregore_runtime/todo_cli.py +15 -12
- package/runtime/claude/egregore_runtime/todos.py +3 -2
- package/runtime/claude/egregore_runtime/upgrade.py +85 -58
- package/runtime/claude/egregore_runtime/writeback.py +330 -24
- package/runtime/claude/manifest.json +65 -5
- package/runtime/codex/.claude/context/handoff-claiming.md +32 -0
- package/runtime/codex/.claude/context/notification-consent.md +12 -9
- package/runtime/codex/.claude/skills/view/SKILL.md +438 -0
- package/runtime/codex/.codex/hooks.json +11 -0
- package/runtime/codex/.codex/skills/dark-mode/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/records/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/summon/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/view/assets/compose-scaffold.html +19 -13
- package/runtime/codex/.codex/spec-manifest.json +13 -19
- package/runtime/codex/AGENTS.md +54 -105
- package/runtime/codex/bin/agent.sh +231 -7
- package/runtime/codex/bin/api-call.sh +44 -23
- package/runtime/codex/bin/artifact-register.sh +7 -1
- package/runtime/codex/bin/artifact-writeback.sh +1 -1
- package/runtime/codex/bin/artifacts.sh +66 -265
- package/runtime/codex/bin/attendant.sh +4 -1
- package/runtime/codex/bin/boundary-guard.cjs +1848 -0
- package/runtime/codex/bin/boundary-hook.sh +41 -0
- package/runtime/codex/bin/capture-run.sh +32 -2
- package/runtime/codex/bin/codex-render-spec.mjs +24 -46
- package/runtime/codex/bin/codex-session-start.sh +19 -0
- package/runtime/codex/bin/codex-skill-render.mjs +18 -0
- package/runtime/codex/bin/config-get.sh +10 -1
- package/runtime/codex/bin/connect-refresh.sh +16 -0
- package/runtime/codex/bin/corpus_map.py +5 -0
- package/runtime/codex/bin/data/capabilities/codex.json +1458 -0
- package/runtime/codex/bin/genui.sh +19 -0
- package/runtime/codex/bin/graph-batch.sh +1 -1
- package/runtime/codex/bin/graph-op.sh +14 -0
- package/runtime/codex/bin/graph.sh +1 -1
- package/runtime/codex/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/codex/bin/handoff-run.sh +11 -6
- package/runtime/codex/bin/ingest-item.sh +1 -1
- package/runtime/codex/bin/ingest-surface.html +22 -4
- package/runtime/codex/bin/issue.sh +1 -1
- package/runtime/codex/bin/knowledge.sh +1 -1
- package/runtime/codex/bin/lib/card-delivery.sh +329 -0
- package/runtime/codex/bin/lib/config.sh +148 -3
- package/runtime/codex/bin/lib/context.sh +38 -67
- package/runtime/codex/bin/lib/genui-entry.py +36 -0
- package/runtime/codex/bin/lib/git-message.sh +25 -6
- package/runtime/codex/bin/lib/git-sync.sh +12 -4
- package/runtime/codex/bin/lib/greeting.sh +255 -28
- package/runtime/codex/bin/lib/handoff_meta.py +294 -0
- package/runtime/codex/bin/lib/health-footer.sh +83 -2
- package/runtime/codex/bin/lib/identity.sh +31 -10
- package/runtime/codex/bin/lib/instance-registry.sh +71 -0
- package/runtime/codex/bin/lib/local_ci_receipt.py +249 -0
- package/runtime/codex/bin/lib/metrics.sh +121 -8
- package/runtime/codex/bin/lib/notices.sh +36 -0
- package/runtime/codex/bin/lib/record_outcomes.py +729 -0
- package/runtime/codex/bin/lib/reflexes.py +267 -0
- package/runtime/codex/bin/lib/retrieval_scorecard.py +624 -0
- package/runtime/codex/bin/lib/session-record-prompt.txt +18 -0
- package/runtime/codex/bin/lib/session_record.py +3747 -0
- package/runtime/codex/bin/lib/session_trail.py +1983 -0
- package/runtime/codex/bin/lib/settings-drift.sh +8 -5
- package/runtime/codex/bin/lib/startup-restart.sh +41 -0
- package/runtime/codex/bin/lib/worktree-links.sh +92 -0
- package/runtime/codex/bin/memory-push.sh +6 -0
- package/runtime/codex/bin/merge-gate.sh +498 -0
- package/runtime/codex/bin/notify.sh +5 -2
- package/runtime/codex/bin/page-access.sh +103 -0
- package/runtime/codex/bin/person.sh +13 -2
- package/runtime/codex/bin/pi-render-spec.mjs +0 -1
- package/runtime/codex/bin/pi-spirit-discovery.mjs +34 -0
- package/runtime/codex/bin/post-update.sh +181 -0
- package/runtime/codex/bin/prime-render-spec.mjs +0 -1
- package/runtime/codex/bin/publish-artifact.sh +89 -42
- package/runtime/codex/bin/qa-inventory.sh +2 -2
- package/runtime/codex/bin/quest.sh +1 -1
- package/runtime/codex/bin/records.sh +11 -0
- package/runtime/codex/bin/reflexes/05-vitals.sh +9 -0
- package/runtime/codex/bin/reflexes/10-open-items.sh +12 -0
- package/runtime/codex/bin/reflexes/15-retrieval.sh +8 -0
- package/runtime/codex/bin/reflexes/20-nightly.sh +100 -0
- package/runtime/codex/bin/reflexes/20-sandbox.sh +85 -0
- package/runtime/codex/bin/reflexes/30-cost.py +116 -0
- package/runtime/codex/bin/reflexes/40-shells.py +51 -0
- package/runtime/codex/bin/reflexes/README.md +61 -0
- package/runtime/codex/bin/reflexes.sh +7 -0
- package/runtime/codex/bin/runtime-identity.sh +140 -42
- package/runtime/codex/bin/screenshot.sh +77 -0
- package/runtime/codex/bin/scroll.sh +1 -1
- package/runtime/codex/bin/search.sh +31 -5
- package/runtime/codex/bin/session-autosave.sh +6 -3
- package/runtime/codex/bin/session-id.sh +147 -0
- package/runtime/codex/bin/session-log.sh +19 -0
- package/runtime/codex/bin/session-record.sh +26 -0
- package/runtime/codex/bin/settings.sh +13 -8
- package/runtime/codex/bin/spirit-discovery-claude.sh +4 -0
- package/runtime/codex/bin/spirit-discovery-codex.sh +4 -0
- package/runtime/codex/bin/spirit-discovery-pi.sh +4 -0
- package/runtime/codex/bin/spirit-discovery-prime.sh +4 -0
- package/runtime/codex/bin/spirit-discovery.sh +4 -0
- package/runtime/codex/bin/startup-check.sh +1 -1
- package/runtime/codex/bin/telemetry.sh +11 -4
- package/runtime/codex/bin/test-baseline.sh +33 -5
- package/runtime/codex/bin/todo.sh +1 -1
- package/runtime/codex/bin/worktree-create.sh +15 -45
- package/runtime/codex/bin/worktree.sh +6 -0
- package/runtime/codex/docs/spirit-discovery.md +96 -0
- package/runtime/codex/egregore_runtime/__init__.py +18 -12
- package/runtime/codex/egregore_runtime/access.py +608 -0
- package/runtime/codex/egregore_runtime/access_cli.py +190 -0
- package/runtime/codex/egregore_runtime/adapters/invitation.py +5 -3
- package/runtime/codex/egregore_runtime/adapters/qmd.py +521 -47
- package/runtime/codex/egregore_runtime/admin_entry.py +56 -0
- package/runtime/codex/egregore_runtime/artifacts.py +222 -16
- package/runtime/codex/egregore_runtime/contracts.py +27 -0
- package/runtime/codex/egregore_runtime/eligibility.py +5 -12
- package/runtime/codex/egregore_runtime/episode_binding.py +16 -5
- package/runtime/codex/egregore_runtime/github_issues.py +2 -2
- package/runtime/codex/egregore_runtime/handoff_lookup.py +9 -20
- package/runtime/codex/egregore_runtime/harness_cli.py +674 -149
- package/runtime/codex/egregore_runtime/held.py +2436 -0
- package/runtime/codex/egregore_runtime/hosted_api.py +137 -0
- package/runtime/codex/egregore_runtime/identity.py +196 -65
- package/runtime/codex/egregore_runtime/identity_transfer.py +6 -2
- package/runtime/codex/egregore_runtime/ingest_cli.py +2 -2
- package/runtime/codex/egregore_runtime/ingest_index_cli.py +4 -16
- package/runtime/codex/egregore_runtime/installation.py +119 -0
- package/runtime/codex/egregore_runtime/investigation.py +64 -41
- package/runtime/codex/egregore_runtime/investigation_guidance.py +16 -11
- package/runtime/codex/egregore_runtime/issue_cli.py +51 -32
- package/runtime/codex/egregore_runtime/issue_reporting.py +4 -3
- package/runtime/codex/egregore_runtime/issue_triage_service.py +2 -2
- package/runtime/codex/egregore_runtime/issues.py +12 -6
- package/runtime/codex/egregore_runtime/knowledge.py +331 -5
- package/runtime/codex/egregore_runtime/knowledge_cli.py +57 -17
- package/runtime/codex/egregore_runtime/lifecycle.py +88 -22
- package/runtime/codex/egregore_runtime/lifecycle_cli.py +21 -13
- package/runtime/codex/egregore_runtime/line_ids.py +10 -0
- package/runtime/codex/egregore_runtime/memory_push.py +349 -0
- package/runtime/codex/egregore_runtime/observe.py +64 -25
- package/runtime/codex/egregore_runtime/observers.py +19 -0
- package/runtime/codex/egregore_runtime/optional_modules.py +13 -0
- package/runtime/codex/egregore_runtime/outputs_cli.py +321 -0
- package/runtime/codex/egregore_runtime/policy.py +651 -24
- package/runtime/codex/egregore_runtime/private_result.py +2 -1
- package/runtime/codex/egregore_runtime/projection.py +946 -0
- package/runtime/codex/egregore_runtime/quest_cli.py +20 -23
- package/runtime/codex/egregore_runtime/question_cli.py +15 -11
- package/runtime/codex/egregore_runtime/questions.py +10 -10
- package/runtime/codex/egregore_runtime/quests.py +10 -10
- package/runtime/codex/egregore_runtime/record_publish.py +548 -0
- package/runtime/codex/egregore_runtime/record_subject.py +564 -0
- package/runtime/codex/egregore_runtime/research_ingest.py +2 -4
- package/runtime/codex/egregore_runtime/runtime.py +169 -60
- package/runtime/codex/egregore_runtime/sandbox.py +194 -0
- package/runtime/codex/egregore_runtime/scroll_cli.py +22 -32
- package/runtime/codex/egregore_runtime/scrolls.py +23 -15
- package/runtime/codex/egregore_runtime/shadow.py +262 -0
- package/runtime/codex/egregore_runtime/spirit_discovery.py +487 -0
- package/runtime/codex/egregore_runtime/spirit_discovery_cli.py +25 -0
- package/runtime/codex/egregore_runtime/spirit_discovery_transport.py +18 -0
- package/runtime/codex/egregore_runtime/status.py +101 -44
- package/runtime/codex/egregore_runtime/status_cli.py +13 -5
- package/runtime/codex/egregore_runtime/sync.py +39 -21
- package/runtime/codex/egregore_runtime/taste.py +934 -0
- package/runtime/codex/egregore_runtime/taste_cli.py +242 -0
- package/runtime/codex/egregore_runtime/taste_rules.py +91 -0
- package/runtime/codex/egregore_runtime/telemetry.py +26 -3
- package/runtime/codex/egregore_runtime/todo_cli.py +15 -12
- package/runtime/codex/egregore_runtime/todos.py +3 -2
- package/runtime/codex/egregore_runtime/upgrade.py +85 -58
- package/runtime/codex/egregore_runtime/writeback.py +330 -24
- package/runtime/codex/manifest.json +66 -5
- package/runtime/pi/.claude/context/handoff-claiming.md +32 -0
- package/runtime/pi/.claude/context/notification-consent.md +12 -9
- package/runtime/pi/.claude/skills/view/SKILL.md +438 -0
- package/runtime/pi/.codex/hooks.json +11 -0
- package/runtime/pi/.codex/skills/dark-mode/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/records/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/summon/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/view/assets/compose-scaffold.html +19 -13
- package/runtime/pi/.codex/spec-manifest.json +13 -19
- package/runtime/pi/.pi/APPEND_SYSTEM.md +53 -99
- package/runtime/pi/.pi/extensions/egregore.ts +13 -1
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +54 -105
- package/runtime/pi/bin/agent.sh +231 -7
- package/runtime/pi/bin/api-call.sh +44 -23
- package/runtime/pi/bin/artifact-register.sh +7 -1
- package/runtime/pi/bin/artifact-writeback.sh +1 -1
- package/runtime/pi/bin/artifacts.sh +66 -265
- package/runtime/pi/bin/attendant.sh +4 -1
- package/runtime/pi/bin/boundary-guard.cjs +1848 -0
- package/runtime/pi/bin/boundary-hook.sh +41 -0
- package/runtime/pi/bin/capture-run.sh +32 -2
- package/runtime/pi/bin/codex-render-spec.mjs +24 -46
- package/runtime/pi/bin/codex-session-start.sh +19 -0
- package/runtime/pi/bin/codex-skill-render.mjs +18 -0
- package/runtime/pi/bin/config-get.sh +10 -1
- package/runtime/pi/bin/connect-refresh.sh +16 -0
- package/runtime/pi/bin/corpus_map.py +5 -0
- package/runtime/pi/bin/data/capabilities/pi.json +1458 -0
- package/runtime/pi/bin/genui.sh +19 -0
- package/runtime/pi/bin/graph-batch.sh +1 -1
- package/runtime/pi/bin/graph-op.sh +14 -0
- package/runtime/pi/bin/graph.sh +1 -1
- package/runtime/pi/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/pi/bin/handoff-run.sh +11 -6
- package/runtime/pi/bin/ingest-item.sh +1 -1
- package/runtime/pi/bin/ingest-surface.html +22 -4
- package/runtime/pi/bin/issue.sh +1 -1
- package/runtime/pi/bin/knowledge.sh +1 -1
- package/runtime/pi/bin/lib/card-delivery.sh +329 -0
- package/runtime/pi/bin/lib/config.sh +148 -3
- package/runtime/pi/bin/lib/context.sh +38 -67
- package/runtime/pi/bin/lib/genui-entry.py +36 -0
- package/runtime/pi/bin/lib/git-message.sh +25 -6
- package/runtime/pi/bin/lib/git-sync.sh +12 -4
- package/runtime/pi/bin/lib/greeting.sh +255 -28
- package/runtime/pi/bin/lib/handoff_meta.py +294 -0
- package/runtime/pi/bin/lib/health-footer.sh +83 -2
- package/runtime/pi/bin/lib/identity.sh +31 -10
- package/runtime/pi/bin/lib/instance-registry.sh +71 -0
- package/runtime/pi/bin/lib/local_ci_receipt.py +249 -0
- package/runtime/pi/bin/lib/metrics.sh +121 -8
- package/runtime/pi/bin/lib/notices.sh +36 -0
- package/runtime/pi/bin/lib/record_outcomes.py +729 -0
- package/runtime/pi/bin/lib/reflexes.py +267 -0
- package/runtime/pi/bin/lib/retrieval_scorecard.py +624 -0
- package/runtime/pi/bin/lib/session-record-prompt.txt +18 -0
- package/runtime/pi/bin/lib/session_record.py +3747 -0
- package/runtime/pi/bin/lib/session_trail.py +1983 -0
- package/runtime/pi/bin/lib/settings-drift.sh +8 -5
- package/runtime/pi/bin/lib/startup-restart.sh +41 -0
- package/runtime/pi/bin/lib/worktree-links.sh +92 -0
- package/runtime/pi/bin/memory-push.sh +6 -0
- package/runtime/pi/bin/merge-gate.sh +498 -0
- package/runtime/pi/bin/notify.sh +5 -2
- package/runtime/pi/bin/page-access.sh +103 -0
- package/runtime/pi/bin/person.sh +13 -2
- package/runtime/pi/bin/pi-branch-policy.mjs +37 -0
- package/runtime/pi/bin/pi-observe-context.mjs +97 -4
- package/runtime/pi/bin/pi-render-spec.mjs +0 -1
- package/runtime/pi/bin/pi-session-start.sh +4 -1
- package/runtime/pi/bin/pi-spirit-discovery.mjs +34 -0
- package/runtime/pi/bin/post-update.sh +181 -0
- package/runtime/pi/bin/prime-render-spec.mjs +0 -1
- package/runtime/pi/bin/publish-artifact.sh +89 -42
- package/runtime/pi/bin/qa-inventory.sh +2 -2
- package/runtime/pi/bin/quest.sh +1 -1
- package/runtime/pi/bin/records.sh +11 -0
- package/runtime/pi/bin/reflexes/05-vitals.sh +9 -0
- package/runtime/pi/bin/reflexes/10-open-items.sh +12 -0
- package/runtime/pi/bin/reflexes/15-retrieval.sh +8 -0
- package/runtime/pi/bin/reflexes/20-nightly.sh +100 -0
- package/runtime/pi/bin/reflexes/20-sandbox.sh +85 -0
- package/runtime/pi/bin/reflexes/30-cost.py +116 -0
- package/runtime/pi/bin/reflexes/40-shells.py +51 -0
- package/runtime/pi/bin/reflexes/README.md +61 -0
- package/runtime/pi/bin/reflexes.sh +7 -0
- package/runtime/pi/bin/runtime-identity.sh +140 -42
- package/runtime/pi/bin/screenshot.sh +77 -0
- package/runtime/pi/bin/scroll.sh +1 -1
- package/runtime/pi/bin/search.sh +31 -5
- package/runtime/pi/bin/session-autosave.sh +6 -3
- package/runtime/pi/bin/session-id.sh +147 -0
- package/runtime/pi/bin/session-log.sh +19 -0
- package/runtime/pi/bin/session-record.sh +26 -0
- package/runtime/pi/bin/settings.sh +13 -8
- package/runtime/pi/bin/spirit-discovery-claude.sh +4 -0
- package/runtime/pi/bin/spirit-discovery-codex.sh +4 -0
- package/runtime/pi/bin/spirit-discovery-pi.sh +4 -0
- package/runtime/pi/bin/spirit-discovery-prime.sh +4 -0
- package/runtime/pi/bin/spirit-discovery.sh +4 -0
- package/runtime/pi/bin/startup-check.sh +1 -1
- package/runtime/pi/bin/telemetry.sh +11 -4
- package/runtime/pi/bin/test-baseline.sh +33 -5
- package/runtime/pi/bin/todo.sh +1 -1
- package/runtime/pi/bin/worktree-create.sh +15 -45
- package/runtime/pi/bin/worktree.sh +6 -0
- package/runtime/pi/docs/spirit-discovery.md +96 -0
- package/runtime/pi/egregore_runtime/__init__.py +18 -12
- package/runtime/pi/egregore_runtime/access.py +608 -0
- package/runtime/pi/egregore_runtime/access_cli.py +190 -0
- package/runtime/pi/egregore_runtime/adapters/invitation.py +5 -3
- package/runtime/pi/egregore_runtime/adapters/qmd.py +521 -47
- package/runtime/pi/egregore_runtime/admin_entry.py +56 -0
- package/runtime/pi/egregore_runtime/artifacts.py +222 -16
- package/runtime/pi/egregore_runtime/contracts.py +27 -0
- package/runtime/pi/egregore_runtime/eligibility.py +5 -12
- package/runtime/pi/egregore_runtime/episode_binding.py +16 -5
- package/runtime/pi/egregore_runtime/github_issues.py +2 -2
- package/runtime/pi/egregore_runtime/handoff_lookup.py +9 -20
- package/runtime/pi/egregore_runtime/harness_cli.py +674 -149
- package/runtime/pi/egregore_runtime/held.py +2436 -0
- package/runtime/pi/egregore_runtime/hosted_api.py +137 -0
- package/runtime/pi/egregore_runtime/identity.py +196 -65
- package/runtime/pi/egregore_runtime/identity_transfer.py +6 -2
- package/runtime/pi/egregore_runtime/ingest_cli.py +2 -2
- package/runtime/pi/egregore_runtime/ingest_index_cli.py +4 -16
- package/runtime/pi/egregore_runtime/installation.py +119 -0
- package/runtime/pi/egregore_runtime/investigation.py +64 -41
- package/runtime/pi/egregore_runtime/investigation_guidance.py +16 -11
- package/runtime/pi/egregore_runtime/issue_cli.py +51 -32
- package/runtime/pi/egregore_runtime/issue_reporting.py +4 -3
- package/runtime/pi/egregore_runtime/issue_triage_service.py +2 -2
- package/runtime/pi/egregore_runtime/issues.py +12 -6
- package/runtime/pi/egregore_runtime/knowledge.py +331 -5
- package/runtime/pi/egregore_runtime/knowledge_cli.py +57 -17
- package/runtime/pi/egregore_runtime/lifecycle.py +88 -22
- package/runtime/pi/egregore_runtime/lifecycle_cli.py +21 -13
- package/runtime/pi/egregore_runtime/line_ids.py +10 -0
- package/runtime/pi/egregore_runtime/memory_push.py +349 -0
- package/runtime/pi/egregore_runtime/observe.py +64 -25
- package/runtime/pi/egregore_runtime/observers.py +19 -0
- package/runtime/pi/egregore_runtime/optional_modules.py +13 -0
- package/runtime/pi/egregore_runtime/outputs_cli.py +321 -0
- package/runtime/pi/egregore_runtime/policy.py +651 -24
- package/runtime/pi/egregore_runtime/private_result.py +2 -1
- package/runtime/pi/egregore_runtime/projection.py +946 -0
- package/runtime/pi/egregore_runtime/quest_cli.py +20 -23
- package/runtime/pi/egregore_runtime/question_cli.py +15 -11
- package/runtime/pi/egregore_runtime/questions.py +10 -10
- package/runtime/pi/egregore_runtime/quests.py +10 -10
- package/runtime/pi/egregore_runtime/record_publish.py +548 -0
- package/runtime/pi/egregore_runtime/record_subject.py +564 -0
- package/runtime/pi/egregore_runtime/research_ingest.py +2 -4
- package/runtime/pi/egregore_runtime/runtime.py +169 -60
- package/runtime/pi/egregore_runtime/sandbox.py +194 -0
- package/runtime/pi/egregore_runtime/scroll_cli.py +22 -32
- package/runtime/pi/egregore_runtime/scrolls.py +23 -15
- package/runtime/pi/egregore_runtime/shadow.py +262 -0
- package/runtime/pi/egregore_runtime/spirit_discovery.py +487 -0
- package/runtime/pi/egregore_runtime/spirit_discovery_cli.py +25 -0
- package/runtime/pi/egregore_runtime/spirit_discovery_transport.py +18 -0
- package/runtime/pi/egregore_runtime/status.py +101 -44
- package/runtime/pi/egregore_runtime/status_cli.py +13 -5
- package/runtime/pi/egregore_runtime/sync.py +39 -21
- package/runtime/pi/egregore_runtime/taste.py +934 -0
- package/runtime/pi/egregore_runtime/taste_cli.py +242 -0
- package/runtime/pi/egregore_runtime/taste_rules.py +91 -0
- package/runtime/pi/egregore_runtime/telemetry.py +26 -3
- package/runtime/pi/egregore_runtime/todo_cli.py +15 -12
- package/runtime/pi/egregore_runtime/todos.py +3 -2
- package/runtime/pi/egregore_runtime/upgrade.py +85 -58
- package/runtime/pi/egregore_runtime/writeback.py +330 -24
- package/runtime/pi/manifest.json +66 -5
- package/runtime/prime/.claude/context/handoff-claiming.md +32 -0
- package/runtime/prime/.claude/context/notification-consent.md +12 -9
- package/runtime/prime/.claude/skills/view/SKILL.md +438 -0
- package/runtime/prime/.codex/hooks.json +11 -0
- package/runtime/prime/.codex/skills/dark-mode/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/records/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/summon/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/view/assets/compose-scaffold.html +19 -13
- package/runtime/prime/.codex/spec-manifest.json +13 -19
- package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +53 -99
- package/runtime/prime/.prime/agent/extensions/egregore.ts +14 -1
- package/runtime/prime/.prime/agent/skills/egregore/SKILL.md +14 -6
- package/runtime/prime/.prime/agent/skills/egregore/src/egregore/__init__.py +66 -1
- package/runtime/prime/.prime/agent/spec-manifest.json +2 -2
- package/runtime/prime/AGENTS.md +54 -105
- package/runtime/prime/bin/agent.sh +231 -7
- package/runtime/prime/bin/api-call.sh +44 -23
- package/runtime/prime/bin/artifact-register.sh +7 -1
- package/runtime/prime/bin/artifact-writeback.sh +1 -1
- package/runtime/prime/bin/artifacts.sh +66 -265
- package/runtime/prime/bin/attendant.sh +4 -1
- package/runtime/prime/bin/boundary-guard.cjs +1848 -0
- package/runtime/prime/bin/boundary-hook.sh +41 -0
- package/runtime/prime/bin/capture-run.sh +32 -2
- package/runtime/prime/bin/codex-render-spec.mjs +24 -46
- package/runtime/prime/bin/codex-session-start.sh +19 -0
- package/runtime/prime/bin/codex-skill-render.mjs +18 -0
- package/runtime/prime/bin/config-get.sh +10 -1
- package/runtime/prime/bin/connect-refresh.sh +16 -0
- package/runtime/prime/bin/corpus_map.py +5 -0
- package/runtime/prime/bin/data/capabilities/prime.json +1458 -0
- package/runtime/prime/bin/genui.sh +19 -0
- package/runtime/prime/bin/graph-batch.sh +1 -1
- package/runtime/prime/bin/graph-op.sh +14 -0
- package/runtime/prime/bin/graph.sh +1 -1
- package/runtime/prime/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/prime/bin/handoff-run.sh +11 -6
- package/runtime/prime/bin/ingest-item.sh +1 -1
- package/runtime/prime/bin/ingest-surface.html +22 -4
- package/runtime/prime/bin/issue.sh +1 -1
- package/runtime/prime/bin/knowledge.sh +1 -1
- package/runtime/prime/bin/lib/card-delivery.sh +329 -0
- package/runtime/prime/bin/lib/config.sh +148 -3
- package/runtime/prime/bin/lib/context.sh +38 -67
- package/runtime/prime/bin/lib/genui-entry.py +36 -0
- package/runtime/prime/bin/lib/git-message.sh +25 -6
- package/runtime/prime/bin/lib/git-sync.sh +12 -4
- package/runtime/prime/bin/lib/greeting.sh +255 -28
- package/runtime/prime/bin/lib/handoff_meta.py +294 -0
- package/runtime/prime/bin/lib/health-footer.sh +83 -2
- package/runtime/prime/bin/lib/identity.sh +31 -10
- package/runtime/prime/bin/lib/instance-registry.sh +71 -0
- package/runtime/prime/bin/lib/local_ci_receipt.py +249 -0
- package/runtime/prime/bin/lib/metrics.sh +121 -8
- package/runtime/prime/bin/lib/notices.sh +36 -0
- package/runtime/prime/bin/lib/record_outcomes.py +729 -0
- package/runtime/prime/bin/lib/reflexes.py +267 -0
- package/runtime/prime/bin/lib/retrieval_scorecard.py +624 -0
- package/runtime/prime/bin/lib/session-record-prompt.txt +18 -0
- package/runtime/prime/bin/lib/session_record.py +3747 -0
- package/runtime/prime/bin/lib/session_trail.py +1983 -0
- package/runtime/prime/bin/lib/settings-drift.sh +8 -5
- package/runtime/prime/bin/lib/startup-restart.sh +41 -0
- package/runtime/prime/bin/lib/worktree-links.sh +92 -0
- package/runtime/prime/bin/memory-push.sh +6 -0
- package/runtime/prime/bin/merge-gate.sh +498 -0
- package/runtime/prime/bin/notify.sh +5 -2
- package/runtime/prime/bin/page-access.sh +103 -0
- package/runtime/prime/bin/person.sh +13 -2
- package/runtime/prime/bin/pi-branch-policy.mjs +37 -0
- package/runtime/prime/bin/pi-observe-context.mjs +97 -4
- package/runtime/prime/bin/pi-render-spec.mjs +0 -1
- package/runtime/prime/bin/pi-spirit-discovery.mjs +34 -0
- package/runtime/prime/bin/post-update.sh +181 -0
- package/runtime/prime/bin/prime-render-spec.mjs +0 -1
- package/runtime/prime/bin/prime-session-start.sh +3 -1
- package/runtime/prime/bin/publish-artifact.sh +89 -42
- package/runtime/prime/bin/qa-inventory.sh +2 -2
- package/runtime/prime/bin/quest.sh +1 -1
- package/runtime/prime/bin/records.sh +11 -0
- package/runtime/prime/bin/reflexes/05-vitals.sh +9 -0
- package/runtime/prime/bin/reflexes/10-open-items.sh +12 -0
- package/runtime/prime/bin/reflexes/15-retrieval.sh +8 -0
- package/runtime/prime/bin/reflexes/20-nightly.sh +100 -0
- package/runtime/prime/bin/reflexes/20-sandbox.sh +85 -0
- package/runtime/prime/bin/reflexes/30-cost.py +116 -0
- package/runtime/prime/bin/reflexes/40-shells.py +51 -0
- package/runtime/prime/bin/reflexes/README.md +61 -0
- package/runtime/prime/bin/reflexes.sh +7 -0
- package/runtime/prime/bin/runtime-identity.sh +140 -42
- package/runtime/prime/bin/screenshot.sh +77 -0
- package/runtime/prime/bin/scroll.sh +1 -1
- package/runtime/prime/bin/search.sh +31 -5
- package/runtime/prime/bin/session-autosave.sh +6 -3
- package/runtime/prime/bin/session-id.sh +147 -0
- package/runtime/prime/bin/session-log.sh +19 -0
- package/runtime/prime/bin/session-record.sh +26 -0
- package/runtime/prime/bin/settings.sh +13 -8
- package/runtime/prime/bin/spirit-discovery-claude.sh +4 -0
- package/runtime/prime/bin/spirit-discovery-codex.sh +4 -0
- package/runtime/prime/bin/spirit-discovery-pi.sh +4 -0
- package/runtime/prime/bin/spirit-discovery-prime.sh +4 -0
- package/runtime/prime/bin/spirit-discovery.sh +4 -0
- package/runtime/prime/bin/startup-check.sh +1 -1
- package/runtime/prime/bin/telemetry.sh +11 -4
- package/runtime/prime/bin/test-baseline.sh +33 -5
- package/runtime/prime/bin/todo.sh +1 -1
- package/runtime/prime/bin/worktree-create.sh +15 -45
- package/runtime/prime/bin/worktree.sh +6 -0
- package/runtime/prime/docs/spirit-discovery.md +96 -0
- package/runtime/prime/egregore_runtime/__init__.py +18 -12
- package/runtime/prime/egregore_runtime/access.py +608 -0
- package/runtime/prime/egregore_runtime/access_cli.py +190 -0
- package/runtime/prime/egregore_runtime/adapters/invitation.py +5 -3
- package/runtime/prime/egregore_runtime/adapters/qmd.py +521 -47
- package/runtime/prime/egregore_runtime/admin_entry.py +56 -0
- package/runtime/prime/egregore_runtime/artifacts.py +222 -16
- package/runtime/prime/egregore_runtime/contracts.py +27 -0
- package/runtime/prime/egregore_runtime/eligibility.py +5 -12
- package/runtime/prime/egregore_runtime/episode_binding.py +16 -5
- package/runtime/prime/egregore_runtime/github_issues.py +2 -2
- package/runtime/prime/egregore_runtime/handoff_lookup.py +9 -20
- package/runtime/prime/egregore_runtime/harness_cli.py +674 -149
- package/runtime/prime/egregore_runtime/held.py +2436 -0
- package/runtime/prime/egregore_runtime/hosted_api.py +137 -0
- package/runtime/prime/egregore_runtime/identity.py +196 -65
- package/runtime/prime/egregore_runtime/identity_transfer.py +6 -2
- package/runtime/prime/egregore_runtime/ingest_cli.py +2 -2
- package/runtime/prime/egregore_runtime/ingest_index_cli.py +4 -16
- package/runtime/prime/egregore_runtime/installation.py +119 -0
- package/runtime/prime/egregore_runtime/investigation.py +64 -41
- package/runtime/prime/egregore_runtime/investigation_guidance.py +16 -11
- package/runtime/prime/egregore_runtime/issue_cli.py +51 -32
- package/runtime/prime/egregore_runtime/issue_reporting.py +4 -3
- package/runtime/prime/egregore_runtime/issue_triage_service.py +2 -2
- package/runtime/prime/egregore_runtime/issues.py +12 -6
- package/runtime/prime/egregore_runtime/knowledge.py +331 -5
- package/runtime/prime/egregore_runtime/knowledge_cli.py +57 -17
- package/runtime/prime/egregore_runtime/lifecycle.py +88 -22
- package/runtime/prime/egregore_runtime/lifecycle_cli.py +21 -13
- package/runtime/prime/egregore_runtime/line_ids.py +10 -0
- package/runtime/prime/egregore_runtime/memory_push.py +349 -0
- package/runtime/prime/egregore_runtime/observe.py +64 -25
- package/runtime/prime/egregore_runtime/observers.py +19 -0
- package/runtime/prime/egregore_runtime/optional_modules.py +13 -0
- package/runtime/prime/egregore_runtime/outputs_cli.py +321 -0
- package/runtime/prime/egregore_runtime/policy.py +651 -24
- package/runtime/prime/egregore_runtime/private_result.py +2 -1
- package/runtime/prime/egregore_runtime/projection.py +946 -0
- package/runtime/prime/egregore_runtime/quest_cli.py +20 -23
- package/runtime/prime/egregore_runtime/question_cli.py +15 -11
- package/runtime/prime/egregore_runtime/questions.py +10 -10
- package/runtime/prime/egregore_runtime/quests.py +10 -10
- package/runtime/prime/egregore_runtime/record_publish.py +548 -0
- package/runtime/prime/egregore_runtime/record_subject.py +564 -0
- package/runtime/prime/egregore_runtime/research_ingest.py +2 -4
- package/runtime/prime/egregore_runtime/runtime.py +169 -60
- package/runtime/prime/egregore_runtime/sandbox.py +194 -0
- package/runtime/prime/egregore_runtime/scroll_cli.py +22 -32
- package/runtime/prime/egregore_runtime/scrolls.py +23 -15
- package/runtime/prime/egregore_runtime/shadow.py +262 -0
- package/runtime/prime/egregore_runtime/spirit_discovery.py +487 -0
- package/runtime/prime/egregore_runtime/spirit_discovery_cli.py +25 -0
- package/runtime/prime/egregore_runtime/spirit_discovery_transport.py +18 -0
- package/runtime/prime/egregore_runtime/status.py +101 -44
- package/runtime/prime/egregore_runtime/status_cli.py +13 -5
- package/runtime/prime/egregore_runtime/sync.py +39 -21
- package/runtime/prime/egregore_runtime/taste.py +934 -0
- package/runtime/prime/egregore_runtime/taste_cli.py +242 -0
- package/runtime/prime/egregore_runtime/taste_rules.py +91 -0
- package/runtime/prime/egregore_runtime/telemetry.py +26 -3
- package/runtime/prime/egregore_runtime/todo_cli.py +15 -12
- package/runtime/prime/egregore_runtime/todos.py +3 -2
- package/runtime/prime/egregore_runtime/upgrade.py +85 -58
- package/runtime/prime/egregore_runtime/writeback.py +330 -24
- package/runtime/prime/manifest.json +66 -5
- package/runtime/claude/bin/thread.sh +0 -9
- package/runtime/claude/egregore_runtime/admin_gate.py +0 -102
- package/runtime/claude/egregore_runtime/thread_cli.py +0 -173
- package/runtime/claude/egregore_runtime/threads.py +0 -521
- package/runtime/codex/bin/thread.sh +0 -9
- package/runtime/codex/egregore_runtime/admin_gate.py +0 -102
- package/runtime/codex/egregore_runtime/thread_cli.py +0 -173
- package/runtime/codex/egregore_runtime/threads.py +0 -521
- package/runtime/pi/bin/thread.sh +0 -9
- package/runtime/pi/egregore_runtime/admin_gate.py +0 -102
- package/runtime/pi/egregore_runtime/thread_cli.py +0 -173
- package/runtime/pi/egregore_runtime/threads.py +0 -521
- package/runtime/prime/bin/thread.sh +0 -9
- package/runtime/prime/egregore_runtime/admin_gate.py +0 -102
- package/runtime/prime/egregore_runtime/thread_cli.py +0 -173
- package/runtime/prime/egregore_runtime/threads.py +0 -521
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
"""Read-only facts about a host-selected outgoing canonical Git history.
|
|
2
|
+
|
|
3
|
+
This module neither authorizes publication nor proves remote freshness. It reads
|
|
4
|
+
the complete selected ancestry, newly reachable objects and changed tree paths.
|
|
5
|
+
Manual Git and transports which do not call it remain outside this prerequisite.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
from contextvars import ContextVar
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
import re
|
|
17
|
+
import selectors
|
|
18
|
+
import signal
|
|
19
|
+
import stat
|
|
20
|
+
import subprocess
|
|
21
|
+
import time
|
|
22
|
+
import unicodedata
|
|
23
|
+
|
|
24
|
+
from .record_subject import classify_record
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class HistoryLimits:
|
|
29
|
+
commits: int = 256
|
|
30
|
+
objects: int = 4096
|
|
31
|
+
object_bytes: int = 256 * 1024
|
|
32
|
+
total_bytes: int = 16 * 1024 * 1024
|
|
33
|
+
tree_entries: int = 8192
|
|
34
|
+
seconds: float = 20
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
DEFAULT_LIMITS = HistoryLimits()
|
|
38
|
+
OID = re.compile(r"(?:[a-f0-9]{40}|[a-f0-9]{64})\Z")
|
|
39
|
+
_CONTAINED_GIT = ContextVar("egregore_host_contained_git", default=False)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@contextmanager
|
|
43
|
+
def contained_git_children():
|
|
44
|
+
"""Fixed detached host owns the outer process group, including Git helpers.
|
|
45
|
+
|
|
46
|
+
Private native host workers opt in directly; no environment or public CLI
|
|
47
|
+
input selects this. Generic managed/history callers keep detached children.
|
|
48
|
+
The host must terminate its whole outer group even after normal completion.
|
|
49
|
+
"""
|
|
50
|
+
if os.getpgrp() != os.getpid():
|
|
51
|
+
raise ValueError("contained Git requires a detached process-group owner")
|
|
52
|
+
token = _CONTAINED_GIT.set(True)
|
|
53
|
+
try:
|
|
54
|
+
yield
|
|
55
|
+
finally:
|
|
56
|
+
_CONTAINED_GIT.reset(token)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class _Unknown(Exception):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _digest(value):
|
|
64
|
+
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _ref(value, *, head=False):
|
|
68
|
+
if head and value == "HEAD":
|
|
69
|
+
return True
|
|
70
|
+
return (isinstance(value, str) and len(value) <= 240
|
|
71
|
+
and value.startswith(("refs/heads/", "refs/remotes/"))
|
|
72
|
+
and not re.search(r"[\x00-\x20\x7f~^:?*\[\\]", value)
|
|
73
|
+
and ".." not in value and "@{" not in value
|
|
74
|
+
and all(part and not part.startswith(".") and not part.endswith((".", ".lock")) for part in value.split("/")))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _regular(path, limit=65536):
|
|
78
|
+
info = path.lstat()
|
|
79
|
+
if not stat.S_ISREG(info.st_mode) or info.st_size > limit:
|
|
80
|
+
raise _Unknown("unsupported_repository")
|
|
81
|
+
with path.open("rb") as handle:
|
|
82
|
+
value = handle.read(limit + 1)
|
|
83
|
+
if len(value) > limit:
|
|
84
|
+
raise _Unknown("unsupported_repository")
|
|
85
|
+
return value
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _repository(repository, deadline):
|
|
89
|
+
def remaining():
|
|
90
|
+
if time.monotonic() >= deadline:
|
|
91
|
+
raise _Unknown("time_limit")
|
|
92
|
+
remaining()
|
|
93
|
+
root = Path(repository)
|
|
94
|
+
if not root.is_absolute() or root != root.resolve() or not root.is_dir() or any(ord(c) < 32 for c in str(root)):
|
|
95
|
+
raise _Unknown("unsupported_repository")
|
|
96
|
+
dot = root / ".git"
|
|
97
|
+
git_dir = dot if dot.is_dir() else root
|
|
98
|
+
if dot.exists() and (dot.is_symlink() or not dot.is_dir()):
|
|
99
|
+
raise _Unknown("unsupported_repository") # Linked worktrees need a separate host binding.
|
|
100
|
+
config = _regular(git_dir / "config")
|
|
101
|
+
_regular(git_dir / "HEAD")
|
|
102
|
+
if (git_dir / "commondir").exists() or re.search(rb"(?im)^\s*\[\s*include(?:if)?\b|\b(?:partialclone|promisor)\s*=", config):
|
|
103
|
+
raise _Unknown("unsupported_repository")
|
|
104
|
+
for name in ("shallow", "info/grafts", "objects/info/alternates", "objects/info/http-alternates"):
|
|
105
|
+
target = git_dir / name
|
|
106
|
+
for parent in target.parents:
|
|
107
|
+
if parent == git_dir:
|
|
108
|
+
break
|
|
109
|
+
if parent.is_symlink():
|
|
110
|
+
raise _Unknown("unsupported_repository")
|
|
111
|
+
if target.exists() or target.is_symlink():
|
|
112
|
+
if _regular(target).strip():
|
|
113
|
+
raise _Unknown("incomplete_repository")
|
|
114
|
+
objects = git_dir / "objects"
|
|
115
|
+
if not objects.is_dir() or objects.is_symlink():
|
|
116
|
+
raise _Unknown("unsupported_repository")
|
|
117
|
+
# Git may consult pack indexes and commit graphs without naming each file
|
|
118
|
+
# in its output. Do not allow those metadata paths to lead to another repo.
|
|
119
|
+
metadata_entries = 0
|
|
120
|
+
def metadata_tree(directory, depth=0):
|
|
121
|
+
nonlocal metadata_entries
|
|
122
|
+
with os.scandir(directory) as stream:
|
|
123
|
+
for entry in stream:
|
|
124
|
+
remaining()
|
|
125
|
+
metadata_entries += 1
|
|
126
|
+
if metadata_entries > 512 or entry.is_symlink():
|
|
127
|
+
raise _Unknown("unsupported_repository")
|
|
128
|
+
if entry.is_dir(follow_symlinks=False):
|
|
129
|
+
if depth >= 2:
|
|
130
|
+
raise _Unknown("unsupported_repository")
|
|
131
|
+
metadata_tree(Path(entry.path), depth + 1)
|
|
132
|
+
elif not entry.is_file(follow_symlinks=False):
|
|
133
|
+
raise _Unknown("unsupported_repository")
|
|
134
|
+
with os.scandir(objects) as stream:
|
|
135
|
+
for index, entry in enumerate(stream):
|
|
136
|
+
remaining()
|
|
137
|
+
if index >= 258 or entry.is_symlink() or not entry.is_dir(follow_symlinks=False):
|
|
138
|
+
raise _Unknown("unsupported_repository")
|
|
139
|
+
if entry.name in {"info", "pack"}:
|
|
140
|
+
metadata_tree(Path(entry.path))
|
|
141
|
+
elif not re.fullmatch(r"[a-f0-9]{2}", entry.name):
|
|
142
|
+
raise _Unknown("unsupported_repository")
|
|
143
|
+
return root, git_dir
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class _Git:
|
|
147
|
+
def __init__(self, root, deadline):
|
|
148
|
+
self.root, self.deadline = root, deadline
|
|
149
|
+
self.env = {key: value for key, value in os.environ.items() if not key.startswith("GIT_")}
|
|
150
|
+
self.env.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL=os.devnull, GIT_TERMINAL_PROMPT="0",
|
|
151
|
+
GIT_OPTIONAL_LOCKS="0", GIT_NO_LAZY_FETCH="1", LC_ALL="C")
|
|
152
|
+
|
|
153
|
+
def start(self, args, *, stdin=False):
|
|
154
|
+
self.remaining()
|
|
155
|
+
contained = _CONTAINED_GIT.get()
|
|
156
|
+
process = subprocess.Popen(["git", "--no-replace-objects", "-c", "protocol.allow=never",
|
|
157
|
+
"-c", "core.fsmonitor=false", "-c", "core.hooksPath=" + os.devnull,
|
|
158
|
+
"-C", str(self.root), *args],
|
|
159
|
+
stdin=subprocess.PIPE if stdin else subprocess.DEVNULL,
|
|
160
|
+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
|
161
|
+
env=self.env, start_new_session=not contained, bufsize=0)
|
|
162
|
+
process._egregore_contained = contained
|
|
163
|
+
return process
|
|
164
|
+
|
|
165
|
+
def remaining(self):
|
|
166
|
+
remaining = self.deadline - time.monotonic()
|
|
167
|
+
if remaining <= 0:
|
|
168
|
+
raise _Unknown("time_limit")
|
|
169
|
+
return remaining
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def close(process):
|
|
173
|
+
if process.poll() is None:
|
|
174
|
+
try:
|
|
175
|
+
if getattr(process, "_egregore_contained", False):
|
|
176
|
+
process.kill()
|
|
177
|
+
else:
|
|
178
|
+
os.killpg(process.pid, signal.SIGKILL)
|
|
179
|
+
except ProcessLookupError:
|
|
180
|
+
pass
|
|
181
|
+
except PermissionError:
|
|
182
|
+
# Some hosts prohibit process-group signals while permitting
|
|
183
|
+
# termination of this exact child. These plumbing commands do
|
|
184
|
+
# not launch filters, hooks, pagers or transport helpers.
|
|
185
|
+
process.kill()
|
|
186
|
+
process.wait()
|
|
187
|
+
if process.stdin:
|
|
188
|
+
process.stdin.close()
|
|
189
|
+
process.stdout.close()
|
|
190
|
+
|
|
191
|
+
def read(self, process, count):
|
|
192
|
+
with selectors.DefaultSelector() as selector:
|
|
193
|
+
selector.register(process.stdout, selectors.EVENT_READ)
|
|
194
|
+
if not selector.select(self.remaining()):
|
|
195
|
+
raise _Unknown("time_limit")
|
|
196
|
+
return os.read(process.stdout.fileno(), count)
|
|
197
|
+
|
|
198
|
+
def run(self, *args, limit=262144, allowed=(0,)):
|
|
199
|
+
process = self.start(args)
|
|
200
|
+
output = bytearray()
|
|
201
|
+
try:
|
|
202
|
+
while True:
|
|
203
|
+
chunk = self.read(process, min(65536, limit + 1 - len(output)))
|
|
204
|
+
if not chunk:
|
|
205
|
+
break
|
|
206
|
+
output.extend(chunk)
|
|
207
|
+
if len(output) > limit:
|
|
208
|
+
raise _Unknown("inventory_limit")
|
|
209
|
+
code = process.wait(timeout=self.remaining())
|
|
210
|
+
if code not in allowed:
|
|
211
|
+
raise _Unknown("git_evidence_unavailable")
|
|
212
|
+
return bytes(output), code
|
|
213
|
+
finally:
|
|
214
|
+
self.close(process)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class _Objects:
|
|
218
|
+
"""One size-aware cat-file protocol process; never buffer an unlimited body."""
|
|
219
|
+
def __init__(self, git, git_dir, limits, counts):
|
|
220
|
+
self.git, self.git_dir, self.limits, self.counts = git, git_dir, limits, counts
|
|
221
|
+
self.process = git.start(["cat-file", "--batch"], stdin=True)
|
|
222
|
+
self.buffer, self.cache = bytearray(), {}
|
|
223
|
+
|
|
224
|
+
def exact(self, size):
|
|
225
|
+
while len(self.buffer) < size:
|
|
226
|
+
chunk = self.git.read(self.process, min(65536, size - len(self.buffer)))
|
|
227
|
+
if not chunk:
|
|
228
|
+
raise _Unknown("object_unavailable")
|
|
229
|
+
self.buffer.extend(chunk)
|
|
230
|
+
value = bytes(self.buffer[:size]); del self.buffer[:size]
|
|
231
|
+
return value
|
|
232
|
+
|
|
233
|
+
def get(self, oid):
|
|
234
|
+
self.git.remaining()
|
|
235
|
+
if oid in self.cache:
|
|
236
|
+
return self.cache[oid]
|
|
237
|
+
if len(self.cache) >= self.limits.objects:
|
|
238
|
+
raise _Unknown("object_limit")
|
|
239
|
+
loose = self.git_dir / "objects" / oid[:2] / oid[2:]
|
|
240
|
+
if loose.is_symlink():
|
|
241
|
+
raise _Unknown("unsupported_repository")
|
|
242
|
+
self.process.stdin.write(oid.encode() + b"\n")
|
|
243
|
+
header = bytearray()
|
|
244
|
+
while not header.endswith(b"\n"):
|
|
245
|
+
if len(header) >= 128:
|
|
246
|
+
raise _Unknown("object_unavailable")
|
|
247
|
+
header.extend(self.exact(1))
|
|
248
|
+
fields = header.split()
|
|
249
|
+
if len(fields) != 3 or fields[0].decode("ascii") != oid or fields[1] not in (b"commit", b"tree", b"blob"):
|
|
250
|
+
raise _Unknown("object_unavailable")
|
|
251
|
+
size = int(fields[2])
|
|
252
|
+
if size < 0 or size > self.limits.object_bytes:
|
|
253
|
+
raise _Unknown("object_byte_limit")
|
|
254
|
+
if self.counts["bytes"] + size > self.limits.total_bytes:
|
|
255
|
+
raise _Unknown("total_byte_limit")
|
|
256
|
+
value = self.exact(size)
|
|
257
|
+
if self.exact(1) != b"\n":
|
|
258
|
+
raise _Unknown("object_unavailable")
|
|
259
|
+
algorithm = hashlib.sha1 if len(oid) == 40 else hashlib.sha256
|
|
260
|
+
if algorithm(fields[1] + b" " + str(size).encode() + b"\0" + value).hexdigest() != oid:
|
|
261
|
+
raise _Unknown("object_corrupt")
|
|
262
|
+
self.counts["objects"] += 1; self.counts["bytes"] += size
|
|
263
|
+
result = fields[1].decode(), value
|
|
264
|
+
self.cache[oid] = result
|
|
265
|
+
return result
|
|
266
|
+
|
|
267
|
+
def close(self):
|
|
268
|
+
self.git.close(self.process)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _tree(value, oid_bytes):
|
|
272
|
+
offset, names, entries = 0, set(), {}
|
|
273
|
+
while offset < len(value):
|
|
274
|
+
end = value.find(b"\0", offset)
|
|
275
|
+
if end < 0 or end + 1 + oid_bytes > len(value):
|
|
276
|
+
raise _Unknown("malformed_tree")
|
|
277
|
+
prefix = value[offset:end]
|
|
278
|
+
mode, sep, raw_name = prefix.partition(b" ")
|
|
279
|
+
if not sep or mode not in (b"40000", b"100644", b"100755"):
|
|
280
|
+
raise _Unknown("unsupported_tree_entry")
|
|
281
|
+
name = raw_name.decode("utf-8")
|
|
282
|
+
folded = unicodedata.normalize("NFC", name).casefold()
|
|
283
|
+
if (not name or name in (".", "..") or folded == ".git" or "/" in name or "\\" in name
|
|
284
|
+
or len(raw_name) > 255 or any(ord(c) < 32 or ord(c) == 127 for c in name)
|
|
285
|
+
or name != unicodedata.normalize("NFC", name) or folded in names):
|
|
286
|
+
raise _Unknown("ambiguous_tree_path")
|
|
287
|
+
entries[name] = (mode.decode(), value[end + 1:end + 1 + oid_bytes].hex())
|
|
288
|
+
names.add(folded); offset = end + 1 + oid_bytes
|
|
289
|
+
return entries
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _commit(value, oid_length):
|
|
293
|
+
header, sep, _ = value.partition(b"\n\n")
|
|
294
|
+
if not sep or b"\0" in header:
|
|
295
|
+
raise _Unknown("malformed_commit")
|
|
296
|
+
lines = header.splitlines()
|
|
297
|
+
trees = [line[5:] for line in lines if line.startswith(b"tree ")]
|
|
298
|
+
parents = [line[7:] for line in lines if line.startswith(b"parent ")]
|
|
299
|
+
if len(trees) != 1 or not lines or lines[0] != b"tree " + trees[0]:
|
|
300
|
+
raise _Unknown("malformed_commit")
|
|
301
|
+
refs = [oid.decode("ascii") for oid in trees + parents]
|
|
302
|
+
if any(not OID.fullmatch(oid) or len(oid) != oid_length for oid in refs):
|
|
303
|
+
raise _Unknown("malformed_commit")
|
|
304
|
+
return refs[0], refs[1:]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _subject(value):
|
|
308
|
+
# Native canonical records start with frontmatter. Arbitrary prose or body
|
|
309
|
+
# code examples are not a native record marker and are never promoted here.
|
|
310
|
+
if not value.startswith((b"---", b"\xef\xbb\xbf---")):
|
|
311
|
+
return None
|
|
312
|
+
text = value.decode("utf-8").replace("\r\n", "\n").replace("\r", "\n").removeprefix("\ufeff")
|
|
313
|
+
lines = text.splitlines()
|
|
314
|
+
if not lines or lines[0].strip() != "---":
|
|
315
|
+
return None
|
|
316
|
+
fields, closed, malformed, marked, block_indent = {}, False, False, False, None
|
|
317
|
+
container = False
|
|
318
|
+
for line in lines[1:]:
|
|
319
|
+
if line == "---":
|
|
320
|
+
closed = True; break
|
|
321
|
+
indent = len(line) - len(line.lstrip(" "))
|
|
322
|
+
if block_indent is not None:
|
|
323
|
+
parent_indent, content_indent = block_indent
|
|
324
|
+
if not line.strip():
|
|
325
|
+
continue
|
|
326
|
+
if content_indent is None and indent > parent_indent:
|
|
327
|
+
content_indent = indent
|
|
328
|
+
block_indent = (parent_indent, content_indent)
|
|
329
|
+
if content_indent is not None and indent >= content_indent:
|
|
330
|
+
continue # An explicit YAML block scalar is prose, not fields.
|
|
331
|
+
block_indent = None
|
|
332
|
+
if not line.strip() or line.lstrip().startswith("#"):
|
|
333
|
+
continue
|
|
334
|
+
key, sep, raw = line.partition(":")
|
|
335
|
+
if sep and classify_record({key: raw.strip().strip("\"'")}) != "human-session":
|
|
336
|
+
marked = True
|
|
337
|
+
if indent or line.startswith("-"):
|
|
338
|
+
# Common YAML lists and nested values are valid human frontmatter.
|
|
339
|
+
# A marker with malformed indentation still remains native-marked.
|
|
340
|
+
if not container or "\t" in line[:indent + 1]:
|
|
341
|
+
malformed = True
|
|
342
|
+
continue
|
|
343
|
+
if not sep or key in fields or not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_-]*|\"[^\"\n]+\"|'[^'\n]+'", key):
|
|
344
|
+
malformed = True
|
|
345
|
+
if sep:
|
|
346
|
+
scalar_text = raw.strip()
|
|
347
|
+
container = not scalar_text or scalar_text.startswith(("[", "{"))
|
|
348
|
+
if re.fullmatch(r"[|>][1-9]?[+-]?(?:\s+#.*)?|[|>][+-][1-9](?:\s+#.*)?", scalar_text):
|
|
349
|
+
explicit = re.search(r"[1-9]", scalar_text.split("#", 1)[0])
|
|
350
|
+
block_indent = (indent, indent + int(explicit[0]) if explicit else None)
|
|
351
|
+
try:
|
|
352
|
+
scalar = json.loads(scalar_text)
|
|
353
|
+
except ValueError:
|
|
354
|
+
scalar = scalar_text
|
|
355
|
+
fields[key] = scalar
|
|
356
|
+
subject = classify_record(fields)
|
|
357
|
+
if marked and subject == "human-session":
|
|
358
|
+
subject = "unsupported-native"
|
|
359
|
+
if subject != "human-session":
|
|
360
|
+
return subject if closed and not malformed else "unsupported-native"
|
|
361
|
+
if not closed or malformed:
|
|
362
|
+
raise _Unknown("ambiguous_record_header")
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def inspect_outgoing_history(repository, *, base_oid, head_oid, base_ref, head_ref, limits=DEFAULT_LIMITS):
|
|
367
|
+
"""Inspect exact local selections. A returned fact is never a SHARE grant.
|
|
368
|
+
|
|
369
|
+
Every newly reachable ancestor blob remains inspected even if the tip later
|
|
370
|
+
replaces, renames or deletes its path. Changed paths in every outgoing
|
|
371
|
+
commit are also inspected on both sides, including blobs reused from base.
|
|
372
|
+
Unchanged base subtrees are excluded. Local ref equality is checked twice;
|
|
373
|
+
a transport must still obtain fresh destination and source-CAS admission.
|
|
374
|
+
An explicitly absent base (both base fields None) scans all head history;
|
|
375
|
+
it does not prove a remote branch is absent.
|
|
376
|
+
"""
|
|
377
|
+
initial = base_oid is None and base_ref is None
|
|
378
|
+
object_base = base_oid is not None and base_ref is None
|
|
379
|
+
binding = {"repository_fingerprint": None, "base_oid": base_oid if isinstance(base_oid, str) and OID.fullmatch(base_oid) else None,
|
|
380
|
+
"head_oid": head_oid if isinstance(head_oid, str) and OID.fullmatch(head_oid) else None,
|
|
381
|
+
"base_ref": base_ref if _ref(base_ref) else None, "head_ref": head_ref if _ref(head_ref, head=True) else None,
|
|
382
|
+
"object_format": None, "range_kind": "initial-history" if initial else "outgoing-range",
|
|
383
|
+
"base_witness": "absent-selection" if initial else "object-only" if object_base else "local-ref"}
|
|
384
|
+
counts = {"commits": 0, "objects": 0, "bytes": 0, "tree_entries": 0, "path_checks": 0, "native_blobs": 0}
|
|
385
|
+
native, reasons, objects, classified = [], [], None, set()
|
|
386
|
+
try:
|
|
387
|
+
if (not isinstance(limits, HistoryLimits) or any(type(getattr(limits, key)) is not int or not 1 <= getattr(limits, key) <= getattr(DEFAULT_LIMITS, key)
|
|
388
|
+
for key in ("commits", "objects", "object_bytes", "total_bytes", "tree_entries"))
|
|
389
|
+
or type(limits.seconds) not in (float, int) or not 0 < limits.seconds <= DEFAULT_LIMITS.seconds):
|
|
390
|
+
raise _Unknown("invalid_limits")
|
|
391
|
+
required = ("head_oid", "head_ref") if initial else ("base_oid", "head_oid", "head_ref") if object_base else ("base_oid", "head_oid", "base_ref", "head_ref")
|
|
392
|
+
if any(binding[key] is None for key in required) or not initial and len(base_oid) != len(head_oid):
|
|
393
|
+
raise _Unknown("invalid_selection")
|
|
394
|
+
deadline = time.monotonic() + limits.seconds
|
|
395
|
+
root, git_dir = _repository(repository, deadline)
|
|
396
|
+
binding["repository_fingerprint"] = _digest({"repository": str(root), "git_dir": str(git_dir)})
|
|
397
|
+
git = _Git(root, deadline)
|
|
398
|
+
def check_refs():
|
|
399
|
+
selections = [(head_ref, head_oid)] if initial or object_base else [(base_ref, base_oid), (head_ref, head_oid)]
|
|
400
|
+
for ref, expected in selections:
|
|
401
|
+
# Follow only bounded, validated symbolic-ref spellings before
|
|
402
|
+
# asking Git to resolve them. HEAD must not hide a ref symlink.
|
|
403
|
+
target, seen = ref, set()
|
|
404
|
+
while True:
|
|
405
|
+
if target in seen or len(seen) >= 8:
|
|
406
|
+
raise _Unknown("ambiguous_range")
|
|
407
|
+
seen.add(target)
|
|
408
|
+
parts = ("HEAD",) if target == "HEAD" else tuple(target.split("/"))
|
|
409
|
+
selected = git_dir
|
|
410
|
+
for part in parts:
|
|
411
|
+
selected /= part
|
|
412
|
+
if selected.is_symlink():
|
|
413
|
+
raise _Unknown("unsupported_repository")
|
|
414
|
+
if not selected.exists():
|
|
415
|
+
break # Git may find this exact ref in packed-refs.
|
|
416
|
+
value = _regular(selected, 1024).strip()
|
|
417
|
+
if not value.startswith(b"ref: "):
|
|
418
|
+
break
|
|
419
|
+
target = value[5:].decode("ascii")
|
|
420
|
+
if not _ref(target):
|
|
421
|
+
raise _Unknown("ambiguous_range")
|
|
422
|
+
if (git_dir / "packed-refs").is_symlink():
|
|
423
|
+
raise _Unknown("unsupported_repository")
|
|
424
|
+
raw, _ = git.run("rev-parse", "--verify", ref, limit=256)
|
|
425
|
+
if raw.strip().decode("ascii") != expected:
|
|
426
|
+
raise _Unknown("stale_ref_binding")
|
|
427
|
+
check_refs()
|
|
428
|
+
fmt, _ = git.run("rev-parse", "--show-object-format", limit=32)
|
|
429
|
+
binding["object_format"] = fmt.strip().decode("ascii")
|
|
430
|
+
if binding["object_format"] != ("sha1" if len(head_oid) == 40 else "sha256"):
|
|
431
|
+
raise _Unknown("invalid_selection")
|
|
432
|
+
objects = _Objects(git, git_dir, limits, counts)
|
|
433
|
+
if any(objects.get(oid)[0] != "commit" for oid in ([head_oid] if initial else [base_oid, head_oid])):
|
|
434
|
+
raise _Unknown("invalid_selection")
|
|
435
|
+
if not initial:
|
|
436
|
+
_, code = git.run("merge-base", "--is-ancestor", base_oid, head_oid, allowed=(0, 1), limit=0)
|
|
437
|
+
if code:
|
|
438
|
+
raise _Unknown("non_ancestor_base")
|
|
439
|
+
selected_range = [head_oid] if initial else [head_oid, "^" + base_oid]
|
|
440
|
+
raw, _ = git.run("rev-list", f"--max-count={limits.commits + 1}", *selected_range, "--",
|
|
441
|
+
limit=(limits.commits + 1) * (len(head_oid) + 1))
|
|
442
|
+
commits = raw.decode("ascii").splitlines()
|
|
443
|
+
if len(commits) > limits.commits:
|
|
444
|
+
raise _Unknown("commit_limit")
|
|
445
|
+
if any(not OID.fullmatch(oid) or len(oid) != len(head_oid) for oid in commits) or len(set(commits)) != len(commits):
|
|
446
|
+
raise _Unknown("ambiguous_range")
|
|
447
|
+
counts["commits"] = len(commits)
|
|
448
|
+
try:
|
|
449
|
+
raw, _ = git.run("rev-list", "--objects", "--no-object-names", *selected_range, "--",
|
|
450
|
+
limit=(limits.objects + 1) * (len(head_oid) + 1))
|
|
451
|
+
except _Unknown as exc:
|
|
452
|
+
if str(exc) == "inventory_limit":
|
|
453
|
+
raise _Unknown("object_limit") from exc
|
|
454
|
+
raise
|
|
455
|
+
outgoing = raw.decode("ascii").splitlines()
|
|
456
|
+
if len(outgoing) > limits.objects:
|
|
457
|
+
raise _Unknown("object_limit")
|
|
458
|
+
if len(set(outgoing)) != len(outgoing) or any(not OID.fullmatch(oid) or len(oid) != len(head_oid) for oid in outgoing) or not set(commits) <= set(outgoing):
|
|
459
|
+
raise _Unknown("ambiguous_range")
|
|
460
|
+
trees, commit_trees = {}, {}
|
|
461
|
+
|
|
462
|
+
def inspect_blob(oid):
|
|
463
|
+
if oid in classified:
|
|
464
|
+
return
|
|
465
|
+
kind, value = objects.get(oid)
|
|
466
|
+
if kind != "blob":
|
|
467
|
+
raise _Unknown("malformed_tree")
|
|
468
|
+
classified.add(oid)
|
|
469
|
+
subject = _subject(value)
|
|
470
|
+
if subject:
|
|
471
|
+
counts["native_blobs"] += 1
|
|
472
|
+
if len(native) < 32:
|
|
473
|
+
native.append({"oid": oid, "subject_kind": subject})
|
|
474
|
+
|
|
475
|
+
def tree(oid):
|
|
476
|
+
if oid is None:
|
|
477
|
+
return {}
|
|
478
|
+
if oid not in trees:
|
|
479
|
+
kind, value = objects.get(oid)
|
|
480
|
+
if kind != "tree":
|
|
481
|
+
raise _Unknown("malformed_tree")
|
|
482
|
+
entries = _tree(value, len(oid) // 2)
|
|
483
|
+
counts["tree_entries"] += len(entries)
|
|
484
|
+
if counts["tree_entries"] > limits.tree_entries:
|
|
485
|
+
raise _Unknown("tree_entry_limit")
|
|
486
|
+
trees[oid] = entries
|
|
487
|
+
return trees[oid]
|
|
488
|
+
|
|
489
|
+
def commit(oid):
|
|
490
|
+
if oid not in commit_trees:
|
|
491
|
+
kind, value = objects.get(oid)
|
|
492
|
+
if kind != "commit":
|
|
493
|
+
raise _Unknown("malformed_commit")
|
|
494
|
+
commit_trees[oid] = _commit(value, len(oid))
|
|
495
|
+
return commit_trees[oid]
|
|
496
|
+
|
|
497
|
+
for oid in outgoing:
|
|
498
|
+
kind, value = objects.get(oid)
|
|
499
|
+
if kind == "tree":
|
|
500
|
+
tree(oid)
|
|
501
|
+
elif kind == "blob":
|
|
502
|
+
inspect_blob(oid)
|
|
503
|
+
elif kind == "commit":
|
|
504
|
+
commit(oid)
|
|
505
|
+
# Object exclusion alone misses a new path which reuses a base blob.
|
|
506
|
+
# Compare each selected commit with its first parent, but enumerate ALL
|
|
507
|
+
# selected ancestry above. Side-branch ancestors therefore still count.
|
|
508
|
+
# This is intentionally conservative about removed/renamed preimages.
|
|
509
|
+
for oid in commits:
|
|
510
|
+
after, parents = commit(oid)
|
|
511
|
+
before = commit(parents[0])[0] if parents else None
|
|
512
|
+
stack = [(before, after, "")]
|
|
513
|
+
while stack:
|
|
514
|
+
git.remaining()
|
|
515
|
+
old_oid, new_oid, prefix = stack.pop()
|
|
516
|
+
if old_oid == new_oid:
|
|
517
|
+
continue
|
|
518
|
+
old, new = tree(old_oid), tree(new_oid)
|
|
519
|
+
for name in sorted(old.keys() | new.keys()):
|
|
520
|
+
counts["path_checks"] += 1
|
|
521
|
+
if counts["path_checks"] > limits.tree_entries:
|
|
522
|
+
raise _Unknown("path_check_limit")
|
|
523
|
+
left, right = old.get(name), new.get(name)
|
|
524
|
+
if left == right:
|
|
525
|
+
continue
|
|
526
|
+
path = prefix + name
|
|
527
|
+
if len(path.encode()) > 4096 or path.count("/") >= 256:
|
|
528
|
+
raise _Unknown("ambiguous_tree_path")
|
|
529
|
+
for entry in (left, right):
|
|
530
|
+
if entry and entry[0] != "40000":
|
|
531
|
+
inspect_blob(entry[1])
|
|
532
|
+
left_tree = left[1] if left and left[0] == "40000" else None
|
|
533
|
+
right_tree = right[1] if right and right[0] == "40000" else None
|
|
534
|
+
if left_tree != right_tree:
|
|
535
|
+
stack.append((left_tree, right_tree, path + "/"))
|
|
536
|
+
check_refs()
|
|
537
|
+
except _Unknown as exc:
|
|
538
|
+
reasons.append(str(exc))
|
|
539
|
+
except (OSError, ValueError, UnicodeError, TypeError, OverflowError, RecursionError, subprocess.TimeoutExpired):
|
|
540
|
+
reasons.append("git_evidence_unavailable")
|
|
541
|
+
finally:
|
|
542
|
+
if objects:
|
|
543
|
+
objects.close()
|
|
544
|
+
result = {"schema": "egregore.native-outgoing-history/v1", "status": "unknown" if reasons else "present" if native else "none",
|
|
545
|
+
"complete": not reasons, "binding": binding, "counts": counts, "native_blobs": sorted(native, key=lambda row: row["oid"]),
|
|
546
|
+
"examples_truncated": counts["native_blobs"] > len(native), "reasons": reasons,
|
|
547
|
+
"remote_freshness": "not_checked", "publication_authority": False}
|
|
548
|
+
return {**result, "digest": _digest(result)}
|