harnery 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +84 -2
- package/bin/agent-coord +42 -0
- package/bin/agent-hook +44 -0
- package/bin/harn +40 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +18 -0
- package/dist/commander.d.ts +128 -0
- package/dist/commander.d.ts.map +1 -0
- package/dist/commander.js +126 -0
- package/dist/commands/agents.d.ts +18 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +3946 -0
- package/dist/commands/backup.d.ts +22 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +262 -0
- package/dist/commands/browse-ai.d.ts +4 -0
- package/dist/commands/browse-ai.d.ts.map +1 -0
- package/dist/commands/browse-ai.js +156 -0
- package/dist/commands/browse.d.ts +4 -0
- package/dist/commands/browse.d.ts.map +1 -0
- package/dist/commands/browse.js +590 -0
- package/dist/commands/callers.d.ts +4 -0
- package/dist/commands/callers.d.ts.map +1 -0
- package/dist/commands/callers.js +276 -0
- package/dist/commands/completion.d.ts +17 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +158 -0
- package/dist/commands/config-get.d.ts +4 -0
- package/dist/commands/config-get.d.ts.map +1 -0
- package/dist/commands/config-get.js +131 -0
- package/dist/commands/context.d.ts +11 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +185 -0
- package/dist/commands/cookies.d.ts +4 -0
- package/dist/commands/cookies.d.ts.map +1 -0
- package/dist/commands/cookies.js +140 -0
- package/dist/commands/docs.d.ts +4 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +137 -0
- package/dist/commands/doctor.d.ts +25 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +200 -0
- package/dist/commands/edit-batch.d.ts +18 -0
- package/dist/commands/edit-batch.d.ts.map +1 -0
- package/dist/commands/edit-batch.js +172 -0
- package/dist/commands/eml.d.ts +4 -0
- package/dist/commands/eml.d.ts.map +1 -0
- package/dist/commands/eml.js +428 -0
- package/dist/commands/env.d.ts +4 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +201 -0
- package/dist/commands/fetch.d.ts +4 -0
- package/dist/commands/fetch.d.ts.map +1 -0
- package/dist/commands/fetch.js +99 -0
- package/dist/commands/file-history.d.ts +4 -0
- package/dist/commands/file-history.d.ts.map +1 -0
- package/dist/commands/file-history.js +152 -0
- package/dist/commands/grep.d.ts +4 -0
- package/dist/commands/grep.d.ts.map +1 -0
- package/dist/commands/grep.js +317 -0
- package/dist/commands/init.d.ts +82 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +288 -0
- package/dist/commands/outline.d.ts +4 -0
- package/dist/commands/outline.d.ts.map +1 -0
- package/dist/commands/outline.js +494 -0
- package/dist/commands/presence.d.ts +12 -0
- package/dist/commands/presence.d.ts.map +1 -0
- package/dist/commands/presence.js +123 -0
- package/dist/commands/read.d.ts +7 -0
- package/dist/commands/read.d.ts.map +1 -0
- package/dist/commands/read.js +46 -0
- package/dist/commands/scratch.d.ts +4 -0
- package/dist/commands/scratch.d.ts.map +1 -0
- package/dist/commands/scratch.js +426 -0
- package/dist/commands/session.d.ts +4 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +162 -0
- package/dist/commands/sync.d.ts +24 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +275 -0
- package/dist/commands/toc.d.ts +5 -0
- package/dist/commands/toc.d.ts.map +1 -0
- package/dist/commands/toc.js +153 -0
- package/dist/commands/tokens.d.ts +4 -0
- package/dist/commands/tokens.d.ts.map +1 -0
- package/dist/commands/tokens.js +48 -0
- package/dist/commands/tunnel.d.ts +4 -0
- package/dist/commands/tunnel.d.ts.map +1 -0
- package/dist/commands/tunnel.js +513 -0
- package/dist/commands/uninstall.d.ts +22 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +126 -0
- package/dist/commands/web.d.ts +4 -0
- package/dist/commands/web.d.ts.map +1 -0
- package/dist/commands/web.js +165 -0
- package/dist/core/agents/canonical-emit.d.ts +27 -0
- package/dist/core/agents/canonical-emit.d.ts.map +1 -0
- package/dist/core/agents/canonical-emit.js +72 -0
- package/dist/core/agents/cli-emit.d.ts +27 -0
- package/dist/core/agents/cli-emit.d.ts.map +1 -0
- package/dist/core/agents/cli-emit.js +57 -0
- package/dist/core/agents/cli.d.ts +10 -0
- package/dist/core/agents/cli.d.ts.map +1 -0
- package/dist/core/agents/cli.js +757 -0
- package/dist/core/agents/codex-replay.d.ts +29 -0
- package/dist/core/agents/codex-replay.d.ts.map +1 -0
- package/dist/core/agents/codex-replay.js +138 -0
- package/dist/core/agents/coord-client.d.ts +98 -0
- package/dist/core/agents/coord-client.d.ts.map +1 -0
- package/dist/core/agents/coord-client.js +212 -0
- package/dist/core/agents/events/consume.d.ts +59 -0
- package/dist/core/agents/events/consume.d.ts.map +1 -0
- package/dist/core/agents/events/consume.js +147 -0
- package/dist/core/agents/events/emit.d.ts +42 -0
- package/dist/core/agents/events/emit.d.ts.map +1 -0
- package/dist/core/agents/events/emit.js +70 -0
- package/dist/core/agents/events/ulid.d.ts +11 -0
- package/dist/core/agents/events/ulid.d.ts.map +1 -0
- package/dist/core/agents/events/ulid.js +47 -0
- package/dist/core/agents/index.d.ts +14 -0
- package/dist/core/agents/index.d.ts.map +1 -0
- package/dist/core/agents/index.js +13 -0
- package/dist/core/agents/paths.d.ts +6 -0
- package/dist/core/agents/paths.d.ts.map +1 -0
- package/dist/core/agents/paths.js +17 -0
- package/dist/core/agents/render/prompt-context.d.ts +43 -0
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -0
- package/dist/core/agents/render/prompt-context.js +335 -0
- package/dist/core/agents/render/session-context.d.ts +39 -0
- package/dist/core/agents/render/session-context.d.ts.map +1 -0
- package/dist/core/agents/render/session-context.js +283 -0
- package/dist/core/agents/rules/claim-conflict.d.ts +35 -0
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -0
- package/dist/core/agents/rules/claim-conflict.js +244 -0
- package/dist/core/agents/rules/commit-conflict.d.ts +59 -0
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -0
- package/dist/core/agents/rules/commit-conflict.js +244 -0
- package/dist/core/agents/rules/stop-hook.d.ts +44 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -0
- package/dist/core/agents/rules/stop-hook.js +161 -0
- package/dist/core/agents/session-events.d.ts +41 -0
- package/dist/core/agents/session-events.d.ts.map +1 -0
- package/dist/core/agents/session-events.js +205 -0
- package/dist/core/agents/state/activity-log.d.ts +18 -0
- package/dist/core/agents/state/activity-log.d.ts.map +1 -0
- package/dist/core/agents/state/activity-log.js +34 -0
- package/dist/core/agents/state/council.d.ts +39 -0
- package/dist/core/agents/state/council.d.ts.map +1 -0
- package/dist/core/agents/state/council.js +216 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts +59 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -0
- package/dist/core/agents/state/heartbeat-projector.js +436 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts +64 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -0
- package/dist/core/agents/state/heartbeat-writer.js +271 -0
- package/dist/core/agents/state/names.d.ts +35 -0
- package/dist/core/agents/state/names.d.ts.map +1 -0
- package/dist/core/agents/state/names.js +376 -0
- package/dist/core/agents/state/pidmap.d.ts +11 -0
- package/dist/core/agents/state/pidmap.d.ts.map +1 -0
- package/dist/core/agents/state/pidmap.js +32 -0
- package/dist/core/agents/state/scratch.d.ts +27 -0
- package/dist/core/agents/state/scratch.d.ts.map +1 -0
- package/dist/core/agents/state/scratch.js +90 -0
- package/dist/core/agents/state/shell-mutation.d.ts +17 -0
- package/dist/core/agents/state/shell-mutation.d.ts.map +1 -0
- package/dist/core/agents/state/shell-mutation.js +41 -0
- package/dist/core/agents/state/stale-sweep.d.ts +16 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -0
- package/dist/core/agents/state/stale-sweep.js +166 -0
- package/dist/core/config.d.ts +29 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +108 -0
- package/dist/core/hooks/cli.d.ts +21 -0
- package/dist/core/hooks/cli.d.ts.map +1 -0
- package/dist/core/hooks/cli.js +1123 -0
- package/dist/core/hooks/effects/image-capture.d.ts +43 -0
- package/dist/core/hooks/effects/image-capture.d.ts.map +1 -0
- package/dist/core/hooks/effects/image-capture.js +288 -0
- package/dist/core/hooks/effects/index.d.ts +64 -0
- package/dist/core/hooks/effects/index.d.ts.map +1 -0
- package/dist/core/hooks/effects/index.js +197 -0
- package/dist/core/hooks/events/emit.d.ts +31 -0
- package/dist/core/hooks/events/emit.d.ts.map +1 -0
- package/dist/core/hooks/events/emit.js +89 -0
- package/dist/core/hooks/events/schema.d.ts +235 -0
- package/dist/core/hooks/events/schema.d.ts.map +1 -0
- package/dist/core/hooks/events/schema.js +12 -0
- package/dist/core/hooks/events/ulid.d.ts +10 -0
- package/dist/core/hooks/events/ulid.d.ts.map +1 -0
- package/dist/core/hooks/events/ulid.js +47 -0
- package/dist/core/hooks/harness/detect.d.ts +9 -0
- package/dist/core/hooks/harness/detect.d.ts.map +1 -0
- package/dist/core/hooks/harness/detect.js +29 -0
- package/dist/core/hooks/harness/events.d.ts +45 -0
- package/dist/core/hooks/harness/events.d.ts.map +1 -0
- package/dist/core/hooks/harness/events.js +71 -0
- package/dist/core/hooks/harness/output.d.ts +46 -0
- package/dist/core/hooks/harness/output.d.ts.map +1 -0
- package/dist/core/hooks/harness/output.js +87 -0
- package/dist/core/hooks/harness/parse.d.ts +67 -0
- package/dist/core/hooks/harness/parse.d.ts.map +1 -0
- package/dist/core/hooks/harness/parse.js +132 -0
- package/dist/core/hooks/index.d.ts +8 -0
- package/dist/core/hooks/index.d.ts.map +1 -0
- package/dist/core/hooks/index.js +7 -0
- package/dist/core/hooks/resolve/anchor.d.ts +37 -0
- package/dist/core/hooks/resolve/anchor.d.ts.map +1 -0
- package/dist/core/hooks/resolve/anchor.js +48 -0
- package/dist/core/hooks/resolve/coord-root.d.ts +6 -0
- package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -0
- package/dist/core/hooks/resolve/coord-root.js +27 -0
- package/dist/core/hooks/resolve/intent.d.ts +33 -0
- package/dist/core/hooks/resolve/intent.d.ts.map +1 -0
- package/dist/core/hooks/resolve/intent.js +79 -0
- package/dist/core/hooks/resolve/owner.d.ts +42 -0
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -0
- package/dist/core/hooks/resolve/owner.js +140 -0
- package/dist/core/hooks/resolve/transcript.d.ts +26 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -0
- package/dist/core/hooks/resolve/transcript.js +73 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/lib/agent-browser/client.d.ts +99 -0
- package/dist/lib/agent-browser/client.d.ts.map +1 -0
- package/dist/lib/agent-browser/client.js +177 -0
- package/dist/lib/agent-browser/index.d.ts +2 -0
- package/dist/lib/agent-browser/index.d.ts.map +1 -0
- package/dist/lib/agent-browser/index.js +1 -0
- package/dist/lib/browser/client.d.ts +193 -0
- package/dist/lib/browser/client.d.ts.map +1 -0
- package/dist/lib/browser/client.js +325 -0
- package/dist/lib/browser/dev-overlay.d.ts +23 -0
- package/dist/lib/browser/dev-overlay.d.ts.map +1 -0
- package/dist/lib/browser/dev-overlay.js +153 -0
- package/dist/lib/browser/index.d.ts +5 -0
- package/dist/lib/browser/index.d.ts.map +1 -0
- package/dist/lib/browser/index.js +2 -0
- package/dist/lib/browser/layout.d.ts +79 -0
- package/dist/lib/browser/layout.d.ts.map +1 -0
- package/dist/lib/browser/layout.js +220 -0
- package/dist/lib/browser/visibility.d.ts +86 -0
- package/dist/lib/browser/visibility.d.ts.map +1 -0
- package/dist/lib/browser/visibility.js +333 -0
- package/dist/lib/browser/visual-diff.d.ts +38 -0
- package/dist/lib/browser/visual-diff.d.ts.map +1 -0
- package/dist/lib/browser/visual-diff.js +107 -0
- package/dist/lib/completion/bash.d.ts +25 -0
- package/dist/lib/completion/bash.d.ts.map +1 -0
- package/dist/lib/completion/bash.js +284 -0
- package/dist/lib/completion/fish.d.ts +16 -0
- package/dist/lib/completion/fish.d.ts.map +1 -0
- package/dist/lib/completion/fish.js +118 -0
- package/dist/lib/completion/index.d.ts +5 -0
- package/dist/lib/completion/index.d.ts.map +1 -0
- package/dist/lib/completion/index.js +4 -0
- package/dist/lib/completion/walk.d.ts +68 -0
- package/dist/lib/completion/walk.d.ts.map +1 -0
- package/dist/lib/completion/walk.js +102 -0
- package/dist/lib/completion/zsh.d.ts +13 -0
- package/dist/lib/completion/zsh.d.ts.map +1 -0
- package/dist/lib/completion/zsh.js +249 -0
- package/dist/lib/context/index.d.ts +107 -0
- package/dist/lib/context/index.d.ts.map +1 -0
- package/dist/lib/context/index.js +275 -0
- package/dist/lib/cookies/client.d.ts +131 -0
- package/dist/lib/cookies/client.d.ts.map +1 -0
- package/dist/lib/cookies/client.js +239 -0
- package/dist/lib/cookies/index.d.ts +2 -0
- package/dist/lib/cookies/index.d.ts.map +1 -0
- package/dist/lib/cookies/index.js +1 -0
- package/dist/lib/council/index.d.ts +266 -0
- package/dist/lib/council/index.d.ts.map +1 -0
- package/dist/lib/council/index.js +674 -0
- package/dist/lib/docs-index.d.ts +28 -0
- package/dist/lib/docs-index.d.ts.map +1 -0
- package/dist/lib/docs-index.js +169 -0
- package/dist/lib/docs-lint.d.ts +26 -0
- package/dist/lib/docs-lint.d.ts.map +1 -0
- package/dist/lib/docs-lint.js +378 -0
- package/dist/lib/docs-sweep.d.ts +34 -0
- package/dist/lib/docs-sweep.d.ts.map +1 -0
- package/dist/lib/docs-sweep.js +304 -0
- package/dist/lib/docs.d.ts +27 -0
- package/dist/lib/docs.d.ts.map +1 -0
- package/dist/lib/docs.js +142 -0
- package/dist/lib/env.d.ts +11 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +12 -0
- package/dist/lib/exec.d.ts +32 -0
- package/dist/lib/exec.d.ts.map +1 -0
- package/dist/lib/exec.js +54 -0
- package/dist/lib/format.d.ts +29 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +139 -0
- package/dist/lib/http/client.d.ts +56 -0
- package/dist/lib/http/client.d.ts.map +1 -0
- package/dist/lib/http/client.js +160 -0
- package/dist/lib/http/index.d.ts +2 -0
- package/dist/lib/http/index.d.ts.map +1 -0
- package/dist/lib/http/index.js +1 -0
- package/dist/lib/identities/index.d.ts +77 -0
- package/dist/lib/identities/index.d.ts.map +1 -0
- package/dist/lib/identities/index.js +190 -0
- package/dist/lib/machine.d.ts +19 -0
- package/dist/lib/machine.d.ts.map +1 -0
- package/dist/lib/machine.js +61 -0
- package/dist/lib/presence.d.ts +48 -0
- package/dist/lib/presence.d.ts.map +1 -0
- package/dist/lib/presence.js +123 -0
- package/dist/lib/readability/client.d.ts +32 -0
- package/dist/lib/readability/client.d.ts.map +1 -0
- package/dist/lib/readability/client.js +119 -0
- package/dist/lib/readability/index.d.ts +2 -0
- package/dist/lib/readability/index.d.ts.map +1 -0
- package/dist/lib/readability/index.js +1 -0
- package/dist/lib/scratch/index.d.ts +74 -0
- package/dist/lib/scratch/index.d.ts.map +1 -0
- package/dist/lib/scratch/index.js +393 -0
- package/dist/lib/tunnel/gate.d.ts +12 -0
- package/dist/lib/tunnel/gate.d.ts.map +1 -0
- package/dist/lib/tunnel/gate.js +101 -0
- package/dist/lib/tunnel/state.d.ts +34 -0
- package/dist/lib/tunnel/state.d.ts.map +1 -0
- package/dist/lib/tunnel/state.js +132 -0
- package/package.json +160 -8
- package/schemas/.gitkeep +0 -0
- package/schemas/config.schema.json +109 -0
- package/src/cli.ts +22 -0
- package/src/commander.ts +242 -0
- package/src/commands/.gitkeep +0 -0
- package/src/commands/agents.ts +4567 -0
- package/src/commands/backup.ts +305 -0
- package/src/commands/browse-ai.ts +198 -0
- package/src/commands/browse.ts +849 -0
- package/src/commands/callers.ts +363 -0
- package/src/commands/completion.ts +193 -0
- package/src/commands/config-get.ts +161 -0
- package/src/commands/context.ts +209 -0
- package/src/commands/cookies.ts +198 -0
- package/src/commands/docs.ts +174 -0
- package/src/commands/doctor.ts +231 -0
- package/src/commands/edit-batch.ts +233 -0
- package/src/commands/eml.ts +519 -0
- package/src/commands/env.ts +254 -0
- package/src/commands/fetch.ts +136 -0
- package/src/commands/file-history.ts +202 -0
- package/src/commands/grep.ts +371 -0
- package/src/commands/init.ts +335 -0
- package/src/commands/outline.ts +564 -0
- package/src/commands/presence.ts +152 -0
- package/src/commands/read.ts +64 -0
- package/src/commands/scratch.ts +445 -0
- package/src/commands/session.ts +187 -0
- package/src/commands/sync.ts +306 -0
- package/src/commands/toc.ts +218 -0
- package/src/commands/tokens.ts +79 -0
- package/src/commands/tunnel.ts +633 -0
- package/src/commands/uninstall.ts +144 -0
- package/src/commands/web.ts +193 -0
- package/src/core/agents/canonical-emit.ts +77 -0
- package/src/core/agents/cli-emit.ts +64 -0
- package/src/core/agents/cli.ts +838 -0
- package/src/core/agents/codex-replay.ts +163 -0
- package/src/core/agents/coord-client.ts +249 -0
- package/src/core/agents/events/consume.ts +196 -0
- package/src/core/agents/events/emit.ts +108 -0
- package/src/core/agents/events/ulid.ts +51 -0
- package/src/core/agents/index.ts +14 -0
- package/src/core/agents/paths.ts +16 -0
- package/src/core/agents/render/prompt-context.ts +401 -0
- package/src/core/agents/render/session-context.ts +341 -0
- package/src/core/agents/rules/claim-conflict.ts +282 -0
- package/src/core/agents/rules/commit-conflict.ts +303 -0
- package/src/core/agents/rules/stop-hook.ts +229 -0
- package/src/core/agents/session-events.ts +228 -0
- package/src/core/agents/state/activity-log.ts +33 -0
- package/src/core/agents/state/council.ts +265 -0
- package/src/core/agents/state/heartbeat-projector.ts +488 -0
- package/src/core/agents/state/heartbeat-writer.ts +333 -0
- package/src/core/agents/state/names.ts +399 -0
- package/src/core/agents/state/pidmap.ts +38 -0
- package/src/core/agents/state/scratch.ts +121 -0
- package/src/core/agents/state/shell-mutation.ts +44 -0
- package/src/core/agents/state/stale-sweep.ts +190 -0
- package/src/core/config.ts +111 -0
- package/src/core/hooks/cli.ts +1247 -0
- package/src/core/hooks/effects/image-capture.ts +330 -0
- package/src/core/hooks/effects/index.ts +210 -0
- package/src/core/hooks/events/emit.ts +120 -0
- package/src/core/hooks/events/schema.ts +430 -0
- package/src/core/hooks/events/ulid.ts +51 -0
- package/src/core/hooks/harness/detect.ts +30 -0
- package/src/core/hooks/harness/events.ts +102 -0
- package/src/core/hooks/harness/output.ts +100 -0
- package/src/core/hooks/harness/parse.ts +180 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/resolve/anchor.ts +51 -0
- package/src/core/hooks/resolve/coord-root.ts +25 -0
- package/src/core/hooks/resolve/intent.ts +89 -0
- package/src/core/hooks/resolve/owner.ts +140 -0
- package/src/core/hooks/resolve/transcript.ts +72 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/index.ts +15 -0
- package/src/lib/agent-browser/client.ts +239 -0
- package/src/lib/agent-browser/index.ts +1 -0
- package/src/lib/browser/client.ts +449 -0
- package/src/lib/browser/dev-overlay.ts +207 -0
- package/src/lib/browser/index.ts +24 -0
- package/src/lib/browser/layout.ts +288 -0
- package/src/lib/browser/visibility.ts +419 -0
- package/src/lib/browser/visual-diff.ts +150 -0
- package/src/lib/completion/bash.ts +291 -0
- package/src/lib/completion/fish.ts +134 -0
- package/src/lib/completion/index.ts +10 -0
- package/src/lib/completion/walk.ts +184 -0
- package/src/lib/completion/zsh.ts +262 -0
- package/src/lib/context/index.ts +386 -0
- package/src/lib/cookies/client.ts +301 -0
- package/src/lib/cookies/index.ts +13 -0
- package/src/lib/council/index.ts +803 -0
- package/src/lib/docs-index.ts +216 -0
- package/src/lib/docs-lint.ts +413 -0
- package/src/lib/docs-sweep.ts +348 -0
- package/src/lib/docs.ts +199 -0
- package/src/lib/env.ts +12 -0
- package/src/lib/exec.ts +74 -0
- package/src/lib/format.ts +147 -0
- package/src/lib/http/client.ts +211 -0
- package/src/lib/http/index.ts +1 -0
- package/src/lib/identities/index.ts +210 -0
- package/src/lib/machine.ts +61 -0
- package/src/lib/presence.ts +154 -0
- package/src/lib/readability/client.ts +156 -0
- package/src/lib/readability/index.ts +5 -0
- package/src/lib/readability/turndown-plugin-gfm.d.ts +10 -0
- package/src/lib/scratch/index.ts +470 -0
- package/src/lib/tunnel/gate.ts +113 -0
- package/src/lib/tunnel/state.ts +167 -0
- package/src/web/.gitkeep +0 -0
- package/index.js +0 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command/narration event emitter for the coordination layer.
|
|
3
|
+
*
|
|
4
|
+
* `writeSessionEvent` emits command + narration events straight to the
|
|
5
|
+
* **canonical** `.harnery/events.ndjson` (alongside the hook events), which the
|
|
6
|
+
* `/live` web viewer reads. The exported surface (`writeSessionEvent`,
|
|
7
|
+
* `newCmdId`, `clampField`, `readLastIntent`) is stable so the session-tee
|
|
8
|
+
* middleware callers need no edits.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { randomBytes } from "node:crypto";
|
|
12
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
13
|
+
import { dirname, resolve } from "node:path";
|
|
14
|
+
// Kept dependency-light: vendored verbatim into a downstream consumer, so no coordEnv import.
|
|
15
|
+
import { normalizeHarness } from "./canonical-emit.ts";
|
|
16
|
+
import { emit } from "./events/emit.ts";
|
|
17
|
+
|
|
18
|
+
/** Event types accepted by `writeSessionEvent`. Only the command stream +
|
|
19
|
+
* narration are emitted canonically; the coord/state types are accepted for
|
|
20
|
+
* call-site compatibility but are no-ops (the agents CLI emits those itself). */
|
|
21
|
+
export type SessionEventType =
|
|
22
|
+
| "command_start"
|
|
23
|
+
| "output"
|
|
24
|
+
| "command_end"
|
|
25
|
+
| "end_of_turn"
|
|
26
|
+
| "hook_event"
|
|
27
|
+
| "set_task"
|
|
28
|
+
| "file_claim"
|
|
29
|
+
| "file_release"
|
|
30
|
+
| "peer_change"
|
|
31
|
+
| "narration";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Resolved path of the ndjson sidecar file. Lives inside `.harnery/` so a
|
|
35
|
+
* containerized reader can pick it up through a single bind mount.
|
|
36
|
+
*/
|
|
37
|
+
export function sessionEventsPath(): string {
|
|
38
|
+
// Explicit override (tests + non-monorepo invocations).
|
|
39
|
+
const explicit = process.env.HARNERY_OUTPUT_SESSION_EVENTS;
|
|
40
|
+
if (explicit) return explicit;
|
|
41
|
+
// Walk up looking for an EXISTING .harnery/ directory; only match dirs
|
|
42
|
+
// (submodule .git is a file/gitlink, and CLAUDE.md / AGENTS.md can
|
|
43
|
+
// proliferate per-subdirectory in many monorepos, so neither is a safe
|
|
44
|
+
// anchor). Don't reintroduce CLAUDE.md / .git as anchors, which creates
|
|
45
|
+
// stray .harnery/ dirs under submodule subtrees.
|
|
46
|
+
let dir = process.cwd();
|
|
47
|
+
for (let i = 0; i < 10; i++) {
|
|
48
|
+
const candidate = resolve(dir, ".harnery");
|
|
49
|
+
try {
|
|
50
|
+
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
|
|
51
|
+
return resolve(candidate, "session-events.ndjson");
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
/* keep walking */
|
|
55
|
+
}
|
|
56
|
+
const parent = dirname(dir);
|
|
57
|
+
if (parent === dir) break;
|
|
58
|
+
dir = parent;
|
|
59
|
+
}
|
|
60
|
+
return resolve(process.env.HOME || "/tmp", ".harnery", "session-events.ndjson");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Random 8-char hex id for grouping output lines under a single command. */
|
|
64
|
+
export function newCmdId(): string {
|
|
65
|
+
return randomBytes(4).toString("hex");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Read the model's most recent `<intent>...</intent>` declaration from the
|
|
70
|
+
* intent-stamp file written by the PreToolUse hook. Returns null when the
|
|
71
|
+
* file is missing, empty, or contains the explicit `(no intent)` sentinel.
|
|
72
|
+
* Callers fall back to whatever default they want in that case.
|
|
73
|
+
*
|
|
74
|
+
* Path: `.harnery/.last-intent.<instance_id>` next to the agent's heartbeat.
|
|
75
|
+
*/
|
|
76
|
+
export function readLastIntent(instanceId?: string): string | null {
|
|
77
|
+
if (!instanceId) return null;
|
|
78
|
+
// Walk up from cwd looking for .harnery/, same resolution as
|
|
79
|
+
// sessionEventsPath() (only match existing directories).
|
|
80
|
+
let dir = process.cwd();
|
|
81
|
+
let agentsDir: string | null = null;
|
|
82
|
+
for (let i = 0; i < 10; i++) {
|
|
83
|
+
const candidate = resolve(dir, ".harnery");
|
|
84
|
+
try {
|
|
85
|
+
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
|
|
86
|
+
agentsDir = candidate;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
/* keep walking */
|
|
91
|
+
}
|
|
92
|
+
const parent = dirname(dir);
|
|
93
|
+
if (parent === dir) break;
|
|
94
|
+
dir = parent;
|
|
95
|
+
}
|
|
96
|
+
if (!agentsDir) return null;
|
|
97
|
+
const intentPath = resolve(agentsDir, `.last-intent.${instanceId}`);
|
|
98
|
+
if (!existsSync(intentPath)) return null;
|
|
99
|
+
try {
|
|
100
|
+
const raw = readFileSync(intentPath, "utf8").trim();
|
|
101
|
+
if (!raw || raw === "(no intent)") return null;
|
|
102
|
+
return raw;
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Dual-write: mirror command/narration session-events
|
|
110
|
+
* into the canonical `.harnery/events.ndjson` stream so the legacy
|
|
111
|
+
* session-events.ndjson writer + its web consumers can be retired.
|
|
112
|
+
* Only the command-stream + narration types migrate; the coord/state types
|
|
113
|
+
* (`set_task`, `file_claim`, `peer_change`, …) are already emitted canonically
|
|
114
|
+
* by the agents CLI, so re-emitting them here would double-count.
|
|
115
|
+
*/
|
|
116
|
+
const CANONICAL_TYPE: Partial<Record<SessionEventType, string>> = {
|
|
117
|
+
command_start: "command.start",
|
|
118
|
+
output: "command.output",
|
|
119
|
+
command_end: "command.end",
|
|
120
|
+
narration: "narration",
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
interface HeartbeatEnrichment {
|
|
124
|
+
session_id: string;
|
|
125
|
+
harness: "claude-code" | "cursor" | "codex";
|
|
126
|
+
at: number;
|
|
127
|
+
}
|
|
128
|
+
/** Cache heartbeat-derived envelope fields per instance_id so a burst of
|
|
129
|
+
* `output` lines (one event per stdout line) doesn't re-read + re-parse the
|
|
130
|
+
* heartbeat JSON each time. Short TTL: picks up platform/session changes
|
|
131
|
+
* within a few seconds without hammering the disk on a chatty command. */
|
|
132
|
+
const enrichCache = new Map<string, HeartbeatEnrichment>();
|
|
133
|
+
const ENRICH_TTL_MS = 5000;
|
|
134
|
+
|
|
135
|
+
function enrichFromHeartbeat(coordRoot: string, instanceId: string): HeartbeatEnrichment | null {
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
const cached = enrichCache.get(instanceId);
|
|
138
|
+
if (cached && now - cached.at < ENRICH_TTL_MS) return cached;
|
|
139
|
+
try {
|
|
140
|
+
const hbPath = resolve(coordRoot, ".harnery", "active", `${instanceId}.json`);
|
|
141
|
+
if (!existsSync(hbPath)) return null;
|
|
142
|
+
const hb = JSON.parse(readFileSync(hbPath, "utf8")) as {
|
|
143
|
+
session_id?: string;
|
|
144
|
+
platform?: string;
|
|
145
|
+
};
|
|
146
|
+
const enrichment: HeartbeatEnrichment = {
|
|
147
|
+
session_id: hb.session_id || instanceId,
|
|
148
|
+
harness: normalizeHarness(hb.platform),
|
|
149
|
+
at: now,
|
|
150
|
+
};
|
|
151
|
+
enrichCache.set(instanceId, enrichment);
|
|
152
|
+
return enrichment;
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Project the flat legacy `fields` into the canonical event's `data` shape.
|
|
159
|
+
* Unknown types never reach here, guarded by CANONICAL_TYPE. */
|
|
160
|
+
function canonicalData(
|
|
161
|
+
type: SessionEventType,
|
|
162
|
+
fields: Record<string, unknown>,
|
|
163
|
+
): Record<string, unknown> {
|
|
164
|
+
switch (type) {
|
|
165
|
+
case "command_start":
|
|
166
|
+
return { cmd_id: fields.cmd_id, intent: fields.intent, cmd: fields.cmd };
|
|
167
|
+
case "output":
|
|
168
|
+
return { cmd_id: fields.cmd_id, stream: fields.stream, line: fields.line };
|
|
169
|
+
case "command_end":
|
|
170
|
+
return {
|
|
171
|
+
cmd_id: fields.cmd_id,
|
|
172
|
+
exit: fields.exit,
|
|
173
|
+
duration_ms: fields.duration_ms,
|
|
174
|
+
...(fields.signal ? { signal: fields.signal } : {}),
|
|
175
|
+
};
|
|
176
|
+
case "narration":
|
|
177
|
+
return { message: fields.message };
|
|
178
|
+
default:
|
|
179
|
+
return {};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Emit a command/narration event to the canonical stream. Swallows every
|
|
184
|
+
* error and skips when identity can't be resolved: telemetry must never break
|
|
185
|
+
* (or slow down) a command. Non-command types return early. */
|
|
186
|
+
function emitCanonicalCommand(type: SessionEventType, fields: Record<string, unknown>): void {
|
|
187
|
+
const eventType = CANONICAL_TYPE[type];
|
|
188
|
+
if (!eventType) return;
|
|
189
|
+
const instanceId = typeof fields.instance_id === "string" ? fields.instance_id : undefined;
|
|
190
|
+
if (!instanceId) return;
|
|
191
|
+
try {
|
|
192
|
+
// coordRoot = the dir containing `.harnery/`; sessionEventsPath() anchors it.
|
|
193
|
+
const coordRoot = dirname(dirname(sessionEventsPath()));
|
|
194
|
+
const enrich = enrichFromHeartbeat(coordRoot, instanceId);
|
|
195
|
+
if (!enrich) return;
|
|
196
|
+
emit(coordRoot, {
|
|
197
|
+
event_type: eventType,
|
|
198
|
+
instance_id: instanceId,
|
|
199
|
+
session_id: enrich.session_id,
|
|
200
|
+
harness: enrich.harness,
|
|
201
|
+
data: canonicalData(type, fields),
|
|
202
|
+
});
|
|
203
|
+
} catch {
|
|
204
|
+
/* telemetry only, never break the command */
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Emit a session event. Command + narration events
|
|
210
|
+
* are written to the canonical `.harnery/events.ndjson`; the coord/state types
|
|
211
|
+
* are accepted for call-site compatibility but are no-ops here (the agents CLI
|
|
212
|
+
* emits those itself). Best-effort, never throws into the caller; a command
|
|
213
|
+
* must never break or slow on telemetry. The `agentName` arg is retained for
|
|
214
|
+
* the stable call signature (canonical events key on instance_id, not name).
|
|
215
|
+
*/
|
|
216
|
+
export function writeSessionEvent(
|
|
217
|
+
type: SessionEventType,
|
|
218
|
+
_agentName: string,
|
|
219
|
+
fields: Record<string, unknown> = {},
|
|
220
|
+
): void {
|
|
221
|
+
emitCanonicalCommand(type, fields);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Trim long values to keep individual events small. */
|
|
225
|
+
export function clampField(v: string, max = 1024): string {
|
|
226
|
+
if (v.length <= max) return v;
|
|
227
|
+
return `${v.slice(0, max - 1)}…`;
|
|
228
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display-name resolution for the coord layer.
|
|
3
|
+
*
|
|
4
|
+
* This module once housed `coordLog`, the writer for a human-readable
|
|
5
|
+
* activity log. That log has been retired; the canonical
|
|
6
|
+
* `.harnery/events.ndjson` stream is the single source of truth, and the
|
|
7
|
+
* per-event telemetry that had consumers (heals → health.*, councils →
|
|
8
|
+
* council.*, shell-mutation candidates → decision.warn) is emitted there
|
|
9
|
+
* directly. `coordLog` and its call sites are gone; only `resolveShortName`
|
|
10
|
+
* remains (still used for display-name resolution).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolve `agent-<name>` display string. Looks up the heartbeat's `name`
|
|
18
|
+
* field; falls back to `agent-<8-char-hex>` if name is empty (mirrors bash
|
|
19
|
+
* coord_owner_short). Returns `agent-unknown` when instanceId is null.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveShortName(coordRoot: string, instanceId: string | null): string {
|
|
22
|
+
if (!instanceId) return "agent-unknown";
|
|
23
|
+
const path = join(coordRoot, ".harnery", "active", `${instanceId}.json`);
|
|
24
|
+
if (existsSync(path)) {
|
|
25
|
+
try {
|
|
26
|
+
const hb = JSON.parse(readFileSync(path, "utf8")) as { name?: string };
|
|
27
|
+
if (hb.name && hb.name.length > 0) return `agent-${hb.name}`;
|
|
28
|
+
} catch {
|
|
29
|
+
/* fall through to short-id */
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return `agent-${instanceId.slice(0, 8)}`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Council manifest mutations: lifecycle helpers for the council-* actions.
|
|
3
|
+
*
|
|
4
|
+
* A council is a multi-round debate among agents recorded at
|
|
5
|
+
* `.harnery/councils/<id>.json` (manifest) plus `.harnery/councils/<id>/round-N/<member>.md`
|
|
6
|
+
* (contributions). Archive moves both into `.harnery/councils/archive/`.
|
|
7
|
+
*
|
|
8
|
+
* This module owns: lifecycle (advance, close, archive, unarchive, delete)
|
|
9
|
+
* and steward reassignment. Council CREATION + contribution writes stay
|
|
10
|
+
* separate (operator-side, harness-agnostic).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
cpSync,
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
readFileSync,
|
|
18
|
+
renameSync,
|
|
19
|
+
rmSync,
|
|
20
|
+
writeFileSync,
|
|
21
|
+
} from "node:fs";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
23
|
+
|
|
24
|
+
interface CouncilManifest {
|
|
25
|
+
council_id: string;
|
|
26
|
+
status: "active" | "closed" | "archived";
|
|
27
|
+
current_round: number;
|
|
28
|
+
round_status: "open" | "closed";
|
|
29
|
+
members: string[];
|
|
30
|
+
/** schema_version 2: index-parallel to members; contributions are written as <member_id>.md. */
|
|
31
|
+
member_ids?: string[];
|
|
32
|
+
steward?: string;
|
|
33
|
+
steward_id?: string;
|
|
34
|
+
closed_at?: string;
|
|
35
|
+
archived_at?: string;
|
|
36
|
+
[extra: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function nowIsoSeconds(): string {
|
|
40
|
+
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function atomicWriteText(path: string, content: string): void {
|
|
44
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
45
|
+
const tmp = `${path}.tmp.${process.pid}`;
|
|
46
|
+
writeFileSync(tmp, content, "utf8");
|
|
47
|
+
renameSync(tmp, path);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function councilsDir(coordRoot: string): string {
|
|
51
|
+
return join(coordRoot, ".harnery", "councils");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function archiveDir(coordRoot: string): string {
|
|
55
|
+
return join(councilsDir(coordRoot), "archive");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readManifest(path: string): CouncilManifest | null {
|
|
59
|
+
if (!existsSync(path)) return null;
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(readFileSync(path, "utf8")) as CouncilManifest;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function advanceCouncil(
|
|
68
|
+
coordRoot: string,
|
|
69
|
+
councilId: string,
|
|
70
|
+
opts: { force?: boolean } = {},
|
|
71
|
+
): { ok: boolean; reason?: string; pendingMember?: string } {
|
|
72
|
+
const manifestPath = join(councilsDir(coordRoot), `${councilId}.json`);
|
|
73
|
+
const manifest = readManifest(manifestPath);
|
|
74
|
+
if (!manifest) return { ok: false, reason: `no manifest at ${manifestPath}` };
|
|
75
|
+
if (manifest.status !== "active") {
|
|
76
|
+
return { ok: false, reason: `council is ${manifest.status}, not active` };
|
|
77
|
+
}
|
|
78
|
+
const cur = manifest.current_round;
|
|
79
|
+
|
|
80
|
+
if (!opts.force) {
|
|
81
|
+
const roundDir = join(councilsDir(coordRoot), councilId, `round-${cur}`);
|
|
82
|
+
const memberIds = Array.isArray(manifest.member_ids) ? manifest.member_ids : [];
|
|
83
|
+
for (const [i, member] of manifest.members.entries()) {
|
|
84
|
+
// Contribution filename drifted across schema versions: v1 wrote
|
|
85
|
+
// <agent-Name>.md, v2 (member_ids in the manifest) writes <uuid>.md.
|
|
86
|
+
// Accept either so lifecycle works on both generations of council.
|
|
87
|
+
const candidates = [join(roundDir, `${member}.md`)];
|
|
88
|
+
if (memberIds[i]) candidates.push(join(roundDir, `${memberIds[i]}.md`));
|
|
89
|
+
if (!candidates.some((p) => existsSync(p))) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
reason: `pending members in round ${cur} (incl. ${member}); pass --force to skip`,
|
|
93
|
+
pendingMember: member,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const next = cur + 1;
|
|
100
|
+
const nextDir = join(councilsDir(coordRoot), councilId, `round-${next}`);
|
|
101
|
+
mkdirSync(nextDir, { recursive: true });
|
|
102
|
+
|
|
103
|
+
const updated: CouncilManifest = {
|
|
104
|
+
...manifest,
|
|
105
|
+
current_round: next,
|
|
106
|
+
round_status: "open",
|
|
107
|
+
};
|
|
108
|
+
atomicWriteText(manifestPath, JSON.stringify(updated, null, 2));
|
|
109
|
+
return { ok: true };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function closeCouncil(
|
|
113
|
+
coordRoot: string,
|
|
114
|
+
councilId: string,
|
|
115
|
+
): { ok: boolean; reason?: string } {
|
|
116
|
+
const manifestPath = join(councilsDir(coordRoot), `${councilId}.json`);
|
|
117
|
+
const manifest = readManifest(manifestPath);
|
|
118
|
+
if (!manifest) return { ok: false, reason: `no manifest at ${manifestPath}` };
|
|
119
|
+
if (manifest.status === "archived") return { ok: false, reason: "council is already archived" };
|
|
120
|
+
|
|
121
|
+
const updated: CouncilManifest = {
|
|
122
|
+
...manifest,
|
|
123
|
+
status: "closed",
|
|
124
|
+
closed_at: nowIsoSeconds(),
|
|
125
|
+
};
|
|
126
|
+
atomicWriteText(manifestPath, JSON.stringify(updated, null, 2));
|
|
127
|
+
return { ok: true };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function archiveCouncil(
|
|
131
|
+
coordRoot: string,
|
|
132
|
+
councilId: string,
|
|
133
|
+
): { ok: boolean; reason?: string } {
|
|
134
|
+
const manifestPath = join(councilsDir(coordRoot), `${councilId}.json`);
|
|
135
|
+
const manifest = readManifest(manifestPath);
|
|
136
|
+
if (!manifest) return { ok: false, reason: `no manifest at ${manifestPath}` };
|
|
137
|
+
|
|
138
|
+
const archived: CouncilManifest = {
|
|
139
|
+
...manifest,
|
|
140
|
+
status: "archived",
|
|
141
|
+
archived_at: nowIsoSeconds(),
|
|
142
|
+
};
|
|
143
|
+
const archivedManifest = join(archiveDir(coordRoot), `${councilId}.json`);
|
|
144
|
+
const archivedBody = join(archiveDir(coordRoot), councilId);
|
|
145
|
+
const activeBody = join(councilsDir(coordRoot), councilId);
|
|
146
|
+
|
|
147
|
+
mkdirSync(archiveDir(coordRoot), { recursive: true });
|
|
148
|
+
atomicWriteText(manifestPath, JSON.stringify(archived, null, 2));
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
renameSync(manifestPath, archivedManifest);
|
|
152
|
+
} catch {
|
|
153
|
+
/* idempotent; manifest may already be in archive */
|
|
154
|
+
}
|
|
155
|
+
if (existsSync(activeBody)) {
|
|
156
|
+
if (existsSync(archivedBody)) {
|
|
157
|
+
rmSync(activeBody, { recursive: true, force: true });
|
|
158
|
+
} else {
|
|
159
|
+
try {
|
|
160
|
+
renameSync(activeBody, archivedBody);
|
|
161
|
+
} catch {
|
|
162
|
+
// cross-device or permissions: fall back to copy + remove.
|
|
163
|
+
cpSync(activeBody, archivedBody, { recursive: true });
|
|
164
|
+
rmSync(activeBody, { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { ok: true };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function unarchiveCouncil(
|
|
172
|
+
coordRoot: string,
|
|
173
|
+
councilId: string,
|
|
174
|
+
): { ok: boolean; reason?: string } {
|
|
175
|
+
const archivedManifest = join(archiveDir(coordRoot), `${councilId}.json`);
|
|
176
|
+
const activeManifest = join(councilsDir(coordRoot), `${councilId}.json`);
|
|
177
|
+
if (!existsSync(archivedManifest)) {
|
|
178
|
+
return { ok: false, reason: `no archived manifest at ${archivedManifest}` };
|
|
179
|
+
}
|
|
180
|
+
if (existsSync(activeManifest)) {
|
|
181
|
+
return { ok: false, reason: "active manifest already exists; refusing to clobber" };
|
|
182
|
+
}
|
|
183
|
+
const manifest = readManifest(archivedManifest);
|
|
184
|
+
if (!manifest) return { ok: false, reason: `couldn't parse ${archivedManifest}` };
|
|
185
|
+
|
|
186
|
+
const restored: CouncilManifest = { ...manifest };
|
|
187
|
+
restored.archived_at = undefined;
|
|
188
|
+
restored.status = manifest.closed_at ? "closed" : "active";
|
|
189
|
+
atomicWriteText(archivedManifest, JSON.stringify(restored, null, 2));
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
renameSync(archivedManifest, activeManifest);
|
|
193
|
+
} catch {
|
|
194
|
+
/* ignore; idempotent */
|
|
195
|
+
}
|
|
196
|
+
const archivedBody = join(archiveDir(coordRoot), councilId);
|
|
197
|
+
const activeBody = join(councilsDir(coordRoot), councilId);
|
|
198
|
+
if (existsSync(archivedBody)) {
|
|
199
|
+
if (existsSync(activeBody)) {
|
|
200
|
+
rmSync(archivedBody, { recursive: true, force: true });
|
|
201
|
+
} else {
|
|
202
|
+
try {
|
|
203
|
+
renameSync(archivedBody, activeBody);
|
|
204
|
+
} catch {
|
|
205
|
+
cpSync(archivedBody, activeBody, { recursive: true });
|
|
206
|
+
rmSync(archivedBody, { recursive: true, force: true });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { ok: true };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function deleteCouncil(
|
|
214
|
+
coordRoot: string,
|
|
215
|
+
councilId: string,
|
|
216
|
+
): { ok: boolean; reason?: string } {
|
|
217
|
+
const archivedManifest = join(archiveDir(coordRoot), `${councilId}.json`);
|
|
218
|
+
const activeManifest = join(councilsDir(coordRoot), `${councilId}.json`);
|
|
219
|
+
if (existsSync(activeManifest)) {
|
|
220
|
+
return {
|
|
221
|
+
ok: false,
|
|
222
|
+
reason: `council ${councilId} is not archived; archive first`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (!existsSync(archivedManifest)) {
|
|
226
|
+
return { ok: false, reason: `no archived manifest at ${archivedManifest}` };
|
|
227
|
+
}
|
|
228
|
+
const archivedBody = join(archiveDir(coordRoot), councilId);
|
|
229
|
+
|
|
230
|
+
rmSync(archivedManifest, { force: true });
|
|
231
|
+
if (existsSync(archivedBody)) {
|
|
232
|
+
rmSync(archivedBody, { recursive: true, force: true });
|
|
233
|
+
}
|
|
234
|
+
return { ok: true };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function setCouncilSteward(
|
|
238
|
+
coordRoot: string,
|
|
239
|
+
councilId: string,
|
|
240
|
+
newSteward: string,
|
|
241
|
+
newStewardId: string,
|
|
242
|
+
): { ok: boolean; reason?: string } {
|
|
243
|
+
const manifestPath = join(councilsDir(coordRoot), `${councilId}.json`);
|
|
244
|
+
const manifest = readManifest(manifestPath);
|
|
245
|
+
if (!manifest) return { ok: false, reason: `no manifest at ${manifestPath}` };
|
|
246
|
+
if (manifest.status === "archived") {
|
|
247
|
+
return { ok: false, reason: "council is archived (read-only)" };
|
|
248
|
+
}
|
|
249
|
+
if (newSteward && !/^agent-[A-Za-z][A-Za-z0-9_-]*$/.test(newSteward)) {
|
|
250
|
+
return {
|
|
251
|
+
ok: false,
|
|
252
|
+
reason: `invalid steward "${newSteward}" (must match agent-[A-Za-z][A-Za-z0-9_-]*)`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const updated: CouncilManifest = { ...manifest };
|
|
256
|
+
if (!newSteward) {
|
|
257
|
+
updated.steward = undefined;
|
|
258
|
+
updated.steward_id = undefined;
|
|
259
|
+
} else {
|
|
260
|
+
updated.steward = newSteward;
|
|
261
|
+
if (newStewardId) updated.steward_id = newStewardId;
|
|
262
|
+
}
|
|
263
|
+
atomicWriteText(manifestPath, JSON.stringify(updated, null, 2));
|
|
264
|
+
return { ok: true };
|
|
265
|
+
}
|