create-egregore 0.20.2 → 0.21.0-runtime-mvp.2
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 +1013 -16
- package/bin/cli.js +11 -0
- package/lib/claude-runtime.js +192 -0
- package/lib/local.js +38 -31
- package/lib/pi-runtime.js +1 -0
- package/lib/prime-runtime.js +1 -0
- package/lib/runtime-identity.js +146 -0
- package/lib/setup.js +116 -17
- package/package.json +10 -6
- package/runtime/claude/.claude/context/commit-format.md +110 -0
- package/runtime/claude/.claude/context/git-language.md +46 -0
- package/runtime/claude/.claude/context/notification-consent.md +66 -0
- package/runtime/claude/.claude/context/pr-format.md +67 -0
- package/runtime/claude/.claude/context/telemetry.md +133 -0
- package/runtime/claude/.claude/hooks/boundary-check.sh +333 -0
- package/runtime/claude/.claude/hooks/branch-guard.sh +231 -0
- package/runtime/claude/.claude/hooks/card-context.sh +38 -0
- package/runtime/claude/.claude/hooks/emissary-detect.sh +53 -0
- package/runtime/claude/.claude/hooks/memory-refresh.sh +67 -0
- package/runtime/claude/.claude/hooks/onboarding-guard.sh +86 -0
- package/runtime/claude/.claude/hooks/retrieval-context.sh +43 -0
- package/runtime/claude/.claude/rules/voice-bedrock.md +73 -0
- package/runtime/claude/.claude/settings.json +131 -0
- package/runtime/claude/.claude/skills/activity/SKILL.md +68 -0
- package/runtime/claude/.claude/skills/announce/SKILL.md +54 -0
- package/runtime/claude/.claude/skills/archive/SKILL.md +71 -0
- package/runtime/claude/.claude/skills/branch/SKILL.md +147 -0
- package/runtime/claude/.claude/skills/checkup/SKILL.md +159 -0
- package/runtime/claude/.claude/skills/commit/SKILL.md +103 -0
- package/runtime/claude/.claude/skills/contribute/SKILL.md +343 -0
- package/runtime/claude/.claude/skills/create-skill/SKILL.md +135 -0
- package/runtime/claude/.claude/skills/dark-mode/SKILL.md +410 -0
- package/runtime/claude/.claude/skills/dashboard/SKILL.md +43 -0
- package/runtime/claude/.claude/skills/deep-reflect/REPORT.md +140 -0
- package/runtime/claude/.claude/skills/deep-reflect/RESEARCH.md +132 -0
- package/runtime/claude/.claude/skills/deep-reflect/ROLES.md +104 -0
- package/runtime/claude/.claude/skills/deep-reflect/SKILL.md +170 -0
- package/runtime/claude/.claude/skills/delete-user/SKILL.md +150 -0
- package/runtime/claude/.claude/skills/emissary/SKILL.md +220 -0
- package/runtime/claude/.claude/skills/handoff/SKILL.md +124 -0
- package/runtime/claude/.claude/skills/harvest/AUDIT.md +546 -0
- package/runtime/claude/.claude/skills/harvest/FORMAT.md +436 -0
- package/runtime/claude/.claude/skills/harvest/PROCESS.md +395 -0
- package/runtime/claude/.claude/skills/harvest/QUESTION_PALETTE.md +76 -0
- package/runtime/claude/.claude/skills/harvest/SKILL.md +100 -0
- package/runtime/claude/.claude/skills/ingest/SKILL.md +112 -0
- package/runtime/claude/.claude/skills/ingest-corpus/SKILL.md +187 -0
- package/runtime/claude/.claude/skills/ingest-notion/SKILL.md +214 -0
- package/runtime/claude/.claude/skills/ingest-user-interview/SKILL.md +65 -0
- package/runtime/claude/.claude/skills/ingest-user-interview/references/analysis-contract.md +125 -0
- package/runtime/claude/.claude/skills/invite/SKILL.md +111 -0
- package/runtime/claude/.claude/skills/issue/SKILL.md +104 -0
- package/runtime/claude/.claude/skills/me/SKILL.md +81 -0
- package/runtime/claude/.claude/skills/meeting/SKILL.md +76 -0
- package/runtime/claude/.claude/skills/meeting/references/analysis-contract.md +102 -0
- package/runtime/claude/.claude/skills/note/SKILL.md +71 -0
- package/runtime/claude/.claude/skills/notion-connect/SKILL.md +124 -0
- package/runtime/claude/.claude/skills/onboarding/SKILL.md +159 -0
- package/runtime/claude/.claude/skills/pr/SKILL.md +85 -0
- package/runtime/claude/.claude/skills/pull/SKILL.md +79 -0
- package/runtime/claude/.claude/skills/push/SKILL.md +47 -0
- package/runtime/claude/.claude/skills/quest/SKILL.md +121 -0
- package/runtime/claude/.claude/skills/reflect/SKILL.md +69 -0
- package/runtime/claude/.claude/skills/review-pr/SKILL.md +276 -0
- package/runtime/claude/.claude/skills/save/SKILL.md +92 -0
- package/runtime/claude/.claude/skills/slack-connect/SKILL.md +148 -0
- package/runtime/claude/.claude/skills/summon/SKILL.md +240 -0
- package/runtime/claude/.claude/skills/sync-repos/SKILL.md +82 -0
- package/runtime/claude/.claude/skills/teams-connect/SKILL.md +216 -0
- package/runtime/claude/.claude/skills/telegram-connect/SKILL.md +110 -0
- package/runtime/claude/.claude/skills/telemetry/SKILL.md +117 -0
- package/runtime/claude/.claude/skills/test/SKILL.md +87 -0
- package/runtime/claude/.claude/skills/the-spiral/SKILL.md +492 -0
- package/runtime/claude/.claude/skills/tui-design/SKILL.md +188 -0
- package/runtime/claude/.claude/skills/tutorial/SKILL.md +100 -0
- package/runtime/claude/.claude/skills/update/SKILL.md +265 -0
- package/runtime/claude/.claude/skills/view/SKILL.md +393 -0
- package/runtime/claude/.claude/skills/view/compose-scaffold.html +221 -0
- package/runtime/claude/.claude/skills/visual-explain/SKILL.md +132 -0
- package/runtime/claude/.claude/skills/wrap/SKILL.md +98 -0
- package/runtime/claude/CLAUDE.md +447 -0
- package/runtime/claude/bin/activity-action.sh +92 -0
- package/runtime/claude/bin/activity-data.sh +17 -0
- package/runtime/claude/bin/agent.sh +783 -0
- package/runtime/claude/bin/artifact-register.sh +164 -0
- package/runtime/claude/bin/artifact-writeback.sh +53 -0
- package/runtime/claude/bin/artifacts.sh +288 -0
- package/runtime/claude/bin/attendant.sh +707 -0
- package/runtime/claude/bin/autosave.sh +112 -0
- package/runtime/claude/bin/boundary.sh +253 -0
- package/runtime/claude/bin/build-sealed-dataroom.mjs +722 -0
- package/runtime/claude/bin/capture-reconcile.sh +69 -0
- package/runtime/claude/bin/capture-run.sh +357 -0
- package/runtime/claude/bin/codex-install-skills.sh +78 -0
- package/runtime/claude/bin/codex-render-spec.mjs +462 -0
- package/runtime/claude/bin/codex-session-start.sh +275 -0
- package/runtime/claude/bin/codex-skill-render.mjs +349 -0
- package/runtime/claude/bin/codex-sync-skills.sh +237 -0
- package/runtime/claude/bin/connect-refresh.sh +33 -0
- package/runtime/claude/bin/contribute-guard.sh +79 -0
- package/runtime/claude/bin/corpus_map.py +806 -0
- package/runtime/claude/bin/corpus_survey.py +304 -0
- package/runtime/claude/bin/dashboard-data.sh +18 -0
- package/runtime/claude/bin/github-auth.sh +121 -0
- package/runtime/claude/bin/graph-batch.sh +86 -0
- package/runtime/claude/bin/graph-op.sh +1106 -0
- package/runtime/claude/bin/graph-projection.sh +54 -0
- package/runtime/claude/bin/graph-wal.sh +242 -0
- package/runtime/claude/bin/graph.sh +267 -0
- package/runtime/claude/bin/handoff-lifecycle-job.sh +146 -0
- package/runtime/claude/bin/handoff-lifecycle.sh +280 -0
- package/runtime/claude/bin/handoff-pr-backfill.sh +86 -0
- package/runtime/claude/bin/handoff-preview.sh +169 -0
- package/runtime/claude/bin/handoff-run.sh +692 -0
- package/runtime/claude/bin/handoff-save-egregore.sh +224 -0
- package/runtime/claude/bin/index-handoff.sh +322 -0
- package/runtime/claude/bin/ingest-graph.sh +86 -0
- package/runtime/claude/bin/ingest-item.sh +10 -0
- package/runtime/claude/bin/ingest-surface.html +495 -0
- package/runtime/claude/bin/ingest.py +1011 -0
- package/runtime/claude/bin/ingest.sh +308 -0
- package/runtime/claude/bin/ingest_graph.py +856 -0
- package/runtime/claude/bin/ingest_surface.py +544 -0
- package/runtime/claude/bin/invite.sh +6 -0
- package/runtime/claude/bin/issue.sh +10 -0
- package/runtime/claude/bin/knowledge.sh +10 -0
- package/runtime/claude/bin/lib/artifact-id.sh +46 -0
- package/runtime/claude/bin/lib/config.sh +136 -0
- package/runtime/claude/bin/lib/context.sh +642 -0
- package/runtime/claude/bin/lib/dashboard-artifact.sh +242 -0
- package/runtime/claude/bin/lib/ensure-develop.sh +183 -0
- package/runtime/claude/bin/lib/git-message.sh +37 -0
- package/runtime/claude/bin/lib/git-sync.sh +494 -0
- package/runtime/claude/bin/lib/greeting.sh +548 -0
- package/runtime/claude/bin/lib/handoff-meta.sh +97 -0
- package/runtime/claude/bin/lib/hash.sh +13 -0
- package/runtime/claude/bin/lib/identity.sh +213 -0
- package/runtime/claude/bin/lib/metrics.sh +374 -0
- package/runtime/claude/bin/lib/noncode.sh +31 -0
- package/runtime/claude/bin/lib/permissions.sh +106 -0
- package/runtime/claude/bin/lib/time.sh +56 -0
- package/runtime/claude/bin/lib/worktree-links.sh +85 -0
- package/runtime/claude/bin/node-run.sh +42 -0
- package/runtime/claude/bin/notification.sh +6 -0
- package/runtime/claude/bin/notify.sh +636 -0
- package/runtime/claude/bin/observe-context.sh +14 -0
- package/runtime/claude/bin/onboarding.sh +6 -0
- package/runtime/claude/bin/person.py +634 -0
- package/runtime/claude/bin/person.sh +481 -0
- package/runtime/claude/bin/pi-render-spec.mjs +140 -0
- package/runtime/claude/bin/post-update.sh +37 -0
- package/runtime/claude/bin/prime-render-spec.mjs +147 -0
- package/runtime/claude/bin/publish-artifact.sh +324 -0
- package/runtime/claude/bin/publish-references.sh +144 -0
- package/runtime/claude/bin/quest.sh +9 -0
- package/runtime/claude/bin/question.sh +8 -0
- package/runtime/claude/bin/repo-state.sh +96 -0
- package/runtime/claude/bin/research-ingest.sh +7 -0
- package/runtime/claude/bin/restore-owned-skills.sh +67 -0
- package/runtime/claude/bin/retrieval_eval.py +385 -0
- package/runtime/claude/bin/runtime-identity.sh +70 -0
- package/runtime/claude/bin/runtime-upgrade.sh +43 -0
- package/runtime/claude/bin/scroll.sh +9 -0
- package/runtime/claude/bin/search.sh +318 -0
- package/runtime/claude/bin/session-autosave.sh +220 -0
- package/runtime/claude/bin/session-log.sh +199 -0
- package/runtime/claude/bin/session-start.sh +696 -0
- package/runtime/claude/bin/settings.sh +532 -0
- package/runtime/claude/bin/startup-check.sh +136 -0
- package/runtime/claude/bin/statusline.sh +41 -0
- package/runtime/claude/bin/telemetry.sh +666 -0
- package/runtime/claude/bin/test-changes.sh +344 -0
- package/runtime/claude/bin/thread.sh +9 -0
- package/runtime/claude/bin/todo.sh +9 -0
- package/runtime/claude/bin/view-render.sh +126 -0
- package/runtime/claude/bin/worktree-create.sh +145 -0
- package/runtime/claude/bin/worktree-remove.sh +50 -0
- package/runtime/claude/bin/worktree.sh +198 -0
- package/runtime/claude/egregore_runtime/__init__.py +228 -0
- package/runtime/claude/egregore_runtime/adapters/__init__.py +5 -0
- package/runtime/claude/egregore_runtime/adapters/invitation.py +237 -0
- package/runtime/claude/egregore_runtime/adapters/notification.py +59 -0
- package/runtime/claude/egregore_runtime/adapters/qmd.py +1701 -0
- package/runtime/claude/egregore_runtime/adapters/qmd_cli.py +210 -0
- package/runtime/claude/egregore_runtime/adapters/qmd_embed_worker.py +59 -0
- package/runtime/claude/egregore_runtime/admin_gate.py +102 -0
- package/runtime/claude/egregore_runtime/artifacts.py +523 -0
- package/runtime/claude/egregore_runtime/contracts.py +499 -0
- package/runtime/claude/egregore_runtime/entitlements.py +85 -0
- package/runtime/claude/egregore_runtime/errors.py +10 -0
- package/runtime/claude/egregore_runtime/graph.py +356 -0
- package/runtime/claude/egregore_runtime/handoff_lookup.py +196 -0
- package/runtime/claude/egregore_runtime/harness_cli.py +1221 -0
- package/runtime/claude/egregore_runtime/identity.py +734 -0
- package/runtime/claude/egregore_runtime/identity_cli.py +91 -0
- package/runtime/claude/egregore_runtime/ingest.py +575 -0
- package/runtime/claude/egregore_runtime/ingest_cli.py +254 -0
- package/runtime/claude/egregore_runtime/ingest_index_cli.py +147 -0
- package/runtime/claude/egregore_runtime/invitation_cli.py +51 -0
- package/runtime/claude/egregore_runtime/invitations.py +116 -0
- package/runtime/claude/egregore_runtime/issue_cli.py +305 -0
- package/runtime/claude/egregore_runtime/issues.py +359 -0
- package/runtime/claude/egregore_runtime/knowledge.py +376 -0
- package/runtime/claude/egregore_runtime/knowledge_cli.py +173 -0
- package/runtime/claude/egregore_runtime/lifecycle.py +888 -0
- package/runtime/claude/egregore_runtime/lifecycle_cli.py +269 -0
- package/runtime/claude/egregore_runtime/notification_cli.py +125 -0
- package/runtime/claude/egregore_runtime/notifications.py +230 -0
- package/runtime/claude/egregore_runtime/observe.py +342 -0
- package/runtime/claude/egregore_runtime/onboarding.py +290 -0
- package/runtime/claude/egregore_runtime/onboarding_cli.py +110 -0
- package/runtime/claude/egregore_runtime/policy.py +144 -0
- package/runtime/claude/egregore_runtime/profile.py +193 -0
- package/runtime/claude/egregore_runtime/quest_cli.py +216 -0
- package/runtime/claude/egregore_runtime/question_cli.py +180 -0
- package/runtime/claude/egregore_runtime/questions.py +304 -0
- package/runtime/claude/egregore_runtime/quests.py +419 -0
- package/runtime/claude/egregore_runtime/readiness.py +379 -0
- package/runtime/claude/egregore_runtime/research_ingest.py +256 -0
- package/runtime/claude/egregore_runtime/research_ingest_cli.py +44 -0
- package/runtime/claude/egregore_runtime/runtime.py +464 -0
- package/runtime/claude/egregore_runtime/scroll_cli.py +185 -0
- package/runtime/claude/egregore_runtime/scrolls.py +726 -0
- package/runtime/claude/egregore_runtime/status.py +412 -0
- package/runtime/claude/egregore_runtime/status_cli.py +174 -0
- package/runtime/claude/egregore_runtime/sync.py +218 -0
- package/runtime/claude/egregore_runtime/teamsync.py +139 -0
- package/runtime/claude/egregore_runtime/telemetry.py +640 -0
- package/runtime/claude/egregore_runtime/thread_cli.py +173 -0
- package/runtime/claude/egregore_runtime/threads.py +517 -0
- package/runtime/claude/egregore_runtime/todo_cli.py +142 -0
- package/runtime/claude/egregore_runtime/todos.py +244 -0
- package/runtime/claude/egregore_runtime/upgrade.py +770 -0
- package/runtime/claude/egregore_runtime/upgrade_worker.py +53 -0
- package/runtime/claude/egregore_runtime/writeback.py +391 -0
- package/runtime/claude/egregore_runtime/writeback_cli.py +114 -0
- package/runtime/claude/manifest.json +284 -0
- package/runtime/codex/.codex/hooks/search-hint.js +21 -52
- package/runtime/codex/.codex/hooks.json +3 -3
- package/runtime/codex/.codex/skills/activity/SKILL.md +23 -66
- package/runtime/codex/.codex/skills/announce/SKILL.md +27 -50
- package/runtime/codex/.codex/skills/archive/SKILL.md +21 -36
- package/runtime/codex/.codex/skills/checkup/SKILL.md +77 -36
- package/runtime/codex/.codex/skills/dark-mode/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/dashboard/SKILL.md +17 -47
- package/runtime/codex/.codex/skills/handoff/SKILL.md +84 -133
- package/runtime/codex/.codex/skills/harvest/SKILL.md +32 -117
- package/runtime/codex/.codex/skills/ingest/SKILL.md +47 -25
- package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +18 -36
- package/runtime/codex/.codex/skills/ingest-user-interview/references/analysis-contract.md +42 -0
- package/runtime/codex/.codex/skills/invite/SKILL.md +21 -35
- package/runtime/codex/.codex/skills/issue/SKILL.md +60 -41
- package/runtime/codex/.codex/skills/me/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/meeting/SKILL.md +16 -36
- package/runtime/codex/.codex/skills/meeting/references/analysis-contract.md +48 -0
- package/runtime/codex/.codex/skills/note/SKILL.md +30 -25
- package/runtime/codex/.codex/skills/onboarding/SKILL.md +36 -41
- package/runtime/codex/.codex/skills/pull/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/quest/SKILL.md +27 -66
- package/runtime/codex/.codex/skills/reflect/SKILL.md +22 -36
- package/runtime/codex/.codex/skills/save/SKILL.md +2 -2
- package/runtime/codex/.codex/skills/telemetry/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/test/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/tutorial/SKILL.md +40 -41
- package/runtime/codex/.codex/skills/view/SKILL.md +115 -111
- package/runtime/codex/.codex/skills/wrap/SKILL.md +45 -41
- package/runtime/codex/.codex/spec-manifest.json +9 -9
- package/runtime/codex/AGENTS.md +62 -58
- package/runtime/codex/bin/activity-action.sh +92 -0
- package/runtime/codex/bin/activity-data.sh +12 -233
- package/runtime/codex/bin/agent.sh +84 -100
- package/runtime/codex/bin/artifact-register.sh +164 -0
- package/runtime/codex/bin/artifact-writeback.sh +53 -0
- package/runtime/codex/bin/artifacts.sh +288 -0
- package/runtime/codex/bin/build-sealed-dataroom.mjs +722 -0
- package/runtime/codex/bin/capture-run.sh +18 -2
- package/runtime/codex/bin/codex-render-spec.mjs +41 -16
- package/runtime/codex/bin/codex-session-start.sh +14 -4
- package/runtime/codex/bin/codex-skill-render.mjs +9 -7
- package/runtime/codex/bin/corpus_map.py +806 -0
- package/runtime/codex/bin/corpus_survey.py +304 -0
- package/runtime/codex/bin/dashboard-data.sh +11 -171
- package/runtime/codex/bin/github-auth.sh +121 -0
- package/runtime/codex/bin/graph-batch.sh +7 -0
- package/runtime/codex/bin/graph-op.sh +4 -0
- package/runtime/codex/bin/graph-projection.sh +54 -0
- package/runtime/codex/bin/graph-wal.sh +13 -0
- package/runtime/codex/bin/graph.sh +44 -0
- package/runtime/codex/bin/handoff-lifecycle-job.sh +146 -0
- package/runtime/codex/bin/handoff-lifecycle.sh +280 -0
- package/runtime/codex/bin/handoff-preview.sh +169 -0
- package/runtime/codex/bin/handoff-run.sh +42 -26
- package/runtime/codex/bin/ingest-graph.sh +6 -0
- package/runtime/codex/bin/ingest-item.sh +10 -0
- package/runtime/codex/bin/ingest.sh +59 -30
- package/runtime/codex/bin/invite.sh +6 -0
- package/runtime/codex/bin/issue.sh +10 -0
- package/runtime/codex/bin/knowledge.sh +10 -0
- package/runtime/codex/bin/lib/config.sh +20 -0
- package/runtime/codex/bin/lib/context.sh +46 -25
- package/runtime/codex/bin/lib/git-sync.sh +70 -6
- package/runtime/codex/bin/lib/greeting.sh +34 -5
- package/runtime/codex/bin/lib/handoff-meta.sh +97 -0
- package/runtime/codex/bin/lib/worktree-links.sh +6 -0
- package/runtime/codex/bin/node-run.sh +42 -0
- package/runtime/codex/bin/notification.sh +6 -0
- package/runtime/codex/bin/observe-context.sh +14 -0
- package/runtime/codex/bin/onboarding.sh +6 -0
- package/runtime/codex/bin/person.py +4 -0
- package/runtime/codex/bin/person.sh +43 -10
- package/runtime/codex/bin/pi-render-spec.mjs +140 -0
- package/runtime/codex/bin/post-update.sh +37 -0
- package/runtime/codex/bin/prime-render-spec.mjs +147 -0
- package/runtime/codex/bin/publish-artifact.sh +55 -2
- package/runtime/codex/bin/quest.sh +9 -0
- package/runtime/codex/bin/question.sh +8 -0
- package/runtime/codex/bin/research-ingest.sh +7 -0
- package/runtime/codex/bin/restore-owned-skills.sh +67 -0
- package/runtime/codex/bin/retrieval_eval.py +385 -0
- package/runtime/codex/bin/runtime-identity.sh +70 -0
- package/runtime/codex/bin/runtime-upgrade.sh +43 -0
- package/runtime/codex/bin/scroll.sh +9 -0
- package/runtime/codex/bin/search.sh +231 -195
- package/runtime/codex/bin/settings.sh +174 -5
- package/runtime/codex/bin/startup-check.sh +136 -0
- package/runtime/codex/bin/telemetry.sh +522 -247
- package/runtime/codex/bin/test-changes.sh +344 -0
- package/runtime/codex/bin/thread.sh +9 -0
- package/runtime/codex/bin/todo.sh +9 -0
- package/runtime/codex/bin/view-render.sh +126 -0
- package/runtime/codex/bin/worktree-create.sh +19 -5
- package/runtime/codex/egregore_runtime/__init__.py +228 -0
- package/runtime/codex/egregore_runtime/adapters/__init__.py +5 -0
- package/runtime/codex/egregore_runtime/adapters/invitation.py +237 -0
- package/runtime/codex/egregore_runtime/adapters/notification.py +59 -0
- package/runtime/codex/egregore_runtime/adapters/qmd.py +1701 -0
- package/runtime/codex/egregore_runtime/adapters/qmd_cli.py +210 -0
- package/runtime/codex/egregore_runtime/adapters/qmd_embed_worker.py +59 -0
- package/runtime/codex/egregore_runtime/admin_gate.py +102 -0
- package/runtime/codex/egregore_runtime/artifacts.py +523 -0
- package/runtime/codex/egregore_runtime/contracts.py +499 -0
- package/runtime/codex/egregore_runtime/entitlements.py +85 -0
- package/runtime/codex/egregore_runtime/errors.py +10 -0
- package/runtime/codex/egregore_runtime/graph.py +356 -0
- package/runtime/codex/egregore_runtime/handoff_lookup.py +196 -0
- package/runtime/codex/egregore_runtime/harness_cli.py +1221 -0
- package/runtime/codex/egregore_runtime/identity.py +734 -0
- package/runtime/codex/egregore_runtime/identity_cli.py +91 -0
- package/runtime/codex/egregore_runtime/ingest.py +575 -0
- package/runtime/codex/egregore_runtime/ingest_cli.py +254 -0
- package/runtime/codex/egregore_runtime/ingest_index_cli.py +147 -0
- package/runtime/codex/egregore_runtime/invitation_cli.py +51 -0
- package/runtime/codex/egregore_runtime/invitations.py +116 -0
- package/runtime/codex/egregore_runtime/issue_cli.py +305 -0
- package/runtime/codex/egregore_runtime/issues.py +359 -0
- package/runtime/codex/egregore_runtime/knowledge.py +376 -0
- package/runtime/codex/egregore_runtime/knowledge_cli.py +173 -0
- package/runtime/codex/egregore_runtime/lifecycle.py +888 -0
- package/runtime/codex/egregore_runtime/lifecycle_cli.py +269 -0
- package/runtime/codex/egregore_runtime/notification_cli.py +125 -0
- package/runtime/codex/egregore_runtime/notifications.py +230 -0
- package/runtime/codex/egregore_runtime/observe.py +342 -0
- package/runtime/codex/egregore_runtime/onboarding.py +290 -0
- package/runtime/codex/egregore_runtime/onboarding_cli.py +110 -0
- package/runtime/codex/egregore_runtime/policy.py +144 -0
- package/runtime/codex/egregore_runtime/profile.py +193 -0
- package/runtime/codex/egregore_runtime/quest_cli.py +216 -0
- package/runtime/codex/egregore_runtime/question_cli.py +180 -0
- package/runtime/codex/egregore_runtime/questions.py +304 -0
- package/runtime/codex/egregore_runtime/quests.py +419 -0
- package/runtime/codex/egregore_runtime/readiness.py +379 -0
- package/runtime/codex/egregore_runtime/research_ingest.py +256 -0
- package/runtime/codex/egregore_runtime/research_ingest_cli.py +44 -0
- package/runtime/codex/egregore_runtime/runtime.py +464 -0
- package/runtime/codex/egregore_runtime/scroll_cli.py +185 -0
- package/runtime/codex/egregore_runtime/scrolls.py +726 -0
- package/runtime/codex/egregore_runtime/status.py +412 -0
- package/runtime/codex/egregore_runtime/status_cli.py +174 -0
- package/runtime/codex/egregore_runtime/sync.py +218 -0
- package/runtime/codex/egregore_runtime/teamsync.py +139 -0
- package/runtime/codex/egregore_runtime/telemetry.py +640 -0
- package/runtime/codex/egregore_runtime/thread_cli.py +173 -0
- package/runtime/codex/egregore_runtime/threads.py +517 -0
- package/runtime/codex/egregore_runtime/todo_cli.py +142 -0
- package/runtime/codex/egregore_runtime/todos.py +244 -0
- package/runtime/codex/egregore_runtime/upgrade.py +770 -0
- package/runtime/codex/egregore_runtime/upgrade_worker.py +53 -0
- package/runtime/codex/egregore_runtime/writeback.py +391 -0
- package/runtime/codex/egregore_runtime/writeback_cli.py +114 -0
- package/runtime/codex/manifest.json +98 -1
- package/runtime/pi/.codex/hooks/search-hint.js +21 -52
- package/runtime/pi/.codex/hooks.json +3 -3
- package/runtime/pi/.codex/skills/activity/SKILL.md +23 -66
- package/runtime/pi/.codex/skills/announce/SKILL.md +27 -50
- package/runtime/pi/.codex/skills/archive/SKILL.md +21 -36
- package/runtime/pi/.codex/skills/checkup/SKILL.md +77 -36
- package/runtime/pi/.codex/skills/dark-mode/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/dashboard/SKILL.md +17 -47
- package/runtime/pi/.codex/skills/handoff/SKILL.md +84 -133
- package/runtime/pi/.codex/skills/harvest/SKILL.md +32 -117
- package/runtime/pi/.codex/skills/ingest/SKILL.md +47 -25
- package/runtime/pi/.codex/skills/ingest-user-interview/SKILL.md +18 -36
- package/runtime/pi/.codex/skills/ingest-user-interview/references/analysis-contract.md +42 -0
- package/runtime/pi/.codex/skills/invite/SKILL.md +21 -35
- package/runtime/pi/.codex/skills/issue/SKILL.md +60 -41
- package/runtime/pi/.codex/skills/me/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/meeting/SKILL.md +16 -36
- package/runtime/pi/.codex/skills/meeting/references/analysis-contract.md +48 -0
- package/runtime/pi/.codex/skills/note/SKILL.md +30 -25
- package/runtime/pi/.codex/skills/onboarding/SKILL.md +36 -41
- package/runtime/pi/.codex/skills/pull/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/quest/SKILL.md +27 -66
- package/runtime/pi/.codex/skills/reflect/SKILL.md +22 -36
- package/runtime/pi/.codex/skills/save/SKILL.md +2 -2
- package/runtime/pi/.codex/skills/telemetry/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/test/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/tutorial/SKILL.md +40 -41
- package/runtime/pi/.codex/skills/view/SKILL.md +115 -111
- package/runtime/pi/.codex/skills/wrap/SKILL.md +45 -41
- package/runtime/pi/.codex/spec-manifest.json +9 -9
- package/runtime/pi/.pi/APPEND_SYSTEM.md +57 -48
- package/runtime/pi/.pi/extensions/egregore.ts +7 -0
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +62 -58
- package/runtime/pi/bin/activity-action.sh +92 -0
- package/runtime/pi/bin/activity-data.sh +12 -233
- package/runtime/pi/bin/agent.sh +84 -100
- package/runtime/pi/bin/artifact-register.sh +164 -0
- package/runtime/pi/bin/artifact-writeback.sh +53 -0
- package/runtime/pi/bin/artifacts.sh +288 -0
- package/runtime/pi/bin/build-sealed-dataroom.mjs +722 -0
- package/runtime/pi/bin/capture-run.sh +18 -2
- package/runtime/pi/bin/codex-render-spec.mjs +41 -16
- package/runtime/pi/bin/codex-session-start.sh +14 -4
- package/runtime/pi/bin/codex-skill-render.mjs +9 -7
- package/runtime/pi/bin/corpus_map.py +806 -0
- package/runtime/pi/bin/corpus_survey.py +304 -0
- package/runtime/pi/bin/dashboard-data.sh +11 -171
- package/runtime/pi/bin/github-auth.sh +121 -0
- package/runtime/pi/bin/graph-batch.sh +7 -0
- package/runtime/pi/bin/graph-op.sh +4 -0
- package/runtime/pi/bin/graph-projection.sh +54 -0
- package/runtime/pi/bin/graph-wal.sh +13 -0
- package/runtime/pi/bin/graph.sh +44 -0
- package/runtime/pi/bin/handoff-lifecycle-job.sh +146 -0
- package/runtime/pi/bin/handoff-lifecycle.sh +280 -0
- package/runtime/pi/bin/handoff-preview.sh +169 -0
- package/runtime/pi/bin/handoff-run.sh +42 -26
- package/runtime/pi/bin/ingest-graph.sh +6 -0
- package/runtime/pi/bin/ingest-item.sh +10 -0
- package/runtime/pi/bin/ingest.sh +59 -30
- package/runtime/pi/bin/invite.sh +6 -0
- package/runtime/pi/bin/issue.sh +10 -0
- package/runtime/pi/bin/knowledge.sh +10 -0
- package/runtime/pi/bin/lib/config.sh +20 -0
- package/runtime/pi/bin/lib/context.sh +46 -25
- package/runtime/pi/bin/lib/git-sync.sh +70 -6
- package/runtime/pi/bin/lib/greeting.sh +34 -5
- package/runtime/pi/bin/lib/handoff-meta.sh +97 -0
- package/runtime/pi/bin/lib/worktree-links.sh +6 -0
- package/runtime/pi/bin/node-run.sh +42 -0
- package/runtime/pi/bin/notification.sh +6 -0
- package/runtime/pi/bin/observe-context.sh +14 -0
- package/runtime/pi/bin/onboarding.sh +6 -0
- package/runtime/pi/bin/person.py +4 -0
- package/runtime/pi/bin/person.sh +43 -10
- package/runtime/pi/bin/pi-observe-context.mjs +93 -0
- package/runtime/pi/bin/pi-product-workflows.mjs +1 -1
- package/runtime/pi/bin/pi-render-spec.mjs +1 -1
- package/runtime/pi/bin/post-update.sh +37 -0
- package/runtime/pi/bin/prime-render-spec.mjs +147 -0
- package/runtime/pi/bin/publish-artifact.sh +55 -2
- package/runtime/pi/bin/quest.sh +9 -0
- package/runtime/pi/bin/question.sh +8 -0
- package/runtime/pi/bin/research-ingest.sh +7 -0
- package/runtime/pi/bin/restore-owned-skills.sh +67 -0
- package/runtime/pi/bin/retrieval_eval.py +385 -0
- package/runtime/pi/bin/runtime-identity.sh +70 -0
- package/runtime/pi/bin/runtime-upgrade.sh +43 -0
- package/runtime/pi/bin/scroll.sh +9 -0
- package/runtime/pi/bin/search.sh +231 -195
- package/runtime/pi/bin/settings.sh +174 -5
- package/runtime/pi/bin/startup-check.sh +136 -0
- package/runtime/pi/bin/telemetry.sh +522 -247
- package/runtime/pi/bin/test-changes.sh +344 -0
- package/runtime/pi/bin/thread.sh +9 -0
- package/runtime/pi/bin/todo.sh +9 -0
- package/runtime/pi/bin/view-render.sh +126 -0
- package/runtime/pi/bin/worktree-create.sh +19 -5
- package/runtime/pi/egregore_runtime/__init__.py +228 -0
- package/runtime/pi/egregore_runtime/adapters/__init__.py +5 -0
- package/runtime/pi/egregore_runtime/adapters/invitation.py +237 -0
- package/runtime/pi/egregore_runtime/adapters/notification.py +59 -0
- package/runtime/pi/egregore_runtime/adapters/qmd.py +1701 -0
- package/runtime/pi/egregore_runtime/adapters/qmd_cli.py +210 -0
- package/runtime/pi/egregore_runtime/adapters/qmd_embed_worker.py +59 -0
- package/runtime/pi/egregore_runtime/admin_gate.py +102 -0
- package/runtime/pi/egregore_runtime/artifacts.py +523 -0
- package/runtime/pi/egregore_runtime/contracts.py +499 -0
- package/runtime/pi/egregore_runtime/entitlements.py +85 -0
- package/runtime/pi/egregore_runtime/errors.py +10 -0
- package/runtime/pi/egregore_runtime/graph.py +356 -0
- package/runtime/pi/egregore_runtime/handoff_lookup.py +196 -0
- package/runtime/pi/egregore_runtime/harness_cli.py +1221 -0
- package/runtime/pi/egregore_runtime/identity.py +734 -0
- package/runtime/pi/egregore_runtime/identity_cli.py +91 -0
- package/runtime/pi/egregore_runtime/ingest.py +575 -0
- package/runtime/pi/egregore_runtime/ingest_cli.py +254 -0
- package/runtime/pi/egregore_runtime/ingest_index_cli.py +147 -0
- package/runtime/pi/egregore_runtime/invitation_cli.py +51 -0
- package/runtime/pi/egregore_runtime/invitations.py +116 -0
- package/runtime/pi/egregore_runtime/issue_cli.py +305 -0
- package/runtime/pi/egregore_runtime/issues.py +359 -0
- package/runtime/pi/egregore_runtime/knowledge.py +376 -0
- package/runtime/pi/egregore_runtime/knowledge_cli.py +173 -0
- package/runtime/pi/egregore_runtime/lifecycle.py +888 -0
- package/runtime/pi/egregore_runtime/lifecycle_cli.py +269 -0
- package/runtime/pi/egregore_runtime/notification_cli.py +125 -0
- package/runtime/pi/egregore_runtime/notifications.py +230 -0
- package/runtime/pi/egregore_runtime/observe.py +342 -0
- package/runtime/pi/egregore_runtime/onboarding.py +290 -0
- package/runtime/pi/egregore_runtime/onboarding_cli.py +110 -0
- package/runtime/pi/egregore_runtime/policy.py +144 -0
- package/runtime/pi/egregore_runtime/profile.py +193 -0
- package/runtime/pi/egregore_runtime/quest_cli.py +216 -0
- package/runtime/pi/egregore_runtime/question_cli.py +180 -0
- package/runtime/pi/egregore_runtime/questions.py +304 -0
- package/runtime/pi/egregore_runtime/quests.py +419 -0
- package/runtime/pi/egregore_runtime/readiness.py +379 -0
- package/runtime/pi/egregore_runtime/research_ingest.py +256 -0
- package/runtime/pi/egregore_runtime/research_ingest_cli.py +44 -0
- package/runtime/pi/egregore_runtime/runtime.py +464 -0
- package/runtime/pi/egregore_runtime/scroll_cli.py +185 -0
- package/runtime/pi/egregore_runtime/scrolls.py +726 -0
- package/runtime/pi/egregore_runtime/status.py +412 -0
- package/runtime/pi/egregore_runtime/status_cli.py +174 -0
- package/runtime/pi/egregore_runtime/sync.py +218 -0
- package/runtime/pi/egregore_runtime/teamsync.py +139 -0
- package/runtime/pi/egregore_runtime/telemetry.py +640 -0
- package/runtime/pi/egregore_runtime/thread_cli.py +173 -0
- package/runtime/pi/egregore_runtime/threads.py +517 -0
- package/runtime/pi/egregore_runtime/todo_cli.py +142 -0
- package/runtime/pi/egregore_runtime/todos.py +244 -0
- package/runtime/pi/egregore_runtime/upgrade.py +770 -0
- package/runtime/pi/egregore_runtime/upgrade_worker.py +53 -0
- package/runtime/pi/egregore_runtime/writeback.py +391 -0
- package/runtime/pi/egregore_runtime/writeback_cli.py +114 -0
- package/runtime/pi/manifest.json +98 -1
- package/runtime/prime/.codex/hooks/search-hint.js +21 -52
- package/runtime/prime/.codex/hooks.json +3 -3
- package/runtime/prime/.codex/skills/activity/SKILL.md +23 -66
- package/runtime/prime/.codex/skills/announce/SKILL.md +27 -50
- package/runtime/prime/.codex/skills/archive/SKILL.md +21 -36
- package/runtime/prime/.codex/skills/checkup/SKILL.md +77 -36
- package/runtime/prime/.codex/skills/dark-mode/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/dashboard/SKILL.md +17 -47
- package/runtime/prime/.codex/skills/handoff/SKILL.md +84 -133
- package/runtime/prime/.codex/skills/harvest/SKILL.md +32 -117
- package/runtime/prime/.codex/skills/ingest/SKILL.md +47 -25
- package/runtime/prime/.codex/skills/ingest-user-interview/SKILL.md +18 -36
- package/runtime/prime/.codex/skills/ingest-user-interview/references/analysis-contract.md +42 -0
- package/runtime/prime/.codex/skills/invite/SKILL.md +21 -35
- package/runtime/prime/.codex/skills/issue/SKILL.md +60 -41
- package/runtime/prime/.codex/skills/me/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/meeting/SKILL.md +16 -36
- package/runtime/prime/.codex/skills/meeting/references/analysis-contract.md +48 -0
- package/runtime/prime/.codex/skills/note/SKILL.md +30 -25
- package/runtime/prime/.codex/skills/onboarding/SKILL.md +36 -41
- package/runtime/prime/.codex/skills/pull/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/quest/SKILL.md +27 -66
- package/runtime/prime/.codex/skills/reflect/SKILL.md +22 -36
- package/runtime/prime/.codex/skills/save/SKILL.md +2 -2
- package/runtime/prime/.codex/skills/telemetry/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/test/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/tutorial/SKILL.md +40 -41
- package/runtime/prime/.codex/skills/view/SKILL.md +115 -111
- package/runtime/prime/.codex/skills/wrap/SKILL.md +45 -41
- package/runtime/prime/.codex/spec-manifest.json +9 -9
- package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +57 -48
- package/runtime/prime/.prime/agent/extensions/egregore.ts +7 -0
- package/runtime/prime/.prime/agent/spec-manifest.json +2 -2
- package/runtime/prime/AGENTS.md +62 -58
- package/runtime/prime/bin/activity-action.sh +92 -0
- package/runtime/prime/bin/activity-data.sh +12 -233
- package/runtime/prime/bin/agent.sh +84 -100
- package/runtime/prime/bin/artifact-register.sh +164 -0
- package/runtime/prime/bin/artifact-writeback.sh +53 -0
- package/runtime/prime/bin/artifacts.sh +288 -0
- package/runtime/prime/bin/build-sealed-dataroom.mjs +722 -0
- package/runtime/prime/bin/capture-run.sh +18 -2
- package/runtime/prime/bin/codex-render-spec.mjs +41 -16
- package/runtime/prime/bin/codex-session-start.sh +14 -4
- package/runtime/prime/bin/codex-skill-render.mjs +9 -7
- package/runtime/prime/bin/corpus_map.py +806 -0
- package/runtime/prime/bin/corpus_survey.py +304 -0
- package/runtime/prime/bin/dashboard-data.sh +11 -171
- package/runtime/prime/bin/github-auth.sh +121 -0
- package/runtime/prime/bin/graph-batch.sh +7 -0
- package/runtime/prime/bin/graph-op.sh +4 -0
- package/runtime/prime/bin/graph-projection.sh +54 -0
- package/runtime/prime/bin/graph-wal.sh +13 -0
- package/runtime/prime/bin/graph.sh +44 -0
- package/runtime/prime/bin/handoff-lifecycle-job.sh +146 -0
- package/runtime/prime/bin/handoff-lifecycle.sh +280 -0
- package/runtime/prime/bin/handoff-preview.sh +169 -0
- package/runtime/prime/bin/handoff-run.sh +42 -26
- package/runtime/prime/bin/ingest-graph.sh +6 -0
- package/runtime/prime/bin/ingest-item.sh +10 -0
- package/runtime/prime/bin/ingest.sh +59 -30
- package/runtime/prime/bin/invite.sh +6 -0
- package/runtime/prime/bin/issue.sh +10 -0
- package/runtime/prime/bin/knowledge.sh +10 -0
- package/runtime/prime/bin/lib/config.sh +20 -0
- package/runtime/prime/bin/lib/context.sh +46 -25
- package/runtime/prime/bin/lib/git-sync.sh +70 -6
- package/runtime/prime/bin/lib/greeting.sh +34 -5
- package/runtime/prime/bin/lib/handoff-meta.sh +97 -0
- package/runtime/prime/bin/lib/worktree-links.sh +6 -0
- package/runtime/prime/bin/node-run.sh +42 -0
- package/runtime/prime/bin/notification.sh +6 -0
- package/runtime/prime/bin/observe-context.sh +14 -0
- package/runtime/prime/bin/onboarding.sh +6 -0
- package/runtime/prime/bin/person.py +4 -0
- package/runtime/prime/bin/person.sh +43 -10
- package/runtime/prime/bin/pi-observe-context.mjs +93 -0
- package/runtime/prime/bin/pi-product-workflows.mjs +1 -1
- package/runtime/prime/bin/pi-render-spec.mjs +140 -0
- package/runtime/prime/bin/post-update.sh +37 -0
- package/runtime/prime/bin/prime-render-spec.mjs +1 -1
- package/runtime/prime/bin/publish-artifact.sh +55 -2
- package/runtime/prime/bin/quest.sh +9 -0
- package/runtime/prime/bin/question.sh +8 -0
- package/runtime/prime/bin/research-ingest.sh +7 -0
- package/runtime/prime/bin/restore-owned-skills.sh +67 -0
- package/runtime/prime/bin/retrieval_eval.py +385 -0
- package/runtime/prime/bin/runtime-identity.sh +70 -0
- package/runtime/prime/bin/runtime-upgrade.sh +43 -0
- package/runtime/prime/bin/scroll.sh +9 -0
- package/runtime/prime/bin/search.sh +231 -195
- package/runtime/prime/bin/settings.sh +174 -5
- package/runtime/prime/bin/startup-check.sh +136 -0
- package/runtime/prime/bin/telemetry.sh +522 -247
- package/runtime/prime/bin/test-changes.sh +344 -0
- package/runtime/prime/bin/thread.sh +9 -0
- package/runtime/prime/bin/todo.sh +9 -0
- package/runtime/prime/bin/view-render.sh +126 -0
- package/runtime/prime/bin/worktree-create.sh +19 -5
- package/runtime/prime/egregore_runtime/__init__.py +228 -0
- package/runtime/prime/egregore_runtime/adapters/__init__.py +5 -0
- package/runtime/prime/egregore_runtime/adapters/invitation.py +237 -0
- package/runtime/prime/egregore_runtime/adapters/notification.py +59 -0
- package/runtime/prime/egregore_runtime/adapters/qmd.py +1701 -0
- package/runtime/prime/egregore_runtime/adapters/qmd_cli.py +210 -0
- package/runtime/prime/egregore_runtime/adapters/qmd_embed_worker.py +59 -0
- package/runtime/prime/egregore_runtime/admin_gate.py +102 -0
- package/runtime/prime/egregore_runtime/artifacts.py +523 -0
- package/runtime/prime/egregore_runtime/contracts.py +499 -0
- package/runtime/prime/egregore_runtime/entitlements.py +85 -0
- package/runtime/prime/egregore_runtime/errors.py +10 -0
- package/runtime/prime/egregore_runtime/graph.py +356 -0
- package/runtime/prime/egregore_runtime/handoff_lookup.py +196 -0
- package/runtime/prime/egregore_runtime/harness_cli.py +1221 -0
- package/runtime/prime/egregore_runtime/identity.py +734 -0
- package/runtime/prime/egregore_runtime/identity_cli.py +91 -0
- package/runtime/prime/egregore_runtime/ingest.py +575 -0
- package/runtime/prime/egregore_runtime/ingest_cli.py +254 -0
- package/runtime/prime/egregore_runtime/ingest_index_cli.py +147 -0
- package/runtime/prime/egregore_runtime/invitation_cli.py +51 -0
- package/runtime/prime/egregore_runtime/invitations.py +116 -0
- package/runtime/prime/egregore_runtime/issue_cli.py +305 -0
- package/runtime/prime/egregore_runtime/issues.py +359 -0
- package/runtime/prime/egregore_runtime/knowledge.py +376 -0
- package/runtime/prime/egregore_runtime/knowledge_cli.py +173 -0
- package/runtime/prime/egregore_runtime/lifecycle.py +888 -0
- package/runtime/prime/egregore_runtime/lifecycle_cli.py +269 -0
- package/runtime/prime/egregore_runtime/notification_cli.py +125 -0
- package/runtime/prime/egregore_runtime/notifications.py +230 -0
- package/runtime/prime/egregore_runtime/observe.py +342 -0
- package/runtime/prime/egregore_runtime/onboarding.py +290 -0
- package/runtime/prime/egregore_runtime/onboarding_cli.py +110 -0
- package/runtime/prime/egregore_runtime/policy.py +144 -0
- package/runtime/prime/egregore_runtime/profile.py +193 -0
- package/runtime/prime/egregore_runtime/quest_cli.py +216 -0
- package/runtime/prime/egregore_runtime/question_cli.py +180 -0
- package/runtime/prime/egregore_runtime/questions.py +304 -0
- package/runtime/prime/egregore_runtime/quests.py +419 -0
- package/runtime/prime/egregore_runtime/readiness.py +379 -0
- package/runtime/prime/egregore_runtime/research_ingest.py +256 -0
- package/runtime/prime/egregore_runtime/research_ingest_cli.py +44 -0
- package/runtime/prime/egregore_runtime/runtime.py +464 -0
- package/runtime/prime/egregore_runtime/scroll_cli.py +185 -0
- package/runtime/prime/egregore_runtime/scrolls.py +726 -0
- package/runtime/prime/egregore_runtime/status.py +412 -0
- package/runtime/prime/egregore_runtime/status_cli.py +174 -0
- package/runtime/prime/egregore_runtime/sync.py +218 -0
- package/runtime/prime/egregore_runtime/teamsync.py +139 -0
- package/runtime/prime/egregore_runtime/telemetry.py +640 -0
- package/runtime/prime/egregore_runtime/thread_cli.py +173 -0
- package/runtime/prime/egregore_runtime/threads.py +517 -0
- package/runtime/prime/egregore_runtime/todo_cli.py +142 -0
- package/runtime/prime/egregore_runtime/todos.py +244 -0
- package/runtime/prime/egregore_runtime/upgrade.py +770 -0
- package/runtime/prime/egregore_runtime/upgrade_worker.py +53 -0
- package/runtime/prime/egregore_runtime/writeback.py +391 -0
- package/runtime/prime/egregore_runtime/writeback_cli.py +114 -0
- package/runtime/prime/manifest.json +98 -1
|
@@ -0,0 +1,888 @@
|
|
|
1
|
+
"""Canonical temporal-authority and artifact-lifecycle projection.
|
|
2
|
+
|
|
3
|
+
Markdown and Git remain durable truth. Lifecycle state is derived from artifact
|
|
4
|
+
frontmatter plus append-only lifecycle-event artifacts; this module never
|
|
5
|
+
rewrites or deletes the artifact whose state it projects.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import asdict, dataclass
|
|
11
|
+
from datetime import UTC, datetime, timedelta
|
|
12
|
+
from enum import StrEnum
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
import re
|
|
18
|
+
import subprocess
|
|
19
|
+
from typing import Any, Callable, Iterable, Mapping, Sequence
|
|
20
|
+
from uuid import uuid4
|
|
21
|
+
|
|
22
|
+
from .artifacts import (
|
|
23
|
+
ARTIFACT_SCHEMA_VERSION,
|
|
24
|
+
CanonicalDocument,
|
|
25
|
+
canonical_artifact_id,
|
|
26
|
+
content_digest,
|
|
27
|
+
split_frontmatter,
|
|
28
|
+
)
|
|
29
|
+
from .contracts import (
|
|
30
|
+
ActorContext,
|
|
31
|
+
ArtifactRelationship,
|
|
32
|
+
CanonicalArtifact,
|
|
33
|
+
Serializable,
|
|
34
|
+
)
|
|
35
|
+
from .policy import scope_allows_path
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
LIFECYCLE_PLAN_SCHEMA = "egregore-lifecycle-plan/v1"
|
|
39
|
+
LIFECYCLE_EVENT_SCHEMA = "egregore-lifecycle-event/v1"
|
|
40
|
+
LIFECYCLE_INDEX_SPEC = "egregore-lifecycle-projection/v1"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AuthorityState(StrEnum):
|
|
44
|
+
CURRENT = "current"
|
|
45
|
+
SUPERSEDED = "superseded"
|
|
46
|
+
HISTORICAL = "historical"
|
|
47
|
+
AMBIGUOUS = "ambiguous"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AttentionState(StrEnum):
|
|
51
|
+
ACTIVE = "active"
|
|
52
|
+
REVIEW_DUE = "review_due"
|
|
53
|
+
EXPIRED = "expired"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ObligationState(StrEnum):
|
|
57
|
+
NONE = "none"
|
|
58
|
+
PENDING = "pending"
|
|
59
|
+
READ = "read"
|
|
60
|
+
CLAIMED = "claimed"
|
|
61
|
+
COMPLETED = "completed"
|
|
62
|
+
REVIEW_REQUIRED = "review_required"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class LifecycleAction(StrEnum):
|
|
66
|
+
ACKNOWLEDGE = "acknowledge"
|
|
67
|
+
CLAIM = "claim"
|
|
68
|
+
COMPLETE = "complete"
|
|
69
|
+
EXPIRE_ATTENTION = "expire_attention"
|
|
70
|
+
REOPEN = "reopen"
|
|
71
|
+
REQUEST_REVIEW = "request_review"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class LifecycleError(ValueError):
|
|
75
|
+
"""A requested lifecycle transition is invalid or lacks evidence."""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True, slots=True)
|
|
79
|
+
class LifecyclePolicy(Serializable):
|
|
80
|
+
schema_version: str = "egregore-lifecycle-policy/v1"
|
|
81
|
+
review_after_days: int = 7
|
|
82
|
+
handoff_ttl_days: int = 14
|
|
83
|
+
fyi_expiry_days: int = 14
|
|
84
|
+
retention_days: int | None = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True, slots=True)
|
|
88
|
+
class LifecycleRecord(Serializable):
|
|
89
|
+
artifact_id: str
|
|
90
|
+
canonical_path: str
|
|
91
|
+
artifact_type: str
|
|
92
|
+
title: str
|
|
93
|
+
created_at: datetime
|
|
94
|
+
created_by: str
|
|
95
|
+
declared_status: str
|
|
96
|
+
authority_state: AuthorityState
|
|
97
|
+
attention_state: AttentionState
|
|
98
|
+
obligation_state: ObligationState
|
|
99
|
+
current_authority: bool
|
|
100
|
+
lifecycle_revision: str
|
|
101
|
+
age_days: int
|
|
102
|
+
created_by_alias: str | None = None
|
|
103
|
+
intent: str | None = None
|
|
104
|
+
recipients: tuple[str, ...] = ()
|
|
105
|
+
superseded_by: tuple[str, ...] = ()
|
|
106
|
+
review_at: datetime | None = None
|
|
107
|
+
attention_expires_at: datetime | None = None
|
|
108
|
+
retention_until: datetime | None = None
|
|
109
|
+
terminal_reason: str | None = None
|
|
110
|
+
recommendation: str = "keep"
|
|
111
|
+
automatic_transition_safe: bool = False
|
|
112
|
+
evidence_ids: tuple[str, ...] = ()
|
|
113
|
+
warnings: tuple[str, ...] = ()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True, slots=True)
|
|
117
|
+
class LifecyclePlan(Serializable):
|
|
118
|
+
schema_version: str
|
|
119
|
+
index_spec_version: str
|
|
120
|
+
snapshot_id: str
|
|
121
|
+
generated_at: datetime
|
|
122
|
+
source_revision: str
|
|
123
|
+
records: tuple[LifecycleRecord, ...]
|
|
124
|
+
warnings: tuple[str, ...] = ()
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def by_id(self) -> dict[str, LifecycleRecord]:
|
|
128
|
+
return {record.artifact_id: record for record in self.records}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True, slots=True)
|
|
132
|
+
class LifecycleTransition(Serializable):
|
|
133
|
+
action: LifecycleAction
|
|
134
|
+
target_artifact_id: str
|
|
135
|
+
target_path: str
|
|
136
|
+
previous_obligation: ObligationState
|
|
137
|
+
new_obligation: ObligationState
|
|
138
|
+
previous_attention: AttentionState
|
|
139
|
+
new_attention: AttentionState
|
|
140
|
+
expected_lifecycle_revision: str
|
|
141
|
+
reason: str
|
|
142
|
+
evidence_ids: tuple[str, ...]
|
|
143
|
+
automatic: bool
|
|
144
|
+
document: CanonicalDocument
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass(frozen=True, slots=True)
|
|
148
|
+
class _Snapshot:
|
|
149
|
+
artifact_id: str
|
|
150
|
+
canonical_path: str
|
|
151
|
+
artifact_type: str
|
|
152
|
+
title: str
|
|
153
|
+
created_at: datetime
|
|
154
|
+
created_by: str
|
|
155
|
+
created_by_alias: str | None
|
|
156
|
+
status: str
|
|
157
|
+
intent: str | None
|
|
158
|
+
recipients: tuple[str, ...]
|
|
159
|
+
supersedes: tuple[str, ...]
|
|
160
|
+
review_at: datetime | None
|
|
161
|
+
attention_expires_at: datetime | None
|
|
162
|
+
retention_until: datetime | None
|
|
163
|
+
terminal_reason: str | None
|
|
164
|
+
lifecycle_action: str | None = None
|
|
165
|
+
target_artifact_id: str | None = None
|
|
166
|
+
target_path: str | None = None
|
|
167
|
+
transition_state: str | None = None
|
|
168
|
+
transition_attention: str | None = None
|
|
169
|
+
transition_recipient: str | None = None
|
|
170
|
+
expected_lifecycle_revision: str | None = None
|
|
171
|
+
transition_reason: str | None = None
|
|
172
|
+
transition_evidence: tuple[str, ...] = ()
|
|
173
|
+
transition_automatic: bool = False
|
|
174
|
+
digest: str = ""
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _strings(value: Any) -> tuple[str, ...]:
|
|
178
|
+
if value is None:
|
|
179
|
+
return ()
|
|
180
|
+
if isinstance(value, str):
|
|
181
|
+
stripped = value.strip()
|
|
182
|
+
if not stripped:
|
|
183
|
+
return ()
|
|
184
|
+
if "," in stripped:
|
|
185
|
+
return tuple(part.strip() for part in stripped.split(",") if part.strip())
|
|
186
|
+
return (stripped,)
|
|
187
|
+
if isinstance(value, Mapping):
|
|
188
|
+
return tuple(str(item) for item in value.values() if str(item).strip())
|
|
189
|
+
if isinstance(value, Iterable):
|
|
190
|
+
return tuple(str(item) for item in value if str(item).strip())
|
|
191
|
+
return (str(value),)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _datetime(value: Any) -> datetime | None:
|
|
195
|
+
if isinstance(value, datetime):
|
|
196
|
+
parsed = value
|
|
197
|
+
elif not isinstance(value, str) or not value.strip():
|
|
198
|
+
return None
|
|
199
|
+
else:
|
|
200
|
+
raw = value.strip()
|
|
201
|
+
try:
|
|
202
|
+
parsed = datetime.fromisoformat(raw.replace("Z", "+00:00"))
|
|
203
|
+
except ValueError:
|
|
204
|
+
return None
|
|
205
|
+
if parsed.tzinfo is None:
|
|
206
|
+
parsed = parsed.replace(tzinfo=UTC)
|
|
207
|
+
return parsed.astimezone(UTC)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _field_datetime(fields: Mapping[str, Any], *names: str) -> datetime | None:
|
|
211
|
+
for name in names:
|
|
212
|
+
parsed = _datetime(fields.get(name))
|
|
213
|
+
if parsed is not None:
|
|
214
|
+
return parsed
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _artifact_type(fields: Mapping[str, Any], path: str) -> str:
|
|
219
|
+
explicit = fields.get("type") or fields.get("artifact_type")
|
|
220
|
+
if explicit:
|
|
221
|
+
return str(explicit)
|
|
222
|
+
parts = set(Path(path).parts)
|
|
223
|
+
if "handoffs" in parts:
|
|
224
|
+
return "handoff"
|
|
225
|
+
if "decisions" in parts:
|
|
226
|
+
return "decision"
|
|
227
|
+
if "threads" in parts:
|
|
228
|
+
return "thread"
|
|
229
|
+
if "scrolls" in parts:
|
|
230
|
+
return "scroll"
|
|
231
|
+
return "artifact"
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _title(fields: Mapping[str, Any], path: str) -> str:
|
|
235
|
+
return str(
|
|
236
|
+
fields.get("title")
|
|
237
|
+
or fields.get("topic")
|
|
238
|
+
or fields.get("name")
|
|
239
|
+
or Path(path).stem.replace("-", " ")
|
|
240
|
+
).strip()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _created_at(fields: Mapping[str, Any], path: Path) -> datetime:
|
|
244
|
+
parsed = _field_datetime(
|
|
245
|
+
fields, "created_at", "created", "date", "started", "ingested_at"
|
|
246
|
+
)
|
|
247
|
+
if parsed is not None:
|
|
248
|
+
return parsed
|
|
249
|
+
path_date = re.search(r"(?:^|/)(\d{4})-(\d{2})/(\d{2})-", path.as_posix())
|
|
250
|
+
if path_date:
|
|
251
|
+
candidate = _datetime("-".join(path_date.groups()))
|
|
252
|
+
if candidate is not None:
|
|
253
|
+
return candidate
|
|
254
|
+
for part in reversed(path.parts):
|
|
255
|
+
match = re.match(r"(\d{4}-\d{2}-\d{2})", part)
|
|
256
|
+
if match:
|
|
257
|
+
candidate = _datetime(match.group(1))
|
|
258
|
+
if candidate is not None:
|
|
259
|
+
return candidate
|
|
260
|
+
raise LifecycleError("canonical artifact has no deterministic creation date")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _normal_status(value: Any) -> str:
|
|
264
|
+
normalized = str(value or "active").strip().lower().replace("-", "_")
|
|
265
|
+
aliases = {"completed": "done", "closed": "done", "unread": "pending"}
|
|
266
|
+
return aliases.get(normalized, normalized)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _obligation(value: str, artifact_type: str) -> ObligationState:
|
|
270
|
+
if artifact_type != "handoff":
|
|
271
|
+
return ObligationState.NONE
|
|
272
|
+
normalized = _normal_status(value)
|
|
273
|
+
return {
|
|
274
|
+
"pending": ObligationState.PENDING,
|
|
275
|
+
"read": ObligationState.READ,
|
|
276
|
+
"claimed": ObligationState.CLAIMED,
|
|
277
|
+
"done": ObligationState.COMPLETED,
|
|
278
|
+
"review_required": ObligationState.REVIEW_REQUIRED,
|
|
279
|
+
}.get(normalized, ObligationState.PENDING)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _transition_states(
|
|
283
|
+
record: LifecycleRecord, action: LifecycleAction
|
|
284
|
+
) -> tuple[ObligationState, AttentionState]:
|
|
285
|
+
if action is LifecycleAction.ACKNOWLEDGE:
|
|
286
|
+
return ObligationState.READ, AttentionState.ACTIVE
|
|
287
|
+
if action is LifecycleAction.CLAIM:
|
|
288
|
+
return ObligationState.CLAIMED, AttentionState.ACTIVE
|
|
289
|
+
if action is LifecycleAction.COMPLETE:
|
|
290
|
+
return ObligationState.COMPLETED, AttentionState.ACTIVE
|
|
291
|
+
if action is LifecycleAction.EXPIRE_ATTENTION:
|
|
292
|
+
return record.obligation_state, AttentionState.EXPIRED
|
|
293
|
+
if action is LifecycleAction.REOPEN:
|
|
294
|
+
return ObligationState.PENDING, AttentionState.ACTIVE
|
|
295
|
+
return ObligationState.REVIEW_REQUIRED, AttentionState.REVIEW_DUE
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class CanonicalLifecycleService:
|
|
299
|
+
"""Build a disposable lifecycle projection from authorized canonical files."""
|
|
300
|
+
|
|
301
|
+
def __init__(
|
|
302
|
+
self,
|
|
303
|
+
memory_root: Path,
|
|
304
|
+
*,
|
|
305
|
+
policy: LifecyclePolicy | None = None,
|
|
306
|
+
clock: Callable[[], datetime] | None = None,
|
|
307
|
+
state_dir: Path | None = None,
|
|
308
|
+
default_org_id: str = "local",
|
|
309
|
+
) -> None:
|
|
310
|
+
self.memory_root = memory_root.resolve()
|
|
311
|
+
self.policy = policy or LifecyclePolicy()
|
|
312
|
+
self.clock = clock or (lambda: datetime.now(UTC))
|
|
313
|
+
self.state_dir = state_dir.resolve() if state_dir else None
|
|
314
|
+
self.default_org_id = default_org_id
|
|
315
|
+
|
|
316
|
+
def scan(
|
|
317
|
+
self,
|
|
318
|
+
*,
|
|
319
|
+
source_revision: str | None = None,
|
|
320
|
+
authorized_scopes: Sequence[str] = ("memory",),
|
|
321
|
+
allowed_artifact_ids: Sequence[str] = (),
|
|
322
|
+
artifact_types: Sequence[str] = (),
|
|
323
|
+
user: str | Sequence[str] | None = None,
|
|
324
|
+
) -> LifecyclePlan:
|
|
325
|
+
now = self.clock().astimezone(UTC)
|
|
326
|
+
canonical_revision = source_revision or self._canonical_revision()
|
|
327
|
+
snapshots, warnings = self._snapshots(canonical_revision)
|
|
328
|
+
allowed = frozenset(allowed_artifact_ids)
|
|
329
|
+
kinds = frozenset(artifact_types)
|
|
330
|
+
visible_primary = [
|
|
331
|
+
row
|
|
332
|
+
for row in snapshots
|
|
333
|
+
if row.artifact_type != "lifecycle-event"
|
|
334
|
+
and scope_allows_path(row.canonical_path, authorized_scopes)
|
|
335
|
+
and (
|
|
336
|
+
not allowed
|
|
337
|
+
or row.artifact_id in allowed
|
|
338
|
+
)
|
|
339
|
+
]
|
|
340
|
+
visible_ids = {row.artifact_id for row in visible_primary}
|
|
341
|
+
# Derived lifecycle events inherit the target artifact's scope. Their
|
|
342
|
+
# storage namespace must not require a broader read grant.
|
|
343
|
+
visible_events = [
|
|
344
|
+
row
|
|
345
|
+
for row in snapshots
|
|
346
|
+
if row.artifact_type == "lifecycle-event"
|
|
347
|
+
and row.target_artifact_id in visible_ids
|
|
348
|
+
]
|
|
349
|
+
visible = [*visible_primary, *visible_events]
|
|
350
|
+
superseders: dict[str, list[str]] = {}
|
|
351
|
+
tentative_supersession_targets: set[str] = set()
|
|
352
|
+
events: dict[str, list[_Snapshot]] = {}
|
|
353
|
+
for row in visible:
|
|
354
|
+
for target in row.supersedes:
|
|
355
|
+
if target in visible_ids:
|
|
356
|
+
if row.status in {"draft", "proposed", "review"}:
|
|
357
|
+
tentative_supersession_targets.add(target)
|
|
358
|
+
else:
|
|
359
|
+
superseders.setdefault(target, []).append(row.artifact_id)
|
|
360
|
+
if row.artifact_type == "lifecycle-event" and row.target_artifact_id:
|
|
361
|
+
if row.target_artifact_id in visible_ids:
|
|
362
|
+
events.setdefault(row.target_artifact_id, []).append(row)
|
|
363
|
+
|
|
364
|
+
# Supersession outside the authorized result set must not leak a path
|
|
365
|
+
# or id, but it also must not let Observe falsely assert that the old
|
|
366
|
+
# artifact is current. Mark that target ambiguous without naming the
|
|
367
|
+
# hidden successor.
|
|
368
|
+
visible_paths = {row.canonical_path for row in visible}
|
|
369
|
+
hidden_supersession_targets = {
|
|
370
|
+
target
|
|
371
|
+
for row in snapshots
|
|
372
|
+
if row.canonical_path not in visible_paths
|
|
373
|
+
for target in row.supersedes
|
|
374
|
+
if target in visible_ids
|
|
375
|
+
}
|
|
376
|
+
cycle_ids = self._supersession_cycles(visible)
|
|
377
|
+
|
|
378
|
+
requested_users = (user,) if isinstance(user, str) else tuple(user or ())
|
|
379
|
+
user_keys = {str(item).casefold().strip() for item in requested_users if str(item).strip()}
|
|
380
|
+
records: list[LifecycleRecord] = []
|
|
381
|
+
for row in visible:
|
|
382
|
+
if row.artifact_type == "lifecycle-event":
|
|
383
|
+
continue
|
|
384
|
+
if kinds and row.artifact_type not in kinds:
|
|
385
|
+
continue
|
|
386
|
+
if user_keys and row.artifact_type == "handoff" and not any(
|
|
387
|
+
recipient.casefold() in user_keys for recipient in row.recipients
|
|
388
|
+
):
|
|
389
|
+
continue
|
|
390
|
+
matching_events = events.get(row.artifact_id, ())
|
|
391
|
+
if user_keys:
|
|
392
|
+
matching_events = tuple(
|
|
393
|
+
event
|
|
394
|
+
for event in matching_events
|
|
395
|
+
if not event.transition_recipient
|
|
396
|
+
or event.transition_recipient.casefold() in user_keys
|
|
397
|
+
)
|
|
398
|
+
latest = max(matching_events, key=lambda item: (item.created_at, item.artifact_id), default=None)
|
|
399
|
+
conflicting_events = any(
|
|
400
|
+
len(
|
|
401
|
+
{
|
|
402
|
+
(event.transition_state, event.transition_attention)
|
|
403
|
+
for event in matching_events
|
|
404
|
+
if event.expected_lifecycle_revision == expected
|
|
405
|
+
}
|
|
406
|
+
)
|
|
407
|
+
> 1
|
|
408
|
+
for expected in {
|
|
409
|
+
event.expected_lifecycle_revision
|
|
410
|
+
for event in matching_events
|
|
411
|
+
if event.expected_lifecycle_revision
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
successors = tuple(sorted(superseders.get(row.artifact_id, ())))
|
|
415
|
+
ambiguous = (
|
|
416
|
+
row.artifact_id in hidden_supersession_targets
|
|
417
|
+
or row.artifact_id in tentative_supersession_targets
|
|
418
|
+
or row.artifact_id in cycle_ids
|
|
419
|
+
or len(successors) > 1
|
|
420
|
+
or any(target not in visible_ids for target in row.supersedes)
|
|
421
|
+
or conflicting_events
|
|
422
|
+
)
|
|
423
|
+
records.append(self._record(row, latest, successors, ambiguous, now))
|
|
424
|
+
|
|
425
|
+
records.sort(key=lambda item: (item.created_at, item.artifact_id), reverse=True)
|
|
426
|
+
snapshot_payload = "\n".join(
|
|
427
|
+
f"{row.artifact_id}|{row.digest}" for row in sorted(visible, key=lambda item: item.artifact_id)
|
|
428
|
+
)
|
|
429
|
+
snapshot_id = hashlib.sha256(snapshot_payload.encode("utf-8")).hexdigest()
|
|
430
|
+
return LifecyclePlan(
|
|
431
|
+
schema_version=LIFECYCLE_PLAN_SCHEMA,
|
|
432
|
+
index_spec_version=LIFECYCLE_INDEX_SPEC,
|
|
433
|
+
snapshot_id=snapshot_id,
|
|
434
|
+
generated_at=now,
|
|
435
|
+
source_revision=canonical_revision,
|
|
436
|
+
records=tuple(records),
|
|
437
|
+
warnings=warnings,
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
def resolve_paths(
|
|
441
|
+
self,
|
|
442
|
+
paths: Sequence[str],
|
|
443
|
+
*,
|
|
444
|
+
source_revision: str | None,
|
|
445
|
+
authorized_scopes: Sequence[str],
|
|
446
|
+
allowed_artifact_ids: Sequence[str] = (),
|
|
447
|
+
) -> tuple[Mapping[str, LifecycleRecord], str]:
|
|
448
|
+
requested = {path.replace("\\", "/").strip().lstrip("./") for path in paths}
|
|
449
|
+
plan = self.scan(
|
|
450
|
+
source_revision=source_revision,
|
|
451
|
+
authorized_scopes=authorized_scopes,
|
|
452
|
+
allowed_artifact_ids=allowed_artifact_ids,
|
|
453
|
+
)
|
|
454
|
+
return (
|
|
455
|
+
{record.canonical_path: record for record in plan.records if record.canonical_path in requested},
|
|
456
|
+
plan.snapshot_id,
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
def prepare_transition(
|
|
460
|
+
self,
|
|
461
|
+
actor: ActorContext,
|
|
462
|
+
record: LifecycleRecord,
|
|
463
|
+
action: LifecycleAction,
|
|
464
|
+
*,
|
|
465
|
+
reason: str,
|
|
466
|
+
evidence_ids: Sequence[str] = (),
|
|
467
|
+
automatic: bool = False,
|
|
468
|
+
allow_admin: bool = False,
|
|
469
|
+
expected_lifecycle_revision: str | None = None,
|
|
470
|
+
) -> LifecycleTransition:
|
|
471
|
+
if (
|
|
472
|
+
expected_lifecycle_revision is not None
|
|
473
|
+
and expected_lifecycle_revision != record.lifecycle_revision
|
|
474
|
+
):
|
|
475
|
+
raise LifecycleError("lifecycle revision changed; rebuild the plan before applying")
|
|
476
|
+
aliases = {
|
|
477
|
+
actor.actor.actor_id.casefold(),
|
|
478
|
+
actor.actor.display_name.casefold(),
|
|
479
|
+
*(str(value).casefold() for value in actor.actor.aliases.values()),
|
|
480
|
+
}
|
|
481
|
+
if actor.account is not None:
|
|
482
|
+
aliases.add(actor.account.display_name.casefold())
|
|
483
|
+
aliases.update(str(value).casefold() for value in actor.account.provider_aliases.values())
|
|
484
|
+
if record.artifact_type == "handoff" and record.recipients and not allow_admin:
|
|
485
|
+
if aliases.isdisjoint(recipient.casefold() for recipient in record.recipients):
|
|
486
|
+
raise LifecycleError("handoff transition actor is not an addressed recipient")
|
|
487
|
+
if automatic and action is LifecycleAction.COMPLETE:
|
|
488
|
+
# V1 can record explicit completion evidence, but cannot yet prove
|
|
489
|
+
# that an arbitrary id is a recipient-authored terminal IMPLEMENTS
|
|
490
|
+
# artifact. Fail closed until canonical verification exists.
|
|
491
|
+
raise LifecycleError(
|
|
492
|
+
"automatic completion requires verified canonical implementation evidence"
|
|
493
|
+
)
|
|
494
|
+
if automatic and action is LifecycleAction.EXPIRE_ATTENTION and not record.automatic_transition_safe:
|
|
495
|
+
raise LifecycleError("automatic expiry is not safe for this lifecycle state")
|
|
496
|
+
if action is LifecycleAction.REOPEN and (
|
|
497
|
+
record.obligation_state is not ObligationState.COMPLETED
|
|
498
|
+
and record.attention_state not in {AttentionState.EXPIRED, AttentionState.REVIEW_DUE}
|
|
499
|
+
):
|
|
500
|
+
raise LifecycleError("only terminal or review-due work can be reopened")
|
|
501
|
+
if action is LifecycleAction.COMPLETE and record.authority_state in {
|
|
502
|
+
AuthorityState.SUPERSEDED,
|
|
503
|
+
AuthorityState.HISTORICAL,
|
|
504
|
+
AuthorityState.AMBIGUOUS,
|
|
505
|
+
}:
|
|
506
|
+
raise LifecycleError("non-current artifacts cannot be completed")
|
|
507
|
+
if automatic and record.intent not in {"action", "feedback", "fyi"}:
|
|
508
|
+
raise LifecycleError("legacy or unclassified artifacts cannot transition unattended")
|
|
509
|
+
|
|
510
|
+
occurred_at = self.clock().astimezone(UTC)
|
|
511
|
+
new_obligation, new_attention = _transition_states(record, action)
|
|
512
|
+
event_id = f"lifecycle-{uuid4().hex}"
|
|
513
|
+
canonical_path = f"lifecycle/events/{occurred_at:%Y-%m}/{occurred_at:%d}-{event_id}.md"
|
|
514
|
+
evidence = tuple(dict.fromkeys(str(item) for item in evidence_ids if str(item).strip()))
|
|
515
|
+
body = (
|
|
516
|
+
f"# Lifecycle event: {action.value} {record.title}\n\n"
|
|
517
|
+
f"Target: `{record.artifact_id}`\n\n"
|
|
518
|
+
f"Reason: {reason.strip()}\n"
|
|
519
|
+
)
|
|
520
|
+
artifact = CanonicalArtifact(
|
|
521
|
+
schema_version=ARTIFACT_SCHEMA_VERSION,
|
|
522
|
+
artifact_id=event_id,
|
|
523
|
+
org_id=actor.profile.org_id,
|
|
524
|
+
artifact_type="lifecycle-event",
|
|
525
|
+
title=f"{action.value}: {record.title}",
|
|
526
|
+
created_at=occurred_at,
|
|
527
|
+
created_by=actor.actor.actor_id,
|
|
528
|
+
status="accepted",
|
|
529
|
+
canonical_path=canonical_path,
|
|
530
|
+
revision="",
|
|
531
|
+
content_hash=content_digest(body),
|
|
532
|
+
relationships=(
|
|
533
|
+
ArtifactRelationship("transitions", record.artifact_id),
|
|
534
|
+
*(ArtifactRelationship("evidence", item) for item in evidence),
|
|
535
|
+
),
|
|
536
|
+
)
|
|
537
|
+
document = CanonicalDocument(
|
|
538
|
+
artifact=artifact,
|
|
539
|
+
body=body,
|
|
540
|
+
legacy_fields={
|
|
541
|
+
"lifecycle_schema": LIFECYCLE_EVENT_SCHEMA,
|
|
542
|
+
"lifecycle_action": action.value,
|
|
543
|
+
"target_artifact_id": record.artifact_id,
|
|
544
|
+
"target_path": record.canonical_path,
|
|
545
|
+
"actor_alias": actor.actor.display_name,
|
|
546
|
+
"recipient": next(
|
|
547
|
+
(
|
|
548
|
+
recipient
|
|
549
|
+
for recipient in record.recipients
|
|
550
|
+
if recipient.casefold() in aliases
|
|
551
|
+
),
|
|
552
|
+
None,
|
|
553
|
+
),
|
|
554
|
+
"previous_obligation": record.obligation_state.value,
|
|
555
|
+
"new_state": new_obligation.value,
|
|
556
|
+
"previous_attention": record.attention_state.value,
|
|
557
|
+
"new_attention": new_attention.value,
|
|
558
|
+
"expected_lifecycle_revision": record.lifecycle_revision,
|
|
559
|
+
"reason": reason.strip(),
|
|
560
|
+
"evidence_ids": list(evidence),
|
|
561
|
+
"automatic": automatic,
|
|
562
|
+
"occurred_at": occurred_at.isoformat().replace("+00:00", "Z"),
|
|
563
|
+
},
|
|
564
|
+
)
|
|
565
|
+
return LifecycleTransition(
|
|
566
|
+
action=action,
|
|
567
|
+
target_artifact_id=record.artifact_id,
|
|
568
|
+
target_path=record.canonical_path,
|
|
569
|
+
previous_obligation=record.obligation_state,
|
|
570
|
+
new_obligation=new_obligation,
|
|
571
|
+
previous_attention=record.attention_state,
|
|
572
|
+
new_attention=new_attention,
|
|
573
|
+
expected_lifecycle_revision=record.lifecycle_revision,
|
|
574
|
+
reason=reason.strip(),
|
|
575
|
+
evidence_ids=evidence,
|
|
576
|
+
automatic=automatic,
|
|
577
|
+
document=document,
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
def _record(
|
|
581
|
+
self,
|
|
582
|
+
row: _Snapshot,
|
|
583
|
+
latest: _Snapshot | None,
|
|
584
|
+
superseded_by: tuple[str, ...],
|
|
585
|
+
ambiguous: bool,
|
|
586
|
+
now: datetime,
|
|
587
|
+
) -> LifecycleRecord:
|
|
588
|
+
status = latest.transition_state if latest and latest.transition_state else row.status
|
|
589
|
+
obligation = _obligation(status, row.artifact_type)
|
|
590
|
+
attention = (
|
|
591
|
+
AttentionState(latest.transition_attention)
|
|
592
|
+
if latest
|
|
593
|
+
and latest.transition_attention
|
|
594
|
+
in {state.value for state in AttentionState}
|
|
595
|
+
else AttentionState.ACTIVE
|
|
596
|
+
)
|
|
597
|
+
authority = AuthorityState.CURRENT
|
|
598
|
+
reason = latest.transition_reason if latest else row.terminal_reason
|
|
599
|
+
evidence = latest.transition_evidence if latest else ()
|
|
600
|
+
review_at = row.review_at
|
|
601
|
+
expires_at = row.attention_expires_at
|
|
602
|
+
if row.artifact_type == "handoff" and expires_at is None:
|
|
603
|
+
days = self.policy.fyi_expiry_days if row.intent == "fyi" else self.policy.handoff_ttl_days
|
|
604
|
+
expires_at = row.created_at + timedelta(days=days)
|
|
605
|
+
retention_until = row.retention_until
|
|
606
|
+
if retention_until is None and self.policy.retention_days is not None:
|
|
607
|
+
retention_until = row.created_at + timedelta(days=self.policy.retention_days)
|
|
608
|
+
age_days = max(0, (now.date() - row.created_at.date()).days)
|
|
609
|
+
recommendation = "keep"
|
|
610
|
+
automatic_safe = False
|
|
611
|
+
|
|
612
|
+
if ambiguous:
|
|
613
|
+
authority = AuthorityState.AMBIGUOUS
|
|
614
|
+
recommendation = "review"
|
|
615
|
+
elif superseded_by:
|
|
616
|
+
authority = AuthorityState.SUPERSEDED
|
|
617
|
+
recommendation = "keep_historical"
|
|
618
|
+
elif row.status in {"archived", "historical"}:
|
|
619
|
+
authority = AuthorityState.HISTORICAL
|
|
620
|
+
recommendation = "keep_historical"
|
|
621
|
+
elif obligation is ObligationState.COMPLETED:
|
|
622
|
+
recommendation = "keep_historical"
|
|
623
|
+
elif attention is AttentionState.EXPIRED:
|
|
624
|
+
recommendation = "review_or_reopen"
|
|
625
|
+
elif review_at is not None and review_at <= now:
|
|
626
|
+
attention = AttentionState.REVIEW_DUE
|
|
627
|
+
if obligation not in {ObligationState.NONE, ObligationState.COMPLETED}:
|
|
628
|
+
obligation = ObligationState.REVIEW_REQUIRED
|
|
629
|
+
recommendation = "review"
|
|
630
|
+
elif expires_at is not None and expires_at <= now:
|
|
631
|
+
if row.artifact_type == "handoff" and row.intent == "fyi":
|
|
632
|
+
attention = AttentionState.EXPIRED
|
|
633
|
+
recommendation = "expire_attention"
|
|
634
|
+
automatic_safe = True
|
|
635
|
+
elif row.artifact_type == "handoff" and row.intent in {"action", "feedback"}:
|
|
636
|
+
attention = AttentionState.EXPIRED
|
|
637
|
+
recommendation = "expire_attention"
|
|
638
|
+
automatic_safe = obligation is ObligationState.PENDING
|
|
639
|
+
else:
|
|
640
|
+
attention = AttentionState.REVIEW_DUE
|
|
641
|
+
if obligation not in {ObligationState.NONE, ObligationState.COMPLETED}:
|
|
642
|
+
obligation = ObligationState.REVIEW_REQUIRED
|
|
643
|
+
recommendation = "review"
|
|
644
|
+
elif row.artifact_type == "handoff" and age_days >= self.policy.review_after_days:
|
|
645
|
+
attention = AttentionState.REVIEW_DUE
|
|
646
|
+
if obligation not in {ObligationState.NONE, ObligationState.COMPLETED}:
|
|
647
|
+
obligation = ObligationState.REVIEW_REQUIRED
|
|
648
|
+
recommendation = "review"
|
|
649
|
+
|
|
650
|
+
lifecycle_revision = hashlib.sha256(
|
|
651
|
+
"|".join(
|
|
652
|
+
(
|
|
653
|
+
row.digest,
|
|
654
|
+
latest.digest if latest else "",
|
|
655
|
+
authority.value,
|
|
656
|
+
attention.value,
|
|
657
|
+
obligation.value,
|
|
658
|
+
)
|
|
659
|
+
).encode("utf-8")
|
|
660
|
+
).hexdigest()
|
|
661
|
+
return LifecycleRecord(
|
|
662
|
+
artifact_id=row.artifact_id,
|
|
663
|
+
canonical_path=row.canonical_path,
|
|
664
|
+
artifact_type=row.artifact_type,
|
|
665
|
+
title=row.title,
|
|
666
|
+
created_at=row.created_at,
|
|
667
|
+
created_by=row.created_by,
|
|
668
|
+
declared_status=row.status,
|
|
669
|
+
authority_state=authority,
|
|
670
|
+
attention_state=attention,
|
|
671
|
+
obligation_state=obligation,
|
|
672
|
+
current_authority=authority is AuthorityState.CURRENT,
|
|
673
|
+
lifecycle_revision=f"sha256:{lifecycle_revision}",
|
|
674
|
+
age_days=age_days,
|
|
675
|
+
created_by_alias=row.created_by_alias,
|
|
676
|
+
intent=row.intent,
|
|
677
|
+
recipients=row.recipients,
|
|
678
|
+
superseded_by=superseded_by,
|
|
679
|
+
review_at=review_at,
|
|
680
|
+
attention_expires_at=expires_at,
|
|
681
|
+
retention_until=retention_until,
|
|
682
|
+
terminal_reason=reason,
|
|
683
|
+
recommendation=recommendation,
|
|
684
|
+
automatic_transition_safe=automatic_safe,
|
|
685
|
+
evidence_ids=evidence,
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
@staticmethod
|
|
689
|
+
def _supersession_cycles(rows: Sequence[_Snapshot]) -> frozenset[str]:
|
|
690
|
+
edges = {row.artifact_id: set(row.supersedes) for row in rows if row.supersedes}
|
|
691
|
+
cycles: set[str] = set()
|
|
692
|
+
|
|
693
|
+
def visit(origin: str, current: str, trail: tuple[str, ...]) -> None:
|
|
694
|
+
if current in trail:
|
|
695
|
+
start = trail.index(current)
|
|
696
|
+
cycles.update(trail[start:])
|
|
697
|
+
return
|
|
698
|
+
for target in edges.get(current, ()):
|
|
699
|
+
visit(origin, target, (*trail, current))
|
|
700
|
+
|
|
701
|
+
for artifact_id in edges:
|
|
702
|
+
visit(artifact_id, artifact_id, ())
|
|
703
|
+
return frozenset(cycles)
|
|
704
|
+
|
|
705
|
+
def _snapshots(self, source_revision: str | None) -> tuple[tuple[_Snapshot, ...], tuple[str, ...]]:
|
|
706
|
+
cached = self._read_cache(source_revision) if source_revision else None
|
|
707
|
+
if cached is not None:
|
|
708
|
+
return cached, ()
|
|
709
|
+
rows: list[_Snapshot] = []
|
|
710
|
+
warnings: list[str] = []
|
|
711
|
+
if not self.memory_root.is_dir():
|
|
712
|
+
return (), ("canonical memory root is unavailable",)
|
|
713
|
+
for path in sorted(self.memory_root.rglob("*.md")):
|
|
714
|
+
if ".git" in path.parts or path.name == "index.md":
|
|
715
|
+
continue
|
|
716
|
+
relative = path.relative_to(self.memory_root).as_posix()
|
|
717
|
+
canonical_path = f"memory/{relative}"
|
|
718
|
+
try:
|
|
719
|
+
markdown = path.read_text(encoding="utf-8")
|
|
720
|
+
fields, _ = split_frontmatter(markdown)
|
|
721
|
+
created = _created_at(fields, path)
|
|
722
|
+
except (OSError, ValueError) as exc:
|
|
723
|
+
warnings.append(f"skipped:{canonical_path}:{type(exc).__name__}")
|
|
724
|
+
continue
|
|
725
|
+
artifact_id = canonical_artifact_id(markdown, canonical_path)
|
|
726
|
+
artifact_type = _artifact_type(fields, relative)
|
|
727
|
+
recipients = _strings(
|
|
728
|
+
fields.get("addressed_to")
|
|
729
|
+
or fields.get("addressed_to_user")
|
|
730
|
+
or fields.get("to")
|
|
731
|
+
or fields.get("recipient")
|
|
732
|
+
)
|
|
733
|
+
supersedes = _strings(fields.get("supersedes"))
|
|
734
|
+
rows.append(
|
|
735
|
+
_Snapshot(
|
|
736
|
+
artifact_id=artifact_id,
|
|
737
|
+
canonical_path=canonical_path,
|
|
738
|
+
artifact_type=artifact_type,
|
|
739
|
+
title=_title(fields, relative),
|
|
740
|
+
created_at=created,
|
|
741
|
+
created_by=str(fields.get("created_by") or fields.get("author") or fields.get("from") or "unknown"),
|
|
742
|
+
created_by_alias=(
|
|
743
|
+
str(
|
|
744
|
+
fields.get("creator_alias")
|
|
745
|
+
or fields.get("actor_alias")
|
|
746
|
+
or fields.get("from")
|
|
747
|
+
or fields.get("author")
|
|
748
|
+
or ""
|
|
749
|
+
).strip()
|
|
750
|
+
or None
|
|
751
|
+
),
|
|
752
|
+
status=_normal_status(
|
|
753
|
+
fields.get("status")
|
|
754
|
+
or fields.get("handoff_status")
|
|
755
|
+
or ("pending" if artifact_type == "handoff" else "active")
|
|
756
|
+
),
|
|
757
|
+
intent=str(fields.get("intent")).lower() if fields.get("intent") else None,
|
|
758
|
+
recipients=recipients,
|
|
759
|
+
supersedes=supersedes,
|
|
760
|
+
review_at=_field_datetime(fields, "review_at", "review_on", "review_date"),
|
|
761
|
+
attention_expires_at=_field_datetime(fields, "attention_expires_at", "expires_at"),
|
|
762
|
+
retention_until=_field_datetime(fields, "retention_until"),
|
|
763
|
+
terminal_reason=str(fields.get("terminal_reason") or fields.get("lifecycle_reason"))
|
|
764
|
+
if fields.get("terminal_reason") or fields.get("lifecycle_reason")
|
|
765
|
+
else None,
|
|
766
|
+
lifecycle_action=str(fields.get("lifecycle_action")) if fields.get("lifecycle_action") else None,
|
|
767
|
+
target_artifact_id=str(fields.get("target_artifact_id")) if fields.get("target_artifact_id") else None,
|
|
768
|
+
target_path=str(fields.get("target_path")) if fields.get("target_path") else None,
|
|
769
|
+
transition_state=str(fields.get("new_state")) if fields.get("new_state") else None,
|
|
770
|
+
transition_attention=str(fields.get("new_attention")) if fields.get("new_attention") else None,
|
|
771
|
+
transition_recipient=str(fields.get("recipient")) if fields.get("recipient") else None,
|
|
772
|
+
expected_lifecycle_revision=str(fields.get("expected_lifecycle_revision"))
|
|
773
|
+
if fields.get("expected_lifecycle_revision")
|
|
774
|
+
else None,
|
|
775
|
+
transition_reason=str(fields.get("reason")) if fields.get("reason") else None,
|
|
776
|
+
transition_evidence=_strings(fields.get("evidence_ids")),
|
|
777
|
+
transition_automatic=bool(fields.get("automatic", False)),
|
|
778
|
+
digest=hashlib.sha256(markdown.encode("utf-8")).hexdigest(),
|
|
779
|
+
)
|
|
780
|
+
)
|
|
781
|
+
result = tuple(rows)
|
|
782
|
+
if source_revision:
|
|
783
|
+
self._write_cache(source_revision, result)
|
|
784
|
+
return result, tuple(warnings[:20])
|
|
785
|
+
|
|
786
|
+
def _cache_path(self) -> Path | None:
|
|
787
|
+
return self.state_dir / "projection.json" if self.state_dir else None
|
|
788
|
+
|
|
789
|
+
def _canonical_revision(self) -> str:
|
|
790
|
+
try:
|
|
791
|
+
head = subprocess.run(
|
|
792
|
+
("git", "-C", str(self.memory_root), "rev-parse", "HEAD"),
|
|
793
|
+
capture_output=True,
|
|
794
|
+
text=True,
|
|
795
|
+
timeout=10,
|
|
796
|
+
check=False,
|
|
797
|
+
)
|
|
798
|
+
status = subprocess.run(
|
|
799
|
+
(
|
|
800
|
+
"git", "-C", str(self.memory_root), "status",
|
|
801
|
+
"--porcelain=v1", "--untracked-files=all",
|
|
802
|
+
),
|
|
803
|
+
capture_output=True,
|
|
804
|
+
text=True,
|
|
805
|
+
timeout=30,
|
|
806
|
+
check=False,
|
|
807
|
+
)
|
|
808
|
+
if head.returncode == 0 and head.stdout.strip() and status.returncode == 0:
|
|
809
|
+
revision = f"git:{head.stdout.strip()}"
|
|
810
|
+
if not status.stdout.strip():
|
|
811
|
+
return revision
|
|
812
|
+
return f"{revision}+dirty:{self._markdown_manifest_hash()}"
|
|
813
|
+
except (OSError, subprocess.SubprocessError):
|
|
814
|
+
pass
|
|
815
|
+
return f"filesystem:{self._markdown_manifest_hash()}"
|
|
816
|
+
|
|
817
|
+
def _markdown_manifest_hash(self) -> str:
|
|
818
|
+
digest = hashlib.sha256()
|
|
819
|
+
for source in sorted(self.memory_root.rglob("*.md")):
|
|
820
|
+
if not source.is_file():
|
|
821
|
+
continue
|
|
822
|
+
digest.update(source.relative_to(self.memory_root).as_posix().encode("utf-8"))
|
|
823
|
+
digest.update(b"\0")
|
|
824
|
+
try:
|
|
825
|
+
digest.update(source.read_bytes())
|
|
826
|
+
except OSError:
|
|
827
|
+
digest.update(b"<unreadable>")
|
|
828
|
+
digest.update(b"\0")
|
|
829
|
+
return digest.hexdigest()[:20]
|
|
830
|
+
|
|
831
|
+
def _read_cache(self, source_revision: str) -> tuple[_Snapshot, ...] | None:
|
|
832
|
+
path = self._cache_path()
|
|
833
|
+
if path is None or not path.is_file():
|
|
834
|
+
return None
|
|
835
|
+
try:
|
|
836
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
837
|
+
if (
|
|
838
|
+
payload.get("schema") != LIFECYCLE_PLAN_SCHEMA
|
|
839
|
+
or payload.get("index_spec") != LIFECYCLE_INDEX_SPEC
|
|
840
|
+
or payload.get("source_revision") != source_revision
|
|
841
|
+
):
|
|
842
|
+
return None
|
|
843
|
+
return tuple(
|
|
844
|
+
_Snapshot(
|
|
845
|
+
**{
|
|
846
|
+
**item,
|
|
847
|
+
"created_at": _datetime(item["created_at"]),
|
|
848
|
+
"review_at": _datetime(item.get("review_at")),
|
|
849
|
+
"attention_expires_at": _datetime(item.get("attention_expires_at")),
|
|
850
|
+
"retention_until": _datetime(item.get("retention_until")),
|
|
851
|
+
"recipients": tuple(item.get("recipients", ())),
|
|
852
|
+
"supersedes": tuple(item.get("supersedes", ())),
|
|
853
|
+
"transition_evidence": tuple(item.get("transition_evidence", ())),
|
|
854
|
+
}
|
|
855
|
+
)
|
|
856
|
+
for item in payload.get("snapshots", ())
|
|
857
|
+
)
|
|
858
|
+
except (OSError, TypeError, ValueError, json.JSONDecodeError):
|
|
859
|
+
return None
|
|
860
|
+
|
|
861
|
+
def _write_cache(self, source_revision: str, snapshots: Sequence[_Snapshot]) -> None:
|
|
862
|
+
path = self._cache_path()
|
|
863
|
+
if path is None:
|
|
864
|
+
return
|
|
865
|
+
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
866
|
+
os.chmod(path.parent, 0o700)
|
|
867
|
+
payload = {
|
|
868
|
+
"schema": LIFECYCLE_PLAN_SCHEMA,
|
|
869
|
+
"index_spec": LIFECYCLE_INDEX_SPEC,
|
|
870
|
+
"source_revision": source_revision,
|
|
871
|
+
"snapshots": [
|
|
872
|
+
{
|
|
873
|
+
key: (
|
|
874
|
+
value.isoformat()
|
|
875
|
+
if isinstance(value, datetime)
|
|
876
|
+
else list(value)
|
|
877
|
+
if isinstance(value, tuple)
|
|
878
|
+
else value
|
|
879
|
+
)
|
|
880
|
+
for key, value in asdict(row).items()
|
|
881
|
+
}
|
|
882
|
+
for row in snapshots
|
|
883
|
+
],
|
|
884
|
+
}
|
|
885
|
+
temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp")
|
|
886
|
+
temporary.write_text(json.dumps(payload, sort_keys=True, separators=(",", ":")), encoding="utf-8")
|
|
887
|
+
os.chmod(temporary, 0o600)
|
|
888
|
+
temporary.replace(path)
|