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,56 @@
|
|
|
1
|
+
"""Admin-only entries refuse a non-admin grant before they read anything.
|
|
2
|
+
|
|
3
|
+
Corpus and data-room builders and hosted-index maintenance read or hand over
|
|
4
|
+
memory wholesale, outside the per-path predicate, so they are admin-only
|
|
5
|
+
channels: the entry asks the one policy whether this session's read grant is
|
|
6
|
+
an admin's and stops otherwise. There is no operator-only class; running in
|
|
7
|
+
your own checkout is not a grant. Shell and Node entries run the
|
|
8
|
+
``egregore_runtime.admin_entry`` module with the surface name (exit 5 on refusal).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
REFUSED = 5
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def refusal(surface: str) -> str:
|
|
21
|
+
return (f"{surface} is for organization admins, and this session's grant is not an admin's; "
|
|
22
|
+
"nothing was read. Admins are the GitHub handles listed under `admins` in egregore.json.")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def admin_session(root: Path | str | None = None, harness: str | None = None) -> bool:
|
|
26
|
+
"""True only when the policy names this session's actor an admin; fails closed."""
|
|
27
|
+
from .contracts import Permission
|
|
28
|
+
from .policy import is_admin
|
|
29
|
+
from .runtime import local_runtime, runtime_root
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
runtime = local_runtime(runtime_root(root), push_remote=False)
|
|
33
|
+
actor = runtime.resolve_actor(session_id=os.environ.get("EGREGORE_SESSION_ID") or "admin-entry",
|
|
34
|
+
harness=harness or os.environ.get("EGREGORE_RUNTIME") or "shell")
|
|
35
|
+
return is_admin(runtime.authorize(actor, Permission.READ))
|
|
36
|
+
except Exception:
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def require_admin(surface: str, root: Path | str | None = None) -> None:
|
|
41
|
+
if not admin_session(root):
|
|
42
|
+
print(refusal(surface), file=sys.stderr)
|
|
43
|
+
raise SystemExit(REFUSED)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def main(argv: list[str] | None = None) -> int:
|
|
47
|
+
args = sys.argv[1:] if argv is None else argv
|
|
48
|
+
surface = args[0] if args else "This command"
|
|
49
|
+
if admin_session():
|
|
50
|
+
return 0
|
|
51
|
+
print(refusal(surface), file=sys.stderr)
|
|
52
|
+
return REFUSED
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
raise SystemExit(main())
|
|
@@ -18,12 +18,14 @@ import unicodedata
|
|
|
18
18
|
from dataclasses import dataclass, field, replace
|
|
19
19
|
from datetime import UTC, date, datetime
|
|
20
20
|
from pathlib import Path, PurePosixPath
|
|
21
|
-
from typing import Any, Mapping, Protocol, runtime_checkable
|
|
21
|
+
from typing import Any, Callable, Mapping, Protocol, runtime_checkable
|
|
22
22
|
|
|
23
|
-
from .contracts import ArtifactRelationship, CanonicalArtifact, SourceProvenance
|
|
23
|
+
from .contracts import ActorKind, ArtifactRelationship, CanonicalArtifact, ExecutionAuthority, SourceProvenance
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
ARTIFACT_SCHEMA_VERSION = "egregore-artifact/v1"
|
|
27
|
+
# Names frontmatter keys whose block value the reader could not represent.
|
|
28
|
+
UNPARSED_FRONTMATTER = "unparsed frontmatter"
|
|
27
29
|
_FRONTMATTER_KEY = re.compile(r"^([A-Za-z][A-Za-z0-9_-]*):(?:[ \t]*(.*))?$")
|
|
28
30
|
# Existing hosted artifact ids are URL-safe opaque tokens and may begin with
|
|
29
31
|
# ``-`` or ``_``. Keep those stable during explicit v1 migration.
|
|
@@ -54,6 +56,9 @@ class CanonicalDocument:
|
|
|
54
56
|
replaces_existing: bool = False
|
|
55
57
|
# Optimistic concurrency for a mutable lifecycle update; never serialized.
|
|
56
58
|
expected_revision: str | None = None
|
|
59
|
+
# Stricter optimistic concurrency: the SHA-256 of the stored file's full
|
|
60
|
+
# bytes (header included), checked under the write lock; never serialized.
|
|
61
|
+
expected_sha256: str | None = None
|
|
57
62
|
|
|
58
63
|
|
|
59
64
|
@runtime_checkable
|
|
@@ -156,33 +161,136 @@ def _parse_scalar(raw: str) -> Any:
|
|
|
156
161
|
return value
|
|
157
162
|
|
|
158
163
|
|
|
159
|
-
|
|
160
|
-
"""
|
|
164
|
+
class _Unsupported(Exception):
|
|
165
|
+
"""A frontmatter block outside the supported YAML subset."""
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _indent(line: str) -> int:
|
|
169
|
+
return len(line) - len(line.lstrip(" "))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _dash(text: str) -> bool:
|
|
173
|
+
return text == "-" or text.startswith("- ")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _block_scalar(raw: str) -> Any:
|
|
177
|
+
"""A scalar inside a block. A plain value carrying a YAML comment is refused
|
|
178
|
+
rather than read with the comment as data (``- oz # owner``)."""
|
|
179
|
+
|
|
180
|
+
if raw.startswith("#") or " #" in raw or "\t#" in raw: # YAML: whitespace, then "#"
|
|
181
|
+
try:
|
|
182
|
+
return json.loads(raw)
|
|
183
|
+
except json.JSONDecodeError:
|
|
184
|
+
raise _Unsupported from None
|
|
185
|
+
return _parse_scalar(raw)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _block(lines: list[str], index: int, indent: int) -> tuple[Any, int]:
|
|
189
|
+
"""One block-style YAML value at ``indent``: a list of scalars or a mapping."""
|
|
190
|
+
|
|
191
|
+
if _dash(lines[index][indent:]):
|
|
192
|
+
items = []
|
|
193
|
+
while index < len(lines) and _indent(lines[index]) == indent and _dash(lines[index][indent:]):
|
|
194
|
+
item = lines[index][indent + 1:].strip()
|
|
195
|
+
index += 1
|
|
196
|
+
if not item or (index < len(lines) and _indent(lines[index]) > indent):
|
|
197
|
+
raise _Unsupported # an empty or nested list item
|
|
198
|
+
items.append(_block_scalar(item))
|
|
199
|
+
return items, index
|
|
200
|
+
mapping: dict[str, Any] = {}
|
|
201
|
+
while index < len(lines) and _indent(lines[index]) == indent and not _dash(lines[index][indent:]):
|
|
202
|
+
match = _FRONTMATTER_KEY.match(lines[index][indent:])
|
|
203
|
+
if match is None or match.group(1) in mapping:
|
|
204
|
+
raise _Unsupported
|
|
205
|
+
key, raw = match.group(1), (match.group(2) or "").strip()
|
|
206
|
+
index += 1
|
|
207
|
+
child = _indent(lines[index]) if index < len(lines) else -1
|
|
208
|
+
nested = child > indent or (child == indent and _dash(lines[index][indent:]))
|
|
209
|
+
if raw:
|
|
210
|
+
if nested or raw[:1] in {"|", ">"}:
|
|
211
|
+
raise _Unsupported # a multi-line scalar
|
|
212
|
+
mapping[key] = _block_scalar(raw)
|
|
213
|
+
elif nested:
|
|
214
|
+
mapping[key], index = _block(lines, index, child)
|
|
215
|
+
else:
|
|
216
|
+
mapping[key] = None
|
|
217
|
+
if index < len(lines) and _indent(lines[index]) > indent:
|
|
218
|
+
raise _Unsupported
|
|
219
|
+
return mapping, index
|
|
161
220
|
|
|
162
|
-
JSON values are valid YAML and let the runtime round-trip nested provenance
|
|
163
|
-
without a YAML dependency. Unknown indented legacy fields are ignored and
|
|
164
|
-
preserved in the original file until an explicit migration/write occurs.
|
|
165
|
-
"""
|
|
166
221
|
|
|
222
|
+
def _parse_frontmatter(markdown: str) -> tuple[dict[str, Any], str, tuple[str, ...]]:
|
|
167
223
|
normalized = markdown.replace("\r\n", "\n").replace("\r", "\n")
|
|
168
224
|
if not normalized.startswith("---\n"):
|
|
169
|
-
return {}, normalized
|
|
225
|
+
return {}, normalized, ()
|
|
170
226
|
closing = normalized.find("\n---\n", 4)
|
|
171
227
|
if closing < 0:
|
|
172
228
|
raise ArtifactSchemaError("incomplete frontmatter block")
|
|
173
|
-
|
|
229
|
+
# Only a comment at column 0 is dropped: inside a block, a "#" line may be
|
|
230
|
+
# content (a literal block), so it stays and makes that block unsupported.
|
|
231
|
+
numbered = [(number, line) for number, line in enumerate(normalized[4:closing].splitlines(), 2)
|
|
232
|
+
if line.strip() and not line.startswith("#")]
|
|
233
|
+
lines = [line for _, line in numbered]
|
|
174
234
|
result: dict[str, Any] = {}
|
|
175
|
-
|
|
176
|
-
|
|
235
|
+
unparsed: list[str] = []
|
|
236
|
+
index = 0
|
|
237
|
+
while index < len(lines):
|
|
238
|
+
line = lines[index]
|
|
239
|
+
if line[:1].isspace():
|
|
240
|
+
index += 1 # an indented line no key above it could own
|
|
177
241
|
continue
|
|
178
242
|
match = _FRONTMATTER_KEY.match(line)
|
|
179
243
|
if match is None:
|
|
180
|
-
raise ArtifactSchemaError(f"invalid frontmatter line {
|
|
244
|
+
raise ArtifactSchemaError(f"invalid frontmatter line {numbered[index][0]}")
|
|
181
245
|
key, raw = match.groups()
|
|
182
246
|
if key in result:
|
|
183
247
|
raise ArtifactSchemaError(f"duplicate frontmatter key: {key}")
|
|
184
248
|
result[key] = _parse_scalar(raw or "")
|
|
185
|
-
|
|
249
|
+
index += 1
|
|
250
|
+
if index < len(lines) and lines[index][:1].isspace():
|
|
251
|
+
# A block value under this key. Keep the one-line reading when the
|
|
252
|
+
# block is outside the subset, and name the key so no write drops it.
|
|
253
|
+
following = index
|
|
254
|
+
while following < len(lines) and lines[following][:1].isspace():
|
|
255
|
+
following += 1
|
|
256
|
+
if (raw or "").strip():
|
|
257
|
+
unparsed.append(key)
|
|
258
|
+
else:
|
|
259
|
+
try:
|
|
260
|
+
value, stop = _block(lines, index, _indent(lines[index]))
|
|
261
|
+
if stop != following:
|
|
262
|
+
raise _Unsupported
|
|
263
|
+
result[key] = value
|
|
264
|
+
except _Unsupported:
|
|
265
|
+
unparsed.append(key)
|
|
266
|
+
index = following
|
|
267
|
+
return result, normalized[closing + 5 :], tuple(unparsed)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def split_frontmatter(markdown: str) -> tuple[dict[str, Any], str]:
|
|
271
|
+
"""Parse the deterministic JSON-in-YAML subset used by v1 and legacy records.
|
|
272
|
+
|
|
273
|
+
JSON values are valid YAML and let the runtime round-trip nested provenance
|
|
274
|
+
without a YAML dependency. Legacy block values (an indented list of
|
|
275
|
+
scalars, or a mapping of them) are read too. Anything else under a key
|
|
276
|
+
keeps its one-line reading; ``unparsed_frontmatter`` names those keys so a
|
|
277
|
+
write never drops them.
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
fields, body, _ = _parse_frontmatter(markdown)
|
|
281
|
+
return fields, body
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def unparsed_frontmatter(markdown: str) -> tuple[str, ...]:
|
|
285
|
+
"""Keys whose block value is outside the supported subset."""
|
|
286
|
+
|
|
287
|
+
return _parse_frontmatter(markdown)[2]
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _refuse_unparsed(warnings: tuple[str, ...]) -> None:
|
|
291
|
+
unread = next((note for note in warnings if note.startswith(UNPARSED_FRONTMATTER)), None)
|
|
292
|
+
if unread:
|
|
293
|
+
raise ArtifactSchemaError(f"{unread}; rewriting this record would drop them")
|
|
186
294
|
|
|
187
295
|
|
|
188
296
|
def _as_strings(value: Any) -> tuple[str, ...]:
|
|
@@ -263,6 +371,11 @@ def _infer_type(fields: Mapping[str, Any], canonical_path: str) -> str:
|
|
|
263
371
|
return "artifact"
|
|
264
372
|
|
|
265
373
|
|
|
374
|
+
def plain_revision(markdown: str) -> str:
|
|
375
|
+
"""Version of a plain canonical file: a hash of its exact text."""
|
|
376
|
+
return "sha256:" + hashlib.sha256(markdown.encode("utf-8")).hexdigest()[:16]
|
|
377
|
+
|
|
378
|
+
|
|
266
379
|
def store_managed_reason(markdown: str) -> str | None:
|
|
267
380
|
"""Say why raw Markdown belongs to the artifact store, or None if it does not.
|
|
268
381
|
|
|
@@ -303,6 +416,34 @@ def _relationships(value: Any) -> tuple[ArtifactRelationship, ...]:
|
|
|
303
416
|
return tuple(rows)
|
|
304
417
|
|
|
305
418
|
|
|
419
|
+
def _execution_provenance(value: Any) -> ExecutionAuthority | None:
|
|
420
|
+
"""Read bounded historical attribution, never usable as a credential."""
|
|
421
|
+
if value is None:
|
|
422
|
+
return None
|
|
423
|
+
text_fields = (
|
|
424
|
+
"executor_actor_id", "worker_id", "member_account_id", "member_actor_id",
|
|
425
|
+
"membership_id", "org_id", "grant_id", "binding_id", "audience", "revision",
|
|
426
|
+
)
|
|
427
|
+
if not isinstance(value, Mapping) or set(value) != {*text_fields, "executor_kind", "expires_at"}:
|
|
428
|
+
raise ArtifactSchemaError("invalid execution provenance")
|
|
429
|
+
if any(not isinstance(value[key], str) or not re.fullmatch(r"[A-Za-z0-9._:-]{1,160}", value[key])
|
|
430
|
+
for key in text_fields):
|
|
431
|
+
raise ArtifactSchemaError("invalid execution provenance reference")
|
|
432
|
+
try:
|
|
433
|
+
kind = ActorKind(value["executor_kind"])
|
|
434
|
+
if kind not in {ActorKind.AGENT, ActorKind.SERVICE}:
|
|
435
|
+
raise ValueError("invalid executor kind")
|
|
436
|
+
expires = datetime.fromisoformat(str(value["expires_at"]).replace("Z", "+00:00"))
|
|
437
|
+
if expires.tzinfo is None:
|
|
438
|
+
raise ValueError("timezone required")
|
|
439
|
+
except (ValueError, TypeError):
|
|
440
|
+
raise ArtifactSchemaError("invalid execution provenance") from None
|
|
441
|
+
return ExecutionAuthority(
|
|
442
|
+
**{key: value[key] for key in text_fields},
|
|
443
|
+
executor_kind=kind, expires_at=expires,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
|
|
306
447
|
def _provenance(value: Any) -> tuple[SourceProvenance, ...]:
|
|
307
448
|
if value is None:
|
|
308
449
|
return ()
|
|
@@ -326,6 +467,7 @@ def _provenance(value: Any) -> tuple[SourceProvenance, ...]:
|
|
|
326
467
|
observed_at=_datetime(item["observed_at"], field_name="provenance.observed_at"),
|
|
327
468
|
source_uri=str(item["source_uri"]) if item.get("source_uri") else None,
|
|
328
469
|
imported_by=str(item["imported_by"]) if item.get("imported_by") else None,
|
|
470
|
+
execution=_execution_provenance(item.get("execution")),
|
|
329
471
|
)
|
|
330
472
|
)
|
|
331
473
|
return tuple(rows)
|
|
@@ -356,6 +498,11 @@ def validate_artifact(artifact: CanonicalArtifact, body: str) -> CanonicalArtifa
|
|
|
356
498
|
raise ArtifactSchemaError("content_hash does not match the normalized Markdown body")
|
|
357
499
|
if any(not row.relation or not row.target_id for row in artifact.relationships):
|
|
358
500
|
raise ArtifactSchemaError("relationships require relation and target_id")
|
|
501
|
+
for source in artifact.provenance:
|
|
502
|
+
if source.execution is not None:
|
|
503
|
+
if not isinstance(source.execution, ExecutionAuthority):
|
|
504
|
+
raise ArtifactSchemaError("invalid execution provenance")
|
|
505
|
+
_execution_provenance(source.execution.to_dict())
|
|
359
506
|
return replace(
|
|
360
507
|
artifact,
|
|
361
508
|
canonical_path=canonical_path,
|
|
@@ -382,7 +529,7 @@ def parse_canonical_markdown(
|
|
|
382
529
|
"""
|
|
383
530
|
|
|
384
531
|
path = canonical_relative_path(canonical_path)
|
|
385
|
-
fields, raw_body =
|
|
532
|
+
fields, raw_body, unparsed = _parse_frontmatter(markdown)
|
|
386
533
|
body = normalize_markdown_body(raw_body)
|
|
387
534
|
schema = fields.get("schema_version") or fields.get("schema")
|
|
388
535
|
is_v1 = schema == ARTIFACT_SCHEMA_VERSION
|
|
@@ -406,6 +553,7 @@ def parse_canonical_markdown(
|
|
|
406
553
|
or fields.get("date")
|
|
407
554
|
or fields.get("ingested_at")
|
|
408
555
|
or fields.get("started")
|
|
556
|
+
or fields.get("created")
|
|
409
557
|
or (default_created_at if not is_v1 else None)
|
|
410
558
|
)
|
|
411
559
|
if created_raw is None:
|
|
@@ -482,6 +630,8 @@ def parse_canonical_markdown(
|
|
|
482
630
|
if key in fields:
|
|
483
631
|
policy_hints[key] = fields[key]
|
|
484
632
|
warnings = () if is_v1 else ("legacy frontmatter migrated in memory; source file was not rewritten",)
|
|
633
|
+
if unparsed:
|
|
634
|
+
warnings += (f"{UNPARSED_FRONTMATTER}: {', '.join(unparsed)}",)
|
|
485
635
|
return CanonicalDocument(
|
|
486
636
|
artifact=artifact,
|
|
487
637
|
body=body,
|
|
@@ -533,7 +683,7 @@ class LocalMarkdownArtifactStore:
|
|
|
533
683
|
root: Path,
|
|
534
684
|
*,
|
|
535
685
|
mutable_types: frozenset[str] = frozenset(
|
|
536
|
-
{"scroll", "thread", "question", "quest", "todo", "issue"}
|
|
686
|
+
{"scroll", "thread", "question", "quest", "todo", "issue", "design"}
|
|
537
687
|
),
|
|
538
688
|
):
|
|
539
689
|
self.root = root.resolve()
|
|
@@ -567,8 +717,18 @@ class LocalMarkdownArtifactStore:
|
|
|
567
717
|
# stale digest, decides whether that replacement is legitimate.
|
|
568
718
|
recompute_digest=document.replaces_existing,
|
|
569
719
|
)
|
|
720
|
+
identical = (artifact.artifact_type not in self.mutable_types and not document.replaces_existing
|
|
721
|
+
and existing.artifact.content_hash == artifact.content_hash and document.migrated_from is None)
|
|
722
|
+
if not identical:
|
|
723
|
+
# The reader keeps only a one-line reading of an unparsed key,
|
|
724
|
+
# so replacing a stored file that has one would drop the rest.
|
|
725
|
+
# Refusing here, in the check, stops a batch before any write.
|
|
726
|
+
_refuse_unparsed(existing.warnings)
|
|
570
727
|
if document.expected_revision is not None and existing.artifact.revision != document.expected_revision:
|
|
571
728
|
raise ArtifactConflictError("canonical source revision changed; reopen before retrying")
|
|
729
|
+
if (document.expected_sha256 is not None
|
|
730
|
+
and hashlib.sha256(target.read_bytes()).hexdigest() != document.expected_sha256):
|
|
731
|
+
raise ArtifactConflictError("canonical source changed; reopen before retrying")
|
|
572
732
|
if existing.artifact.artifact_id != artifact.artifact_id:
|
|
573
733
|
raise ArtifactConflictError("artifact path belongs to a different stable id")
|
|
574
734
|
if artifact.artifact_type not in self.mutable_types and not document.replaces_existing:
|
|
@@ -579,6 +739,7 @@ class LocalMarkdownArtifactStore:
|
|
|
579
739
|
return target
|
|
580
740
|
else:
|
|
581
741
|
raise ArtifactConflictError(f"{artifact.artifact_type} artifacts are append-only")
|
|
742
|
+
_refuse_unparsed(document.warnings) # a document built from such a read drops them too
|
|
582
743
|
return target
|
|
583
744
|
|
|
584
745
|
@contextmanager
|
|
@@ -612,6 +773,51 @@ class LocalMarkdownArtifactStore:
|
|
|
612
773
|
raise ArtifactConflictError("canonical source changed during identity transfer")
|
|
613
774
|
return self._persist(document)
|
|
614
775
|
|
|
776
|
+
def replace_plain_if_unchanged(self, canonical_path: str, expected_revision: str, text: str) -> Path:
|
|
777
|
+
"""Replace a plain canonical Markdown file while its bytes still match.
|
|
778
|
+
|
|
779
|
+
For files that carry no artifact envelope (people pages, ledgers, older
|
|
780
|
+
notes). ``expected_revision`` is ``sha256:`` plus the first 16 hex of the
|
|
781
|
+
current file. A file the store manages is refused: those go through a
|
|
782
|
+
document write so their envelope stays valid.
|
|
783
|
+
"""
|
|
784
|
+
with self._write_lock(canonical_path):
|
|
785
|
+
target = self._target(canonical_path)
|
|
786
|
+
if not target.is_file():
|
|
787
|
+
raise ArtifactConflictError("canonical source disappeared; reopen before retrying")
|
|
788
|
+
current = target.read_text(encoding="utf-8")
|
|
789
|
+
if plain_revision(current) != expected_revision:
|
|
790
|
+
raise ArtifactConflictError("canonical source revision changed; reopen before retrying")
|
|
791
|
+
for markdown in (current, text):
|
|
792
|
+
reason = store_managed_reason(markdown)
|
|
793
|
+
if reason is not None:
|
|
794
|
+
raise ArtifactConflictError(f"this file is store-managed ({reason}); write it as a document")
|
|
795
|
+
temporary = target.with_name(f".{target.name}.{os.getpid()}.tmp")
|
|
796
|
+
temporary.write_text(text, encoding="utf-8")
|
|
797
|
+
temporary.replace(target)
|
|
798
|
+
return target
|
|
799
|
+
|
|
800
|
+
def restore_if(self, canonical_path: str, previous: str, holds: Callable[[str], bool]) -> bool:
|
|
801
|
+
"""Put ``previous`` back while the file still holds a write nothing versioned.
|
|
802
|
+
|
|
803
|
+
``holds`` says whether the current text is that write. Under the same
|
|
804
|
+
lock as every store write. True when the file holds ``previous`` again
|
|
805
|
+
(or never left it); False when it moved on, so nothing was touched.
|
|
806
|
+
"""
|
|
807
|
+
with self._write_lock(canonical_path):
|
|
808
|
+
target = self._target(canonical_path)
|
|
809
|
+
if not target.is_file():
|
|
810
|
+
return False
|
|
811
|
+
current = target.read_text(encoding="utf-8")
|
|
812
|
+
if current == previous:
|
|
813
|
+
return True
|
|
814
|
+
if not holds(current):
|
|
815
|
+
return False
|
|
816
|
+
temporary = target.with_name(f".{target.name}.{os.getpid()}.tmp")
|
|
817
|
+
temporary.write_text(previous, encoding="utf-8")
|
|
818
|
+
temporary.replace(target)
|
|
819
|
+
return True
|
|
820
|
+
|
|
615
821
|
def _persist(self, document: CanonicalDocument) -> Path:
|
|
616
822
|
target = self.check_write(document)
|
|
617
823
|
artifact = validate_artifact(document.artifact, document.body)
|
|
@@ -204,6 +204,14 @@ class PolicyDecision(Serializable):
|
|
|
204
204
|
resource_ids: tuple[str, ...] = ()
|
|
205
205
|
scopes: tuple[str, ...] = ()
|
|
206
206
|
reasons: tuple[str, ...] = ()
|
|
207
|
+
audience: str = "member"
|
|
208
|
+
scope: str = "read"
|
|
209
|
+
read_prefixes: tuple[str, ...] = ()
|
|
210
|
+
write_prefixes: tuple[str, ...] = ()
|
|
211
|
+
append_prefixes: tuple[str, ...] = ()
|
|
212
|
+
holds_epoch: str = ""
|
|
213
|
+
released: tuple[str, ...] = ()
|
|
214
|
+
memory_revision: str = ""
|
|
207
215
|
|
|
208
216
|
|
|
209
217
|
@dataclass(frozen=True, slots=True)
|
|
@@ -550,6 +558,8 @@ class ArtifactWriteback(Protocol):
|
|
|
550
558
|
self,
|
|
551
559
|
actor: ActorContext,
|
|
552
560
|
document: "CanonicalDocument",
|
|
561
|
+
*,
|
|
562
|
+
message: str | None = None,
|
|
553
563
|
) -> WritebackEvent: ...
|
|
554
564
|
|
|
555
565
|
def write_documents(
|
|
@@ -559,12 +569,29 @@ class ArtifactWriteback(Protocol):
|
|
|
559
569
|
) -> Sequence[WritebackEvent]: ...
|
|
560
570
|
|
|
561
571
|
|
|
572
|
+
class CommitMessage(str):
|
|
573
|
+
"""A commit's subject (and body) that names its own trailers, as ``Key: value`` lines.
|
|
574
|
+
|
|
575
|
+
It is a string, so every transport can commit it as text. The Git transport
|
|
576
|
+
writes ``trailers`` as the message's one trailer paragraph and adds only the
|
|
577
|
+
provenance trailers they do not name. The caller keeps every value on one line.
|
|
578
|
+
"""
|
|
579
|
+
|
|
580
|
+
trailers: tuple[str, ...]
|
|
581
|
+
|
|
582
|
+
def __new__(cls, text: str, trailers: Sequence[str] = ()) -> "CommitMessage":
|
|
583
|
+
made = super().__new__(cls, text)
|
|
584
|
+
made.trailers = tuple(trailers)
|
|
585
|
+
return made
|
|
586
|
+
|
|
587
|
+
|
|
562
588
|
@runtime_checkable
|
|
563
589
|
class SyncTransport(Protocol):
|
|
564
590
|
def status(self) -> SyncStatus: ...
|
|
565
591
|
|
|
566
592
|
def pull(self) -> SyncStatus: ...
|
|
567
593
|
|
|
594
|
+
# ``message`` may be a CommitMessage that carries its own trailers.
|
|
568
595
|
def push(self, *, message: str, paths: Sequence[str] = ()) -> SyncStatus: ...
|
|
569
596
|
|
|
570
597
|
|
|
@@ -15,7 +15,7 @@ from typing import Any, Callable
|
|
|
15
15
|
from .contracts import TemporalScope
|
|
16
16
|
from .errors import AuthorizationDenied, EgregoreRuntimeError
|
|
17
17
|
from .lifecycle import AuthorityState
|
|
18
|
-
from .policy import
|
|
18
|
+
from .policy import check
|
|
19
19
|
|
|
20
20
|
ELIGIBILITY_VERSION = 'egregore-eligibility/v1'
|
|
21
21
|
MAX_DOCUMENTS = 50_000
|
|
@@ -51,7 +51,7 @@ class EligibilitySnapshot:
|
|
|
51
51
|
'documents': [document.to_wire() for document in self.documents]}
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
def build_eligibility(retriever, request, *, actor,
|
|
54
|
+
def build_eligibility(retriever, request, *, actor, decision, lifecycle=None,
|
|
55
55
|
path_filter: Callable[[str], bool] | None = None,
|
|
56
56
|
metadata_filter: Callable[[str, Any], bool] | None = None):
|
|
57
57
|
"""Read current scoped files once and fence the resulting view by revision.
|
|
@@ -66,20 +66,13 @@ def build_eligibility(retriever, request, *, actor, admin_gate=None, lifecycle=N
|
|
|
66
66
|
root = retriever.memory_root.resolve()
|
|
67
67
|
rows = []
|
|
68
68
|
warnings = []
|
|
69
|
-
is_admin = admin_gate is None or admin_gate.actor_is_admin(actor)
|
|
70
69
|
for path in sorted(root.rglob('*.md')):
|
|
71
70
|
canonical = 'memory/' + path.relative_to(root).as_posix()
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
resolved = path.resolve()
|
|
75
|
-
if not path.is_file() or not resolved.is_relative_to(root):
|
|
76
|
-
continue
|
|
77
|
-
if not scope_allows_path('memory/' + resolved.relative_to(root).as_posix(), request.authorized_scopes):
|
|
71
|
+
verdict = check(decision, canonical, "read", root=root)
|
|
72
|
+
if not verdict.allowed or not verdict.resolved.is_file():
|
|
78
73
|
continue
|
|
79
74
|
if path_filter and not path_filter(canonical):
|
|
80
75
|
continue
|
|
81
|
-
if not is_admin and admin_gate.path_is_admin(canonical):
|
|
82
|
-
continue
|
|
83
76
|
metadata = retriever.describe_source(canonical)
|
|
84
77
|
if request.allowed_artifact_ids and metadata.artifact_id not in request.allowed_artifact_ids:
|
|
85
78
|
continue
|
|
@@ -98,7 +91,7 @@ def build_eligibility(retriever, request, *, actor, admin_gate=None, lifecycle=N
|
|
|
98
91
|
try:
|
|
99
92
|
lifecycle_by_path, _ = lifecycle.resolve_paths(
|
|
100
93
|
[row.canonical_path for row in rows], source_revision=revision,
|
|
101
|
-
|
|
94
|
+
decision=decision,
|
|
102
95
|
allowed_artifact_ids=request.allowed_artifact_ids)
|
|
103
96
|
except Exception as error:
|
|
104
97
|
# Preserve Observe's advisory lifecycle fallback, but do not claim
|
|
@@ -13,11 +13,11 @@ import fcntl
|
|
|
13
13
|
import hashlib
|
|
14
14
|
import json
|
|
15
15
|
import os
|
|
16
|
-
from pathlib import Path
|
|
17
16
|
import secrets
|
|
18
17
|
import time
|
|
19
18
|
|
|
20
19
|
from .errors import EgregoreRuntimeError
|
|
20
|
+
from .installation import installation_root
|
|
21
21
|
|
|
22
22
|
MAX_QUESTION_BYTES = 262144
|
|
23
23
|
|
|
@@ -25,9 +25,20 @@ class EpisodeBindingError(EgregoreRuntimeError):
|
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def _store(root):
|
|
29
|
+
"""One binding store per installation, shared by its main checkout and task worktrees.
|
|
30
|
+
|
|
31
|
+
A prompt is bound from wherever its hook runs (Claude keeps CLAUDE_PROJECT_DIR
|
|
32
|
+
at the launch checkout after the session enters a worktree, and a prompt that
|
|
33
|
+
names a worktree moves the hook there); its tools, a subagent's included, run
|
|
34
|
+
in whichever checkout they stand in. All of them must see one current prompt.
|
|
35
|
+
"""
|
|
36
|
+
return installation_root(root) / ".egregore/runtime/bindings"
|
|
37
|
+
|
|
38
|
+
|
|
28
39
|
def _directory(root):
|
|
29
|
-
directory =
|
|
30
|
-
if not directory.resolve().is_relative_to(
|
|
40
|
+
directory = _store(root)
|
|
41
|
+
if not directory.resolve().is_relative_to(installation_root(root)):
|
|
31
42
|
raise EpisodeBindingError("episode bindings must remain inside this instance")
|
|
32
43
|
directory.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
33
44
|
os.chmod(directory, 0o700)
|
|
@@ -148,7 +159,7 @@ def resolve(root, episode_id):
|
|
|
148
159
|
|
|
149
160
|
|
|
150
161
|
def requires_binding(root):
|
|
151
|
-
directory =
|
|
162
|
+
directory = _store(root)
|
|
152
163
|
return directory.exists() and any(directory.glob("*.current"))
|
|
153
164
|
|
|
154
165
|
|
|
@@ -168,7 +179,7 @@ def resolve_native(root, *, harness, session_id):
|
|
|
168
179
|
if not session_id or len(session_id) > 200:
|
|
169
180
|
raise EpisodeBindingError('native session identity is required')
|
|
170
181
|
conversation = _key(f'{harness}:{session_id}')
|
|
171
|
-
path =
|
|
182
|
+
path = _store(root) / (conversation + '.current')
|
|
172
183
|
if not path.exists():
|
|
173
184
|
return None
|
|
174
185
|
current = _load(path)
|
|
@@ -13,7 +13,7 @@ from urllib.parse import urlencode
|
|
|
13
13
|
from uuid import uuid4
|
|
14
14
|
|
|
15
15
|
from .contracts import Permission
|
|
16
|
-
from .policy import
|
|
16
|
+
from .policy import allows, action_for
|
|
17
17
|
from .issue_markers import artifact_url, issue_request_id, observation_id, request_id
|
|
18
18
|
|
|
19
19
|
REPO = re.compile(r"[A-Za-z0-9][A-Za-z0-9-]{0,38}/(?!(?:\.|\.\.)$)[A-Za-z0-9_.-]{1,100}")
|
|
@@ -76,7 +76,7 @@ class GitHubIssues:
|
|
|
76
76
|
permissions += [Permission.WRITE, Permission.SHARE]
|
|
77
77
|
for permission in permissions:
|
|
78
78
|
decision = self.runtime.authorize(self.actor, permission, (f"github:{self.repo}:issues",))
|
|
79
|
-
if not decision.allowed or not
|
|
79
|
+
if not decision.allowed or not allows(decision, "memory/knowledge/issues/", action_for(decision.permission), root=self.runtime.canonical_root):
|
|
80
80
|
raise PermissionError(f"GitHub issues {permission.value} denied or whole issue namespace not authorized")
|
|
81
81
|
|
|
82
82
|
def number(self, reference):
|
|
@@ -23,10 +23,10 @@ from pathlib import Path
|
|
|
23
23
|
from typing import Any, Iterable
|
|
24
24
|
|
|
25
25
|
from .artifacts import split_frontmatter
|
|
26
|
-
from .contracts import ActorContext, Permission
|
|
26
|
+
from .contracts import ActorContext, Permission, PolicyDecision
|
|
27
27
|
from .errors import AuthorizationDenied
|
|
28
28
|
from .identity import organization_member_presentations
|
|
29
|
-
from .policy import
|
|
29
|
+
from .policy import check
|
|
30
30
|
from .questions import actor_aliases
|
|
31
31
|
|
|
32
32
|
_CLOSED_STATUSES = frozenset(
|
|
@@ -72,13 +72,13 @@ def _occurred_at(fields: Any, path_stem: str) -> str:
|
|
|
72
72
|
return f"0000-00-00T00:00:00Z:{path_stem}"
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def _member_terms(root: Path, actor: ActorContext, name: str) -> frozenset[str]:
|
|
76
|
-
"""Stable-identity terms for a NAMED member (addressing resolution)."""
|
|
75
|
+
def _member_terms(root: Path, actor: ActorContext, name: str, decision: PolicyDecision) -> frozenset[str]:
|
|
76
|
+
"""Stable-identity terms for a NAMED member (addressing resolution), from readable profiles."""
|
|
77
77
|
|
|
78
78
|
needle = name.strip().removeprefix("@").casefold()
|
|
79
79
|
if not needle:
|
|
80
80
|
return frozenset()
|
|
81
|
-
for member in organization_member_presentations(root, actor):
|
|
81
|
+
for member in organization_member_presentations(root, actor, decision=decision):
|
|
82
82
|
values = {
|
|
83
83
|
str(member.display_name).casefold(),
|
|
84
84
|
*(str(alias).casefold() for alias in member.aliases),
|
|
@@ -113,16 +113,6 @@ class HandoffLookupService:
|
|
|
113
113
|
decision = self.runtime.authorize(actor, Permission.READ)
|
|
114
114
|
if not decision.allowed:
|
|
115
115
|
raise AuthorizationDenied("; ".join(decision.reasons))
|
|
116
|
-
scopes = tuple(decision.scopes)
|
|
117
|
-
|
|
118
|
-
gate = getattr(self.runtime, "admin_gate", None)
|
|
119
|
-
admin_visible = True
|
|
120
|
-
if gate is not None:
|
|
121
|
-
try:
|
|
122
|
-
admin_visible = bool(gate.actor_is_admin(actor))
|
|
123
|
-
except Exception:
|
|
124
|
-
admin_visible = False
|
|
125
|
-
|
|
126
116
|
recipient_terms = self._terms(actor, addressed_to)
|
|
127
117
|
sender_terms = self._terms(actor, sent_by)
|
|
128
118
|
|
|
@@ -135,12 +125,11 @@ class HandoffLookupService:
|
|
|
135
125
|
continue
|
|
136
126
|
relative = path.relative_to(self.memory_root).as_posix()
|
|
137
127
|
canonical = f"memory/{relative}"
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if not admin_visible and gate is not None and gate.path_is_admin(canonical):
|
|
128
|
+
verdict = check(decision, canonical, "read", root=self.memory_root)
|
|
129
|
+
if not verdict.allowed:
|
|
141
130
|
continue
|
|
142
131
|
try:
|
|
143
|
-
fields, _ = split_frontmatter(
|
|
132
|
+
fields, _ = split_frontmatter(verdict.resolved.read_text(encoding="utf-8"))
|
|
144
133
|
except (OSError, ValueError):
|
|
145
134
|
continue
|
|
146
135
|
status = (_scalar(fields, "status") or "active").casefold()
|
|
@@ -181,7 +170,7 @@ class HandoffLookupService:
|
|
|
181
170
|
return None
|
|
182
171
|
if who.strip().casefold() in ("me", "self", actor.actor.actor_id.casefold()):
|
|
183
172
|
return _self_terms(actor)
|
|
184
|
-
return _member_terms(self.root, actor, who)
|
|
173
|
+
return _member_terms(self.root, actor, who, self.runtime.authorize(actor, Permission.READ))
|
|
185
174
|
|
|
186
175
|
@staticmethod
|
|
187
176
|
def _addressed_match(text: str, terms: frozenset[str]) -> bool:
|