@mclawnet/swarm 0.1.12 → 0.1.14
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/dist/__tests__/always-on-activity-reader.test.d.ts +2 -0
- package/dist/__tests__/always-on-activity-reader.test.d.ts.map +1 -0
- package/dist/__tests__/always-on-activity-reader.test.js +193 -0
- package/dist/__tests__/always-on-activity-reader.test.js.map +1 -0
- package/dist/__tests__/always-on-config.test.d.ts +2 -0
- package/dist/__tests__/always-on-config.test.d.ts.map +1 -0
- package/dist/__tests__/always-on-config.test.js +285 -0
- package/dist/__tests__/always-on-config.test.js.map +1 -0
- package/dist/__tests__/always-on-manager.test.d.ts +2 -0
- package/dist/__tests__/always-on-manager.test.d.ts.map +1 -0
- package/dist/__tests__/always-on-manager.test.js +797 -0
- package/dist/__tests__/always-on-manager.test.js.map +1 -0
- package/dist/__tests__/always-on-parity.test.d.ts +2 -0
- package/dist/__tests__/always-on-parity.test.d.ts.map +1 -0
- package/dist/__tests__/always-on-parity.test.js +20 -0
- package/dist/__tests__/always-on-parity.test.js.map +1 -0
- package/dist/__tests__/cascade-picker.test.d.ts +2 -0
- package/dist/__tests__/cascade-picker.test.d.ts.map +1 -0
- package/dist/__tests__/cascade-picker.test.js +122 -0
- package/dist/__tests__/cascade-picker.test.js.map +1 -0
- package/dist/__tests__/coordinator-shipment.test.d.ts +2 -0
- package/dist/__tests__/coordinator-shipment.test.d.ts.map +1 -0
- package/dist/__tests__/coordinator-shipment.test.js +280 -0
- package/dist/__tests__/coordinator-shipment.test.js.map +1 -0
- package/dist/__tests__/coordinator-workspace-recover.test.d.ts +2 -0
- package/dist/__tests__/coordinator-workspace-recover.test.d.ts.map +1 -0
- package/dist/__tests__/coordinator-workspace-recover.test.js +140 -0
- package/dist/__tests__/coordinator-workspace-recover.test.js.map +1 -0
- package/dist/__tests__/coordinator-workspace.test.d.ts +2 -0
- package/dist/__tests__/coordinator-workspace.test.d.ts.map +1 -0
- package/dist/__tests__/coordinator-workspace.test.js +135 -0
- package/dist/__tests__/coordinator-workspace.test.js.map +1 -0
- package/dist/__tests__/default-runner-epipe.test.d.ts +2 -0
- package/dist/__tests__/default-runner-epipe.test.d.ts.map +1 -0
- package/dist/__tests__/default-runner-epipe.test.js +43 -0
- package/dist/__tests__/default-runner-epipe.test.js.map +1 -0
- package/dist/__tests__/discovery-scheduler.test.d.ts +2 -0
- package/dist/__tests__/discovery-scheduler.test.d.ts.map +1 -0
- package/dist/__tests__/discovery-scheduler.test.js +367 -0
- package/dist/__tests__/discovery-scheduler.test.js.map +1 -0
- package/dist/__tests__/env-forward-e2e.test.d.ts +2 -0
- package/dist/__tests__/env-forward-e2e.test.d.ts.map +1 -0
- package/dist/__tests__/env-forward-e2e.test.js +57 -0
- package/dist/__tests__/env-forward-e2e.test.js.map +1 -0
- package/dist/__tests__/gh-pr-creator.test.d.ts +2 -0
- package/dist/__tests__/gh-pr-creator.test.d.ts.map +1 -0
- package/dist/__tests__/gh-pr-creator.test.js +107 -0
- package/dist/__tests__/gh-pr-creator.test.js.map +1 -0
- package/dist/__tests__/git-worktree-provider.test.d.ts +2 -0
- package/dist/__tests__/git-worktree-provider.test.d.ts.map +1 -0
- package/dist/__tests__/git-worktree-provider.test.js +98 -0
- package/dist/__tests__/git-worktree-provider.test.js.map +1 -0
- package/dist/__tests__/gitignore-check.test.d.ts +2 -0
- package/dist/__tests__/gitignore-check.test.d.ts.map +1 -0
- package/dist/__tests__/gitignore-check.test.js +39 -0
- package/dist/__tests__/gitignore-check.test.js.map +1 -0
- package/dist/__tests__/idea-research-source.test.d.ts +2 -0
- package/dist/__tests__/idea-research-source.test.d.ts.map +1 -0
- package/dist/__tests__/idea-research-source.test.js +425 -0
- package/dist/__tests__/idea-research-source.test.js.map +1 -0
- package/dist/__tests__/idea-todo-source.test.d.ts +2 -0
- package/dist/__tests__/idea-todo-source.test.d.ts.map +1 -0
- package/dist/__tests__/idea-todo-source.test.js +258 -0
- package/dist/__tests__/idea-todo-source.test.js.map +1 -0
- package/dist/__tests__/inbox-store-notbefore.test.d.ts +2 -0
- package/dist/__tests__/inbox-store-notbefore.test.d.ts.map +1 -0
- package/dist/__tests__/inbox-store-notbefore.test.js +47 -0
- package/dist/__tests__/inbox-store-notbefore.test.js.map +1 -0
- package/dist/__tests__/inbox-store-swarmid-validation.test.d.ts +2 -0
- package/dist/__tests__/inbox-store-swarmid-validation.test.d.ts.map +1 -0
- package/dist/__tests__/inbox-store-swarmid-validation.test.js +32 -0
- package/dist/__tests__/inbox-store-swarmid-validation.test.js.map +1 -0
- package/dist/__tests__/introspection-dedupe.test.d.ts +2 -0
- package/dist/__tests__/introspection-dedupe.test.d.ts.map +1 -0
- package/dist/__tests__/introspection-dedupe.test.js +484 -0
- package/dist/__tests__/introspection-dedupe.test.js.map +1 -0
- package/dist/__tests__/introspection-source.test.d.ts +2 -0
- package/dist/__tests__/introspection-source.test.d.ts.map +1 -0
- package/dist/__tests__/introspection-source.test.js +1051 -0
- package/dist/__tests__/introspection-source.test.js.map +1 -0
- package/dist/__tests__/migration-roles.test.js +1 -22
- package/dist/__tests__/migration-roles.test.js.map +1 -1
- package/dist/__tests__/reconcile-researching.test.d.ts +2 -0
- package/dist/__tests__/reconcile-researching.test.d.ts.map +1 -0
- package/dist/__tests__/reconcile-researching.test.js +224 -0
- package/dist/__tests__/reconcile-researching.test.js.map +1 -0
- package/dist/__tests__/role-loader-editor.test.js +40 -10
- package/dist/__tests__/role-loader-editor.test.js.map +1 -1
- package/dist/__tests__/role-loader-preamble-all.test.js +3 -1
- package/dist/__tests__/role-loader-preamble-all.test.js.map +1 -1
- package/dist/__tests__/role-loader.test.js +95 -0
- package/dist/__tests__/role-loader.test.js.map +1 -1
- package/dist/__tests__/role-prompt-no-legacy-protocol.test.js +3 -1
- package/dist/__tests__/role-prompt-no-legacy-protocol.test.js.map +1 -1
- package/dist/__tests__/secret-scrub.test.d.ts +2 -0
- package/dist/__tests__/secret-scrub.test.d.ts.map +1 -0
- package/dist/__tests__/secret-scrub.test.js +55 -0
- package/dist/__tests__/secret-scrub.test.js.map +1 -0
- package/dist/__tests__/shipment-actions.test.d.ts +2 -0
- package/dist/__tests__/shipment-actions.test.d.ts.map +1 -0
- package/dist/__tests__/shipment-actions.test.js +378 -0
- package/dist/__tests__/shipment-actions.test.js.map +1 -0
- package/dist/__tests__/shipment-persistence.test.d.ts +2 -0
- package/dist/__tests__/shipment-persistence.test.d.ts.map +1 -0
- package/dist/__tests__/shipment-persistence.test.js +120 -0
- package/dist/__tests__/shipment-persistence.test.js.map +1 -0
- package/dist/__tests__/shipment-pipeline.test.d.ts +2 -0
- package/dist/__tests__/shipment-pipeline.test.d.ts.map +1 -0
- package/dist/__tests__/shipment-pipeline.test.js +392 -0
- package/dist/__tests__/shipment-pipeline.test.js.map +1 -0
- package/dist/__tests__/shipment-report.test.d.ts +2 -0
- package/dist/__tests__/shipment-report.test.d.ts.map +1 -0
- package/dist/__tests__/shipment-report.test.js +78 -0
- package/dist/__tests__/shipment-report.test.js.map +1 -0
- package/dist/__tests__/shipment-stdin-integration.test.d.ts +2 -0
- package/dist/__tests__/shipment-stdin-integration.test.d.ts.map +1 -0
- package/dist/__tests__/shipment-stdin-integration.test.js +49 -0
- package/dist/__tests__/shipment-stdin-integration.test.js.map +1 -0
- package/dist/__tests__/shipment-type-parity.test.d.ts +2 -0
- package/dist/__tests__/shipment-type-parity.test.d.ts.map +1 -0
- package/dist/__tests__/shipment-type-parity.test.js +10 -0
- package/dist/__tests__/shipment-type-parity.test.js.map +1 -0
- package/dist/__tests__/snapshot-copy-provider.test.d.ts +2 -0
- package/dist/__tests__/snapshot-copy-provider.test.d.ts.map +1 -0
- package/dist/__tests__/snapshot-copy-provider.test.js +88 -0
- package/dist/__tests__/snapshot-copy-provider.test.js.map +1 -0
- package/dist/__tests__/swarm-coordinator-backend.test.js +153 -0
- package/dist/__tests__/swarm-coordinator-backend.test.js.map +1 -1
- package/dist/__tests__/swarm-coordinator-complete-intercept.test.d.ts +2 -0
- package/dist/__tests__/swarm-coordinator-complete-intercept.test.d.ts.map +1 -0
- package/dist/__tests__/swarm-coordinator-complete-intercept.test.js +111 -0
- package/dist/__tests__/swarm-coordinator-complete-intercept.test.js.map +1 -0
- package/dist/__tests__/task-store-source.test.d.ts +2 -0
- package/dist/__tests__/task-store-source.test.d.ts.map +1 -0
- package/dist/__tests__/task-store-source.test.js +56 -0
- package/dist/__tests__/task-store-source.test.js.map +1 -0
- package/dist/__tests__/template-loader-editor.test.js +28 -13
- package/dist/__tests__/template-loader-editor.test.js.map +1 -1
- package/dist/__tests__/transport-detect.test.d.ts +2 -0
- package/dist/__tests__/transport-detect.test.d.ts.map +1 -0
- package/dist/__tests__/transport-detect.test.js +92 -0
- package/dist/__tests__/transport-detect.test.js.map +1 -0
- package/dist/__tests__/wakeup-end-to-end.test.d.ts +2 -0
- package/dist/__tests__/wakeup-end-to-end.test.d.ts.map +1 -0
- package/dist/__tests__/wakeup-end-to-end.test.js +80 -0
- package/dist/__tests__/wakeup-end-to-end.test.js.map +1 -0
- package/dist/__tests__/wakeup-scheduler-restore-fires.test.d.ts +2 -0
- package/dist/__tests__/wakeup-scheduler-restore-fires.test.d.ts.map +1 -0
- package/dist/__tests__/wakeup-scheduler-restore-fires.test.js +33 -0
- package/dist/__tests__/wakeup-scheduler-restore-fires.test.js.map +1 -0
- package/dist/__tests__/wakeup-scheduler-restore.test.d.ts +2 -0
- package/dist/__tests__/wakeup-scheduler-restore.test.d.ts.map +1 -0
- package/dist/__tests__/wakeup-scheduler-restore.test.js +62 -0
- package/dist/__tests__/wakeup-scheduler-restore.test.js.map +1 -0
- package/dist/__tests__/wakeup-scheduler.test.d.ts +2 -0
- package/dist/__tests__/wakeup-scheduler.test.d.ts.map +1 -0
- package/dist/__tests__/wakeup-scheduler.test.js +65 -0
- package/dist/__tests__/wakeup-scheduler.test.js.map +1 -0
- package/dist/__tests__/watch-manager.test.d.ts +2 -0
- package/dist/__tests__/watch-manager.test.d.ts.map +1 -0
- package/dist/__tests__/watch-manager.test.js +203 -0
- package/dist/__tests__/watch-manager.test.js.map +1 -0
- package/dist/__tests__/workcycle-runner-cascade.test.d.ts +2 -0
- package/dist/__tests__/workcycle-runner-cascade.test.d.ts.map +1 -0
- package/dist/__tests__/workcycle-runner-cascade.test.js +203 -0
- package/dist/__tests__/workcycle-runner-cascade.test.js.map +1 -0
- package/dist/__tests__/workcycle-runner.test.d.ts +2 -0
- package/dist/__tests__/workcycle-runner.test.d.ts.map +1 -0
- package/dist/__tests__/workcycle-runner.test.js +369 -0
- package/dist/__tests__/workcycle-runner.test.js.map +1 -0
- package/dist/__tests__/workspace-diff.test.d.ts +2 -0
- package/dist/__tests__/workspace-diff.test.d.ts.map +1 -0
- package/dist/__tests__/workspace-diff.test.js +62 -0
- package/dist/__tests__/workspace-diff.test.js.map +1 -0
- package/dist/__tests__/workspace-manager.test.d.ts +2 -0
- package/dist/__tests__/workspace-manager.test.d.ts.map +1 -0
- package/dist/__tests__/workspace-manager.test.js +120 -0
- package/dist/__tests__/workspace-manager.test.js.map +1 -0
- package/dist/__tests__/workspace-types.test.d.ts +2 -0
- package/dist/__tests__/workspace-types.test.d.ts.map +1 -0
- package/dist/__tests__/workspace-types.test.js +37 -0
- package/dist/__tests__/workspace-types.test.js.map +1 -0
- package/dist/__tests__/worktree-gc.test.d.ts +2 -0
- package/dist/__tests__/worktree-gc.test.d.ts.map +1 -0
- package/dist/__tests__/worktree-gc.test.js +183 -0
- package/dist/__tests__/worktree-gc.test.js.map +1 -0
- package/dist/always-on/activity-reader.d.ts +27 -0
- package/dist/always-on/activity-reader.d.ts.map +1 -0
- package/dist/always-on/activity-reader.js +95 -0
- package/dist/always-on/activity-reader.js.map +1 -0
- package/dist/always-on/always-on-manager.d.ts +170 -0
- package/dist/always-on/always-on-manager.d.ts.map +1 -0
- package/dist/always-on/always-on-manager.js +538 -0
- package/dist/always-on/always-on-manager.js.map +1 -0
- package/dist/always-on/config.d.ts +141 -0
- package/dist/always-on/config.d.ts.map +1 -0
- package/dist/always-on/config.js +324 -0
- package/dist/always-on/config.js.map +1 -0
- package/dist/always-on/discovery-scheduler.d.ts +60 -0
- package/dist/always-on/discovery-scheduler.d.ts.map +1 -0
- package/dist/always-on/discovery-scheduler.js +287 -0
- package/dist/always-on/discovery-scheduler.js.map +1 -0
- package/dist/always-on/ideas-client.d.ts +23 -0
- package/dist/always-on/ideas-client.d.ts.map +1 -0
- package/dist/always-on/ideas-client.js +13 -0
- package/dist/always-on/ideas-client.js.map +1 -0
- package/dist/always-on/reconcile-researching.d.ts +42 -0
- package/dist/always-on/reconcile-researching.d.ts.map +1 -0
- package/dist/always-on/reconcile-researching.js +133 -0
- package/dist/always-on/reconcile-researching.js.map +1 -0
- package/dist/always-on/task-sources/cascade-picker.d.ts +42 -0
- package/dist/always-on/task-sources/cascade-picker.d.ts.map +1 -0
- package/dist/always-on/task-sources/cascade-picker.js +65 -0
- package/dist/always-on/task-sources/cascade-picker.js.map +1 -0
- package/dist/always-on/task-sources/idea-dedupe.d.ts +62 -0
- package/dist/always-on/task-sources/idea-dedupe.d.ts.map +1 -0
- package/dist/always-on/task-sources/idea-dedupe.js +130 -0
- package/dist/always-on/task-sources/idea-dedupe.js.map +1 -0
- package/dist/always-on/task-sources/idea-research-source.d.ts +46 -0
- package/dist/always-on/task-sources/idea-research-source.d.ts.map +1 -0
- package/dist/always-on/task-sources/idea-research-source.js +308 -0
- package/dist/always-on/task-sources/idea-research-source.js.map +1 -0
- package/dist/always-on/task-sources/idea-sort.d.ts +3 -0
- package/dist/always-on/task-sources/idea-sort.d.ts.map +1 -0
- package/dist/always-on/task-sources/idea-sort.js +25 -0
- package/dist/always-on/task-sources/idea-sort.js.map +1 -0
- package/dist/always-on/task-sources/idea-todo-source.d.ts +48 -0
- package/dist/always-on/task-sources/idea-todo-source.d.ts.map +1 -0
- package/dist/always-on/task-sources/idea-todo-source.js +226 -0
- package/dist/always-on/task-sources/idea-todo-source.js.map +1 -0
- package/dist/always-on/task-sources/introspection-source.d.ts +101 -0
- package/dist/always-on/task-sources/introspection-source.d.ts.map +1 -0
- package/dist/always-on/task-sources/introspection-source.js +695 -0
- package/dist/always-on/task-sources/introspection-source.js.map +1 -0
- package/dist/always-on/task-sources/task-store-source.d.ts +15 -0
- package/dist/always-on/task-sources/task-store-source.d.ts.map +1 -0
- package/dist/always-on/task-sources/task-store-source.js +59 -0
- package/dist/always-on/task-sources/task-store-source.js.map +1 -0
- package/dist/always-on/task-sources/types.d.ts +108 -0
- package/dist/always-on/task-sources/types.d.ts.map +1 -0
- package/dist/always-on/task-sources/types.js +13 -0
- package/dist/always-on/task-sources/types.js.map +1 -0
- package/dist/always-on/types.d.ts +76 -0
- package/dist/always-on/types.d.ts.map +1 -0
- package/dist/always-on/types.js +17 -0
- package/dist/always-on/types.js.map +1 -0
- package/dist/always-on/workcycle-runner.d.ts +115 -0
- package/dist/always-on/workcycle-runner.d.ts.map +1 -0
- package/dist/always-on/workcycle-runner.js +285 -0
- package/dist/always-on/workcycle-runner.js.map +1 -0
- package/dist/always-on/worktree-gc.d.ts +41 -0
- package/dist/always-on/worktree-gc.d.ts.map +1 -0
- package/dist/always-on/worktree-gc.js +167 -0
- package/dist/always-on/worktree-gc.js.map +1 -0
- package/dist/inbox-store.d.ts +7 -0
- package/dist/inbox-store.d.ts.map +1 -1
- package/dist/inbox-store.js +8 -1
- package/dist/inbox-store.js.map +1 -1
- package/dist/index.d.ts +29 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -2
- package/dist/index.js.map +1 -1
- package/dist/persistence.d.ts +37 -1
- package/dist/persistence.d.ts.map +1 -1
- package/dist/persistence.js +48 -0
- package/dist/persistence.js.map +1 -1
- package/dist/retrospective.d.ts.map +1 -1
- package/dist/retrospective.js +6 -0
- package/dist/retrospective.js.map +1 -1
- package/dist/roles/role-loader.d.ts +31 -6
- package/dist/roles/role-loader.d.ts.map +1 -1
- package/dist/roles/role-loader.js +58 -32
- package/dist/roles/role-loader.js.map +1 -1
- package/dist/roles/types.d.ts +12 -0
- package/dist/roles/types.d.ts.map +1 -1
- package/dist/shipment/gh-pr-creator.d.ts +28 -0
- package/dist/shipment/gh-pr-creator.d.ts.map +1 -0
- package/dist/shipment/gh-pr-creator.js +80 -0
- package/dist/shipment/gh-pr-creator.js.map +1 -0
- package/dist/shipment/report.d.ts +27 -0
- package/dist/shipment/report.d.ts.map +1 -0
- package/dist/shipment/report.js +41 -0
- package/dist/shipment/report.js.map +1 -0
- package/dist/shipment/secret-scrub.d.ts +12 -0
- package/dist/shipment/secret-scrub.d.ts.map +1 -0
- package/dist/shipment/secret-scrub.js +30 -0
- package/dist/shipment/secret-scrub.js.map +1 -0
- package/dist/shipment/shipment-actions.d.ts +85 -0
- package/dist/shipment/shipment-actions.d.ts.map +1 -0
- package/dist/shipment/shipment-actions.js +190 -0
- package/dist/shipment/shipment-actions.js.map +1 -0
- package/dist/shipment/shipment-pipeline.d.ts +48 -0
- package/dist/shipment/shipment-pipeline.d.ts.map +1 -0
- package/dist/shipment/shipment-pipeline.js +256 -0
- package/dist/shipment/shipment-pipeline.js.map +1 -0
- package/dist/shipment/transport-detect.d.ts +16 -0
- package/dist/shipment/transport-detect.d.ts.map +1 -0
- package/dist/shipment/transport-detect.js +54 -0
- package/dist/shipment/transport-detect.js.map +1 -0
- package/dist/shipment/workspace-diff.d.ts +39 -0
- package/dist/shipment/workspace-diff.d.ts.map +1 -0
- package/dist/shipment/workspace-diff.js +64 -0
- package/dist/shipment/workspace-diff.js.map +1 -0
- package/dist/swarm-coordinator.d.ts +20 -1
- package/dist/swarm-coordinator.d.ts.map +1 -1
- package/dist/swarm-coordinator.js +193 -10
- package/dist/swarm-coordinator.js.map +1 -1
- package/dist/templates/template-loader.d.ts +23 -9
- package/dist/templates/template-loader.d.ts.map +1 -1
- package/dist/templates/template-loader.js +33 -26
- package/dist/templates/template-loader.js.map +1 -1
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/wakeup-scheduler.d.ts +36 -0
- package/dist/wakeup-scheduler.d.ts.map +1 -0
- package/dist/wakeup-scheduler.js +107 -0
- package/dist/wakeup-scheduler.js.map +1 -0
- package/dist/watch-manager.d.ts +32 -0
- package/dist/watch-manager.d.ts.map +1 -0
- package/dist/watch-manager.js +153 -0
- package/dist/watch-manager.js.map +1 -0
- package/dist/workspace/git-worktree-provider.d.ts +11 -0
- package/dist/workspace/git-worktree-provider.d.ts.map +1 -0
- package/dist/workspace/git-worktree-provider.js +123 -0
- package/dist/workspace/git-worktree-provider.js.map +1 -0
- package/dist/workspace/gitignore-check.d.ts +10 -0
- package/dist/workspace/gitignore-check.d.ts.map +1 -0
- package/dist/workspace/gitignore-check.js +25 -0
- package/dist/workspace/gitignore-check.js.map +1 -0
- package/dist/workspace/index.d.ts +5 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +5 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/snapshot-copy-provider.d.ts +11 -0
- package/dist/workspace/snapshot-copy-provider.d.ts.map +1 -0
- package/dist/workspace/snapshot-copy-provider.js +66 -0
- package/dist/workspace/snapshot-copy-provider.js.map +1 -0
- package/dist/workspace/types.d.ts +36 -0
- package/dist/workspace/types.d.ts.map +1 -0
- package/dist/workspace/types.js +2 -0
- package/dist/workspace/types.js.map +1 -0
- package/dist/workspace/workspace-manager.d.ts +30 -0
- package/dist/workspace/workspace-manager.d.ts.map +1 -0
- package/dist/workspace/workspace-manager.js +104 -0
- package/dist/workspace/workspace-manager.js.map +1 -0
- package/package.json +5 -5
- package/roles/queen.md +1 -0
- package/templates/introspection.md +64 -0
- package/templates/research-only.md +58 -0
- package/roles/preset-analyst-simons.md +0 -39
- package/roles/preset-architect-knuth.md +0 -39
- package/roles/preset-designer-norman.md +0 -39
- package/roles/preset-designer.md +0 -39
- package/roles/preset-dev-carmack.md +0 -39
- package/roles/preset-dev-gosling.md +0 -39
- package/roles/preset-developer.md +0 -52
- package/roles/preset-manager-grove.md +0 -39
- package/roles/preset-manager-musk.md +0 -39
- package/roles/preset-pm.md +0 -78
- package/roles/preset-researcher-feynman.md +0 -39
- package/roles/preset-reviewer.md +0 -46
- package/roles/preset-strategist-buffett.md +0 -39
- package/roles/preset-strategist-munger.md +0 -39
- package/roles/preset-strategist-sunzi.md +0 -39
- package/roles/preset-tester-beck.md +0 -40
- package/roles/preset-tester.md +0 -47
- package/roles/preset-writer-orwell.md +0 -39
- package/roles/preset-writer.md +0 -39
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
// M8.2 — IntrospectionSource: L3 fallback in the task-source cascade.
|
|
2
|
+
//
|
|
3
|
+
// Unlike L1/L2a/L2b, this source NEVER returns an executable TaskRef. Its
|
|
4
|
+
// `pick(projectRoot)` side-effectfully drives the agent's self-introspection
|
|
5
|
+
// loop and always returns null — the cascade short-circuits, the cycle "does
|
|
6
|
+
// nothing" from the runner's perspective, and the user wakes up to new
|
|
7
|
+
// auto-discovered idea suggestions on the next visit.
|
|
8
|
+
//
|
|
9
|
+
// M8 hotfix I1+I2 — fire-and-forget. Previously pick() spawned the swarm
|
|
10
|
+
// and awaited it inline (up to 10 min), which:
|
|
11
|
+
// (a) blocked the always-on scheduler from firing anything else for the
|
|
12
|
+
// entire wait window (I2), and
|
|
13
|
+
// (b) burned a dailyBudget tick on a cycle that produced no in-cycle work
|
|
14
|
+
// (I1, because runner returned status="skipped" but ticksToday still
|
|
15
|
+
// incremented).
|
|
16
|
+
// The fix is a two-phase handshake:
|
|
17
|
+
// 1. spawn cycle — pick() creates the swarm, persists
|
|
18
|
+
// `pendingIntrospectionSwarmId` + bumps `lastIntrospectionAt`, returns
|
|
19
|
+
// null without awaiting the swarm. The scheduler is free to fire other
|
|
20
|
+
// sources on the next tick.
|
|
21
|
+
// 2. ingest cycle (next pick after terminal) — pick() observes the
|
|
22
|
+
// pending swarm has finished, reads the output file, POSTs the cohort
|
|
23
|
+
// to /api/ideas, clears the pending breadcrumb, and returns null. The
|
|
24
|
+
// ingest never blocks; if the swarm is still running, we just return
|
|
25
|
+
// null again and try the next tick.
|
|
26
|
+
// An agent restart between spawn and ingest is safe: the pending swarmId is
|
|
27
|
+
// persisted to always-on.json and the recovery.json on disk lets the
|
|
28
|
+
// SwarmCoordinator re-attach.
|
|
29
|
+
//
|
|
30
|
+
// Guard-rails (per design doc D4):
|
|
31
|
+
// - Opt-in (default off). `config.taskSources?.introspection === true`.
|
|
32
|
+
// - Independent 24h cooldown via `introspectionCooldownHours`.
|
|
33
|
+
// - Self-suppression: if the previous introspection's ideas were all
|
|
34
|
+
// archived by the user, skip this one (`lastIntrospectionRejected`).
|
|
35
|
+
// - Cap at 3 ideas/cycle regardless of how many the queen produced.
|
|
36
|
+
// - Priority is always forced to "low" — agent suggestions never compete
|
|
37
|
+
// with user-written ideas for top slots (Q4=A enforcement).
|
|
38
|
+
//
|
|
39
|
+
// Output format (mirrors the introspection.md template):
|
|
40
|
+
// The queen writes a JSON array to a path provided in the task prompt:
|
|
41
|
+
// <projectRoot>/.clawnet/introspection/<swarmId>.json
|
|
42
|
+
// The ingest phase reads + parses + POSTs the file. Failure modes (no
|
|
43
|
+
// file / malformed JSON / empty array) clear the pending breadcrumb and
|
|
44
|
+
// move on — the cooldown gates the next spawn so a broken swarm can't
|
|
45
|
+
// spin in a tight loop.
|
|
46
|
+
import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
47
|
+
import { randomUUID } from "node:crypto";
|
|
48
|
+
import { dirname, join } from "node:path";
|
|
49
|
+
import { encodeCwd } from "@mclawnet/shared";
|
|
50
|
+
import { DEFAULT_INTROSPECTION_COOLDOWN_HOURS, resolveTaskSources, } from "../config.js";
|
|
51
|
+
import { appendToIndex, buildDedupeIndex, findDuplicate, } from "./idea-dedupe.js";
|
|
52
|
+
export const INTROSPECTION_TEMPLATE_NAME = "introspection";
|
|
53
|
+
export const INTROSPECTION_SOURCE_TAG = "auto-discovered";
|
|
54
|
+
export const INTROSPECTION_SOURCE_ROLE = "queen-introspection";
|
|
55
|
+
/** Hard cap per design doc D4 — no more than 3 ideas per introspection. */
|
|
56
|
+
export const INTROSPECTION_MAX_IDEAS = 3;
|
|
57
|
+
/**
|
|
58
|
+
* M8.2c — write-race guard for the spawner-owned completion signal. The
|
|
59
|
+
* queen's `cat > <output> <<'EOF' ... EOF` lands as several syscalls; we
|
|
60
|
+
* require N ms of mtime stillness before declaring "queen finished writing".
|
|
61
|
+
*/
|
|
62
|
+
export const SPAWNER_COMPLETE_MTIME_GUARD_MS = 5_000;
|
|
63
|
+
/**
|
|
64
|
+
* M8.2c — absolute spawn-to-ingest cap. Even if the output file never lands
|
|
65
|
+
* (queen crashed / token exhausted / coordinator lost the swarm), the
|
|
66
|
+
* spawner stops waiting after this. `processPendingSwarm` then takes the
|
|
67
|
+
* "missing output" path: clears the pending breadcrumb and the next cycle
|
|
68
|
+
* (subject to cooldown) gets to retry. Set high enough that a normal
|
|
69
|
+
* introspection cycle (~30s of read+grep+write) is never near the cap.
|
|
70
|
+
*/
|
|
71
|
+
export const SPAWNER_COMPLETE_HARD_TIMEOUT_MS = 2 * 60 * 60 * 1000;
|
|
72
|
+
/**
|
|
73
|
+
* Parse the queen's output file into a candidate array. Accepts either:
|
|
74
|
+
* (a) a clean JSON file whose root is an array, or
|
|
75
|
+
* (b) a file that contains other text and ends with a JSON array — we
|
|
76
|
+
* scan for the *last* `[` and try JSON.parse from there.
|
|
77
|
+
*
|
|
78
|
+
* Returns [] on any failure. Callers treat [] as "queen produced nothing
|
|
79
|
+
* useful" — they still bump lastIntrospectionAt so cooldown holds.
|
|
80
|
+
*/
|
|
81
|
+
export function parseCandidatesOutput(raw) {
|
|
82
|
+
if (!raw || raw.trim().length === 0)
|
|
83
|
+
return [];
|
|
84
|
+
// Strategy 1: parse the entire file.
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(raw);
|
|
87
|
+
if (Array.isArray(parsed))
|
|
88
|
+
return coerceCandidates(parsed);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// fall through to strategy 2
|
|
92
|
+
}
|
|
93
|
+
// Strategy 2: locate the last `[` and parse a balanced slice from there.
|
|
94
|
+
const lastOpen = raw.lastIndexOf("[");
|
|
95
|
+
if (lastOpen < 0)
|
|
96
|
+
return [];
|
|
97
|
+
// Find a matching close. Walk forward counting brackets; bail on first
|
|
98
|
+
// balanced match. This is good enough for queen-produced JSON which is
|
|
99
|
+
// never going to have stringly-embedded brackets at our cap of 3 items.
|
|
100
|
+
let depth = 0;
|
|
101
|
+
let end = -1;
|
|
102
|
+
let inString = false;
|
|
103
|
+
let escape = false;
|
|
104
|
+
for (let i = lastOpen; i < raw.length; i++) {
|
|
105
|
+
const ch = raw[i];
|
|
106
|
+
if (inString) {
|
|
107
|
+
if (escape) {
|
|
108
|
+
escape = false;
|
|
109
|
+
}
|
|
110
|
+
else if (ch === "\\") {
|
|
111
|
+
escape = true;
|
|
112
|
+
}
|
|
113
|
+
else if (ch === '"') {
|
|
114
|
+
inString = false;
|
|
115
|
+
}
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (ch === '"') {
|
|
119
|
+
inString = true;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (ch === "[")
|
|
123
|
+
depth++;
|
|
124
|
+
else if (ch === "]") {
|
|
125
|
+
depth--;
|
|
126
|
+
if (depth === 0) {
|
|
127
|
+
end = i;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (end < 0)
|
|
133
|
+
return [];
|
|
134
|
+
const slice = raw.slice(lastOpen, end + 1);
|
|
135
|
+
try {
|
|
136
|
+
const parsed = JSON.parse(slice);
|
|
137
|
+
if (Array.isArray(parsed))
|
|
138
|
+
return coerceCandidates(parsed);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
/* fall through */
|
|
142
|
+
}
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
function coerceCandidates(arr) {
|
|
146
|
+
const out = [];
|
|
147
|
+
for (const raw of arr) {
|
|
148
|
+
if (!raw || typeof raw !== "object")
|
|
149
|
+
continue;
|
|
150
|
+
const r = raw;
|
|
151
|
+
const title = typeof r.title === "string" ? r.title.trim() : "";
|
|
152
|
+
if (!title)
|
|
153
|
+
continue;
|
|
154
|
+
const body = typeof r.body === "string" ? r.body : "";
|
|
155
|
+
const priority = typeof r.priority === "string" ? r.priority : undefined;
|
|
156
|
+
out.push({ title, body, ...(priority ? { priority } : {}) });
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Build the task prompt handed to the introspection swarm. Embeds a
|
|
162
|
+
* workspace-RELATIVE output path so the queen writes inside whatever cwd
|
|
163
|
+
* her role process is given (workspace.cwd when worktree isolation is on,
|
|
164
|
+
* or projectRoot when it falls through to no workspace). Spawner reads it
|
|
165
|
+
* back by resolving the same relative path against the actual workspace
|
|
166
|
+
* cwd at drain time.
|
|
167
|
+
*
|
|
168
|
+
* Pre-B4: this used to embed `<projectRoot>/.clawnet/introspection/<id>.json`
|
|
169
|
+
* absolute and the queen would `cat >` straight to projectRoot, bypassing
|
|
170
|
+
* the worktree. PR-B Suspect B fix.
|
|
171
|
+
*/
|
|
172
|
+
export function buildIntrospectionTaskPrompt(projectRoot, relativeOutputPath) {
|
|
173
|
+
return [
|
|
174
|
+
`You are running an introspection cycle on the project rooted at: ${projectRoot}`,
|
|
175
|
+
"",
|
|
176
|
+
"Goal: produce 1-3 NEW idea candidates that aren't already tracked.",
|
|
177
|
+
"Tools you may use: Read, Grep, Bash (read-only: ls / cat / git log / git status / git diff --stat / git show).",
|
|
178
|
+
"Tools you must NOT use: Write, Edit, NotebookEdit, or any state-mutating command (git apply, git commit, package install, etc.).",
|
|
179
|
+
"",
|
|
180
|
+
`Output: write a JSON array to this path RELATIVE to your current working directory:`,
|
|
181
|
+
` ${relativeOutputPath}`,
|
|
182
|
+
"(your cwd is the swarm workspace — write the file there, do NOT use an absolute path)",
|
|
183
|
+
"",
|
|
184
|
+
"Schema of each array element:",
|
|
185
|
+
' { "title": "short title", "body": "why it matters + scope", "priority": "low" }',
|
|
186
|
+
"",
|
|
187
|
+
"Hard limits: at most 3 items. Priority is overridden to 'low' downstream — you can omit it.",
|
|
188
|
+
"When you have written the file, call `swarm_complete` to end the cycle. The spawner will pick up the candidates next tick and release coordinator resources immediately. If you skip the call, the spawner-owned fallback (output-file detection or paused-state detection) will reap the cycle eventually, but explicit completion is faster and avoids holding the backend session.",
|
|
189
|
+
].join("\n");
|
|
190
|
+
}
|
|
191
|
+
/** Internal: returns true when the swarm is in a terminal state (or gone from registry). */
|
|
192
|
+
function isTerminalSwarm(coord, swarmId) {
|
|
193
|
+
const swarm = coord.getSwarm(swarmId);
|
|
194
|
+
if (!swarm) {
|
|
195
|
+
// Gone from registry — could mean cleanup already ran, or the agent
|
|
196
|
+
// restarted and we didn't re-recover this swarm. Treat as "unknown
|
|
197
|
+
// terminal" so the ingest phase still tries to read the output file
|
|
198
|
+
// (it's the source of truth) and clears the pending breadcrumb.
|
|
199
|
+
return { terminal: true, status: "unknown" };
|
|
200
|
+
}
|
|
201
|
+
if (swarm.status === "completed")
|
|
202
|
+
return { terminal: true, status: "completed" };
|
|
203
|
+
if (swarm.status === "failed")
|
|
204
|
+
return { terminal: true, status: "failed" };
|
|
205
|
+
if (swarm.status === "cancelled")
|
|
206
|
+
return { terminal: true, status: "cancelled" };
|
|
207
|
+
return { terminal: false, status: "running" };
|
|
208
|
+
}
|
|
209
|
+
export function createIntrospectionSource(opts) {
|
|
210
|
+
const { ideasClient, swarmCoordinator, getConfig, updateConfig, introspectionTemplateName = INTROSPECTION_TEMPLATE_NAME, clock = () => new Date(), warn, info, generateSwarmId = () => `m8-introspect-${randomUUID()}`, resolveOutputPath = (projectRoot, swarmId) => join(projectRoot, ".clawnet", "introspection", `${swarmId}.json`), } = opts;
|
|
211
|
+
/**
|
|
212
|
+
* PR-B Suspect B fix — workspace-aware output path resolver.
|
|
213
|
+
* Prefer the swarm's actual workspace.cwd (set when WorkspaceManager
|
|
214
|
+
* prepared a worktree / snapshot copy) so we read where the queen
|
|
215
|
+
* actually wrote. Falls back to projectRoot via the (now historical)
|
|
216
|
+
* `resolveOutputPath` opt — preserves existing tests that fake the
|
|
217
|
+
* coordinator without a workspace.
|
|
218
|
+
*
|
|
219
|
+
* PR-B follow-up — after `swarm_complete` deletes the swarm from the
|
|
220
|
+
* registry, `swarmCoordinator.getSwarm()` returns undefined and the
|
|
221
|
+
* spawner loses the workspace handle. Pass the persisted
|
|
222
|
+
* `pendingIntrospectionWorkspaceCwd` (captured at spawn time) so the
|
|
223
|
+
* resolver still lands inside the worktree even when the live handle is
|
|
224
|
+
* gone. Without this, B2 (queen-driven swarm_complete) silently loses
|
|
225
|
+
* every cohort: lookup → undefined → projectRoot fallback → file-not-
|
|
226
|
+
* found warn → 0 ingest → breadcrumb cleared.
|
|
227
|
+
*/
|
|
228
|
+
function resolveActualOutputPath(projectRoot, swarmId, persistedCwd) {
|
|
229
|
+
const swarm = swarmCoordinator.getSwarm(swarmId);
|
|
230
|
+
const base = swarm?.workspace?.cwd ?? persistedCwd ?? projectRoot;
|
|
231
|
+
// Keep the well-known relative tail so an injected resolveOutputPath
|
|
232
|
+
// override (tests) still wins when base === projectRoot.
|
|
233
|
+
if (base === projectRoot)
|
|
234
|
+
return resolveOutputPath(projectRoot, swarmId);
|
|
235
|
+
return join(base, ".clawnet", "introspection", `${swarmId}.json`);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* PR-B — the prompt embeds this RELATIVE path; the queen's role process
|
|
239
|
+
* is given workspace.cwd as its cwd, so writing to a relative path lands
|
|
240
|
+
* inside the workspace. Pre-B4 we embedded an absolute projectRoot path,
|
|
241
|
+
* which made the queen escape the worktree.
|
|
242
|
+
*/
|
|
243
|
+
function relativeOutputPath(swarmId) {
|
|
244
|
+
return join(".", ".clawnet", "introspection", `${swarmId}.json`);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* M8.2c — spawner-owned completion signal. Returns true when the queen
|
|
248
|
+
* appears to have finished (output file exists and has been stable for the
|
|
249
|
+
* mtime guard window) OR the hard timeout has elapsed. Pure inspection —
|
|
250
|
+
* never mutates state. Used by `processPendingSwarm` to decide whether to
|
|
251
|
+
* force the swarm into a terminal state when the coordinator's idle
|
|
252
|
+
* fallback would otherwise leave it in `paused`. Pulled out as a helper
|
|
253
|
+
* so the public `isComplete` source hook can share the same logic.
|
|
254
|
+
*/
|
|
255
|
+
function isOwnerComplete(projectRoot, pendingId, cfg) {
|
|
256
|
+
const now = clock().getTime();
|
|
257
|
+
// Hard timeout: spawn-to-give-up cap. Uses lastIntrospectionAt which is
|
|
258
|
+
// set to `clock().toISOString()` at spawn time. (The S1 hub-outage rewind
|
|
259
|
+
// only fires AFTER ingest has cleared pendingId, so it never overlaps
|
|
260
|
+
// with the hard-timeout path here.)
|
|
261
|
+
if (cfg.lastIntrospectionAt) {
|
|
262
|
+
const spawnedAt = new Date(cfg.lastIntrospectionAt).getTime();
|
|
263
|
+
if (Number.isFinite(spawnedAt) && now - spawnedAt > SPAWNER_COMPLETE_HARD_TIMEOUT_MS) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const outputPath = resolveActualOutputPath(projectRoot, pendingId, cfg.pendingIntrospectionWorkspaceCwd);
|
|
268
|
+
if (!existsSync(outputPath))
|
|
269
|
+
return false;
|
|
270
|
+
try {
|
|
271
|
+
const st = statSync(outputPath);
|
|
272
|
+
return now - st.mtimeMs > SPAWNER_COMPLETE_MTIME_GUARD_MS;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Ingest phase — read the pending swarm's output, POST ideas, clear
|
|
280
|
+
* pending. Always-clears the pending breadcrumb (terminal swarm) so a
|
|
281
|
+
* malformed/missing output doesn't wedge the source forever; the
|
|
282
|
+
* cooldown (set at spawn) gates the next attempt.
|
|
283
|
+
*
|
|
284
|
+
* S1 hotfix: when the swarm produced candidates but every POST failed
|
|
285
|
+
* (likely hub outage), rewind `lastIntrospectionAt` so the cooldown
|
|
286
|
+
* shortens to ~1 hour instead of carrying the full 24h cooldown for an
|
|
287
|
+
* effectively-empty cycle.
|
|
288
|
+
*
|
|
289
|
+
* M8.2c: when the coordinator hasn't transitioned to a terminal state yet
|
|
290
|
+
* (single-queen introspection swarms get stuck in `paused` because the
|
|
291
|
+
* idle-fallback in startQueenCheck never sees an "active worker"), the
|
|
292
|
+
* spawner-owned completion signal (output file + mtime guard, or hard
|
|
293
|
+
* timeout) lets us force `coord.complete(swarmId)` ourselves and proceed
|
|
294
|
+
* to ingest in the same tick. Without this the cycle would have to wait
|
|
295
|
+
* for the coordinator's 100-min auto-pause AND a follow-up B2a-style
|
|
296
|
+
* tolerance path that doesn't exist.
|
|
297
|
+
*/
|
|
298
|
+
async function processPendingSwarm(projectRoot, pendingId, cfg) {
|
|
299
|
+
let state = isTerminalSwarm(swarmCoordinator, pendingId);
|
|
300
|
+
// M8.2c follow-up — read the output BEFORE any coordinator transition.
|
|
301
|
+
// The destroy fallback below disposes the swarm's workspace
|
|
302
|
+
// (workspaceManager.dispose deletes it from disk). Today the
|
|
303
|
+
// introspection swarm doesn't run inside an isolated worktree so the
|
|
304
|
+
// output lives in the project's main `.clawnet/introspection/` dir and
|
|
305
|
+
// survives. Once B4 lands worktree isolation for always-on cycles, the
|
|
306
|
+
// output will land inside the disposable workspace and a destroy-before-
|
|
307
|
+
// read would silently lose the 3 candidates. Caching candidates here
|
|
308
|
+
// makes the ingest order-independent of the coordinator transition.
|
|
309
|
+
const outputPath = resolveActualOutputPath(projectRoot, pendingId, cfg.pendingIntrospectionWorkspaceCwd);
|
|
310
|
+
let candidates = [];
|
|
311
|
+
let outputExisted = false;
|
|
312
|
+
if (existsSync(outputPath)) {
|
|
313
|
+
outputExisted = true;
|
|
314
|
+
try {
|
|
315
|
+
const raw = readFileSync(outputPath, "utf-8");
|
|
316
|
+
candidates = parseCandidatesOutput(raw);
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
warn?.({ err: err instanceof Error ? err.message : String(err), outputPath, pendingId }, "introspection-source: failed to read pending output (clearing pending)");
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (!state.terminal) {
|
|
323
|
+
if (!isOwnerComplete(projectRoot, pendingId, cfg)) {
|
|
324
|
+
info?.({ pendingId, projectRoot }, "introspection-source: pending swarm still running — will check next tick");
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
// Spawner observed its own completion signal (output file written, or
|
|
328
|
+
// hard timeout elapsed). Force the coordinator to transition out of
|
|
329
|
+
// paused/running so the rest of the ingest path runs against a stable
|
|
330
|
+
// terminal state and downstream UI shows "completed" not "paused".
|
|
331
|
+
try {
|
|
332
|
+
await swarmCoordinator.complete(pendingId);
|
|
333
|
+
info?.({ pendingId, projectRoot }, "introspection-source: spawner-owned complete (forced terminal transition)");
|
|
334
|
+
}
|
|
335
|
+
catch (err) {
|
|
336
|
+
// Non-fatal: the output file's already been cached above and the
|
|
337
|
+
// pending breadcrumb is always cleared at the end. Try `destroy` as
|
|
338
|
+
// a last-resort cleanup so the swarm doesn't leak in the
|
|
339
|
+
// coordinator registry when `complete` was the wrong transition —
|
|
340
|
+
// the R3 I-A chain below keys off `state.status` and would skip
|
|
341
|
+
// both `completed` and `failed` branches when state is still
|
|
342
|
+
// "paused"/"running", leaving the registry entry alive forever.
|
|
343
|
+
warn?.({ err: err instanceof Error ? err.message : String(err), pendingId }, "introspection-source: spawner-owned complete failed (trying destroy fallback)");
|
|
344
|
+
try {
|
|
345
|
+
await swarmCoordinator.destroy(pendingId);
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
/* best-effort — output is already cached above */
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
state = isTerminalSwarm(swarmCoordinator, pendingId);
|
|
352
|
+
}
|
|
353
|
+
if (!outputExisted) {
|
|
354
|
+
warn?.({ pendingId, outputPath, swarmStatus: state.status }, "introspection-source: pending swarm output missing (clearing pending)");
|
|
355
|
+
}
|
|
356
|
+
const cohort = candidates.slice(0, INTROSPECTION_MAX_IDEAS);
|
|
357
|
+
let projectId = null;
|
|
358
|
+
try {
|
|
359
|
+
projectId = encodeCwd(projectRoot);
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
projectId = null;
|
|
363
|
+
}
|
|
364
|
+
// M8.2 dedupe pre-fetch: pull existing same-project intro ideas so we can
|
|
365
|
+
// skip candidates the queen has already proposed in a prior cycle. Failure
|
|
366
|
+
// here is non-fatal: we set `dedupeAvailable=false` and fall through to
|
|
367
|
+
// the insert loop unchanged. The hub-side `listIdeasSchema` caps `limit`
|
|
368
|
+
// at 100; that's plenty for the typical introspection corpus (a few months
|
|
369
|
+
// of 3-ideas-per-cycle still fits comfortably).
|
|
370
|
+
let dedupeIndex = [];
|
|
371
|
+
let dedupeAvailable = false;
|
|
372
|
+
if (projectId && cohort.length > 0) {
|
|
373
|
+
try {
|
|
374
|
+
const existing = await ideasClient.list({
|
|
375
|
+
projectId,
|
|
376
|
+
tag: INTROSPECTION_SOURCE_TAG,
|
|
377
|
+
limit: 100,
|
|
378
|
+
});
|
|
379
|
+
// Defensive client-side filter — at the time of writing `list` returns
|
|
380
|
+
// every status, but if that ever changes to "active-only" this filter
|
|
381
|
+
// is still safe (archived rows we don't want to dedupe against either
|
|
382
|
+
// way — the user explicitly archived them).
|
|
383
|
+
const active = existing.filter((i) => i.status !== "archived");
|
|
384
|
+
dedupeIndex = buildDedupeIndex(active.map((i) => ({ id: i.id, title: i.title, body: i.body ?? "" })));
|
|
385
|
+
dedupeAvailable = true;
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
warn?.({ err: err instanceof Error ? err.message : String(err), projectId }, "introspection-source: dedupe pre-fetch failed (fail-open, proceeding with insert)");
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
const posted = [];
|
|
392
|
+
let suppressedDuplicates = 0;
|
|
393
|
+
for (const c of cohort) {
|
|
394
|
+
if (dedupeAvailable) {
|
|
395
|
+
const match = findDuplicate({ title: c.title, body: c.body ?? "" }, dedupeIndex);
|
|
396
|
+
if (match) {
|
|
397
|
+
suppressedDuplicates++;
|
|
398
|
+
info?.({
|
|
399
|
+
title: c.title,
|
|
400
|
+
matchedIdeaId: match.matchedIdeaId,
|
|
401
|
+
projectId,
|
|
402
|
+
reason: match.reason,
|
|
403
|
+
...(match.score !== undefined ? { jaccard: match.score } : {}),
|
|
404
|
+
}, "introspection-source: duplicate-suppressed");
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const input = {
|
|
409
|
+
title: c.title.slice(0, 500),
|
|
410
|
+
body: (c.body ?? "").slice(0, 50_000),
|
|
411
|
+
status: "idea",
|
|
412
|
+
priority: "low",
|
|
413
|
+
tags: [INTROSPECTION_SOURCE_TAG],
|
|
414
|
+
sourceRole: INTROSPECTION_SOURCE_ROLE,
|
|
415
|
+
sourceSwarmId: pendingId,
|
|
416
|
+
...(projectId ? { projectId } : {}),
|
|
417
|
+
};
|
|
418
|
+
try {
|
|
419
|
+
const created = await ideasClient.create(input);
|
|
420
|
+
posted.push(created);
|
|
421
|
+
// Extend the index with the freshly-created idea so any subsequent
|
|
422
|
+
// candidate in this same cohort is checked against it too. Prevents
|
|
423
|
+
// the queen accidentally smuggling two near-identical ideas through
|
|
424
|
+
// in one cycle.
|
|
425
|
+
if (dedupeAvailable) {
|
|
426
|
+
appendToIndex(dedupeIndex, {
|
|
427
|
+
id: created.id,
|
|
428
|
+
title: c.title,
|
|
429
|
+
body: c.body ?? "",
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
catch (err) {
|
|
434
|
+
warn?.({ err: err instanceof Error ? err.message : String(err), title: c.title }, "introspection-source: ideas.create failed");
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// S2 hotfix + R3 I-A: release the swarm from the coordinator registry
|
|
438
|
+
// for EVERY terminal state, not just "completed". Mirrors
|
|
439
|
+
// WorkCycleRunner.waitForTerminalState (workcycle-runner.ts:340-376):
|
|
440
|
+
// completed → coord.complete(swarmId) (idempotent — also runs cleanup)
|
|
441
|
+
// failed → coord.fail(swarmId)
|
|
442
|
+
// cancelled → no-op (already terminal externally; the coordinator
|
|
443
|
+
// cancel path is what put it there and already cleaned up)
|
|
444
|
+
// unknown → no-op (gone from registry — nothing to release)
|
|
445
|
+
// Pre-R3, only `completed` ran cleanup, so a swarm that returned
|
|
446
|
+
// `failed`/`cancelled`/`unknown` stayed in the registry forever and
|
|
447
|
+
// leaked across cycles.
|
|
448
|
+
if (state.status === "completed") {
|
|
449
|
+
try {
|
|
450
|
+
await swarmCoordinator.complete(pendingId);
|
|
451
|
+
}
|
|
452
|
+
catch (err) {
|
|
453
|
+
warn?.({ err: err instanceof Error ? err.message : String(err), pendingId }, "introspection-source: coordinator.complete failed (best-effort)");
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
else if (state.status === "failed") {
|
|
457
|
+
try {
|
|
458
|
+
await swarmCoordinator.fail(pendingId);
|
|
459
|
+
}
|
|
460
|
+
catch (err) {
|
|
461
|
+
warn?.({ err: err instanceof Error ? err.message : String(err), pendingId }, "introspection-source: coordinator.fail failed (best-effort)");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// "cancelled" and "unknown" need no coordinator call: cancelled is
|
|
465
|
+
// already terminal in the coordinator (whoever cancelled it cleaned up),
|
|
466
|
+
// and unknown means the swarm is no longer in the registry at all.
|
|
467
|
+
info?.({
|
|
468
|
+
pendingId,
|
|
469
|
+
swarmStatus: state.status,
|
|
470
|
+
produced: candidates.length,
|
|
471
|
+
posted: posted.length,
|
|
472
|
+
skippedOverflow: Math.max(0, candidates.length - INTROSPECTION_MAX_IDEAS),
|
|
473
|
+
suppressedDuplicates,
|
|
474
|
+
}, "introspection-source: ingest complete");
|
|
475
|
+
// S1 hotfix: hub outage triage. If the queen produced candidates but
|
|
476
|
+
// every POST failed, rewind the cooldown so we retry in ~1h instead of
|
|
477
|
+
// waiting the full window. Only triggers when there *were* candidates
|
|
478
|
+
// (no candidates = nothing to retry, no point shortening cooldown).
|
|
479
|
+
//
|
|
480
|
+
// M8.2 dedupe note: subtract `suppressedDuplicates` so a cohort where
|
|
481
|
+
// every idea was a known duplicate (legitimate zero-POST outcome) does
|
|
482
|
+
// not falsely trip the hub-outage path.
|
|
483
|
+
const patch = {
|
|
484
|
+
pendingIntrospectionSwarmId: null,
|
|
485
|
+
pendingIntrospectionWorkspaceCwd: null,
|
|
486
|
+
};
|
|
487
|
+
const attempted = cohort.length - suppressedDuplicates;
|
|
488
|
+
if (attempted > 0 && posted.length === 0) {
|
|
489
|
+
const cooldownHours = cfg.introspectionCooldownHours ?? DEFAULT_INTROSPECTION_COOLDOWN_HOURS;
|
|
490
|
+
const rewindMs = Math.max(0, cooldownHours - 1) * 3600_000;
|
|
491
|
+
patch.lastIntrospectionAt = new Date(clock().getTime() - rewindMs).toISOString();
|
|
492
|
+
info?.({ pendingId, candidates: cohort.length, attempted, posted: 0, rewindMs }, "introspection-source: hub outage triage — rewinding lastIntrospectionAt");
|
|
493
|
+
}
|
|
494
|
+
await updateConfig(projectRoot, patch);
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
name: "queen-introspection",
|
|
498
|
+
kind: "queen-introspection",
|
|
499
|
+
/**
|
|
500
|
+
* M8.2c — spawner-owned completion signal exposed publicly. Introspection
|
|
501
|
+
* swarms are single-queen fire-and-forget cycles: there's never an active
|
|
502
|
+
* non-queen worker, so the coordinator's idle-check would otherwise stall
|
|
503
|
+
* them in `paused` after ~100 min, and `processPendingSwarm` (which keys
|
|
504
|
+
* off swarm status) would never ingest. The source's own `drainPending`
|
|
505
|
+
* calls `coordinator.complete()` once this returns true.
|
|
506
|
+
*/
|
|
507
|
+
async isComplete(projectRoot, swarmId) {
|
|
508
|
+
return isOwnerComplete(projectRoot, swarmId, getConfig(projectRoot));
|
|
509
|
+
},
|
|
510
|
+
/**
|
|
511
|
+
* R3 I-B — pre-cascade drain hook. Runs every tick BEFORE the cascade
|
|
512
|
+
* picker, regardless of whether `taskSources.introspection` is on.
|
|
513
|
+
* Without this hook, the scenario "user enables introspection → swarm
|
|
514
|
+
* spawned → user toggles introspection OFF before the swarm reaches
|
|
515
|
+
* terminal state" leaks `pendingIntrospectionSwarmId` (the source's
|
|
516
|
+
* own pick() is skipped while disabled, so its Step-0 drain never
|
|
517
|
+
* fires) AND the swarm stays in the coordinator registry forever
|
|
518
|
+
* (only re-enabling the toggle ever reaps it). The drain is opt-in
|
|
519
|
+
* (skips when no pending breadcrumb), independent of the toggle, and
|
|
520
|
+
* best-effort — internal failures are swallowed by processPendingSwarm.
|
|
521
|
+
*/
|
|
522
|
+
async drainPending(projectRoot) {
|
|
523
|
+
const cfg = getConfig(projectRoot);
|
|
524
|
+
if (!cfg.pendingIntrospectionSwarmId)
|
|
525
|
+
return;
|
|
526
|
+
await processPendingSwarm(projectRoot, cfg.pendingIntrospectionSwarmId, cfg);
|
|
527
|
+
},
|
|
528
|
+
async pick(projectRoot) {
|
|
529
|
+
const cfg = getConfig(projectRoot);
|
|
530
|
+
const sources = resolveTaskSources(cfg.taskSources);
|
|
531
|
+
if (!sources.introspection) {
|
|
532
|
+
// Default path: introspection is opt-in. Silent (debug-level) — no
|
|
533
|
+
// log spam when the user simply hasn't turned it on.
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
// ── Step 0: drain any pending swarm spawned on a prior tick ──────
|
|
537
|
+
// Fire-and-forget design (hotfix I1/I2). If a spawn cycle left a
|
|
538
|
+
// breadcrumb, we ingest the result here without ever blocking the
|
|
539
|
+
// scheduler. Always returns null after — the cascade falls through
|
|
540
|
+
// and the runner skips this cycle. We deliberately do NOT proceed to
|
|
541
|
+
// step 1+ even when the ingest completed; one cycle does one thing
|
|
542
|
+
// (ingest OR spawn), never both. This keeps reasoning simple and
|
|
543
|
+
// avoids surprising the user with two POSTs in one tick.
|
|
544
|
+
//
|
|
545
|
+
// R3 I-B note: in the on-toggle path, `AlwaysOnManager.drainPending`
|
|
546
|
+
// has already attempted the drain before this pick() runs, so by the
|
|
547
|
+
// time we get here the breadcrumb is usually cleared. The defensive
|
|
548
|
+
// re-check still matters because (a) the swarm may still be running
|
|
549
|
+
// (drainPending leaves the breadcrumb in place when not terminal),
|
|
550
|
+
// and (b) when `taskSources` is wired without an AlwaysOnManager
|
|
551
|
+
// (tests, ad-hoc usage) the manager-level drain doesn't run.
|
|
552
|
+
if (cfg.pendingIntrospectionSwarmId) {
|
|
553
|
+
await processPendingSwarm(projectRoot, cfg.pendingIntrospectionSwarmId, cfg);
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
// ── Step 1: self-suppression pre-check ──────────────────────────
|
|
557
|
+
// If the previous introspection ran, evaluate whether all of its
|
|
558
|
+
// produced ideas got archived. We do this BEFORE the cooldown gate so
|
|
559
|
+
// a user who actually kept some ideas clears the suppression sooner
|
|
560
|
+
// (and so a stuck suppression flag doesn't outlast the cooldown).
|
|
561
|
+
if (cfg.lastIntrospectionAt) {
|
|
562
|
+
try {
|
|
563
|
+
const projectId = encodeCwd(projectRoot);
|
|
564
|
+
// Pull a generous slice — we filter further in-memory by
|
|
565
|
+
// lastIntrospectionAt and tag/source. Limit 100 covers up to ~33
|
|
566
|
+
// introspections at 3 ideas each.
|
|
567
|
+
const recent = await ideasClient.list({
|
|
568
|
+
projectId,
|
|
569
|
+
tag: INTROSPECTION_SOURCE_TAG,
|
|
570
|
+
limit: 100,
|
|
571
|
+
});
|
|
572
|
+
const epsilonMs = 60_000; // 1-minute slack for clock skew
|
|
573
|
+
// N2 hotfix: use the injected clock + Date.getTime() consistently
|
|
574
|
+
// instead of mixing Date.parse with the wall clock. Tests
|
|
575
|
+
// injecting `clock` were observing the wall-clock now() during
|
|
576
|
+
// the cohort filter, which made deterministic fixtures flaky.
|
|
577
|
+
const since = new Date(cfg.lastIntrospectionAt).getTime();
|
|
578
|
+
const cohort = recent.filter((i) => i.sourceRole === INTROSPECTION_SOURCE_ROLE &&
|
|
579
|
+
new Date(i.createdAt).getTime() >= since - epsilonMs);
|
|
580
|
+
if (cohort.length > 0) {
|
|
581
|
+
const allArchived = cohort.every((i) => i.status === "archived");
|
|
582
|
+
const desired = allArchived;
|
|
583
|
+
if (cfg.lastIntrospectionRejected !== desired) {
|
|
584
|
+
await updateConfig(projectRoot, { lastIntrospectionRejected: desired });
|
|
585
|
+
}
|
|
586
|
+
if (allArchived) {
|
|
587
|
+
info?.({ cohortSize: cohort.length, projectId }, "introspection-source: previous cohort all archived — suppressing");
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
catch (err) {
|
|
593
|
+
// Best-effort: a hub outage shouldn't permanently break introspection.
|
|
594
|
+
warn?.({ err: err instanceof Error ? err.message : String(err) }, "introspection-source: self-suppression check failed (continuing)");
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
else if (cfg.lastIntrospectionRejected) {
|
|
598
|
+
// Defensive: cooldown got cleared but the rejected flag persisted.
|
|
599
|
+
// Don't trust it without a cohort to back it up.
|
|
600
|
+
await updateConfig(projectRoot, { lastIntrospectionRejected: null });
|
|
601
|
+
}
|
|
602
|
+
// After the pre-check, the flag may still be set (skipped the cohort
|
|
603
|
+
// sync above due to no lastIntrospectionAt). Honour it.
|
|
604
|
+
const cfg2 = getConfig(projectRoot);
|
|
605
|
+
if (cfg2.lastIntrospectionRejected === true) {
|
|
606
|
+
info?.({ projectRoot }, "introspection-source: lastIntrospectionRejected=true — skipping");
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
// ── Step 2: cooldown gate ────────────────────────────────────────
|
|
610
|
+
const cooldownHours = cfg2.introspectionCooldownHours ?? DEFAULT_INTROSPECTION_COOLDOWN_HOURS;
|
|
611
|
+
const cooldownMs = Math.max(0, cooldownHours) * 3600_000;
|
|
612
|
+
if (cfg2.lastIntrospectionAt && cooldownMs > 0) {
|
|
613
|
+
// N2 hotfix: same Date.getTime() consistency as the cohort filter.
|
|
614
|
+
const sinceMs = clock().getTime() - new Date(cfg2.lastIntrospectionAt).getTime();
|
|
615
|
+
if (sinceMs < cooldownMs) {
|
|
616
|
+
info?.({ sinceMs, cooldownMs, lastIntrospectionAt: cfg2.lastIntrospectionAt }, "introspection-source: cooldown not elapsed — skipping");
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// ── Step 3: spawn the introspection swarm (fire-and-forget) ──────
|
|
621
|
+
const swarmId = generateSwarmId();
|
|
622
|
+
// Pre-create the output directory in projectRoot so a fallback (no
|
|
623
|
+
// workspace) write succeeds without queen having to mkdir herself.
|
|
624
|
+
// With workspace isolation the queen's cwd will be workspace.cwd
|
|
625
|
+
// and she creates the directory there as needed; the projectRoot
|
|
626
|
+
// pre-mkdir is harmless in that case.
|
|
627
|
+
const fallbackOutputPath = resolveOutputPath(projectRoot, swarmId);
|
|
628
|
+
try {
|
|
629
|
+
mkdirSync(dirname(fallbackOutputPath), { recursive: true });
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
/* best-effort */
|
|
633
|
+
}
|
|
634
|
+
// PR-B Suspect B fix: prompt embeds a RELATIVE path. Queen's cwd is
|
|
635
|
+
// workspace.cwd (set by spawnRole → sessionAdapter.createSession with
|
|
636
|
+
// `workDir: swarm.workspace?.cwd ?? swarm.workDir`), so a relative
|
|
637
|
+
// write lands inside the workspace. Spawner reads back via
|
|
638
|
+
// `resolveActualOutputPath` which checks workspace.cwd first.
|
|
639
|
+
const taskPrompt = buildIntrospectionTaskPrompt(projectRoot, relativeOutputPath(swarmId));
|
|
640
|
+
// We await coordinator.create — it's the *registration* call (fast)
|
|
641
|
+
// and we need to know it succeeded before persisting the pending
|
|
642
|
+
// breadcrumb. The swarm itself runs in the background after create()
|
|
643
|
+
// returns; this is the fire-and-forget edge.
|
|
644
|
+
try {
|
|
645
|
+
await swarmCoordinator.create(swarmId, {
|
|
646
|
+
workDir: projectRoot,
|
|
647
|
+
templateName: introspectionTemplateName,
|
|
648
|
+
task: taskPrompt,
|
|
649
|
+
kind: "always-on-introspection",
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
catch (err) {
|
|
653
|
+
warn?.({ err: err instanceof Error ? err.message : String(err), swarmId }, "introspection-source: coordinator.create failed — bumping cooldown to avoid retry storm");
|
|
654
|
+
await updateConfig(projectRoot, {
|
|
655
|
+
lastIntrospectionAt: clock().toISOString(),
|
|
656
|
+
lastIntrospectionRejected: false,
|
|
657
|
+
});
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
// ── Step 4: persist pending breadcrumb + cooldown ────────────────
|
|
661
|
+
// This is the entire side-effect of a spawn cycle. The ingest cycle
|
|
662
|
+
// (next pick after swarm reaches terminal state) handles the rest.
|
|
663
|
+
//
|
|
664
|
+
// PR-B follow-up — capture the workspace.cwd assigned by
|
|
665
|
+
// WorkspaceManager.create so `processPendingSwarm` can still resolve
|
|
666
|
+
// the candidates file after `swarm_complete` deletes the swarm from
|
|
667
|
+
// the registry (B2 path). `getSwarm()` is the only reliable way to
|
|
668
|
+
// read workspace.cwd post-create; we snapshot it here and let the
|
|
669
|
+
// persisted value carry through restart + post-delete reads.
|
|
670
|
+
const spawned = swarmCoordinator.getSwarm(swarmId);
|
|
671
|
+
const workspaceCwd = spawned?.workspace?.cwd;
|
|
672
|
+
await updateConfig(projectRoot, {
|
|
673
|
+
lastIntrospectionAt: clock().toISOString(),
|
|
674
|
+
lastIntrospectionRejected: false,
|
|
675
|
+
pendingIntrospectionSwarmId: swarmId,
|
|
676
|
+
...(workspaceCwd ? { pendingIntrospectionWorkspaceCwd: workspaceCwd } : {}),
|
|
677
|
+
});
|
|
678
|
+
info?.({ swarmId, projectRoot, templateName: introspectionTemplateName }, "introspection-source: swarm spawned (fire-and-forget) — ingest on next tick after terminal");
|
|
679
|
+
return null;
|
|
680
|
+
},
|
|
681
|
+
// Source-driven flow — runner never claims an introspection task because
|
|
682
|
+
// pick() always returns null. These hooks satisfy the interface but are
|
|
683
|
+
// no-ops by design.
|
|
684
|
+
async onClaim() {
|
|
685
|
+
/* no-op */
|
|
686
|
+
},
|
|
687
|
+
async onSuccess() {
|
|
688
|
+
/* no-op */
|
|
689
|
+
},
|
|
690
|
+
async onFailure() {
|
|
691
|
+
/* no-op */
|
|
692
|
+
},
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
//# sourceMappingURL=introspection-source.js.map
|