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,707 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# The attendant — Egregore's ambient listener. A per-instance daemon that
|
|
3
|
+
# watches for work Claude Code sessions leave behind when they die without
|
|
4
|
+
# ceremony (terminal closed, laptop shut) and captures it:
|
|
5
|
+
#
|
|
6
|
+
# ripcord dead session + no handoff → deterministic auto-handoff to
|
|
7
|
+
# memory/handoffs/ (kind: ripcord), assembled from the session
|
|
8
|
+
# transcript + git state. No model, no API calls.
|
|
9
|
+
# auto-push dirty/unpushed dev|feature|bugfix branches in this repo's
|
|
10
|
+
# worktrees, once no session is active → commit + push the
|
|
11
|
+
# branch. Never merges, never touches develop/main.
|
|
12
|
+
#
|
|
13
|
+
# In-harness hooks (SessionEnd, PreCompact) die with the process; the
|
|
14
|
+
# attendant lives outside it. The signal is Claude Code's own transcript
|
|
15
|
+
# files (~/.claude/projects/<key>/*.jsonl), written continuously regardless
|
|
16
|
+
# of how a session ends: transcript gone stale + work on disk = pull the cord.
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# bash bin/attendant.sh ensure # spawn daemon if not running (fire-and-forget from session-start)
|
|
20
|
+
# bash bin/attendant.sh run # daemon loop (foreground)
|
|
21
|
+
# bash bin/attendant.sh sweep # single pass (tests, cron)
|
|
22
|
+
# bash bin/attendant.sh status # is it alive + last log lines
|
|
23
|
+
# bash bin/attendant.sh stop # kill the daemon
|
|
24
|
+
#
|
|
25
|
+
# Env overrides (mainly for tests):
|
|
26
|
+
# ATTENDANT_STALE_MIN minutes of transcript silence = dead session (default 12)
|
|
27
|
+
# ATTENDANT_DRY_RUN=1 log actions, mutate nothing
|
|
28
|
+
# ATTENDANT_HOME state dir (default ~/.egregore/attendant)
|
|
29
|
+
# CLAUDE_PROJECTS_DIR transcript root (default ~/.claude/projects)
|
|
30
|
+
# EGREGORE_MEMORY_DIR memory repo (default <repo>/memory)
|
|
31
|
+
|
|
32
|
+
set -uo pipefail
|
|
33
|
+
|
|
34
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
35
|
+
MAIN_DIR="$SCRIPT_DIR"
|
|
36
|
+
if [ -f "$SCRIPT_DIR/.git" ]; then
|
|
37
|
+
WT_GITDIR=$(sed 's/^gitdir: //' "$SCRIPT_DIR/.git" 2>/dev/null || true)
|
|
38
|
+
[ -n "$WT_GITDIR" ] && MAIN_DIR=$(cd "$WT_GITDIR/../../.." 2>/dev/null && pwd || echo "$SCRIPT_DIR")
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
CONFIG="$MAIN_DIR/egregore.json"
|
|
42
|
+
MEMORY_DIR="${EGREGORE_MEMORY_DIR:-$MAIN_DIR/memory}"
|
|
43
|
+
STALE_MIN="${ATTENDANT_STALE_MIN:-12}"
|
|
44
|
+
MAX_AGE_MIN=1440 # ignore transcripts older than 24h
|
|
45
|
+
RIPCORD_MIN_BYTES=20000 # skip trivial sessions
|
|
46
|
+
DRY_RUN="${ATTENDANT_DRY_RUN:-0}"
|
|
47
|
+
CLAUDE_PROJECTS="${CLAUDE_PROJECTS_DIR:-$HOME/.claude/projects}"
|
|
48
|
+
|
|
49
|
+
SLUG=$(jq -r '.slug // "egregore"' "$CONFIG" 2>/dev/null || echo "egregore")
|
|
50
|
+
KEY="${SLUG}-$(echo -n "$MAIN_DIR" | cksum | cut -d' ' -f1)"
|
|
51
|
+
STATE_DIR="${ATTENDANT_HOME:-$HOME/.egregore/attendant}"
|
|
52
|
+
PIDFILE="$STATE_DIR/$KEY.pid"
|
|
53
|
+
VERSIONFILE="$STATE_DIR/$KEY.version"
|
|
54
|
+
LOGFILE="$STATE_DIR/$KEY.log"
|
|
55
|
+
JOURNAL="$STATE_DIR/$KEY.journal"
|
|
56
|
+
mkdir -p "$STATE_DIR"
|
|
57
|
+
|
|
58
|
+
# Munged project key, same rule Claude Code uses for ~/.claude/projects dirs.
|
|
59
|
+
PROJ_KEY=$(echo -n "$MAIN_DIR" | sed 's|[^A-Za-z0-9]|-|g')
|
|
60
|
+
|
|
61
|
+
log() {
|
|
62
|
+
echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOGFILE"
|
|
63
|
+
# keep the log bounded
|
|
64
|
+
if [ "$(wc -c < "$LOGFILE" 2>/dev/null | tr -d ' ')" -gt 1000000 ]; then
|
|
65
|
+
tail -500 "$LOGFILE" > "$LOGFILE.tmp" && mv "$LOGFILE.tmp" "$LOGFILE"
|
|
66
|
+
fi
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_script_version() {
|
|
70
|
+
cksum "$MAIN_DIR/bin/attendant.sh" 2>/dev/null | awk '{print $1 ":" $2}'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_enabled() {
|
|
74
|
+
[ "$(jq -r 'if .features.attendant == false then "false" else "true" end' "$CONFIG" 2>/dev/null)" != "false" ]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_weaver_enabled() {
|
|
78
|
+
[ "$(jq -r 'if .features.weaver == false then "false" else "true" end' "$CONFIG" 2>/dev/null)" != "false" ]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_trigger_auto_thread() {
|
|
82
|
+
local reason="$1"
|
|
83
|
+
local runner="$MAIN_DIR/threads/bin/auto-thread.sh"
|
|
84
|
+
local dry_arg=""
|
|
85
|
+
_weaver_enabled || return 0
|
|
86
|
+
[ -f "$runner" ] || return 0
|
|
87
|
+
[ "$DRY_RUN" = "1" ] && dry_arg="--dry-run"
|
|
88
|
+
|
|
89
|
+
# Projection is downstream of capture. Run it detached so a slow or broken
|
|
90
|
+
# renderer can never delay ripcord persistence or branch auto-save. The
|
|
91
|
+
# runner's atomic lock coalesces ripcord + end-of-sweep triggers.
|
|
92
|
+
(
|
|
93
|
+
if ! bash "$runner" \
|
|
94
|
+
--repo-dir "$MAIN_DIR" \
|
|
95
|
+
--memory-dir "$MEMORY_DIR" \
|
|
96
|
+
${dry_arg:+"$dry_arg"}; then
|
|
97
|
+
log "auto-thread failed ($reason)"
|
|
98
|
+
fi
|
|
99
|
+
) >> "$LOGFILE" 2>&1 &
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Personal off switch for ambient capture. ripcord_enabled=false disables BOTH
|
|
103
|
+
# ripcord notes and auto-push of dirty worktrees — a user who opts out of
|
|
104
|
+
# ambient capture must not find silent commits either. The attendant's other
|
|
105
|
+
# duties (graph cache, context seed, handoff lifecycle) are unaffected.
|
|
106
|
+
# NOTE: jq's `//` treats false as empty (the auto_update:false gotcha), so the
|
|
107
|
+
# test compares the raw value, never `.key // true`.
|
|
108
|
+
_capture_enabled() {
|
|
109
|
+
[ "$(jq -r '.ripcord_enabled' "$MAIN_DIR/.egregore-state.json" 2>/dev/null)" != "false" ]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
_author() {
|
|
113
|
+
local a
|
|
114
|
+
a=$(jq -r '.github_username // empty' "$MAIN_DIR/.egregore-state.json" 2>/dev/null)
|
|
115
|
+
[ -z "$a" ] && a=$(git -C "$MAIN_DIR" config user.name 2>/dev/null | tr ' ' '-' | tr '[:upper:]' '[:lower:]')
|
|
116
|
+
echo "${a:-unknown}"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# --- Session liveness ------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
_transcript_dirs() {
|
|
122
|
+
find "$CLAUDE_PROJECTS" -maxdepth 1 -type d -name "${PROJ_KEY}*" 2>/dev/null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_active_session() {
|
|
126
|
+
local d
|
|
127
|
+
for d in $(_transcript_dirs); do
|
|
128
|
+
if [ -n "$(find "$d" -maxdepth 1 -name '*.jsonl' -mmin "-$STALE_MIN" 2>/dev/null | head -1)" ]; then
|
|
129
|
+
return 0
|
|
130
|
+
fi
|
|
131
|
+
done
|
|
132
|
+
return 1
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
_stale_transcripts() {
|
|
136
|
+
local d
|
|
137
|
+
for d in $(_transcript_dirs); do
|
|
138
|
+
find "$d" -maxdepth 1 -name '*.jsonl' -mmin "+$STALE_MIN" -mmin "-$MAX_AGE_MIN" -size "+${RIPCORD_MIN_BYTES}c" 2>/dev/null
|
|
139
|
+
done
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# --- Ripcord ---------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
_last_prompts() {
|
|
145
|
+
# Last few human prompts from a transcript, cleaned of harness noise.
|
|
146
|
+
jq -r 'select(.type=="user") | .message.content
|
|
147
|
+
| if type=="string" then .
|
|
148
|
+
else ([.[]? | select(.type?=="text") | .text] | join(" ")) end' "$1" 2>/dev/null \
|
|
149
|
+
| grep -v -e '^\s*$' -e 'system-reminder' -e '<command-' -e 'tool_use_error' -e '^Caveat:' -e '<task-notification' \
|
|
150
|
+
-e '<local-command-caveat' -e '</local-command-caveat>' -e '<bash-input>' -e '<bash-stdout>' -e '<bash-stderr>' \
|
|
151
|
+
| tail -5 | cut -c1-300
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
_slugify() {
|
|
155
|
+
printf '%s' "$1" \
|
|
156
|
+
| tr '[:upper:]' '[:lower:]' \
|
|
157
|
+
| sed 's/<[^>]*>/ /g; s/[`*_#|:;,.!?()[\]{}"'"'"']/ /g; s/[^a-z0-9]/-/g; s/--*/-/g; s/^-//; s/-$//' \
|
|
158
|
+
| cut -c1-72 \
|
|
159
|
+
| sed 's/-$//'
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
_prompt_topic_slug() {
|
|
163
|
+
local prompts="$1"
|
|
164
|
+
local line clean lower slug score best_score=0 best_slug=""
|
|
165
|
+
|
|
166
|
+
while IFS= read -r line; do
|
|
167
|
+
[ -z "$line" ] && continue
|
|
168
|
+
lower=$(printf '%s' "$line" | tr '[:upper:]' '[:lower:]')
|
|
169
|
+
case "$lower" in
|
|
170
|
+
start|*request\ interrupted*|*system-reminder*|*local-command-caveat*|*bash-input*|*bash-stdout*|*bash-stderr*|*tool_use_error*)
|
|
171
|
+
continue
|
|
172
|
+
;;
|
|
173
|
+
esac
|
|
174
|
+
|
|
175
|
+
clean=$(printf '%s' "$line" | sed 's/^>[[:space:]]*//; s/<[^>]*>/ /g; s/[|`*_#]/ /g')
|
|
176
|
+
slug=$(_slugify "$clean")
|
|
177
|
+
[ -z "$slug" ] && continue
|
|
178
|
+
[ "${#slug}" -lt 8 ] && continue
|
|
179
|
+
|
|
180
|
+
score=${#slug}
|
|
181
|
+
case "$slug" in
|
|
182
|
+
*handoff*|*emissary*|*harvest*|*ssh*|*pr*|*review*|*egregore*|*invite*|*deploy*|*release*)
|
|
183
|
+
score=$((score + 20))
|
|
184
|
+
;;
|
|
185
|
+
esac
|
|
186
|
+
|
|
187
|
+
if [ "$score" -gt "$best_score" ]; then
|
|
188
|
+
best_score="$score"
|
|
189
|
+
best_slug="$slug"
|
|
190
|
+
fi
|
|
191
|
+
done <<EOF
|
|
192
|
+
$prompts
|
|
193
|
+
EOF
|
|
194
|
+
|
|
195
|
+
printf '%s\n' "$best_slug"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
_branch_topic_slug() {
|
|
199
|
+
local gitsum="$1"
|
|
200
|
+
local author="$2"
|
|
201
|
+
local branch rest score best_score=0 best_slug=""
|
|
202
|
+
|
|
203
|
+
while IFS= read -r branch; do
|
|
204
|
+
[ -z "$branch" ] && continue
|
|
205
|
+
case "$branch" in
|
|
206
|
+
develop|main|master) continue ;;
|
|
207
|
+
dev/"$author"/*) rest="${branch#dev/$author/}"; score=90 ;;
|
|
208
|
+
feature/*|bugfix/*|hotfix/*) rest="${branch#*/}"; score=80 ;;
|
|
209
|
+
dev/*/*) rest="${branch#dev/}"; rest="${rest#*/}"; score=60 ;;
|
|
210
|
+
*/*) rest="${branch#*/}"; score=50 ;;
|
|
211
|
+
*) rest="$branch"; score=40 ;;
|
|
212
|
+
esac
|
|
213
|
+
|
|
214
|
+
rest=$(_slugify "$rest")
|
|
215
|
+
[ -z "$rest" ] && continue
|
|
216
|
+
if [ "$score" -gt "$best_score" ]; then
|
|
217
|
+
best_score="$score"
|
|
218
|
+
best_slug="$rest"
|
|
219
|
+
fi
|
|
220
|
+
done <<EOF
|
|
221
|
+
$(printf '%s\n' "$gitsum" | awk -F'`' '/^- `/{print $2}')
|
|
222
|
+
EOF
|
|
223
|
+
|
|
224
|
+
printf '%s\n' "$best_slug"
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_ripcord_topic_slug() {
|
|
228
|
+
local prompts="$1"
|
|
229
|
+
local gitsum="$2"
|
|
230
|
+
local author="$3"
|
|
231
|
+
local slug
|
|
232
|
+
|
|
233
|
+
slug=$(_prompt_topic_slug "$prompts")
|
|
234
|
+
[ -z "$slug" ] && slug=$(_branch_topic_slug "$gitsum" "$author")
|
|
235
|
+
[ -z "$slug" ] && slug="session"
|
|
236
|
+
printf '%s\n' "$slug"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
_topic_from_slug() {
|
|
240
|
+
printf '%s\n' "$1" | tr '-' ' '
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
_git_summary() {
|
|
244
|
+
# Branch + dirty/unpushed state for the main checkout and every worktree.
|
|
245
|
+
local wt branch dirty ahead
|
|
246
|
+
git -C "$MAIN_DIR" worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2}' | while read -r wt; do
|
|
247
|
+
branch=$(git -C "$wt" branch --show-current 2>/dev/null)
|
|
248
|
+
[ -z "$branch" ] && continue
|
|
249
|
+
dirty=$(git -C "$wt" status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
250
|
+
ahead=$(git -C "$wt" rev-list --count '@{u}..HEAD' 2>/dev/null || echo "?")
|
|
251
|
+
if [ "$dirty" != "0" ] || { [ "$ahead" != "0" ] && [ "$ahead" != "?" ]; }; then
|
|
252
|
+
echo "- \`$branch\` — $dirty uncommitted, $ahead unpushed ($(basename "$wt"))"
|
|
253
|
+
git -C "$wt" status --porcelain 2>/dev/null | head -8 | sed 's/^/ /'
|
|
254
|
+
fi
|
|
255
|
+
done
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
_ripcord() {
|
|
259
|
+
local transcript="$1" base author date_ym date_d slug topic file prompts gitsum
|
|
260
|
+
base=$(basename "$transcript" .jsonl)
|
|
261
|
+
|
|
262
|
+
_capture_enabled || return 0
|
|
263
|
+
|
|
264
|
+
grep -q "$base" "$JOURNAL" 2>/dev/null && return 0
|
|
265
|
+
# Session already ended with ceremony? Then the attendant has no business
|
|
266
|
+
# here. Scan only the tail — a session that merely *read* the /handoff or
|
|
267
|
+
# /save skill text mid-way would otherwise false-positive.
|
|
268
|
+
if tail -c 300000 "$transcript" 2>/dev/null | grep -q -e 'handoff-run.sh' -e 'wraps/'; then
|
|
269
|
+
echo "ripcord-skip $base (explicit handoff/wrap)" >> "$JOURNAL"
|
|
270
|
+
return 0
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
author=$(_author)
|
|
274
|
+
prompts=$(_last_prompts "$transcript")
|
|
275
|
+
gitsum=$(_git_summary)
|
|
276
|
+
|
|
277
|
+
# Nothing on disk and nothing said — not worth a handoff.
|
|
278
|
+
if [ -z "$gitsum" ] && [ -z "$prompts" ]; then
|
|
279
|
+
echo "ripcord-skip $base (no work found)" >> "$JOURNAL"
|
|
280
|
+
return 0
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
slug=$(_ripcord_topic_slug "$prompts" "$gitsum" "$author")
|
|
284
|
+
topic=$(_topic_from_slug "$slug")
|
|
285
|
+
|
|
286
|
+
date_ym=$(date '+%Y-%m'); date_d=$(date '+%d')
|
|
287
|
+
file="$MEMORY_DIR/handoffs/$date_ym/${date_d}-${author}-ripcord-${slug}.md"
|
|
288
|
+
|
|
289
|
+
if [ "$DRY_RUN" = "1" ]; then
|
|
290
|
+
log "DRY ripcord → $file"
|
|
291
|
+
_trigger_auto_thread "ripcord"
|
|
292
|
+
return 0
|
|
293
|
+
fi
|
|
294
|
+
|
|
295
|
+
mkdir -p "$(dirname "$file")"
|
|
296
|
+
{
|
|
297
|
+
echo "---"
|
|
298
|
+
echo "date: $(date '+%Y-%m-%d')"
|
|
299
|
+
echo "author: $author"
|
|
300
|
+
echo "to: $author"
|
|
301
|
+
echo "kind: ripcord"
|
|
302
|
+
echo "status: draft"
|
|
303
|
+
echo "topic: $topic"
|
|
304
|
+
echo "---"
|
|
305
|
+
echo ""
|
|
306
|
+
echo "# Ripcord: $topic"
|
|
307
|
+
echo ""
|
|
308
|
+
echo "Automatic capture — this session ended without an explicit handoff."
|
|
309
|
+
echo "Assembled by the attendant from the flight recorder (transcript + git state)."
|
|
310
|
+
echo ""
|
|
311
|
+
if [ -n "$gitsum" ]; then
|
|
312
|
+
echo "## Where the work lives"
|
|
313
|
+
echo ""
|
|
314
|
+
echo "$gitsum"
|
|
315
|
+
echo ""
|
|
316
|
+
fi
|
|
317
|
+
if [ -n "$prompts" ]; then
|
|
318
|
+
echo "## Last things the user asked for"
|
|
319
|
+
echo ""
|
|
320
|
+
echo "$prompts" | sed 's/^/> /'
|
|
321
|
+
echo ""
|
|
322
|
+
fi
|
|
323
|
+
echo "## Pick it up"
|
|
324
|
+
echo ""
|
|
325
|
+
echo "Check out the branch above and run \`/activity\` for surrounding context."
|
|
326
|
+
echo "Session transcript id: \`$base\` ($(date -r "$transcript" '+%Y-%m-%d %H:%M' 2>/dev/null))"
|
|
327
|
+
} > "$file"
|
|
328
|
+
|
|
329
|
+
# Commit + push memory (retry for concurrent pushers; tolerate no remote).
|
|
330
|
+
local rel="${file#"$MEMORY_DIR"/}"
|
|
331
|
+
git -C "$MEMORY_DIR" add "$rel" 2>/dev/null
|
|
332
|
+
git -C "$MEMORY_DIR" commit -q -m "Ripcord: auto-handoff for $author ($slug)" 2>/dev/null
|
|
333
|
+
if git -C "$MEMORY_DIR" remote get-url origin >/dev/null 2>&1; then
|
|
334
|
+
local i
|
|
335
|
+
for i in 1 2 3; do
|
|
336
|
+
git -C "$MEMORY_DIR" pull --rebase origin main --quiet 2>/dev/null && \
|
|
337
|
+
git -C "$MEMORY_DIR" push origin main --quiet 2>/dev/null && break
|
|
338
|
+
sleep 2
|
|
339
|
+
done
|
|
340
|
+
fi
|
|
341
|
+
bash "$MAIN_DIR/bin/index-handoff.sh" "$rel" >/dev/null 2>&1 || true
|
|
342
|
+
|
|
343
|
+
echo "ripcord $base $rel" >> "$JOURNAL"
|
|
344
|
+
log "ripcord → memory/$rel"
|
|
345
|
+
_trigger_auto_thread "ripcord"
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
# --- Auto-push -------------------------------------------------------------
|
|
349
|
+
|
|
350
|
+
_auto_push() {
|
|
351
|
+
_capture_enabled || return 0
|
|
352
|
+
local wt branch dirty ahead upstream
|
|
353
|
+
git -C "$MAIN_DIR" worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2}' | while read -r wt; do
|
|
354
|
+
branch=$(git -C "$wt" branch --show-current 2>/dev/null)
|
|
355
|
+
case "$branch" in dev/*|feature/*|bugfix/*) ;; *) continue ;; esac
|
|
356
|
+
# Mid-rebase/merge — never touch.
|
|
357
|
+
local gd; gd=$(git -C "$wt" rev-parse --git-dir 2>/dev/null)
|
|
358
|
+
{ [ -d "$gd/rebase-merge" ] || [ -d "$gd/rebase-apply" ] || [ -f "$gd/MERGE_HEAD" ]; } && continue
|
|
359
|
+
|
|
360
|
+
dirty=$(git -C "$wt" status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
361
|
+
# Only act on work that has itself gone quiet (nothing modified recently).
|
|
362
|
+
if [ "$dirty" != "0" ]; then
|
|
363
|
+
local fresh
|
|
364
|
+
fresh=$(git -C "$wt" status --porcelain 2>/dev/null | sed 's/^...//' | head -50 | while read -r f; do
|
|
365
|
+
find "$wt/$f" -mmin "-$STALE_MIN" 2>/dev/null | head -1
|
|
366
|
+
done | head -1)
|
|
367
|
+
[ -n "$fresh" ] && continue
|
|
368
|
+
if [ "$DRY_RUN" = "1" ]; then log "DRY auto-commit $branch ($dirty files, $wt)"; else
|
|
369
|
+
git -C "$wt" add -A 2>/dev/null
|
|
370
|
+
git -C "$wt" commit -q -m "chore(attendant): ripcord auto-save
|
|
371
|
+
|
|
372
|
+
Uncommitted work captured after the session went quiet.
|
|
373
|
+
|
|
374
|
+
Co-Authored-By: Egregore Attendant <noreply@egregore.xyz>" 2>/dev/null
|
|
375
|
+
log "auto-commit $branch ($dirty files)"
|
|
376
|
+
fi
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
upstream=$(git -C "$wt" rev-parse --abbrev-ref '@{u}' 2>/dev/null || true)
|
|
380
|
+
if [ -z "$upstream" ]; then
|
|
381
|
+
ahead=$(git -C "$wt" rev-list --count origin/develop..HEAD 2>/dev/null || echo 0)
|
|
382
|
+
else
|
|
383
|
+
ahead=$(git -C "$wt" rev-list --count '@{u}..HEAD' 2>/dev/null || echo 0)
|
|
384
|
+
fi
|
|
385
|
+
if [ "${ahead:-0}" != "0" ]; then
|
|
386
|
+
if [ "$DRY_RUN" = "1" ]; then log "DRY auto-push $branch ($ahead commits)"; else
|
|
387
|
+
git -C "$wt" push -u origin "$branch" --quiet 2>/dev/null && log "auto-push $branch ($ahead commits)"
|
|
388
|
+
fi
|
|
389
|
+
fi
|
|
390
|
+
done
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
# --- Warm: pre-pay the launch path -------------------------------------------
|
|
394
|
+
# Session-start's cost is network: git fetches + the greeting's graph queries.
|
|
395
|
+
# The attendant pays them in the background every WARM_MIN minutes so launch
|
|
396
|
+
# finds fresh refs (git-sync skips its fetch+wait) and a hot graph cache
|
|
397
|
+
# (context.sh reads answers instead of making round-trips). Runs even while
|
|
398
|
+
# sessions are active — fetch and read-only replay are safe mid-work.
|
|
399
|
+
|
|
400
|
+
WARM_MIN="${ATTENDANT_WARM_MIN:-5}"
|
|
401
|
+
WARM_MARKER="$STATE_DIR/$KEY.warm-ts"
|
|
402
|
+
|
|
403
|
+
# Non-secret fingerprint of the effective graph endpoint + credential, matching
|
|
404
|
+
# bin/graph.sh's resolution order exactly (process env → .env → committed
|
|
405
|
+
# api_url). Scopes the graph cache and the snapshot provenance to the tenant
|
|
406
|
+
# answers actually come from, not just the tenant the committed config names.
|
|
407
|
+
_effective_scope() {
|
|
408
|
+
local url key
|
|
409
|
+
url="${EGREGORE_API_URL:-$(grep '^EGREGORE_API_URL=' "$MAIN_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
|
|
410
|
+
key="${EGREGORE_API_KEY:-$(grep '^EGREGORE_API_KEY=' "$MAIN_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
|
|
411
|
+
[ -n "$url" ] || url=$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)
|
|
412
|
+
echo -n "${url}|${key}" | cksum | cut -d' ' -f1
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
_warm() {
|
|
416
|
+
local now last
|
|
417
|
+
now=$(date +%s)
|
|
418
|
+
last=$(cat "$WARM_MARKER" 2>/dev/null || echo 0)
|
|
419
|
+
# Numeric + not-future guard: a malformed legacy marker must not abort
|
|
420
|
+
# warming, and a future timestamp must not suppress it indefinitely.
|
|
421
|
+
case "$last" in ''|*[!0-9]*) last=0 ;; esac
|
|
422
|
+
[ "$last" -gt "$now" ] && last=0
|
|
423
|
+
[ $(( now - last )) -lt $(( WARM_MIN * 60 )) ] && return 0
|
|
424
|
+
[ "$DRY_RUN" = "1" ] && { log "DRY warm"; return 0; }
|
|
425
|
+
|
|
426
|
+
# --prune keeps remote-tracking refs honest so the launch path can answer
|
|
427
|
+
# "does my branch still exist on origin?" from local refs (worktree stale
|
|
428
|
+
# check in git-sync.sh) instead of a ~2s ls-remote round-trip.
|
|
429
|
+
local _origin_pid _origin_ok="false"
|
|
430
|
+
git -C "$MAIN_DIR" fetch origin --prune --quiet 2>/dev/null &
|
|
431
|
+
_origin_pid=$!
|
|
432
|
+
[ -d "$MEMORY_DIR/.git" ] || [ -f "$MEMORY_DIR/.git" ] && git -C "$MEMORY_DIR" fetch origin --quiet 2>/dev/null &
|
|
433
|
+
local repo
|
|
434
|
+
jq -r '(.repos[]? // empty) | if type == "object" then .name else . end' "$CONFIG" 2>/dev/null | while read -r repo; do
|
|
435
|
+
[ -d "$MAIN_DIR/../$repo/.git" ] && git -C "$MAIN_DIR/../$repo" fetch origin --quiet 2>/dev/null
|
|
436
|
+
done &
|
|
437
|
+
wait "$_origin_pid" 2>/dev/null && _origin_ok="true"
|
|
438
|
+
wait
|
|
439
|
+
# Stamp the marker ONLY when the origin fetch actually succeeded, and stamp
|
|
440
|
+
# it atomically (tmp+mv): the launch path treats a fresh marker as "local
|
|
441
|
+
# refs are authoritative", so a failed fetch must not silence live fetches
|
|
442
|
+
# for ten minutes, and a reader must never catch a half-written timestamp.
|
|
443
|
+
if [ "$_origin_ok" = "true" ]; then
|
|
444
|
+
date +%s > "$WARM_MARKER.tmp" && mv "$WARM_MARKER.tmp" "$WARM_MARKER"
|
|
445
|
+
fi
|
|
446
|
+
|
|
447
|
+
# Replay graph cache entries touched in the last 24h (connected mode only —
|
|
448
|
+
# in local mode graph.sh exits instantly and no cache exists).
|
|
449
|
+
local cache_dir f q p
|
|
450
|
+
# Key must match graph.sh's _CACHE_DIR derivation exactly: repo root plus
|
|
451
|
+
# the effective endpoint/credential fingerprint, so entries never cross
|
|
452
|
+
# tenants when the same checkout is repointed at a different org/API.
|
|
453
|
+
cache_dir="$HOME/.egregore/graph-cache/$(echo -n "${MAIN_DIR}|$(_effective_scope)" | cksum | cut -d' ' -f1)"
|
|
454
|
+
if [ -d "$cache_dir" ]; then
|
|
455
|
+
for f in $(find "$cache_dir" -name '*.json' -mmin -1440 2>/dev/null); do
|
|
456
|
+
q=$(jq -r '.query // empty' "$f" 2>/dev/null)
|
|
457
|
+
p=$(jq -c '.params // {}' "$f" 2>/dev/null)
|
|
458
|
+
[ -n "$q" ] && EGREGORE_GRAPH_CACHE_TTL=1 bash "$MAIN_DIR/bin/graph.sh" query "$q" "$p" >/dev/null 2>&1 || true
|
|
459
|
+
done
|
|
460
|
+
fi
|
|
461
|
+
_prebake_context
|
|
462
|
+
log "warm: refs fetched, graph cache replayed, context baked"
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
# --- Pre-bake the session-start context snapshot -----------------------------
|
|
466
|
+
# Runs the same context gather session-start would (bin/lib/context.sh) and
|
|
467
|
+
# stores the result as a single tar at $STATE_DIR/$KEY.ctx.tar. On boot,
|
|
468
|
+
# context.sh reuses the baked graph-bound results (todos, lifecycle, pulse,
|
|
469
|
+
# metrics, service health) when the bake is <15min old and its provenance
|
|
470
|
+
# matches (author, mode, config), re-running the local collectors live —
|
|
471
|
+
# taking the greeting's graph work off the launch path entirely.
|
|
472
|
+
#
|
|
473
|
+
# One tar + atomic rename, not a directory swap: readers open either the whole
|
|
474
|
+
# old bake or the whole new one, never a half-replaced directory. The tmp name
|
|
475
|
+
# carries $$ so two attendants racing (possible before the run-loop self-check
|
|
476
|
+
# converges them) never write through each other's file.
|
|
477
|
+
_prebake_context() {
|
|
478
|
+
[ -f "$MAIN_DIR/bin/lib/context.sh" ] || return 0
|
|
479
|
+
local snap="$STATE_DIR/$KEY.ctx.tar"
|
|
480
|
+
(
|
|
481
|
+
set +u +e
|
|
482
|
+
unset CTX_SEED_TAR CTX_SEED_DIR # never let a bake seed itself from an old bake
|
|
483
|
+
SCRIPT_DIR="$MAIN_DIR"
|
|
484
|
+
CONFIG="$MAIN_DIR/egregore.json"
|
|
485
|
+
STATE_FILE="$MAIN_DIR/.egregore-state.json"
|
|
486
|
+
ENV_FILE="$MAIN_DIR/.env"
|
|
487
|
+
AUTHOR=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
|
|
488
|
+
[ -n "$AUTHOR" ] || exit 0
|
|
489
|
+
# Canonical mode semantics (config.sh _detect_mode): local when the config
|
|
490
|
+
# says so OR api_url is absent — legacy OSS configs carry no mode key, and
|
|
491
|
+
# a bare '.mode == local' test would bake failed connected health for them.
|
|
492
|
+
LOCAL_MODE="false"
|
|
493
|
+
source "$MAIN_DIR/bin/lib/config.sh" 2>/dev/null || true
|
|
494
|
+
if type _detect_mode >/dev/null 2>&1; then
|
|
495
|
+
[ "$(_detect_mode)" = "local" ] && LOCAL_MODE="true"
|
|
496
|
+
elif ! jq -e '(.mode // "") != "local" and ((.api_url // "") | length > 0)' "$CONFIG" >/dev/null 2>&1; then
|
|
497
|
+
LOCAL_MODE="true"
|
|
498
|
+
fi
|
|
499
|
+
# Capture ALL provenance BEFORE gathering, then publish only if it is
|
|
500
|
+
# unchanged AFTER — a user, config, or credential switch mid-gather would
|
|
501
|
+
# otherwise produce mixed-tenant/mixed-user data labeled with whatever the
|
|
502
|
+
# final values happened to be (TOCTOU).
|
|
503
|
+
_PRE_CONFIG=$(cksum "$CONFIG" 2>/dev/null | cut -d' ' -f1)
|
|
504
|
+
_PRE_SCOPE=$(_effective_scope)
|
|
505
|
+
_PRE_SCHEMA=$(cksum "$MAIN_DIR/bin/lib/context.sh" 2>/dev/null | cut -d' ' -f1)
|
|
506
|
+
# PIN the effective credentials for the entire gather: graph.sh re-reads
|
|
507
|
+
# env/.env in every subprocess, so without pinning, an A→B→A .env flip
|
|
508
|
+
# during the gather could fetch B-tenant data that the pre/post scope
|
|
509
|
+
# samples cannot see. With the pin, every graph call in this bake uses
|
|
510
|
+
# the credentials the provenance stamp names. Empty values stay empty
|
|
511
|
+
# (local mode) — graph.sh treats them as unset.
|
|
512
|
+
export EGREGORE_API_URL="${EGREGORE_API_URL:-$(grep '^EGREGORE_API_URL=' "$MAIN_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
|
|
513
|
+
export EGREGORE_API_KEY="${EGREGORE_API_KEY:-$(grep '^EGREGORE_API_KEY=' "$MAIN_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
|
|
514
|
+
export EGREGORE_GRAPH_CACHE_TTL=600
|
|
515
|
+
source "$MAIN_DIR/bin/lib/context.sh"
|
|
516
|
+
[ -n "$CTX_DIR" ] && [ -d "$CTX_DIR" ] || exit 0
|
|
517
|
+
_POST_AUTHOR=$(jq -r '.github_username // empty' "$STATE_FILE" 2>/dev/null)
|
|
518
|
+
_POST_CONFIG=$(cksum "$CONFIG" 2>/dev/null | cut -d' ' -f1)
|
|
519
|
+
_POST_SCOPE=$(_effective_scope)
|
|
520
|
+
_POST_SCHEMA=$(cksum "$MAIN_DIR/bin/lib/context.sh" 2>/dev/null | cut -d' ' -f1)
|
|
521
|
+
if [ "$_POST_AUTHOR" != "$AUTHOR" ] || [ "$_POST_CONFIG" != "$_PRE_CONFIG" ] \
|
|
522
|
+
|| [ "$_POST_SCOPE" != "$_PRE_SCOPE" ] || [ "$_POST_SCHEMA" != "$_PRE_SCHEMA" ]; then
|
|
523
|
+
rm -rf "$CTX_DIR"
|
|
524
|
+
exit 0
|
|
525
|
+
fi
|
|
526
|
+
# Provenance the reader validates before trusting the bake.
|
|
527
|
+
date +%s > "$CTX_DIR/.baked-ts"
|
|
528
|
+
printf '%s\n' "$AUTHOR" > "$CTX_DIR/.baked-author"
|
|
529
|
+
printf '%s\n' "$LOCAL_MODE" > "$CTX_DIR/.baked-local-mode"
|
|
530
|
+
printf '%s\n' "$_PRE_CONFIG" > "$CTX_DIR/.baked-config"
|
|
531
|
+
# Effective endpoint/credential + the context.sh revision that produced
|
|
532
|
+
# this bake: a seed from a different tenant or a different collector
|
|
533
|
+
# schema must never be trusted.
|
|
534
|
+
printf '%s\n' "$_PRE_SCOPE" > "$CTX_DIR/.baked-scope"
|
|
535
|
+
printf '%s\n' "$_PRE_SCHEMA" > "$CTX_DIR/.baked-schema"
|
|
536
|
+
if (cd "$CTX_DIR" && tar -cf "$snap.tmp.$$" .) 2>/dev/null; then
|
|
537
|
+
mv -f "$snap.tmp.$$" "$snap" 2>/dev/null || rm -f "$snap.tmp.$$" 2>/dev/null
|
|
538
|
+
else
|
|
539
|
+
rm -f "$snap.tmp.$$" 2>/dev/null
|
|
540
|
+
fi
|
|
541
|
+
rm -rf "$CTX_DIR"
|
|
542
|
+
) >/dev/null 2>&1 || true
|
|
543
|
+
# Clean up the directory-format snapshot from the previous iteration
|
|
544
|
+
rm -rf "$STATE_DIR/$KEY.ctx" 2>/dev/null || true
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
# --- Handoff lifecycle: recurring close / nudge / expiry --------------------
|
|
548
|
+
# Connected-mode only. The job itself filters to lifecycle-v1 handoffs with an
|
|
549
|
+
# explicit intent, so legacy unclassified backfill never runs unattended.
|
|
550
|
+
|
|
551
|
+
LIFECYCLE_MIN="${ATTENDANT_LIFECYCLE_MIN:-360}"
|
|
552
|
+
LIFECYCLE_MARKER="$STATE_DIR/$KEY.handoff-lifecycle-ts"
|
|
553
|
+
|
|
554
|
+
_handoff_lifecycle() {
|
|
555
|
+
local now last mode api_url api_key result
|
|
556
|
+
# Read the raw value, not `// true` — jq's `//` returns its right-hand side
|
|
557
|
+
# for null *and false*, so `{"features":{"handoff_lifecycle":false}}` used to
|
|
558
|
+
# resolve to "true" and the opt-out did nothing. Absent key still means on.
|
|
559
|
+
case "$(jq -r '.features.handoff_lifecycle | tojson' "$CONFIG" 2>/dev/null)" in
|
|
560
|
+
null|true) ;;
|
|
561
|
+
*) return 0 ;;
|
|
562
|
+
esac
|
|
563
|
+
[ -f "$MAIN_DIR/bin/handoff-lifecycle-job.sh" ] || return 0
|
|
564
|
+
|
|
565
|
+
mode=$(jq -r '.mode // "connected"' "$CONFIG" 2>/dev/null)
|
|
566
|
+
api_url=$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)
|
|
567
|
+
api_key=$(grep '^EGREGORE_API_KEY=' "$MAIN_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)
|
|
568
|
+
[ "$mode" = "connected" ] && [ -n "$api_url" ] && [ -n "$api_key" ] || return 0
|
|
569
|
+
|
|
570
|
+
now=$(date +%s)
|
|
571
|
+
last=$(cat "$LIFECYCLE_MARKER" 2>/dev/null || echo 0)
|
|
572
|
+
[ $(( now - last )) -lt $(( LIFECYCLE_MIN * 60 )) ] && return 0
|
|
573
|
+
if [ "$DRY_RUN" = "1" ]; then
|
|
574
|
+
log "DRY handoff-lifecycle"
|
|
575
|
+
return 0
|
|
576
|
+
fi
|
|
577
|
+
|
|
578
|
+
if result=$(bash "$MAIN_DIR/bin/handoff-lifecycle-job.sh" run 2>&1); then
|
|
579
|
+
log "handoff-lifecycle: $(printf '%s' "$result" | jq -c \
|
|
580
|
+
'{transitions,nudges:(.nudges|length)}' 2>/dev/null || echo success)"
|
|
581
|
+
else
|
|
582
|
+
log "handoff-lifecycle failed: $(printf '%s' "$result" | tail -1 | cut -c1-300)"
|
|
583
|
+
fi
|
|
584
|
+
date +%s > "$LIFECYCLE_MARKER"
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
# --- Commands ---------------------------------------------------------------
|
|
588
|
+
|
|
589
|
+
cmd_sweep() {
|
|
590
|
+
_enabled || return 0
|
|
591
|
+
_warm
|
|
592
|
+
_handoff_lifecycle
|
|
593
|
+
if _active_session; then
|
|
594
|
+
log "sweep: session active — standing by"
|
|
595
|
+
return 0
|
|
596
|
+
fi
|
|
597
|
+
local t
|
|
598
|
+
for t in $(_stale_transcripts); do
|
|
599
|
+
_ripcord "$t"
|
|
600
|
+
done
|
|
601
|
+
_auto_push
|
|
602
|
+
_trigger_auto_thread "sweep"
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
cmd_run() {
|
|
606
|
+
_enabled || exit 0
|
|
607
|
+
echo $$ > "$PIDFILE"
|
|
608
|
+
_script_version > "$VERSIONFILE"
|
|
609
|
+
log "attendant up (pid $$, stale=${STALE_MIN}m, $MAIN_DIR)"
|
|
610
|
+
while true; do
|
|
611
|
+
[ -d "$MAIN_DIR" ] || { log "project gone — exiting"; break; }
|
|
612
|
+
# Duplicate-daemon convergence: cmd_ensure's check-then-spawn is lockless,
|
|
613
|
+
# so two concurrent cold starts can both launch a run loop. Whoever owns
|
|
614
|
+
# the pidfile keeps running; the other notices within one cycle and exits
|
|
615
|
+
# without touching shared state.
|
|
616
|
+
[ "$(cat "$PIDFILE" 2>/dev/null)" = "$$" ] || { log "superseded (pid $$) — exiting"; exit 0; }
|
|
617
|
+
cmd_sweep
|
|
618
|
+
sleep 60
|
|
619
|
+
done
|
|
620
|
+
rm -f "$PIDFILE"
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
cmd_ensure() {
|
|
624
|
+
_enabled || exit 0
|
|
625
|
+
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null; then
|
|
626
|
+
if [ "$(cat "$VERSIONFILE" 2>/dev/null)" = "$(_script_version)" ]; then
|
|
627
|
+
exit 0
|
|
628
|
+
fi
|
|
629
|
+
log "attendant code changed — restarting"
|
|
630
|
+
kill "$(cat "$PIDFILE")" 2>/dev/null || true
|
|
631
|
+
rm -f "$PIDFILE"
|
|
632
|
+
fi
|
|
633
|
+
# Atomic spawn gate: the pidfile check above is check-then-spawn, so two
|
|
634
|
+
# concurrent cold starts could both reach here and launch two daemons that
|
|
635
|
+
# overlap for a full cycle (lifecycle jobs, ripcord, auto-push). mkdir is
|
|
636
|
+
# the portable atomic primitive; a crashed spawner's stale lock breaks
|
|
637
|
+
# after 5 minutes. Losing the race means someone else is spawning — done.
|
|
638
|
+
local _spawn_lock="$STATE_DIR/$KEY.spawn-lock"
|
|
639
|
+
if ! mkdir "$_spawn_lock" 2>/dev/null; then
|
|
640
|
+
local _lock_age
|
|
641
|
+
_lock_age=$(( $(date +%s) - $(stat -f %m "$_spawn_lock" 2>/dev/null || stat -c %Y "$_spawn_lock" 2>/dev/null || echo 0) ))
|
|
642
|
+
# Stale reap by atomic rename, never rmdir+mkdir: two reapers doing
|
|
643
|
+
# remove-then-recreate can alternately delete each other's FRESH lock and
|
|
644
|
+
# both spawn. Exactly one mv succeeds; losing the mv or the follow-up
|
|
645
|
+
# mkdir means someone else owns a fresh lock — back off.
|
|
646
|
+
if [ "$_lock_age" -gt 300 ] && mv "$_spawn_lock" "$_spawn_lock.reap.$$" 2>/dev/null; then
|
|
647
|
+
rm -rf "$_spawn_lock.reap.$$" 2>/dev/null
|
|
648
|
+
mkdir "$_spawn_lock" 2>/dev/null || exit 0
|
|
649
|
+
else
|
|
650
|
+
exit 0
|
|
651
|
+
fi
|
|
652
|
+
fi
|
|
653
|
+
# Re-check under the lock — the racing ensure may already have spawned.
|
|
654
|
+
if ! { [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null; }; then
|
|
655
|
+
nohup bash "$MAIN_DIR/bin/attendant.sh" run >> "$LOGFILE" 2>&1 &
|
|
656
|
+
disown 2>/dev/null || true
|
|
657
|
+
# Hold the lock until the child's ownership is durably visible (it writes
|
|
658
|
+
# its pidfile first thing in cmd_run). Releasing earlier reopens the race:
|
|
659
|
+
# another ensure passes the pidfile re-check before the child registers
|
|
660
|
+
# and spawns a second daemon whose first sweep overlaps this one.
|
|
661
|
+
local _i _spawned="false"
|
|
662
|
+
for _i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25; do
|
|
663
|
+
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null; then
|
|
664
|
+
_spawned="true"
|
|
665
|
+
break
|
|
666
|
+
fi
|
|
667
|
+
sleep 0.2
|
|
668
|
+
done
|
|
669
|
+
if [ "$_spawned" != "true" ]; then
|
|
670
|
+
# Child still hasn't registered: KEEP the lock rather than reopen the
|
|
671
|
+
# spawn window — a merely-delayed child may yet come up, and the stale
|
|
672
|
+
# reaper above frees the lock in 5 minutes if it never does. Fail
|
|
673
|
+
# closed: no attendant for a few minutes beats two attendants sweeping.
|
|
674
|
+
log "ensure: spawned child slow to register — holding spawn lock (pid $$)"
|
|
675
|
+
exit 0
|
|
676
|
+
fi
|
|
677
|
+
fi
|
|
678
|
+
rmdir "$_spawn_lock" 2>/dev/null || true
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
cmd_status() {
|
|
682
|
+
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null; then
|
|
683
|
+
echo "attendant: running (pid $(cat "$PIDFILE"), key $KEY)"
|
|
684
|
+
else
|
|
685
|
+
echo "attendant: not running (key $KEY)"
|
|
686
|
+
fi
|
|
687
|
+
[ -f "$LOGFILE" ] && tail -5 "$LOGFILE"
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
cmd_stop() {
|
|
691
|
+
if [ -f "$PIDFILE" ]; then
|
|
692
|
+
kill "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null
|
|
693
|
+
rm -f "$PIDFILE" "$VERSIONFILE"
|
|
694
|
+
echo "attendant: stopped"
|
|
695
|
+
else
|
|
696
|
+
echo "attendant: not running"
|
|
697
|
+
fi
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
case "${1:-}" in
|
|
701
|
+
ensure) cmd_ensure ;;
|
|
702
|
+
run) cmd_run ;;
|
|
703
|
+
sweep) cmd_sweep ;;
|
|
704
|
+
status) cmd_status ;;
|
|
705
|
+
stop) cmd_stop ;;
|
|
706
|
+
*) echo "usage: bash bin/attendant.sh {ensure|run|sweep|status|stop}" >&2; exit 1 ;;
|
|
707
|
+
esac
|