@cluesmith/codev 3.2.4 → 3.3.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/dashboard-dist/assets/index-BZRTEMtH.css +32 -0
- package/dashboard-dist/assets/{index-BZ1x7x1X.js → index-JCbkpAcV.js} +33 -33
- package/dashboard-dist/assets/index-JCbkpAcV.js.map +1 -0
- package/dashboard-dist/index.html +2 -2
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +142 -0
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +16 -0
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/inbox.d.ts +42 -0
- package/dist/agent-farm/commands/inbox.d.ts.map +1 -0
- package/dist/agent-farm/commands/inbox.js +129 -0
- package/dist/agent-farm/commands/inbox.js.map +1 -0
- package/dist/agent-farm/commands/interrupt.d.ts +20 -0
- package/dist/agent-farm/commands/interrupt.d.ts.map +1 -0
- package/dist/agent-farm/commands/interrupt.js +65 -0
- package/dist/agent-farm/commands/interrupt.js.map +1 -0
- package/dist/agent-farm/commands/reset/constants.d.ts +52 -0
- package/dist/agent-farm/commands/reset/constants.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/constants.js +52 -0
- package/dist/agent-farm/commands/reset/constants.js.map +1 -0
- package/dist/agent-farm/commands/reset/context.d.ts +230 -0
- package/dist/agent-farm/commands/reset/context.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/context.js +428 -0
- package/dist/agent-farm/commands/reset/context.js.map +1 -0
- package/dist/agent-farm/commands/reset/index.d.ts +192 -0
- package/dist/agent-farm/commands/reset/index.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/index.js +488 -0
- package/dist/agent-farm/commands/reset/index.js.map +1 -0
- package/dist/agent-farm/commands/reset/receipt.d.ts +97 -0
- package/dist/agent-farm/commands/reset/receipt.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/receipt.js +149 -0
- package/dist/agent-farm/commands/reset/receipt.js.map +1 -0
- package/dist/agent-farm/commands/reset/reorient.d.ts +117 -0
- package/dist/agent-farm/commands/reset/reorient.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset/reorient.js +313 -0
- package/dist/agent-farm/commands/reset/reorient.js.map +1 -0
- package/dist/agent-farm/commands/reset.d.ts +19 -0
- package/dist/agent-farm/commands/reset.d.ts.map +1 -0
- package/dist/agent-farm/commands/reset.js +292 -0
- package/dist/agent-farm/commands/reset.js.map +1 -0
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +49 -8
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts +105 -6
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +289 -66
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +13 -1
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/status.d.ts.map +1 -1
- package/dist/agent-farm/commands/status.js +80 -8
- package/dist/agent-farm/commands/status.js.map +1 -1
- package/dist/agent-farm/db/index.d.ts +1 -1
- package/dist/agent-farm/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +80 -1
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/mailbox.d.ts +222 -0
- package/dist/agent-farm/db/mailbox.d.ts.map +1 -0
- package/dist/agent-farm/db/mailbox.js +304 -0
- package/dist/agent-farm/db/mailbox.js.map +1 -0
- package/dist/agent-farm/db/schema.d.ts +1 -1
- package/dist/agent-farm/db/schema.d.ts.map +1 -1
- package/dist/agent-farm/db/schema.js +35 -0
- package/dist/agent-farm/db/schema.js.map +1 -1
- package/dist/agent-farm/db/types.d.ts +45 -0
- package/dist/agent-farm/db/types.d.ts.map +1 -1
- package/dist/agent-farm/db/types.js.map +1 -1
- package/dist/agent-farm/lib/builder-lookup.d.ts +1 -1
- package/dist/agent-farm/lib/builder-lookup.js +2 -2
- package/dist/agent-farm/lib/builder-lookup.js.map +1 -1
- package/dist/agent-farm/lib/reconnect-backoff.d.ts +54 -0
- package/dist/agent-farm/lib/reconnect-backoff.d.ts.map +1 -0
- package/dist/agent-farm/lib/reconnect-backoff.js +47 -0
- package/dist/agent-farm/lib/reconnect-backoff.js.map +1 -0
- package/dist/agent-farm/lib/tower-client.d.ts +16 -6
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tower-client.js +37 -6
- package/dist/agent-farm/lib/tower-client.js.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.d.ts +69 -1
- package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.js +218 -27
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
- package/dist/agent-farm/servers/cron-delivery.d.ts +72 -0
- package/dist/agent-farm/servers/cron-delivery.d.ts.map +1 -0
- package/dist/agent-farm/servers/cron-delivery.js +80 -0
- package/dist/agent-farm/servers/cron-delivery.js.map +1 -0
- package/dist/agent-farm/servers/delayed-send.d.ts +85 -0
- package/dist/agent-farm/servers/delayed-send.d.ts.map +1 -0
- package/dist/agent-farm/servers/delayed-send.js +165 -0
- package/dist/agent-farm/servers/delayed-send.js.map +1 -0
- package/dist/agent-farm/servers/gate-profiles.d.ts +67 -0
- package/dist/agent-farm/servers/gate-profiles.d.ts.map +1 -0
- package/dist/agent-farm/servers/gate-profiles.js +113 -0
- package/dist/agent-farm/servers/gate-profiles.js.map +1 -0
- package/dist/agent-farm/servers/mailbox-delivery.d.ts +370 -0
- package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -0
- package/dist/agent-farm/servers/mailbox-delivery.js +617 -0
- package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -0
- package/dist/agent-farm/servers/mailbox-wiring.d.ts +125 -0
- package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -0
- package/dist/agent-farm/servers/mailbox-wiring.js +425 -0
- package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -0
- package/dist/agent-farm/servers/message-write.d.ts +53 -1
- package/dist/agent-farm/servers/message-write.d.ts.map +1 -1
- package/dist/agent-farm/servers/message-write.js +63 -0
- package/dist/agent-farm/servers/message-write.js.map +1 -1
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +23 -4
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/render-gate.d.ts +141 -0
- package/dist/agent-farm/servers/render-gate.d.ts.map +1 -0
- package/dist/agent-farm/servers/render-gate.js +251 -0
- package/dist/agent-farm/servers/render-gate.js.map +1 -0
- package/dist/agent-farm/servers/session-submit.d.ts +90 -0
- package/dist/agent-farm/servers/session-submit.d.ts.map +1 -0
- package/dist/agent-farm/servers/session-submit.js +127 -0
- package/dist/agent-farm/servers/session-submit.js.map +1 -0
- package/dist/agent-farm/servers/tower-cron.d.ts +10 -7
- package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-cron.js +58 -53
- package/dist/agent-farm/servers/tower-cron.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts +14 -2
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +78 -16
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-messages.d.ts +38 -1
- package/dist/agent-farm/servers/tower-messages.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-messages.js +106 -1
- package/dist/agent-farm/servers/tower-messages.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts +16 -4
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +670 -105
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +163 -31
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +17 -2
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +104 -122
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.js +63 -4
- package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
- package/dist/agent-farm/servers/tower-types.d.ts +2 -1
- package/dist/agent-farm/servers/tower-types.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.d.ts +64 -1
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.js +168 -1
- package/dist/agent-farm/servers/tower-utils.js.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.js +6 -21
- package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
- package/dist/agent-farm/servers/write-queue.d.ts +35 -0
- package/dist/agent-farm/servers/write-queue.d.ts.map +1 -0
- package/dist/agent-farm/servers/write-queue.js +56 -0
- package/dist/agent-farm/servers/write-queue.js.map +1 -0
- package/dist/agent-farm/types.d.ts +39 -0
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/agent-names.d.ts +2 -2
- package/dist/agent-farm/utils/agent-names.d.ts.map +1 -1
- package/dist/agent-farm/utils/agent-names.js +3 -3
- package/dist/agent-farm/utils/agent-names.js.map +1 -1
- package/dist/agent-farm/utils/architect-name.d.ts +2 -2
- package/dist/agent-farm/utils/architect-name.d.ts.map +1 -1
- package/dist/agent-farm/utils/architect-name.js +3 -3
- package/dist/agent-farm/utils/architect-name.js.map +1 -1
- package/dist/agent-farm/utils/config.d.ts +13 -0
- package/dist/agent-farm/utils/config.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.js +33 -1
- package/dist/agent-farm/utils/config.js.map +1 -1
- package/dist/agent-farm/utils/harness.d.ts +103 -7
- package/dist/agent-farm/utils/harness.d.ts.map +1 -1
- package/dist/agent-farm/utils/harness.js +126 -19
- package/dist/agent-farm/utils/harness.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -32
- package/dist/cli.js.map +1 -1
- package/dist/commands/consult/cli-options.d.ts +34 -0
- package/dist/commands/consult/cli-options.d.ts.map +1 -0
- package/dist/commands/consult/cli-options.js +67 -0
- package/dist/commands/consult/cli-options.js.map +1 -0
- package/dist/commands/consult/index.d.ts +56 -2
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +272 -28
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/consult/metrics.d.ts +42 -0
- package/dist/commands/consult/metrics.d.ts.map +1 -1
- package/dist/commands/consult/metrics.js +129 -9
- package/dist/commands/consult/metrics.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +71 -19
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/porch/checks.d.ts +23 -0
- package/dist/commands/porch/checks.d.ts.map +1 -1
- package/dist/commands/porch/checks.js +60 -0
- package/dist/commands/porch/checks.js.map +1 -1
- package/dist/commands/porch/config.d.ts +20 -0
- package/dist/commands/porch/config.d.ts.map +1 -1
- package/dist/commands/porch/config.js +20 -0
- package/dist/commands/porch/config.js.map +1 -1
- package/dist/commands/porch/index.js +21 -28
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/porch/next.d.ts.map +1 -1
- package/dist/commands/porch/next.js +2 -41
- package/dist/commands/porch/next.js.map +1 -1
- package/dist/lib/config.d.ts +69 -3
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +67 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/consult-lanes.d.ts +149 -0
- package/dist/lib/consult-lanes.d.ts.map +1 -0
- package/dist/lib/consult-lanes.js +360 -0
- package/dist/lib/consult-lanes.js.map +1 -0
- package/dist/lib/github.js +1 -1
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/prompt-behavior-metrics.d.ts +67 -0
- package/dist/lib/prompt-behavior-metrics.d.ts.map +1 -0
- package/dist/lib/prompt-behavior-metrics.js +290 -0
- package/dist/lib/prompt-behavior-metrics.js.map +1 -0
- package/dist/lib/skeleton.d.ts +22 -1
- package/dist/lib/skeleton.d.ts.map +1 -1
- package/dist/lib/skeleton.js +120 -0
- package/dist/lib/skeleton.js.map +1 -1
- package/dist/lib/test-env.d.ts +74 -0
- package/dist/lib/test-env.d.ts.map +1 -0
- package/dist/lib/test-env.js +92 -0
- package/dist/lib/test-env.js.map +1 -0
- package/dist/terminal/pty-manager.d.ts +2 -0
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +16 -6
- package/dist/terminal/pty-manager.js.map +1 -1
- package/dist/terminal/pty-session.d.ts +131 -0
- package/dist/terminal/pty-session.d.ts.map +1 -1
- package/dist/terminal/pty-session.js +232 -39
- package/dist/terminal/pty-session.js.map +1 -1
- package/dist/terminal/ring-buffer.d.ts +39 -6
- package/dist/terminal/ring-buffer.d.ts.map +1 -1
- package/dist/terminal/ring-buffer.js +74 -6
- package/dist/terminal/ring-buffer.js.map +1 -1
- package/dist/terminal/session-manager.d.ts +53 -0
- package/dist/terminal/session-manager.d.ts.map +1 -1
- package/dist/terminal/session-manager.js +94 -9
- package/dist/terminal/session-manager.js.map +1 -1
- package/dist/terminal/session-screen.d.ts +91 -0
- package/dist/terminal/session-screen.d.ts.map +1 -0
- package/dist/terminal/session-screen.js +135 -0
- package/dist/terminal/session-screen.js.map +1 -0
- package/dist/terminal/shellper-main.js +2 -1
- package/dist/terminal/shellper-main.js.map +1 -1
- package/dist/terminal/shellper-process.d.ts +1 -1
- package/dist/terminal/shellper-process.d.ts.map +1 -1
- package/dist/terminal/shellper-process.js +17 -8
- package/dist/terminal/shellper-process.js.map +1 -1
- package/dist/terminal/shellper-replay-buffer.d.ts +69 -7
- package/dist/terminal/shellper-replay-buffer.d.ts.map +1 -1
- package/dist/terminal/shellper-replay-buffer.js +167 -31
- package/dist/terminal/shellper-replay-buffer.js.map +1 -1
- package/package.json +8 -6
- package/skeleton/.claude/skills/afx/SKILL.md +14 -1
- package/skeleton/.claude/skills/arch-init/SKILL.md +22 -9
- package/skeleton/.claude/skills/arch-save/SKILL.md +183 -0
- package/skeleton/.claude/skills/codev/SKILL.md +31 -0
- package/skeleton/.claude/skills/runnable-worktrees/SKILL.md +119 -0
- package/skeleton/.codex/skills/afx/SKILL.md +14 -1
- package/skeleton/.codex/skills/arch-init/SKILL.md +22 -9
- package/skeleton/.codex/skills/arch-save/SKILL.md +183 -0
- package/skeleton/.codex/skills/codev/SKILL.md +31 -0
- package/skeleton/.codex/skills/runnable-worktrees/SKILL.md +119 -0
- package/skeleton/protocols/air/builder-prompt.md +28 -47
- package/skeleton/protocols/air/consult-types/impl-review.md +17 -34
- package/skeleton/protocols/air/consult-types/pr-review.md +14 -37
- package/skeleton/protocols/air/prompts/implement.md +20 -73
- package/skeleton/protocols/air/prompts/pr.md +22 -54
- package/skeleton/protocols/air/protocol.md +40 -71
- package/skeleton/protocols/aspir/builder-prompt.md +29 -66
- package/skeleton/protocols/aspir/consult-types/impl-review.md +17 -44
- package/skeleton/protocols/aspir/consult-types/phase-review.md +17 -44
- package/skeleton/protocols/aspir/consult-types/plan-review.md +14 -38
- package/skeleton/protocols/aspir/consult-types/pr-review.md +13 -39
- package/skeleton/protocols/aspir/consult-types/spec-review.md +13 -33
- package/skeleton/protocols/aspir/prompts/implement.md +27 -196
- package/skeleton/protocols/aspir/prompts/plan.md +21 -85
- package/skeleton/protocols/aspir/prompts/review.md +29 -228
- package/skeleton/protocols/aspir/prompts/specify.md +25 -111
- package/skeleton/protocols/aspir/protocol.json +10 -0
- package/skeleton/protocols/aspir/protocol.md +36 -82
- package/skeleton/protocols/bugfix/builder-prompt.md +23 -45
- package/skeleton/protocols/bugfix/consult-types/impl-review.md +21 -45
- package/skeleton/protocols/bugfix/consult-types/pr-review.md +18 -52
- package/skeleton/protocols/bugfix/prompts/fix.md +16 -61
- package/skeleton/protocols/bugfix/prompts/investigate.md +18 -62
- package/skeleton/protocols/bugfix/prompts/pr.md +22 -57
- package/skeleton/protocols/bugfix/protocol.md +44 -50
- package/skeleton/protocols/experiment/builder-prompt.md +25 -58
- package/skeleton/protocols/experiment/protocol.md +22 -188
- package/skeleton/protocols/experiment/templates/notes.md +11 -71
- package/skeleton/protocols/maintain/builder-prompt.md +24 -45
- package/skeleton/protocols/maintain/consult-types/impl-review.md +17 -44
- package/skeleton/protocols/maintain/consult-types/pr-review.md +13 -39
- package/skeleton/protocols/maintain/prompts/maintain.md +1 -1
- package/skeleton/protocols/maintain/protocol.md +32 -266
- package/skeleton/protocols/maintain/templates/maintenance-run.md +11 -0
- package/skeleton/protocols/pir/builder-prompt.md +30 -64
- package/skeleton/protocols/pir/consult-types/impl-review.md +17 -50
- package/skeleton/protocols/pir/consult-types/pr-review.md +11 -40
- package/skeleton/protocols/pir/prompts/implement.md +1 -2
- package/skeleton/protocols/pir/prompts/plan.md +0 -3
- package/skeleton/protocols/pir/prompts/review.md +1 -1
- package/skeleton/protocols/pir/protocol.md +54 -177
- package/skeleton/protocols/research/builder-prompt.md +27 -58
- package/skeleton/protocols/research/protocol.md +28 -153
- package/skeleton/protocols/spike/builder-prompt.md +30 -48
- package/skeleton/protocols/spike/protocol.md +24 -107
- package/skeleton/protocols/spike/templates/findings.md +9 -39
- package/skeleton/protocols/spir/builder-prompt.md +33 -62
- package/skeleton/protocols/spir/consult-types/impl-review.md +17 -44
- package/skeleton/protocols/spir/consult-types/phase-review.md +17 -44
- package/skeleton/protocols/spir/consult-types/plan-review.md +14 -38
- package/skeleton/protocols/spir/consult-types/pr-review.md +13 -39
- package/skeleton/protocols/spir/consult-types/spec-review.md +13 -33
- package/skeleton/protocols/spir/prompts/implement.md +26 -195
- package/skeleton/protocols/spir/prompts/plan.md +20 -84
- package/skeleton/protocols/spir/prompts/review.md +28 -227
- package/skeleton/protocols/spir/prompts/specify.md +24 -110
- package/skeleton/protocols/spir/protocol.json +10 -0
- package/skeleton/protocols/spir/protocol.md +84 -633
- package/skeleton/protocols/spir/templates/plan.md +28 -147
- package/skeleton/protocols/spir/templates/review.md +21 -80
- package/skeleton/protocols/spir/templates/spec.md +24 -118
- package/skeleton/resources/commands/agent-farm.md +259 -0
- package/skeleton/resources/commands/consult.md +236 -8
- package/skeleton/resources/commands/overview.md +1 -0
- package/skeleton/roles/architect.md +67 -317
- package/skeleton/roles/builder.md +77 -191
- package/skeleton/templates/AGENTS.md +2 -0
- package/skeleton/templates/CLAUDE.md +2 -0
- package/templates/tower.html +39 -2
- package/dashboard-dist/assets/index-BZ1x7x1X.js.map +0 -1
- package/dashboard-dist/assets/index-DB1KBSNE.css +0 -32
- package/dist/agent-farm/servers/send-buffer.d.ts +0 -59
- package/dist/agent-farm/servers/send-buffer.d.ts.map +0 -1
- package/dist/agent-farm/servers/send-buffer.js +0 -116
- package/dist/agent-farm/servers/send-buffer.js.map +0 -1
- package/skeleton/porch/prompts/defend.md +0 -108
- package/skeleton/porch/prompts/diagnose.md +0 -70
- package/skeleton/porch/prompts/evaluate.md +0 -132
- package/skeleton/porch/prompts/fix.md +0 -59
- package/skeleton/porch/prompts/implement.md +0 -88
- package/skeleton/porch/prompts/plan.md +0 -74
- package/skeleton/porch/prompts/pr.md +0 -84
- package/skeleton/porch/prompts/review.md +0 -249
- package/skeleton/porch/prompts/specify.md +0 -53
- package/skeleton/porch/prompts/test.md +0 -63
- package/skeleton/protocols/aspir/templates/plan.md +0 -184
- package/skeleton/protocols/aspir/templates/review.md +0 -120
- package/skeleton/protocols/aspir/templates/spec.md +0 -154
|
@@ -2,56 +2,126 @@
|
|
|
2
2
|
* Standalone replay buffer for the shellper process.
|
|
3
3
|
*
|
|
4
4
|
* Unlike RingBuffer (which stores lines), this stores raw byte chunks
|
|
5
|
-
* to preserve exact terminal output including escape sequences. It
|
|
6
|
-
* the
|
|
5
|
+
* to preserve exact terminal output including escape sequences. It evicts
|
|
6
|
+
* the oldest chunks when either the line or the byte ceiling is exceeded;
|
|
7
|
+
* both ceilings are needed, because a full-screen TUI emits almost no
|
|
8
|
+
* newlines and so is bounded only by the byte one (#1205).
|
|
7
9
|
*
|
|
8
10
|
* This module has NO dependencies beyond Node.js built-ins so the shellper
|
|
9
11
|
* process doesn't need to pull in the full package dependency tree.
|
|
12
|
+
* (shellper-protocol.ts is a sibling module under the same constraint.)
|
|
10
13
|
*/
|
|
14
|
+
import { REPLAY_PAYLOAD_MAX } from './shellper-protocol.js';
|
|
15
|
+
/**
|
|
16
|
+
* Byte ceiling on what this buffer retains (#1205). Until it existed the buffer
|
|
17
|
+
* was bounded only by line count, and a full-screen TUI (which redraws in place
|
|
18
|
+
* and emits almost no newlines) grew it without limit for the life of the
|
|
19
|
+
* session — multi-GB in the field.
|
|
20
|
+
*
|
|
21
|
+
* Derived from REPLAY_PAYLOAD_MAX rather than written as its own literal, and
|
|
22
|
+
* that derivation is the point: every send is capped at REPLAY_PAYLOAD_MAX and
|
|
23
|
+
* ShellperProcess.getReplayData() has no production callers, so it is provably
|
|
24
|
+
* the most that can ever leave the process. Bytes retained above it are
|
|
25
|
+
* unreadable by any consumer and are pure resident cost. Tying the two together
|
|
26
|
+
* keeps that true if the send cap ever moves — retaining more than can be sent
|
|
27
|
+
* would be waste, retaining less would make the send cap unreachable.
|
|
28
|
+
*
|
|
29
|
+
* This is a retention policy, not a wire constraint, which is why it lives here
|
|
30
|
+
* next to the buffer rather than in shellper-protocol.ts.
|
|
31
|
+
*/
|
|
32
|
+
export const REPLAY_BUFFER_MAX_BYTES = REPLAY_PAYLOAD_MAX;
|
|
33
|
+
const ESC = 0x1b;
|
|
34
|
+
/**
|
|
35
|
+
* How far past a byte-driven trim point we scan looking for an ESC to align to.
|
|
36
|
+
* Bounded so alignment can never turn into a long scan over ESC-free data.
|
|
37
|
+
*/
|
|
38
|
+
const ESC_ALIGN_SCAN_LIMIT = 4096;
|
|
39
|
+
/**
|
|
40
|
+
* Nudge a trim offset forward to the next ESC byte, so a cut doesn't land in
|
|
41
|
+
* the middle of an escape sequence and leave the client rendering its tail as
|
|
42
|
+
* literal garbage. Returns `offset` unchanged when no ESC is within the scan
|
|
43
|
+
* window, which is the correct fallback: a raw cut is what we'd have done
|
|
44
|
+
* anyway, and the post-connect repaint nudge covers the rest.
|
|
45
|
+
*
|
|
46
|
+
* Only byte-driven cuts need this. A newline-driven cut lands just past a
|
|
47
|
+
* `\n`, which can never appear inside an escape sequence, so it is already
|
|
48
|
+
* safe and aligning it would discard content for nothing.
|
|
49
|
+
*/
|
|
50
|
+
function alignToEscape(buf, offset) {
|
|
51
|
+
const limit = Math.min(buf.length, offset + ESC_ALIGN_SCAN_LIMIT);
|
|
52
|
+
for (let i = offset; i < limit; i++) {
|
|
53
|
+
if (buf[i] === ESC)
|
|
54
|
+
return i;
|
|
55
|
+
}
|
|
56
|
+
return offset;
|
|
57
|
+
}
|
|
58
|
+
function countNewlines(buf) {
|
|
59
|
+
let count = 0;
|
|
60
|
+
for (let i = 0; i < buf.length; i++) {
|
|
61
|
+
if (buf[i] === 0x0a)
|
|
62
|
+
count++;
|
|
63
|
+
}
|
|
64
|
+
return count;
|
|
65
|
+
}
|
|
11
66
|
export class ShellperReplayBuffer {
|
|
12
67
|
chunks = [];
|
|
13
68
|
totalBytes = 0;
|
|
14
69
|
maxLines;
|
|
70
|
+
maxBytes;
|
|
15
71
|
lineCount = 0;
|
|
16
72
|
/**
|
|
17
73
|
* @param maxLines Maximum number of lines to retain. Lines are delimited
|
|
18
74
|
* by newline characters in the raw data stream.
|
|
75
|
+
* @param maxBytes Maximum bytes to retain. Required because `maxLines` alone
|
|
76
|
+
* does not bound anything for a full-screen TUI: such an app redraws in
|
|
77
|
+
* place via cursor addressing and emits almost no newlines, so the line
|
|
78
|
+
* ceiling never fires and the buffer grows for the life of the session
|
|
79
|
+
* (#1205). Defaults to REPLAY_BUFFER_MAX_BYTES.
|
|
19
80
|
*/
|
|
20
|
-
constructor(maxLines = 10_000) {
|
|
81
|
+
constructor(maxLines = 10_000, maxBytes = REPLAY_BUFFER_MAX_BYTES) {
|
|
21
82
|
this.maxLines = maxLines;
|
|
83
|
+
this.maxBytes = maxBytes;
|
|
22
84
|
}
|
|
23
85
|
/**
|
|
24
86
|
* Append raw PTY output data to the buffer.
|
|
25
|
-
* Evicts oldest chunks if the line
|
|
87
|
+
* Evicts oldest chunks if either the line or the byte ceiling is exceeded.
|
|
26
88
|
*/
|
|
27
89
|
append(data) {
|
|
28
90
|
const buf = typeof data === 'string' ? Buffer.from(data, 'utf-8') : data;
|
|
29
91
|
if (buf.length === 0)
|
|
30
92
|
return;
|
|
31
|
-
// Count newlines in this chunk
|
|
32
|
-
let newLines = 0;
|
|
33
|
-
for (let i = 0; i < buf.length; i++) {
|
|
34
|
-
if (buf[i] === 0x0a)
|
|
35
|
-
newLines++;
|
|
36
|
-
}
|
|
37
93
|
this.chunks.push(buf);
|
|
38
94
|
this.totalBytes += buf.length;
|
|
39
|
-
this.lineCount +=
|
|
40
|
-
|
|
41
|
-
|
|
95
|
+
this.lineCount += countNewlines(buf);
|
|
96
|
+
this.evict();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Drop the oldest data until both ceilings are satisfied.
|
|
100
|
+
*
|
|
101
|
+
* Trimming from the front can cut mid-escape-sequence or discard an
|
|
102
|
+
* alt-screen-enter, which is why it was rejected in #1047. It is accepted
|
|
103
|
+
* here because every layer above already does exactly this (the send cap,
|
|
104
|
+
* the ring seed, the frame-skip path) and relies on the client's
|
|
105
|
+
* post-connect repaint nudge; an unbounded buffer, by contrast, killed
|
|
106
|
+
* sessions. Byte-driven cuts are ESC-aligned to narrow the window in which
|
|
107
|
+
* a client renders a truncated sequence as literal text.
|
|
108
|
+
*/
|
|
109
|
+
evict() {
|
|
110
|
+
while ((this.lineCount > this.maxLines || this.totalBytes > this.maxBytes) &&
|
|
111
|
+
this.chunks.length > 1) {
|
|
42
112
|
const oldest = this.chunks[0];
|
|
43
|
-
let removedLines = 0;
|
|
44
|
-
for (let i = 0; i < oldest.length; i++) {
|
|
45
|
-
if (oldest[i] === 0x0a)
|
|
46
|
-
removedLines++;
|
|
47
|
-
}
|
|
48
113
|
this.chunks.shift();
|
|
49
114
|
this.totalBytes -= oldest.length;
|
|
50
|
-
this.lineCount -=
|
|
115
|
+
this.lineCount -= countNewlines(oldest);
|
|
51
116
|
}
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
117
|
+
// A single remaining chunk can still exceed either ceiling on its own.
|
|
118
|
+
if (this.chunks.length !== 1)
|
|
119
|
+
return;
|
|
120
|
+
// Line ceiling: cut just past the newline that leaves maxLines behind.
|
|
121
|
+
// A newline never appears inside an escape sequence, so this cut is
|
|
122
|
+
// inherently safe and must not be ESC-aligned (that would discard content
|
|
123
|
+
// for no benefit).
|
|
124
|
+
if (this.lineCount > this.maxLines) {
|
|
55
125
|
const chunk = this.chunks[0];
|
|
56
126
|
let linesToSkip = this.lineCount - this.maxLines;
|
|
57
127
|
let offset = 0;
|
|
@@ -60,21 +130,87 @@ export class ShellperReplayBuffer {
|
|
|
60
130
|
linesToSkip--;
|
|
61
131
|
offset++;
|
|
62
132
|
}
|
|
63
|
-
this.
|
|
64
|
-
|
|
65
|
-
|
|
133
|
+
this.replaceSoleChunk(chunk.subarray(offset));
|
|
134
|
+
}
|
|
135
|
+
// Byte ceiling: keep the tail. This cut can land anywhere, so align it.
|
|
136
|
+
const chunk = this.chunks[0];
|
|
137
|
+
if (chunk.length > this.maxBytes) {
|
|
138
|
+
this.replaceSoleChunk(chunk.subarray(alignToEscape(chunk, chunk.length - this.maxBytes)));
|
|
66
139
|
}
|
|
67
140
|
}
|
|
68
141
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
142
|
+
* Replace the only chunk with a trimmed version of itself.
|
|
143
|
+
*
|
|
144
|
+
* Copies rather than retaining the `subarray` view: a view keeps the entire
|
|
145
|
+
* original allocation alive, so trimming an oversized chunk down to the
|
|
146
|
+
* ceiling would free none of the memory this class exists to bound. The copy
|
|
147
|
+
* is at most `maxBytes` and only happens on the rare path where a single
|
|
148
|
+
* append exceeded a ceiling by itself.
|
|
71
149
|
*/
|
|
72
|
-
|
|
150
|
+
replaceSoleChunk(next) {
|
|
151
|
+
const owned = Buffer.from(next);
|
|
152
|
+
this.chunks[0] = owned;
|
|
153
|
+
this.totalBytes = owned.length;
|
|
154
|
+
this.lineCount = countNewlines(owned);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get buffered data as a single Buffer, for the REPLAY frame on reconnection.
|
|
158
|
+
*
|
|
159
|
+
* @param maxBytes When given, return at most this many bytes (the most recent
|
|
160
|
+
* ones). The tail is collected by walking `chunks` backwards and slicing
|
|
161
|
+
* only the boundary chunk, so the allocation is O(maxBytes) rather than
|
|
162
|
+
* O(history).
|
|
163
|
+
*
|
|
164
|
+
* That distinction is the whole point (#1205). This runs on *every* client
|
|
165
|
+
* connect, and the caller's cap used to be applied to the result — meaning a
|
|
166
|
+
* multi-GB buffer allocated a multi-GB copy on top of itself at exactly the
|
|
167
|
+
* moment a user opened the terminal, then threw almost all of it away. Peak
|
|
168
|
+
* footprint doubled, and sessions were killed for it. Capping here bounds the
|
|
169
|
+
* spike regardless of how large the buffer has already grown, which also
|
|
170
|
+
* makes this correct for a buffer that accumulated before any byte ceiling
|
|
171
|
+
* existed.
|
|
172
|
+
*/
|
|
173
|
+
getReplayData(maxBytes) {
|
|
73
174
|
if (this.chunks.length === 0)
|
|
74
175
|
return Buffer.alloc(0);
|
|
75
|
-
if (this.
|
|
76
|
-
|
|
77
|
-
|
|
176
|
+
if (maxBytes === undefined || this.totalBytes <= maxBytes) {
|
|
177
|
+
// Single-chunk fast path returns the internal Buffer *by reference*
|
|
178
|
+
// rather than copying. Safe because nothing mutates chunks in place and
|
|
179
|
+
// callers only read, but it is an aliasing property the old
|
|
180
|
+
// always-concat version did not have: a caller that mutated the result
|
|
181
|
+
// would corrupt the buffer. Copy here if that ever stops being true.
|
|
182
|
+
if (this.chunks.length === 1)
|
|
183
|
+
return this.chunks[0];
|
|
184
|
+
return Buffer.concat(this.chunks, this.totalBytes);
|
|
185
|
+
}
|
|
186
|
+
if (maxBytes <= 0)
|
|
187
|
+
return Buffer.alloc(0);
|
|
188
|
+
const tail = [];
|
|
189
|
+
let collected = 0;
|
|
190
|
+
for (let i = this.chunks.length - 1; i >= 0 && collected < maxBytes; i--) {
|
|
191
|
+
const chunk = this.chunks[i];
|
|
192
|
+
const remaining = maxBytes - collected;
|
|
193
|
+
if (chunk.length <= remaining) {
|
|
194
|
+
tail.push(chunk);
|
|
195
|
+
collected += chunk.length;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
// Boundary chunk: take only its tail, aligned off a mid-sequence cut.
|
|
199
|
+
//
|
|
200
|
+
// `break` is load-bearing. ESC alignment moves the cut *forward*, so the
|
|
201
|
+
// piece is usually shorter than `remaining` — without breaking, the loop
|
|
202
|
+
// would see spare capacity and keep taking bytes from still-older chunks,
|
|
203
|
+
// stitching fragments across a gap into what must be one contiguous
|
|
204
|
+
// suffix. That also re-admits the unaligned mid-sequence prefix this
|
|
205
|
+
// alignment exists to prevent. Returning fewer than `maxBytes` is the
|
|
206
|
+
// accepted contract; returning a spliced stream is not.
|
|
207
|
+
const piece = chunk.subarray(alignToEscape(chunk, chunk.length - remaining));
|
|
208
|
+
tail.push(piece);
|
|
209
|
+
collected += piece.length;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
tail.reverse();
|
|
213
|
+
return Buffer.concat(tail, collected);
|
|
78
214
|
}
|
|
79
215
|
/** Current number of bytes stored. */
|
|
80
216
|
get size() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shellper-replay-buffer.js","sourceRoot":"","sources":["../../src/terminal/shellper-replay-buffer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"shellper-replay-buffer.js","sourceRoot":"","sources":["../../src/terminal/shellper-replay-buffer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cluesmith/codev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Codev CLI - AI-assisted software development framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@anthropic-ai/claude-agent-sdk": "^0.2.41",
|
|
25
25
|
"@google/genai": "^1.0.0",
|
|
26
|
-
"@openai/codex-sdk": "^0.
|
|
26
|
+
"@openai/codex-sdk": "^0.146.0",
|
|
27
|
+
"@xterm/headless": "^6.0.0",
|
|
27
28
|
"better-sqlite3": "^12.10.0",
|
|
28
29
|
"chalk": "^5.3.0",
|
|
29
30
|
"commander": "^12.1.0",
|
|
@@ -33,7 +34,8 @@
|
|
|
33
34
|
"open": "^10.1.0",
|
|
34
35
|
"tree-kill": "^1.2.2",
|
|
35
36
|
"ws": "^8.18.0",
|
|
36
|
-
"@cluesmith/codev-core": "3.
|
|
37
|
+
"@cluesmith/codev-core": "3.3.0",
|
|
38
|
+
"@cluesmith/codev-sdk": "3.3.0"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
|
39
41
|
"@playwright/test": "^1.58.0",
|
|
@@ -49,7 +51,7 @@
|
|
|
49
51
|
"typescript": "^6.0.3",
|
|
50
52
|
"vitest": "^4.0.15",
|
|
51
53
|
"@cluesmith/codev-web": "0.0.0",
|
|
52
|
-
"@cluesmith/codev-types": "3.
|
|
54
|
+
"@cluesmith/codev-types": "3.3.0"
|
|
53
55
|
},
|
|
54
56
|
"engines": {
|
|
55
57
|
"node": ">=20.0.0"
|
|
@@ -71,8 +73,8 @@
|
|
|
71
73
|
},
|
|
72
74
|
"scripts": {
|
|
73
75
|
"clean": "rm -rf dist",
|
|
74
|
-
"build": "pnpm clean && tsc && pnpm
|
|
75
|
-
"
|
|
76
|
+
"build": "pnpm --filter \"@cluesmith/codev^...\" build && pnpm clean && tsc && pnpm copy-dashboard && pnpm copy-skeleton",
|
|
77
|
+
"copy-dashboard": "rm -rf dashboard-dist && cp -r ../../apps/web/dist dashboard-dist",
|
|
76
78
|
"dev:dashboard": "cd ../../apps/web && pnpm dev",
|
|
77
79
|
"copy-skeleton": "rm -rf skeleton && cp -r ../../codev-skeleton skeleton",
|
|
78
80
|
"dev": "tsx src/cli.ts",
|
|
@@ -144,7 +144,20 @@ afx cron enable <name> # Enable
|
|
|
144
144
|
afx cron disable <name> # Disable
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
There is NO `afx cron add` — create YAML files in `.af-cron/` directly
|
|
147
|
+
There is NO `afx cron add` — create YAML files in `.af-cron/` directly:
|
|
148
|
+
|
|
149
|
+
```yaml
|
|
150
|
+
name: Service Health Check # required, unique per workspace
|
|
151
|
+
schedule: "*/15 * * * *" # required, cron expression (or @hourly/@daily/@startup)
|
|
152
|
+
command: ./health-check.sh # required, run via shell
|
|
153
|
+
message: "Health alert: ${output}" # required, ${output} = trimmed command output
|
|
154
|
+
condition: "exitCode != 0" # optional JS expression, see below
|
|
155
|
+
target: architect # optional, default architect
|
|
156
|
+
timeout: 30 # optional, seconds, default 30
|
|
157
|
+
enabled: true # optional, default true
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`condition` is a JavaScript expression with two variables in scope: `output` (string — the command's trimmed output) and `exitCode` (number — 0 on success, the command's exit code on non-zero exit, 124 on timeout, -1 on spawn failure). With a `condition`, the message is delivered exactly when it evaluates truthy — including on failed runs (e.g. `exitCode != 0` alerts when the command fails). Without a `condition`, the message is delivered only when the command exits 0.
|
|
148
161
|
|
|
149
162
|
## Other commands
|
|
150
163
|
|
|
@@ -63,7 +63,11 @@ not choose; a state save happens at a boundary **you** pick, with a summary
|
|
|
63
63
|
**you** curate. That is strictly better, so use it:
|
|
64
64
|
|
|
65
65
|
```
|
|
66
|
-
/arch-init (recover) → work → save at a checkpoint →
|
|
66
|
+
/arch-init (recover) → work → save at a checkpoint → refresh → /arch-init (recover) → …
|
|
67
|
+
│
|
|
68
|
+
packaged: /arch-save ─────────────────┤ stops monitors, saves, clears,
|
|
69
|
+
│ schedules /arch-init
|
|
70
|
+
manual: suggest /clear → human clears ┘ then human runs /arch-init
|
|
67
71
|
```
|
|
68
72
|
|
|
69
73
|
**When to save.** Save at a *resumable boundary* — a point a fresh session
|
|
@@ -99,17 +103,26 @@ plus the most recent dated section*, so a save must leave exactly that behind:
|
|
|
99
103
|
dumps or raw tool output. Include only: current focus, open loops, and the
|
|
100
104
|
instructions a fresh session needs to resume.
|
|
101
105
|
|
|
102
|
-
**Then — and only then — suggest
|
|
103
|
-
it is a good time to clear. You
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
**Then — and only then — suggest the refresh.** Save first, *then* tell the
|
|
107
|
+
human it is a good time to clear. You must never decide unilaterally to lose
|
|
108
|
+
your context; keeping the irreversible step behind a human decision means
|
|
109
|
+
accepting the suggestion can never lose anything, because the save already
|
|
110
|
+
happened. Make the suggestion **advisory, never nagging**, and only right
|
|
111
|
+
after a save — e.g.:
|
|
108
112
|
|
|
109
|
-
> State saved to `codev/state/<name>.md` — good time to
|
|
113
|
+
> State saved to `codev/state/<name>.md` — good time to refresh if this
|
|
110
114
|
> session is feeling heavy.
|
|
111
115
|
|
|
112
|
-
Do not repeat it, and do not prompt
|
|
116
|
+
Do not repeat it, and do not prompt for it at any other time.
|
|
117
|
+
|
|
118
|
+
**`/arch-save` packages this whole loop**, and is the preferred path when the
|
|
119
|
+
owner directs a refresh: it stops your monitors, writes the pruned state file,
|
|
120
|
+
clears, and schedules `/arch-init` to bring you back — in that order, which is
|
|
121
|
+
the part that matters. The save discipline above is what it performs at its
|
|
122
|
+
step 3, so this section remains the source of truth for *how to write the
|
|
123
|
+
file*; `/arch-save` is the source of truth for *the sequence*. The manual path
|
|
124
|
+
(save → human clears → `/arch-init`) stays valid and is the fallback when
|
|
125
|
+
Tower is unavailable.
|
|
113
126
|
|
|
114
127
|
## Guardrails (architect-wide; the state file may add more)
|
|
115
128
|
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: arch-save
|
|
3
|
+
description: Save an architect's state, clear its context, and re-init automatically — the packaged save→clear→re-init refresh cycle. Use when the owner directs a context refresh, or says "/arch-save", "save and clear", "refresh your context". Runs on the owner's direction; an architect does not invoke it autonomously mid-task. Counterpart to /arch-init, which recovers the state this writes.
|
|
4
|
+
argument-hint: "[name] (e.g. main; omit to auto-detect via afx whoami)"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /arch-save — save state, clear, and come back as yourself
|
|
8
|
+
|
|
9
|
+
Long sessions accumulate stale context. This is the deliberate cure: you choose the
|
|
10
|
+
moment, you choose what survives, and a fresh session resumes from what you wrote.
|
|
11
|
+
|
|
12
|
+
`$ARGUMENTS` is the architect name (e.g. `main`). Omit it to auto-detect.
|
|
13
|
+
|
|
14
|
+
## When NOT to run this
|
|
15
|
+
|
|
16
|
+
**On the owner's direction, or when the owner runs it themselves.** Do not invoke this
|
|
17
|
+
autonomously mid-task on your own judgement — the irreversible step is a human decision,
|
|
18
|
+
relocated from "press `/clear`" to "invoke `/arch-save`", not removed. If the owner tells
|
|
19
|
+
you to run it, run it; if you think it is time, *suggest* it and wait.
|
|
20
|
+
|
|
21
|
+
**Only at a resumable boundary** — a gate approval, a PR merge, a completed investigation,
|
|
22
|
+
the end of a long tool-heavy stretch. **Never mid-task.** Nothing here can check that; the
|
|
23
|
+
state file must describe a point a fresh session can resume *from*, not a half-finished
|
|
24
|
+
action. A mid-task snapshot resumes into confusion.
|
|
25
|
+
|
|
26
|
+
## The procedure
|
|
27
|
+
|
|
28
|
+
Do these in order. **The order is the feature** — step 3 must precede step 4, because the
|
|
29
|
+
context that knows what to write is the one about to be destroyed.
|
|
30
|
+
|
|
31
|
+
### 1. Resolve your name
|
|
32
|
+
|
|
33
|
+
If `$ARGUMENTS` is non-empty, that is your name. Otherwise run `afx whoami` and use the
|
|
34
|
+
reported `name` when `type: architect`.
|
|
35
|
+
|
|
36
|
+
- `type: builder` → **STOP.** This terminal is a builder. Report the mismatch.
|
|
37
|
+
- Non-zero exit → **STOP** and ask which architect you are. Do **not** guess, and do not
|
|
38
|
+
default to `main` — writing another architect's state file is the exact failure
|
|
39
|
+
`/arch-init` exists to prevent (#1094).
|
|
40
|
+
|
|
41
|
+
**Validate the name before building any path**: `[a-z][a-z0-9-]*`, at most 64 characters.
|
|
42
|
+
Reject slashes, `..`, uppercase, spaces. Never interpolate an unvalidated name into
|
|
43
|
+
`codev/state/<name>.md`.
|
|
44
|
+
|
|
45
|
+
### 2. Stop your own monitors
|
|
46
|
+
|
|
47
|
+
Enumerate every monitor, watcher or background task you armed, and stop it.
|
|
48
|
+
|
|
49
|
+
This is the half that only *you* can do. Monitors are **session-bound, not
|
|
50
|
+
context-bound**: they survive `/clear` and keep firing into a context that cannot evaluate
|
|
51
|
+
their alerts. `pgrep` cannot see them — they are harness background tasks, not shell
|
|
52
|
+
processes — so the instance after the clear has no handle on them. You do. Use it.
|
|
53
|
+
|
|
54
|
+
### 3. Write the pruned state file
|
|
55
|
+
|
|
56
|
+
Rewrite `codev/state/<name>.md`. **Pruning is part of the save, not polish afterwards — a
|
|
57
|
+
save that only appends has not done its job.**
|
|
58
|
+
|
|
59
|
+
- **Rewrite the current-state / open-loops section in place.** Do not accumulate stale
|
|
60
|
+
"current state" blocks. Never leave two sections with the same heading — a duplicated
|
|
61
|
+
"How to resume" means you appended where you should have overwritten.
|
|
62
|
+
- **Delete resolved loops outright.** A closed item's record is the log entry, not a
|
|
63
|
+
lingering line in current state.
|
|
64
|
+
- **Append one short dated entry** for what changed this stretch.
|
|
65
|
+
- **Collapse older entries into one-line summaries that point at durable artifacts** — the
|
|
66
|
+
merged PRs, closed issues and reviews where the detail actually lives.
|
|
67
|
+
- **Aim for one screen.** If the file has grown past easy reading, prune as part of *this*
|
|
68
|
+
save rather than leaving it for next time.
|
|
69
|
+
|
|
70
|
+
**Prune by pointer, never by deletion.** These files are gitignored (`.gitignore:15`), so
|
|
71
|
+
there is no history to recover from — pruned prose is gone for good. Replace detail with a
|
|
72
|
+
pointer to something durable; never delete the only record of something. Copying the file
|
|
73
|
+
first (`cp codev/state/<name>.md codev/state/.<name>.bak.md`) is cheap insurance.
|
|
74
|
+
|
|
75
|
+
**Content guardrails.** No secrets — tokens, keys, credentials. No transcript dumps, no
|
|
76
|
+
raw tool output. Only: current focus, open loops, and what a fresh session needs to resume.
|
|
77
|
+
|
|
78
|
+
Use the template at the end of this document.
|
|
79
|
+
|
|
80
|
+
### 4. Clear
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
afx send architect:<name> --raw '/clear'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**`architect:<name>`, never bare `architect`.** For a non-builder sender the bare form
|
|
87
|
+
resolves to `main`, or to the first registered architect — so a *sibling* architect
|
|
88
|
+
running this would clear **main's** terminal instead of its own. That destroys the context
|
|
89
|
+
of someone who never asked for anything, and it is one word away from correct.
|
|
90
|
+
|
|
91
|
+
**`--raw`, never the escape channel.** The escape route writes a bare ESC and discards the
|
|
92
|
+
message body, so `/clear` sent that way delivers an interrupt: the command appears to
|
|
93
|
+
succeed and nothing is cleared.
|
|
94
|
+
|
|
95
|
+
### 5. Schedule the re-init
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
afx send architect:<name> --delay 15 --raw '/arch-init <name>'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Tower holds this for 15 seconds and then delivers it. It has to come from outside the
|
|
102
|
+
session, because the clear destroys the context that would otherwise send it.
|
|
103
|
+
|
|
104
|
+
**Tower does not know whether the clear landed** — it waits out a delay, it does not
|
|
105
|
+
observe the result. 15 seconds is a value chosen because it works in practice, not a
|
|
106
|
+
guarantee about the clear's completion. If the timing is wrong the re-init arrives at the
|
|
107
|
+
wrong moment, which costs one manual message (see below) and nothing else. That is the
|
|
108
|
+
whole reason this cycle can be built on a delay rather than on machinery.
|
|
109
|
+
|
|
110
|
+
Delayed sends are **not persisted** — if Tower restarts inside the window, the message is
|
|
111
|
+
dropped. That is recoverable; see below.
|
|
112
|
+
|
|
113
|
+
**If this send fails, do not end your turn.** Step 4 queued the `/clear`, but it does not
|
|
114
|
+
take effect until your turn ends — so at this moment you still have your full context and
|
|
115
|
+
the failure is recoverable. Retry the send; if it keeps failing (Tower down, for example),
|
|
116
|
+
tell the owner that the clear is queued with no re-init scheduled, and give them the
|
|
117
|
+
command to send by hand once Tower is back. Ending the turn here is the one way to turn a
|
|
118
|
+
recoverable failure into a cleared session nobody is coming back for.
|
|
119
|
+
|
|
120
|
+
### 6. Stop
|
|
121
|
+
|
|
122
|
+
Do not start new work. End your turn so the clear can take effect.
|
|
123
|
+
|
|
124
|
+
## After the clear
|
|
125
|
+
|
|
126
|
+
`/arch-init` will read your state file and resume. Two things it should do first, in this
|
|
127
|
+
order:
|
|
128
|
+
|
|
129
|
+
1. **Reconcile monitors.** You stopped yours in step 2, but treat any alert you cannot
|
|
130
|
+
account for from the state block's monitor list as **stale** — disregard it, and stop
|
|
131
|
+
it if you can. An alert from a decommissioned target is indistinguishable from a live
|
|
132
|
+
one.
|
|
133
|
+
2. **Then re-arm** the monitors the block lists, and let each one **self-test once** before
|
|
134
|
+
trusting its alerts. A freshly-armed monitor's first alert has been a false positive in
|
|
135
|
+
practice.
|
|
136
|
+
|
|
137
|
+
## If `/arch-init` never arrives
|
|
138
|
+
|
|
139
|
+
Nothing is lost. The state file is on disk, the terminal is alive. Send it by hand:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
afx send architect:<name> --raw '/arch-init <name>'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This is the recovery the whole design leans on, which is why the cycle can accept imprecise
|
|
146
|
+
timing rather than needing machinery to guarantee it.
|
|
147
|
+
|
|
148
|
+
**If the clear did not take effect** — you still have your full context and a stray
|
|
149
|
+
`/arch-init` arrived — nothing was destroyed. Check whether `/clear` was submitted as its
|
|
150
|
+
own message rather than merged into another one, and report it; a `/clear` that arrives as
|
|
151
|
+
literal text on the front of the next message never executes.
|
|
152
|
+
|
|
153
|
+
## State block template
|
|
154
|
+
|
|
155
|
+
The structure below comes from a live run of this cycle. Every element earns its place;
|
|
156
|
+
keep them all, including a `MONITORS:` line even when the answer is "none armed" — an
|
|
157
|
+
omitted monitor list is indistinguishable from a forgotten one.
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
# <lane> architect — state (vNN, <date> ~HH:MM UTC — <milestone>, DELIBERATE /clear cycle)
|
|
161
|
+
# ⭐ THIS /clear IS INTENTIONAL (owner-directed context refresh). On re-init: normal
|
|
162
|
+
# /arch-init flow, then:
|
|
163
|
+
# 1. MONITORS: <what to stop if it is still firing, then what to re-arm> — watch target,
|
|
164
|
+
# cadence, alert pattern. Self-test once before trusting alerts. ("none armed" is a
|
|
165
|
+
# valid and complete answer.)
|
|
166
|
+
# 2. DONE pre-clear, with receipts: <PR> MERGED (<sha>, verified on origin/<branch>);
|
|
167
|
+
# <branch> PUSH-VERIFIED (<sha> local==origin). Distinguish "written" from "verified" —
|
|
168
|
+
# a cold reader cannot tell.
|
|
169
|
+
# 3. ACTIVE LANES: <builder-id> = <workstream> (<brief file on disk>; <standing rule>).
|
|
170
|
+
# Name the file, so no instruction lives only in the context being destroyed.
|
|
171
|
+
# 4. LATEST RESULTS: <the decision-relevant numbers>, so the first post-resume decision
|
|
172
|
+
# needs no archaeology.
|
|
173
|
+
# 5. QUEUED, with ordering: <item> — WAITS for <verdict>; <item> — <when>.
|
|
174
|
+
# 6. ENVELOPE: <standing authorization that survives>; <what expired with the completed
|
|
175
|
+
# work>.
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Guardrails (architect-wide)
|
|
179
|
+
|
|
180
|
+
- **Never auto-approve porch gates.** A gate notification is for the human, not you.
|
|
181
|
+
- **Touch only your own builders / spawns / filings.**
|
|
182
|
+
- **Never `cd` into a builder worktree**; use `git -C` and absolute paths.
|
|
183
|
+
- **Stay on the default branch at the workspace root.**
|
|
@@ -57,3 +57,34 @@ codev doctor
|
|
|
57
57
|
- `codev init` creates a new directory — use `codev adopt` for existing projects
|
|
58
58
|
- Always run `codev adopt` and `codev update` from the project root
|
|
59
59
|
- `codev update` only updates framework files — it never touches specs/plans/reviews
|
|
60
|
+
|
|
61
|
+
## Local build and install (this repository)
|
|
62
|
+
|
|
63
|
+
Test changes locally before publishing. Run from the repository root:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pnpm build # builds core first, then codev (including dashboard)
|
|
67
|
+
pnpm -w run local-install # packs both packages, installs globally, restarts Tower
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`local-install` (`scripts/local-install.sh`) packs `@cluesmith/codev-core` and
|
|
71
|
+
`@cluesmith/codev`, installs both in a single `npm install -g` (separate installs fail —
|
|
72
|
+
`codev-core` is not on the public registry), restores the executable bit on
|
|
73
|
+
`scripts/forge/**/*.sh` that `pnpm pack` strips, and restarts Tower last. Install runs while
|
|
74
|
+
Tower is up; only the final restart causes downtime. **Do not stop Tower first**, and do not use
|
|
75
|
+
`npm link` / `pnpm link` — it breaks global installs.
|
|
76
|
+
|
|
77
|
+
`pnpm build` also runs `copy-skeleton`, which copies `codev-skeleton/` into
|
|
78
|
+
`packages/codev/skeleton`. **Tests read that copy**, so after editing anything under
|
|
79
|
+
`codev-skeleton/` you must rebuild before the suite reflects your change.
|
|
80
|
+
|
|
81
|
+
### Where to run things
|
|
82
|
+
|
|
83
|
+
- `pnpm install` — repository root (installs all workspace packages)
|
|
84
|
+
- `pnpm build` / `pnpm test` — `packages/codev/`, or `pnpm --filter @cluesmith/codev build`
|
|
85
|
+
- Unit tests `packages/codev/tests/unit/` · E2E `packages/codev/tests/e2e/`
|
|
86
|
+
- Never run npm commands from the repository root unless told to
|
|
87
|
+
|
|
88
|
+
### Measuring code size
|
|
89
|
+
|
|
90
|
+
`tokei -e "tests/lib" -e "node_modules" -e ".git" -e ".builders" -e "dist" .`
|