@cohortapp/agent-sdk 2.17.0 → 2.18.4
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/.claude/settings.json +18 -0
- package/.env.example +18 -5
- package/README.md +1 -0
- package/bin/maestro.mjs +62 -0
- package/docs/guides/billing-console-keys.md +60 -0
- package/docs/guides/front-door-session.md +54 -9
- package/docs/guides/mac-mini.md +20 -25
- package/docs/guides/setup-wizard.md +1 -1
- package/docs/runbooks/fleet-rollout.md +156 -0
- package/docs/runbooks/mac-mini-bootstrap.md +12 -14
- package/lib/action-executor.js +19 -3
- package/lib/budget-guard.mjs +279 -3
- package/lib/channels/base-adapter.mjs +3 -1
- package/lib/channels/contract.mjs +2 -1
- package/lib/channels/inbox-item.mjs +8 -0
- package/lib/claude-bin.mjs +5 -6
- package/lib/cli/doctor-checks.mjs +141 -10
- package/lib/cli/global-setup-extras.mjs +5 -1
- package/lib/cli/inbox.mjs +100 -15
- package/lib/cli/seat-auth.mjs +463 -0
- package/lib/cli/session.mjs +80 -12
- package/lib/collective/capture.mjs +8 -6
- package/lib/collective/global-config.mjs +63 -1
- package/lib/collective/presence.mjs +142 -5
- package/lib/comms/send-gate.mjs +559 -1
- package/lib/diagnostics/alerts.mjs +49 -0
- package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
- package/lib/engine/agents/definitions.mjs +343 -0
- package/lib/engine/agents/persist.mjs +275 -0
- package/lib/engine/agents/runtime.mjs +748 -0
- package/lib/engine/agents/usage.mjs +95 -0
- package/lib/engine/auth-status.mjs +139 -0
- package/lib/engine/budget.mjs +194 -0
- package/lib/engine/cli.mjs +1204 -0
- package/lib/engine/commands/index.mjs +269 -0
- package/lib/engine/context/budget.mjs +219 -0
- package/lib/engine/context/cache.mjs +125 -0
- package/lib/engine/context/child-env.mjs +215 -0
- package/lib/engine/context/compaction.mjs +342 -0
- package/lib/engine/context/images.mjs +90 -0
- package/lib/engine/context/instructions.mjs +327 -0
- package/lib/engine/context/lazy-instructions.mjs +169 -0
- package/lib/engine/context/manager.mjs +182 -0
- package/lib/engine/context/real-path.mjs +91 -0
- package/lib/engine/context/secret-values.mjs +163 -0
- package/lib/engine/context/settings.mjs +274 -0
- package/lib/engine/context/stream-input.mjs +159 -0
- package/lib/engine/guard.mjs +152 -0
- package/lib/engine/hooks.mjs +713 -0
- package/lib/engine/loop.mjs +560 -0
- package/lib/engine/mcp/client.mjs +254 -0
- package/lib/engine/mcp/config.mjs +301 -0
- package/lib/engine/mcp/http.mjs +201 -0
- package/lib/engine/mcp/index.mjs +146 -0
- package/lib/engine/mcp/jsonrpc.mjs +147 -0
- package/lib/engine/mcp/naming.mjs +66 -0
- package/lib/engine/mcp/resources.mjs +89 -0
- package/lib/engine/mcp/results.mjs +133 -0
- package/lib/engine/mcp/stdio.mjs +137 -0
- package/lib/engine/mcp/supervisor.mjs +116 -0
- package/lib/engine/messages.mjs +104 -0
- package/lib/engine/output/json.mjs +164 -0
- package/lib/engine/output/stream-json.mjs +266 -0
- package/lib/engine/permissions.mjs +845 -0
- package/lib/engine/process-identity.mjs +164 -0
- package/lib/engine/process-tree.mjs +551 -0
- package/lib/engine/prompt.mjs +60 -0
- package/lib/engine/session/store.mjs +299 -0
- package/lib/engine/session-runtime/args.mjs +97 -0
- package/lib/engine/session-runtime/host.mjs +143 -0
- package/lib/engine/session-runtime/inbox.mjs +122 -0
- package/lib/engine/session-runtime/notifications.mjs +129 -0
- package/lib/engine/session-runtime/registry.mjs +328 -0
- package/lib/engine/session-runtime/runner.mjs +344 -0
- package/lib/engine/session-runtime/socket.mjs +212 -0
- package/lib/engine/session-runtime/wakeup.mjs +115 -0
- package/lib/engine/skills/index.mjs +321 -0
- package/lib/engine/tools/bash-background.mjs +533 -0
- package/lib/engine/tools/bash.mjs +216 -0
- package/lib/engine/tools/edit.mjs +97 -0
- package/lib/engine/tools/glob.mjs +81 -0
- package/lib/engine/tools/grep.mjs +224 -0
- package/lib/engine/tools/index.mjs +84 -0
- package/lib/engine/tools/list-agents.mjs +32 -0
- package/lib/engine/tools/ls.mjs +127 -0
- package/lib/engine/tools/monitor.mjs +82 -0
- package/lib/engine/tools/notebook-edit.mjs +218 -0
- package/lib/engine/tools/read.mjs +103 -0
- package/lib/engine/tools/schedule-wakeup.mjs +45 -0
- package/lib/engine/tools/schema.mjs +144 -0
- package/lib/engine/tools/send-message.mjs +77 -0
- package/lib/engine/tools/session.mjs +70 -0
- package/lib/engine/tools/todo.mjs +144 -0
- package/lib/engine/tools/toolsearch.mjs +217 -0
- package/lib/engine/tools/walk.mjs +193 -0
- package/lib/engine/tools/web-switch.mjs +31 -0
- package/lib/engine/tools/webfetch-html.mjs +387 -0
- package/lib/engine/tools/webfetch-net.mjs +340 -0
- package/lib/engine/tools/webfetch.mjs +198 -0
- package/lib/engine/tools/websearch.mjs +91 -0
- package/lib/engine/tools/workflow.mjs +95 -0
- package/lib/engine/tools/write.mjs +76 -0
- package/lib/engine/tui/line-editor.mjs +137 -0
- package/lib/engine/tui/render.mjs +86 -0
- package/lib/engine/tui/tui.mjs +274 -0
- package/lib/engine/wire/anthropic-messages.mjs +263 -0
- package/lib/engine/wire/effort.mjs +36 -0
- package/lib/engine/wire/errors.mjs +496 -0
- package/lib/engine/wire/http.mjs +441 -0
- package/lib/engine/wire/index.mjs +76 -0
- package/lib/engine/wire/openai-chat.mjs +332 -0
- package/lib/engine/wire/prompt-cache.mjs +79 -0
- package/lib/engine/wire/search.mjs +140 -0
- package/lib/engine/wire/sse.mjs +114 -0
- package/lib/engine/wire/stall.mjs +349 -0
- package/lib/engine/wire/token-provider.mjs +175 -0
- package/lib/engine/wire/usage.mjs +192 -0
- package/lib/engine/workflow/host.mjs +524 -0
- package/lib/engine/workflow/journal.mjs +188 -0
- package/lib/engine/workflow/json-schema.mjs +171 -0
- package/lib/engine/workflow/meta.mjs +329 -0
- package/lib/engine/workflow/notifications.mjs +52 -0
- package/lib/engine/workflow/runtime.mjs +447 -0
- package/lib/engine/workflow/sandbox.mjs +534 -0
- package/lib/engine/workflow/worker.mjs +141 -0
- package/lib/engine/workflow/worktree.mjs +74 -0
- package/lib/execution/disposition.mjs +1 -1
- package/lib/execution/intake.mjs +10 -0
- package/lib/execution/surface-policy.mjs +15 -0
- package/lib/learning/curator.mjs +8 -6
- package/lib/learning/reflect.mjs +8 -6
- package/lib/model-router/catalog/cohort.yaml +137 -0
- package/lib/model-router/catalog.mjs +118 -1
- package/lib/model-router/failover.mjs +67 -16
- package/lib/model-router/llm-task.mjs +39 -3
- package/lib/model-router/resolve.mjs +89 -3
- package/lib/model-router/spawn.mjs +46 -47
- package/lib/model-router/taxonomy.mjs +126 -4
- package/lib/org/cost-sync.mjs +141 -11
- package/lib/org/inbound/broadcast.mjs +289 -0
- package/lib/org/inbound/collective.mjs +375 -0
- package/lib/org/inbound/directedness.mjs +96 -8
- package/lib/org/inbound/facts.mjs +78 -2
- package/lib/org/inbound/project.mjs +22 -0
- package/lib/org/inbound/surfaces.mjs +14 -0
- package/lib/org/llm-token.mjs +879 -0
- package/lib/org/mesh.mjs +61 -0
- package/lib/org/messaging.mjs +3 -1
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +15 -0
- package/lib/org/quota.mjs +520 -0
- package/lib/org/tool-surface.mjs +104 -16
- package/lib/org/ui-parity.mjs +16 -1
- package/lib/org/work-ledger.mjs +37 -6
- package/lib/rate-guard.mjs +114 -1
- package/lib/resource-governor.mjs +41 -6
- package/lib/runtime/adapter.mjs +823 -0
- package/lib/runtime/child-env.mjs +191 -0
- package/lib/runtime/legacy-shell-guard.mjs +97 -0
- package/lib/runtime/seat-engine.mjs +162 -0
- package/lib/session/ask-ledger.mjs +271 -0
- package/lib/session/current-work.mjs +676 -0
- package/lib/session/feed-core.mjs +40 -3
- package/lib/session/launch-args.mjs +56 -4
- package/lib/session/status-summary.mjs +26 -9
- package/lib/session/upgrade-notice.mjs +42 -0
- package/lib/setup/claude-probe.mjs +117 -13
- package/lib/setup/enrich.mjs +13 -10
- package/lib/setup/sections/model.mjs +39 -13
- package/lib/telemetry/collect.mjs +208 -9
- package/lib/upgrade/ignored-drift.mjs +105 -0
- package/lib/voice/post-call-brief.mjs +30 -17
- package/package.json +13 -3
- package/plugins/maestro-skills/skills/board-work.md +5 -0
- package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
- package/plugins/maestro-skills/skills/main-session.md +18 -7
- package/scripts/ci/check-tarball-fidelity.mjs +126 -2
- package/scripts/ci/run-tests.mjs +47 -19
- package/scripts/cohort-llm/api-key-helper.mjs +92 -0
- package/scripts/collective/hook-runner.mjs +29 -2
- package/scripts/continuous-monitor.sh +13 -0
- package/scripts/cost/track-claude-usage.mjs +15 -0
- package/scripts/daemon/agent-daemon.mjs +408 -20
- package/scripts/daemon/assurance.mjs +48 -12
- package/scripts/daemon/cadence-consumer.mjs +218 -68
- package/scripts/daemon/cadence-handlers.mjs +73 -4
- package/scripts/daemon/classifier.mjs +75 -26
- package/scripts/daemon/context-compiler.mjs +51 -37
- package/scripts/daemon/deliver.mjs +30 -1
- package/scripts/daemon/dispatcher.mjs +595 -149
- package/scripts/daemon/health.mjs +14 -1
- package/scripts/daemon/maestro-daemon.mjs +11 -0
- package/scripts/daemon/prompt-builder.mjs +24 -0
- package/scripts/daemon/responder.mjs +246 -79
- package/scripts/daemon/sdk-version.mjs +98 -16
- package/scripts/eval/probe-gateway.mjs +635 -0
- package/scripts/eval/replay/extract.mjs +270 -0
- package/scripts/eval/replay/grade.mjs +260 -0
- package/scripts/eval/replay/lib/config.mjs +50 -0
- package/scripts/eval/replay/lib/effects.mjs +65 -0
- package/scripts/eval/replay/lib/fixture.mjs +188 -0
- package/scripts/eval/replay/lib/judge.mjs +72 -0
- package/scripts/eval/replay/lib/redact.mjs +136 -0
- package/scripts/eval/replay/lib/sandbox.mjs +170 -0
- package/scripts/eval/replay/lib/schema-check.mjs +63 -0
- package/scripts/eval/replay/lib/transcript.mjs +76 -0
- package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
- package/scripts/eval/replay/report.mjs +185 -0
- package/scripts/eval/replay/run.mjs +404 -0
- package/scripts/fleet/rollout.mjs +1094 -0
- package/scripts/hooks/pre-send-audit.sh +36 -245
- package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
- package/scripts/hooks/validate-state-yaml.sh +190 -0
- package/scripts/huddle/huddle-llm.mjs +361 -0
- package/scripts/huddle/huddle-server.mjs +46 -121
- package/scripts/local-triggers/autoupdate.sh +448 -78
- package/scripts/local-triggers/run-trigger.sh +13 -0
- package/scripts/maintenance/pin-integrity.mjs +364 -0
- package/scripts/poll-slack-events.sh +41 -9
- package/scripts/poller/slack-socket-mode.mjs +28 -3
- package/scripts/session/supervisor.mjs +80 -13
- package/scripts/spawn-session.sh +13 -0
- package/bin/maestro.test.mjs +0 -1574
- package/lib/action-executor.test.mjs +0 -871
- package/lib/archetype.test.mjs +0 -132
- package/lib/assurance/plan-note.test.mjs +0 -234
- package/lib/assurance/room-budget.test.mjs +0 -486
- package/lib/assurance/tier.test.mjs +0 -174
- package/lib/autonomy.test.mjs +0 -66
- package/lib/backlog.test.mjs +0 -302
- package/lib/backup/policy.test.mjs +0 -305
- package/lib/budget-escalate.test.mjs +0 -232
- package/lib/budget-guard.envelope.test.mjs +0 -476
- package/lib/budget-guard.test.mjs +0 -427
- package/lib/cadence-bus-requeue.test.mjs +0 -83
- package/lib/cadence-bus-schedule.test.mjs +0 -194
- package/lib/cadence-bus.test.mjs +0 -720
- package/lib/cadences.test.mjs +0 -230
- package/lib/capability/inventory.test.mjs +0 -232
- package/lib/capability.test.mjs +0 -78
- package/lib/channels/base-adapter.test.mjs +0 -590
- package/lib/channels/channels.test.mjs +0 -371
- package/lib/channels/contract.test.mjs +0 -162
- package/lib/channels/inbox-item.test.mjs +0 -368
- package/lib/channels/orgmail/adapter.test.mjs +0 -448
- package/lib/channels/pairing.test.mjs +0 -270
- package/lib/channels/repeat-suppressor.test.mjs +0 -134
- package/lib/channels/slack-adapter.test.mjs +0 -212
- package/lib/channels/telegram-adapter.test.mjs +0 -306
- package/lib/channels/voice/adapter.test.mjs +0 -278
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
- package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
- package/lib/charter.test.mjs +0 -89
- package/lib/claude-bin.test.mjs +0 -131
- package/lib/cli/board.test.mjs +0 -227
- package/lib/cli/design.test.mjs +0 -270
- package/lib/cli/doctor-checks.test.mjs +0 -336
- package/lib/cli/global-setup-extras.test.mjs +0 -462
- package/lib/cli/inbox.test.mjs +0 -230
- package/lib/cli/session-ack.test.mjs +0 -63
- package/lib/cli/session.test.mjs +0 -613
- package/lib/collective/capture.test.mjs +0 -121
- package/lib/collective/cards.test.mjs +0 -114
- package/lib/collective/config.test.mjs +0 -123
- package/lib/collective/global-config.test.mjs +0 -220
- package/lib/collective/global-skills.test.mjs +0 -126
- package/lib/collective/presence.test.mjs +0 -95
- package/lib/collective/recall.test.mjs +0 -116
- package/lib/collective/vendor-skills.test.mjs +0 -306
- package/lib/comms/send-gate.test.mjs +0 -770
- package/lib/comms.test.mjs +0 -41
- package/lib/context/budget.test.mjs +0 -252
- package/lib/context/history-scope.test.mjs +0 -79
- package/lib/cost/ledger-row.test.mjs +0 -183
- package/lib/design/design-md.test.mjs +0 -318
- package/lib/design/fixtures/DESIGN.golden.md +0 -238
- package/lib/design/fixtures/PRODUCT.golden.md +0 -67
- package/lib/design/fixtures/foundation.json +0 -133
- package/lib/design/refresh-gate.test.mjs +0 -144
- package/lib/design/write.test.mjs +0 -241
- package/lib/diagnostics/alerts.test.mjs +0 -318
- package/lib/diagnostics/backup-freshness.test.mjs +0 -185
- package/lib/diagnostics/counters.test.mjs +0 -206
- package/lib/diagnostics/events.test.mjs +0 -290
- package/lib/diagnostics/otel.test.mjs +0 -196
- package/lib/diagnostics/trace.test.mjs +0 -251
- package/lib/env-compat.test.mjs +0 -104
- package/lib/execution/disposition.test.mjs +0 -553
- package/lib/execution/drive.test.mjs +0 -270
- package/lib/execution/effects.test.mjs +0 -344
- package/lib/execution/intake.test.mjs +0 -389
- package/lib/execution/journal.test.mjs +0 -261
- package/lib/execution/match.test.mjs +0 -235
- package/lib/execution/pipeline.test.mjs +0 -392
- package/lib/execution/route.test.mjs +0 -186
- package/lib/execution/surface-policy.test.mjs +0 -162
- package/lib/fs-atomic.test.mjs +0 -72
- package/lib/fs-ownership.test.mjs +0 -158
- package/lib/goals/admission.test.mjs +0 -164
- package/lib/goals/classify.test.mjs +0 -167
- package/lib/goals/collaborate.test.mjs +0 -336
- package/lib/goals/gaps.test.mjs +0 -284
- package/lib/goals/loop.test.mjs +0 -845
- package/lib/hooks/bus.test.mjs +0 -387
- package/lib/identity/persona.test.mjs +0 -142
- package/lib/kpi-sensors.test.mjs +0 -278
- package/lib/kpi.test.mjs +0 -244
- package/lib/learning/config.test.mjs +0 -75
- package/lib/learning/counters.test.mjs +0 -69
- package/lib/learning/curator-consolidate.test.mjs +0 -238
- package/lib/learning/curator.test.mjs +0 -106
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.test.mjs +0 -125
- package/lib/learning/skill-writer.test.mjs +0 -210
- package/lib/mandate/audit.test.mjs +0 -195
- package/lib/mandate/contract.test.mjs +0 -185
- package/lib/mandate/derive.test.mjs +0 -274
- package/lib/mandate/model.test.mjs +0 -164
- package/lib/mandate/refresh.test.mjs +0 -389
- package/lib/mcp/server.test.mjs +0 -426
- package/lib/model-router/auth-profiles.test.mjs +0 -580
- package/lib/model-router/catalog.test.mjs +0 -385
- package/lib/model-router/economics.test.mjs +0 -438
- package/lib/model-router/failover.test.mjs +0 -439
- package/lib/model-router/health.test.mjs +0 -338
- package/lib/model-router/integration-coverage.test.mjs +0 -831
- package/lib/model-router/integration.test.mjs +0 -564
- package/lib/model-router/ledger.test.mjs +0 -415
- package/lib/model-router/llm-task.test.mjs +0 -392
- package/lib/model-router/org-credentials.test.mjs +0 -265
- package/lib/model-router/pricing-refresh.test.mjs +0 -286
- package/lib/model-router/reconcile.test.mjs +0 -316
- package/lib/model-router/repair.test.mjs +0 -180
- package/lib/model-router/spawn.test.mjs +0 -446
- package/lib/model-router/taxonomy.test.mjs +0 -410
- package/lib/model-router.test.mjs +0 -1207
- package/lib/org/activity.test.mjs +0 -134
- package/lib/org/approvals.test.mjs +0 -216
- package/lib/org/awareness.test.mjs +0 -159
- package/lib/org/board-mine-cache.test.mjs +0 -53
- package/lib/org/board.test.mjs +0 -187
- package/lib/org/bootstrap-context.test.mjs +0 -153
- package/lib/org/client.test.mjs +0 -1206
- package/lib/org/cohort-client.test.mjs +0 -126
- package/lib/org/cost-sync.test.mjs +0 -153
- package/lib/org/doctor.test.mjs +0 -346
- package/lib/org/engagement-ledger.test.mjs +0 -112
- package/lib/org/engagement.test.mjs +0 -739
- package/lib/org/handoff.test.mjs +0 -269
- package/lib/org/inbound/directedness.test.mjs +0 -668
- package/lib/org/inbound/facts.test.mjs +0 -471
- package/lib/org/inbound/hydrate.test.mjs +0 -908
- package/lib/org/inbound/index.test.mjs +0 -429
- package/lib/org/inbound/project.test.mjs +0 -287
- package/lib/org/integration-tools.test.mjs +0 -160
- package/lib/org/keys.test.mjs +0 -92
- package/lib/org/knowledge.test.mjs +0 -326
- package/lib/org/leases.test.mjs +0 -235
- package/lib/org/mesh-directives.test.mjs +0 -110
- package/lib/org/mesh-integration.test.mjs +0 -127
- package/lib/org/mesh.test.mjs +0 -400
- package/lib/org/messaging.test.mjs +0 -471
- package/lib/org/param-contract.test.mjs +0 -477
- package/lib/org/policy.test.mjs +0 -237
- package/lib/org/protocol.checksum.test.mjs +0 -90
- package/lib/org/protocol.test.mjs +0 -323
- package/lib/org/push.test.mjs +0 -792
- package/lib/org/registry.test.mjs +0 -100
- package/lib/org/resource-tools.test.mjs +0 -361
- package/lib/org/tool-access.test.mjs +0 -144
- package/lib/org/tool-surface-integration.test.mjs +0 -120
- package/lib/org/tool-surface.test.mjs +0 -1268
- package/lib/org/typing.test.mjs +0 -291
- package/lib/org/ui-parity.test.mjs +0 -560
- package/lib/org/verify.test.mjs +0 -194
- package/lib/org/work-ledger.test.mjs +0 -273
- package/lib/plan/adoption-e2e.test.mjs +0 -366
- package/lib/plan/budget-enforcement.test.mjs +0 -400
- package/lib/plan/compile.test.mjs +0 -382
- package/lib/plan/emit.test.mjs +0 -269
- package/lib/plan/explain.test.mjs +0 -188
- package/lib/prompts/parallelism.test.mjs +0 -177
- package/lib/rag/rag.test.mjs +0 -505
- package/lib/rate-guard.test.mjs +0 -272
- package/lib/reactive-gate.test.mjs +0 -57
- package/lib/render.test.mjs +0 -68
- package/lib/resource-governor.test.mjs +0 -488
- package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
- package/lib/scheduling/jitter.test.mjs +0 -140
- package/lib/secrets/broker.test.mjs +0 -280
- package/lib/secrets/providers.test.mjs +0 -274
- package/lib/security/audit-engine.test.mjs +0 -424
- package/lib/security/coerce-args.test.mjs +0 -281
- package/lib/security/dangerous-tools.test.mjs +0 -68
- package/lib/security/external-content.test.mjs +0 -84
- package/lib/security/redact.test.mjs +0 -441
- package/lib/security/secret-equal.test.mjs +0 -55
- package/lib/session/config.test.mjs +0 -92
- package/lib/session/feed-core.test.mjs +0 -198
- package/lib/session/first-run.test.mjs +0 -121
- package/lib/session/frontdoor.test.mjs +0 -205
- package/lib/session/handoffs.test.mjs +0 -183
- package/lib/session/identity.test.mjs +0 -180
- package/lib/session/inbox-claims.test.mjs +0 -286
- package/lib/session/launch-args.test.mjs +0 -157
- package/lib/session/liveness.test.mjs +0 -100
- package/lib/session/status-summary.test.mjs +0 -118
- package/lib/session-permissions.test.mjs +0 -120
- package/lib/setup/claude-probe.test.mjs +0 -187
- package/lib/setup/completeness.test.mjs +0 -110
- package/lib/setup/context-pack.test.mjs +0 -89
- package/lib/setup/enrich.test.mjs +0 -115
- package/lib/setup/enroll-from-cohort.test.mjs +0 -300
- package/lib/setup/integration.test.mjs +0 -162
- package/lib/setup/io.test.mjs +0 -77
- package/lib/setup/runner.test.mjs +0 -132
- package/lib/setup/sections/identity.test.mjs +0 -234
- package/lib/setup/sections/inventory.test.mjs +0 -198
- package/lib/setup/sections/learning.test.mjs +0 -81
- package/lib/setup/sections/mandate.test.mjs +0 -388
- package/lib/setup/sections/messaging.test.mjs +0 -127
- package/lib/setup/sections/model.test.mjs +0 -240
- package/lib/setup/sections/org.test.mjs +0 -346
- package/lib/setup/sections/orgmail.test.mjs +0 -118
- package/lib/setup/sections/recovery.test.mjs +0 -98
- package/lib/setup/sections/subagents.test.mjs +0 -429
- package/lib/setup/sections/verify.test.mjs +0 -175
- package/lib/setup/sot.test.mjs +0 -81
- package/lib/setup/state.test.mjs +0 -115
- package/lib/singleton.test.mjs +0 -151
- package/lib/subagents/cli.test.mjs +0 -389
- package/lib/subagents/client.test.mjs +0 -309
- package/lib/subagents/gap.test.mjs +0 -234
- package/lib/subagents/lock.test.mjs +0 -248
- package/lib/subagents/manifest.test.mjs +0 -175
- package/lib/subagents/refs.test.mjs +0 -204
- package/lib/subagents/resolve.test.mjs +0 -422
- package/lib/subagents/schema.test.mjs +0 -328
- package/lib/telemetry/alerts.test.mjs +0 -109
- package/lib/telemetry/collect.test.mjs +0 -1274
- package/lib/tool-definitions-integration.test.mjs +0 -83
- package/lib/tool-definitions.test.mjs +0 -437
- package/lib/upgrade/global-refresh.test.mjs +0 -65
- package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
- package/lib/upgrade/post-steps.test.mjs +0 -200
- package/lib/upgrade/verify.test.mjs +0 -164
- package/lib/util/fetch-timeout.test.mjs +0 -202
- package/lib/util/reconnect.test.mjs +0 -369
- package/lib/util/unhandled.test.mjs +0 -216
- package/lib/voice/outbound.test.mjs +0 -69
- package/lib/voice/session-rotation.test.mjs +0 -114
- package/lib/voice/stt.test.mjs +0 -226
- package/lib/voice/voice.test.mjs +0 -990
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
- package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
- package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
- package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
- package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
- package/scripts/ci/check-skill-packs.test.mjs +0 -495
- package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
- package/scripts/ci/check.test.mjs +0 -194
- package/scripts/ci/conformance-org-api.test.mjs +0 -425
- package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
- package/scripts/collective/hook-runner.test.mjs +0 -173
- package/scripts/cost/fleet-digest.test.mjs +0 -207
- package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
- package/scripts/cost/track-claude-usage.test.mjs +0 -148
- package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
- package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
- package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
- package/scripts/daemon/agent-daemon.test.mjs +0 -995
- package/scripts/daemon/assurance-e2e.test.mjs +0 -613
- package/scripts/daemon/assurance.test.mjs +0 -1791
- package/scripts/daemon/board-mirror.test.mjs +0 -165
- package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
- package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
- package/scripts/daemon/cadence-consumer.test.mjs +0 -776
- package/scripts/daemon/cadence-handlers.test.mjs +0 -837
- package/scripts/daemon/classifier-identity.test.mjs +0 -137
- package/scripts/daemon/classifier.test.mjs +0 -266
- package/scripts/daemon/classify-kind.test.mjs +0 -40
- package/scripts/daemon/context-compiler.test.mjs +0 -406
- package/scripts/daemon/deliver.test.mjs +0 -564
- package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
- package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
- package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
- package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
- package/scripts/daemon/execution-ladder.test.mjs +0 -470
- package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
- package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
- package/scripts/daemon/inbox-deferral.test.mjs +0 -336
- package/scripts/daemon/inbox-wake.test.mjs +0 -199
- package/scripts/daemon/integration.test.mjs +0 -149
- package/scripts/daemon/lib/self-echo.test.mjs +0 -153
- package/scripts/daemon/lib/session-router.test.mjs +0 -554
- package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
- package/scripts/daemon/prompt-builder.test.mjs +0 -556
- package/scripts/daemon/responder-cost.test.mjs +0 -68
- package/scripts/daemon/responder-history.test.mjs +0 -221
- package/scripts/daemon/sdk-version.test.mjs +0 -31
- package/scripts/daemon/session-lock.test.mjs +0 -252
- package/scripts/daemon/session-outcomes.test.mjs +0 -533
- package/scripts/daemon/typing-registry.test.mjs +0 -102
- package/scripts/hooks/pre-send-audit.test.mjs +0 -354
- package/scripts/huddle/huddle-prompt.test.mjs +0 -176
- package/scripts/local-triggers/autoupdate.test.mjs +0 -518
- package/scripts/local-triggers/generate-plists.test.mjs +0 -456
- package/scripts/media-generation/brand-clause.test.mjs +0 -135
- package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
- package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
- package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
- package/scripts/poller/slack-socket-mode.test.mjs +0 -805
- package/scripts/poller-launchd/install.test.mjs +0 -243
- package/scripts/restore-from-backup.test.mjs +0 -181
- package/scripts/session/feed.test.mjs +0 -196
- package/scripts/session/supervisor-sh.test.mjs +0 -218
- package/scripts/session/supervisor.test.mjs +0 -482
- package/scripts/setup/configure-macos.test.mjs +0 -306
- package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
- package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
- package/scripts/setup/generate-capability.test.mjs +0 -134
- package/scripts/setup/init-agent.test.mjs +0 -370
- package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
- package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
- package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lock.test.mjs — the registry's on-disk state (§3, §4): the lock (provenance),
|
|
3
|
-
* the cache (the offline fourth layer), the outbox (offline writes) and the
|
|
4
|
-
* headless gaps report.
|
|
5
|
-
* Run: node --test lib/subagents/lock.test.mjs
|
|
6
|
-
*/
|
|
7
|
-
"use strict";
|
|
8
|
-
|
|
9
|
-
import { test } from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
-
import { tmpdir } from "node:os";
|
|
13
|
-
import { dirname, join } from "node:path";
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
CACHE_REL,
|
|
17
|
-
GAPS_REL,
|
|
18
|
-
LOCK_REL,
|
|
19
|
-
OUTBOX_REL,
|
|
20
|
-
acceptDrop,
|
|
21
|
-
cacheDir,
|
|
22
|
-
cachePath,
|
|
23
|
-
emptyLock,
|
|
24
|
-
gapsPath,
|
|
25
|
-
isAcceptedDrop,
|
|
26
|
-
latestCached,
|
|
27
|
-
listOutbox,
|
|
28
|
-
lockPath,
|
|
29
|
-
mergeLock,
|
|
30
|
-
outboxDir,
|
|
31
|
-
queueOutbox,
|
|
32
|
-
readCache,
|
|
33
|
-
readGaps,
|
|
34
|
-
readLock,
|
|
35
|
-
removeOutbox,
|
|
36
|
-
writeCache,
|
|
37
|
-
writeGaps,
|
|
38
|
-
writeLock,
|
|
39
|
-
} from "./lock.mjs";
|
|
40
|
-
|
|
41
|
-
const root = () => mkdtempSync(join(tmpdir(), "subagent-lock-"));
|
|
42
|
-
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
// lock
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
test("paths: the lock is provenance (.maestro/), the rest is scratch (state/)", () => {
|
|
48
|
-
// Blowing away state/ must DEGRADE the registry (cold cache) but never lose
|
|
49
|
-
// provenance — that split is the whole reason for two directories.
|
|
50
|
-
assert.equal(LOCK_REL, join(".maestro", "subagents.lock.json"));
|
|
51
|
-
assert.equal(CACHE_REL, join("state", "subagents", "cache"));
|
|
52
|
-
assert.equal(OUTBOX_REL, join("state", "subagents", "outbox"));
|
|
53
|
-
assert.equal(GAPS_REL, join("state", "subagents", "gaps.json"));
|
|
54
|
-
const r = root();
|
|
55
|
-
assert.equal(lockPath(r), join(r, LOCK_REL));
|
|
56
|
-
assert.equal(cacheDir(r), join(r, CACHE_REL));
|
|
57
|
-
assert.equal(outboxDir(r), join(r, OUTBOX_REL));
|
|
58
|
-
assert.equal(gapsPath(r), join(r, GAPS_REL));
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("readLock: missing OR corrupt → the empty-but-valid shape, never null", () => {
|
|
62
|
-
const r = root();
|
|
63
|
-
assert.deepEqual(readLock(r), emptyLock());
|
|
64
|
-
mkdirSync(join(r, ".maestro"), { recursive: true });
|
|
65
|
-
writeFileSync(lockPath(r), "{ not json");
|
|
66
|
-
assert.deepEqual(readLock(r), emptyLock());
|
|
67
|
-
writeFileSync(lockPath(r), '"a string"');
|
|
68
|
-
assert.deepEqual(readLock(r), emptyLock());
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("readLock: coerces every field so a hand-edited lock cannot crash a consumer", () => {
|
|
72
|
-
const r = root();
|
|
73
|
-
mkdirSync(join(r, ".maestro"), { recursive: true });
|
|
74
|
-
writeFileSync(lockPath(r), JSON.stringify({
|
|
75
|
-
version: "nope",
|
|
76
|
-
rosterVersion: "7",
|
|
77
|
-
degraded: "workspace",
|
|
78
|
-
acceptedDrops: [{ ref: "x" }, "junk", null],
|
|
79
|
-
agents: { alpha: { layer: 3, version: "9", contentHash: 5, localEdits: "yes" }, bad: "not-an-object" },
|
|
80
|
-
}));
|
|
81
|
-
const lock = readLock(r);
|
|
82
|
-
assert.equal(lock.version, 1);
|
|
83
|
-
assert.equal(lock.rosterVersion, 0, "a string rosterVersion is not finite → 0");
|
|
84
|
-
assert.deepEqual(lock.degraded, []);
|
|
85
|
-
assert.deepEqual(lock.acceptedDrops, [{ ref: "x" }]);
|
|
86
|
-
assert.equal(lock.agents.alpha.layer, "3");
|
|
87
|
-
assert.equal(lock.agents.alpha.version, null);
|
|
88
|
-
assert.equal(lock.agents.alpha.contentHash, "5");
|
|
89
|
-
assert.equal(lock.agents.alpha.localEdits, true);
|
|
90
|
-
assert.equal("bad" in lock.agents, false);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("writeLock / mergeLock: per-slug merge, null deletes, arrays replace", () => {
|
|
94
|
-
const r = root();
|
|
95
|
-
assert.equal(writeLock(r, { ...emptyLock(), agents: { alpha: { layer: "sdk", fileHash: "h1" } } }), true);
|
|
96
|
-
|
|
97
|
-
const merged = mergeLock(r, { agents: { alpha: { fileHash: "h2" }, beta: { layer: "workspace" } }, rosterVersion: 5 });
|
|
98
|
-
assert.equal(merged.agents.alpha.layer, "sdk", "existing keys survive the merge");
|
|
99
|
-
assert.equal(merged.agents.alpha.fileHash, "h2");
|
|
100
|
-
assert.equal(merged.agents.beta.layer, "workspace");
|
|
101
|
-
assert.equal(merged.rosterVersion, 5);
|
|
102
|
-
assert.deepEqual(readLock(r).agents.beta.layer, "workspace", "written to disk");
|
|
103
|
-
|
|
104
|
-
const dropped = mergeLock(r, { agents: { beta: null } });
|
|
105
|
-
assert.equal("beta" in dropped.agents, false);
|
|
106
|
-
assert.equal(dropped.rosterVersion, 5, "unsupplied fields are untouched");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("mergeLock: degraded is deduped", () => {
|
|
110
|
-
const r = root();
|
|
111
|
-
const l = mergeLock(r, { degraded: ["workspace", "workspace", "assessment"] });
|
|
112
|
-
assert.deepEqual(l.degraded, ["workspace", "assessment"]);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("writeLock: fail-open (returns false) rather than throwing", () => {
|
|
116
|
-
// A failed lock write degrades the NEXT run to "everything looks locally
|
|
117
|
-
// edited" — safe. It must not abort the current one, because apply() throws
|
|
118
|
-
// are fatal to the whole wizard.
|
|
119
|
-
const r = root();
|
|
120
|
-
writeFileSync(join(r, ".maestro"), "i am a file, not a directory");
|
|
121
|
-
assert.equal(writeLock(r, emptyLock()), false);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
// accepted drops — the section NEVER edits a workflow, it records the location
|
|
126
|
-
// ---------------------------------------------------------------------------
|
|
127
|
-
|
|
128
|
-
test("acceptDrop: records ref + file:line, dedupes on the same location", () => {
|
|
129
|
-
const r = root();
|
|
130
|
-
acceptDrop(r, { ref: "ghost", file: "workflows/a.yaml", line: 12, reason: "vestigial" });
|
|
131
|
-
acceptDrop(r, { ref: "ghost", file: "workflows/a.yaml", line: 12, reason: "vestigial again" });
|
|
132
|
-
acceptDrop(r, { ref: "ghost", file: "workflows/b.yaml", line: 3 });
|
|
133
|
-
const lock = readLock(r);
|
|
134
|
-
assert.equal(lock.acceptedDrops.length, 2);
|
|
135
|
-
assert.equal(lock.acceptedDrops[0].reason, "vestigial again");
|
|
136
|
-
assert.ok(lock.acceptedDrops[0].acceptedAt);
|
|
137
|
-
assert.equal(isAcceptedDrop(lock, "ghost"), true);
|
|
138
|
-
assert.equal(isAcceptedDrop(lock, "other"), false);
|
|
139
|
-
assert.equal(isAcceptedDrop(null, "ghost"), false);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// ---------------------------------------------------------------------------
|
|
143
|
-
// cache — the offline fourth layer
|
|
144
|
-
// ---------------------------------------------------------------------------
|
|
145
|
-
|
|
146
|
-
test("writeCache / readCache: version lives in the filename so versions coexist", () => {
|
|
147
|
-
const r = root();
|
|
148
|
-
assert.equal(writeCache(r, "alpha", 1, "v1 body"), true);
|
|
149
|
-
assert.equal(writeCache(r, "alpha", 2, "v2 body"), true);
|
|
150
|
-
assert.equal(readCache(r, "alpha", 1), "v1 body");
|
|
151
|
-
assert.equal(readCache(r, "alpha", 2), "v2 body");
|
|
152
|
-
assert.equal(readCache(r, "alpha", 3), null);
|
|
153
|
-
assert.equal(cachePath(r, "alpha", 2).endsWith("alpha@2.md"), true);
|
|
154
|
-
assert.equal(cachePath(r, "alpha", null).endsWith("alpha@head.md"), true);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("latestCached: highest numeric version wins; null when nothing is cached", () => {
|
|
158
|
-
const r = root();
|
|
159
|
-
assert.equal(latestCached(r, "alpha"), null);
|
|
160
|
-
writeCache(r, "alpha", 2, "two");
|
|
161
|
-
writeCache(r, "alpha", 10, "ten");
|
|
162
|
-
writeCache(r, "alpha", 3, "three");
|
|
163
|
-
writeCache(r, "beta", 1, "beta-one");
|
|
164
|
-
const best = latestCached(r, "alpha");
|
|
165
|
-
assert.equal(best.version, 10);
|
|
166
|
-
assert.equal(best.text, "ten");
|
|
167
|
-
assert.equal(latestCached(r, "gamma"), null, "prefix match must not leak across slugs");
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// ---------------------------------------------------------------------------
|
|
171
|
-
// outbox — offline writes
|
|
172
|
-
// ---------------------------------------------------------------------------
|
|
173
|
-
|
|
174
|
-
test("queueOutbox / listOutbox: chronological, payload stored VERBATIM", () => {
|
|
175
|
-
const r = root();
|
|
176
|
-
// Verbatim matters: at replay time the local file may have changed again, and
|
|
177
|
-
// replaying a different body than the operator approved is the worst outcome.
|
|
178
|
-
const p1 = queueOutbox(r, "subagent.create", { slug: "alpha", body: "b1" });
|
|
179
|
-
const p2 = queueOutbox(r, "subagent.pin", { slug: "alpha" });
|
|
180
|
-
assert.ok(p1 && p2);
|
|
181
|
-
const list = listOutbox(r);
|
|
182
|
-
assert.equal(list.length, 2);
|
|
183
|
-
assert.deepEqual(list.map((e) => e.op).sort(), ["subagent.create", "subagent.pin"]);
|
|
184
|
-
const create = list.find((e) => e.op === "subagent.create");
|
|
185
|
-
assert.deepEqual(create.payload, { slug: "alpha", body: "b1" });
|
|
186
|
-
assert.ok(create.queuedAt);
|
|
187
|
-
assert.equal(listOutbox(r).map((e) => e.path).every(existsSync), true);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test("queueOutbox: two writes of the SAME op in the same millisecond do not collide", () => {
|
|
191
|
-
// `create` immediately followed by `pin` queues twice inside one millisecond.
|
|
192
|
-
// A timestamp-only filename made the second overwrite the first and silently
|
|
193
|
-
// lost the operator's work — the exact failure the outbox exists to prevent.
|
|
194
|
-
const r = root();
|
|
195
|
-
for (let i = 0; i < 25; i++) queueOutbox(r, "subagent.pin", { slug: `s${i}` });
|
|
196
|
-
const list = listOutbox(r);
|
|
197
|
-
assert.equal(list.length, 25);
|
|
198
|
-
assert.deepEqual(list.map((e) => e.payload.slug), Array.from({ length: 25 }, (_, i) => `s${i}`), "and replay order is still chronological");
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("listOutbox: a corrupt entry is skipped, never fatal; missing dir → []", () => {
|
|
202
|
-
const r = root();
|
|
203
|
-
assert.deepEqual(listOutbox(r), []);
|
|
204
|
-
queueOutbox(r, "subagent.pin", { slug: "alpha" });
|
|
205
|
-
writeFileSync(join(outboxDir(r), "0000-corrupt.json"), "{ nope");
|
|
206
|
-
const list = listOutbox(r);
|
|
207
|
-
assert.equal(list.length, 1);
|
|
208
|
-
assert.equal(list[0].op, "subagent.pin");
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
test("removeOutbox: removes a replayed entry, fail-open on a missing path", () => {
|
|
212
|
-
const r = root();
|
|
213
|
-
const p = queueOutbox(r, "subagent.pin", {});
|
|
214
|
-
assert.equal(removeOutbox(p), true);
|
|
215
|
-
assert.deepEqual(listOutbox(r), []);
|
|
216
|
-
assert.equal(removeOutbox(p), false);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("queueOutbox: sanitises the op so a hostile op name cannot escape the outbox", () => {
|
|
220
|
-
const r = root();
|
|
221
|
-
const p = queueOutbox(r, "subagent.pin/../../evil", {});
|
|
222
|
-
// The separators are stripped, so the file lands in the outbox directory
|
|
223
|
-
// itself — path traversal via the op name is not representable.
|
|
224
|
-
assert.equal(dirname(p), outboxDir(r));
|
|
225
|
-
assert.equal(listOutbox(r).length, 1);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// ---------------------------------------------------------------------------
|
|
229
|
-
// gaps report
|
|
230
|
-
// ---------------------------------------------------------------------------
|
|
231
|
-
|
|
232
|
-
test("writeGaps / readGaps: stamps generatedAt and round trips", () => {
|
|
233
|
-
const r = root();
|
|
234
|
-
assert.equal(readGaps(r), null);
|
|
235
|
-
assert.equal(writeGaps(r, { summary: "1 missing", counts: { missing: 1 } }), true);
|
|
236
|
-
const g = readGaps(r);
|
|
237
|
-
assert.equal(g.summary, "1 missing");
|
|
238
|
-
assert.deepEqual(g.counts, { missing: 1 });
|
|
239
|
-
assert.ok(g.generatedAt);
|
|
240
|
-
assert.equal(JSON.parse(readFileSync(gapsPath(r), "utf8")).summary, "1 missing");
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test("readGaps: fail-open on a corrupt report", () => {
|
|
244
|
-
const r = root();
|
|
245
|
-
mkdirSync(join(r, "state", "subagents"), { recursive: true });
|
|
246
|
-
writeFileSync(gapsPath(r), "{ nope");
|
|
247
|
-
assert.equal(readGaps(r), null);
|
|
248
|
-
});
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* manifest.test.mjs — layer 0's index (§2.2): the sha256 classification that
|
|
3
|
-
* makes shipped agent.md files updatable WITHOUT touching `create`'s copy list
|
|
4
|
-
* or `UPGRADE_PATHS`.
|
|
5
|
-
* Run: node --test lib/subagents/manifest.test.mjs
|
|
6
|
-
*/
|
|
7
|
-
"use strict";
|
|
8
|
-
|
|
9
|
-
import { test } from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
-
import { tmpdir } from "node:os";
|
|
13
|
-
import { dirname, join } from "node:path";
|
|
14
|
-
import { fileURLToPath } from "node:url";
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
MANIFEST_REL,
|
|
18
|
-
buildManifest,
|
|
19
|
-
isManagedByManifest,
|
|
20
|
-
listAgentDirs,
|
|
21
|
-
manifestPath,
|
|
22
|
-
manifestShas,
|
|
23
|
-
manifestSlugs,
|
|
24
|
-
readManifest,
|
|
25
|
-
readSdkAgent,
|
|
26
|
-
writeManifest,
|
|
27
|
-
} from "./manifest.mjs";
|
|
28
|
-
import { hashBody, hashFile, stringifyAgentMd } from "./schema.mjs";
|
|
29
|
-
|
|
30
|
-
const MAESTRO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
31
|
-
|
|
32
|
-
function sdkRoot() {
|
|
33
|
-
return mkdtempSync(join(tmpdir(), "subagent-manifest-"));
|
|
34
|
-
}
|
|
35
|
-
function putAgent(root, slug, over = {}, body = "# body\n\nprose\n") {
|
|
36
|
-
const fm = { name: slug, description: `${slug} does things`, model: "sonnet", tools: ["Read"], ...over };
|
|
37
|
-
const text = stringifyAgentMd(fm, body);
|
|
38
|
-
mkdirSync(join(root, "agents", slug), { recursive: true });
|
|
39
|
-
writeFileSync(join(root, "agents", slug, "agent.md"), text);
|
|
40
|
-
return text;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
|
|
45
|
-
test("listAgentDirs: an agents/<slug>/ with NO agent.md is reported empty, not ignored", () => {
|
|
46
|
-
// That empty directory is exactly what used to pass `doctor`.
|
|
47
|
-
const root = sdkRoot();
|
|
48
|
-
putAgent(root, "alpha");
|
|
49
|
-
mkdirSync(join(root, "agents", "hollow"), { recursive: true });
|
|
50
|
-
writeFileSync(join(root, "agents", "loose-file.txt"), "x");
|
|
51
|
-
const { slugs, empty } = listAgentDirs(root);
|
|
52
|
-
assert.deepEqual(slugs, ["alpha"]);
|
|
53
|
-
assert.deepEqual(empty, ["hollow"]);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("listAgentDirs: fail-open when agents/ does not exist", () => {
|
|
57
|
-
assert.deepEqual(listAgentDirs(sdkRoot()), { slugs: [], empty: [] });
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("buildManifest: one entry per agent, with BOTH hashes and the routing facts", () => {
|
|
61
|
-
const root = sdkRoot();
|
|
62
|
-
const text = putAgent(root, "alpha", { tier: "standard", functions: ["executive-operator"] }, "# alpha\n\nhi {{agent.firstName}}\n");
|
|
63
|
-
const { manifest, invalid } = buildManifest({ maestroRoot: root, sdkVersion: "9.9.9", now: new Date("2026-01-01T00:00:00Z") });
|
|
64
|
-
|
|
65
|
-
assert.deepEqual(invalid, []);
|
|
66
|
-
assert.equal(manifest.sdkVersion, "9.9.9");
|
|
67
|
-
assert.equal(manifest.generatedAt, "2026-01-01T00:00:00.000Z");
|
|
68
|
-
const e = manifest.agents.alpha;
|
|
69
|
-
assert.equal(e.sha256, hashFile(text), "sha256 is the WHOLE file (local-edit classification)");
|
|
70
|
-
assert.equal(e.bodyHash, hashBody("# alpha\n\nhi {{agent.firstName}}\n"), "bodyHash matches hq's contentHash preimage");
|
|
71
|
-
assert.notEqual(e.sha256, e.bodyHash);
|
|
72
|
-
assert.equal(e.model, "claude-sonnet-4-6", "the alias is normalised in the manifest");
|
|
73
|
-
assert.deepEqual(e.tools, ["Read"]);
|
|
74
|
-
assert.equal(e.tier, "standard");
|
|
75
|
-
assert.deepEqual(e.functions, ["executive-operator"]);
|
|
76
|
-
assert.deepEqual(e.tokens, ["agent.firstName"]);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("buildManifest: an INVALID agent.md still gets an entry (so it stays classifiable)", () => {
|
|
80
|
-
const root = sdkRoot();
|
|
81
|
-
putAgent(root, "alpha", { name: "wrong-name" });
|
|
82
|
-
const { manifest, invalid } = buildManifest({ maestroRoot: root });
|
|
83
|
-
assert.equal(invalid.length, 1);
|
|
84
|
-
assert.equal(invalid[0].slug, "alpha");
|
|
85
|
-
assert.ok(manifest.agents.alpha, "still listed — otherwise every deployed copy would freeze as a local edit");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("buildManifest: carries the PREVIOUS sha forward as history (multi-release upgrades)", () => {
|
|
89
|
-
const root = sdkRoot();
|
|
90
|
-
putAgent(root, "alpha", {}, "v1\n");
|
|
91
|
-
const first = buildManifest({ maestroRoot: root, sdkVersion: "1.0.0" }).manifest;
|
|
92
|
-
const shaV1 = first.agents.alpha.sha256;
|
|
93
|
-
|
|
94
|
-
putAgent(root, "alpha", {}, "v2\n");
|
|
95
|
-
const second = buildManifest({ maestroRoot: root, sdkVersion: "2.0.0", previous: first }).manifest;
|
|
96
|
-
assert.notEqual(second.agents.alpha.sha256, shaV1);
|
|
97
|
-
assert.deepEqual(second.agents.alpha.history, [shaV1]);
|
|
98
|
-
|
|
99
|
-
// An agent still holding the v1 bytes classifies as MANAGED, not locally edited.
|
|
100
|
-
assert.equal(manifestShas(second).has(shaV1), true);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("buildManifest: history never contains the current sha and is capped at 10", () => {
|
|
104
|
-
const root = sdkRoot();
|
|
105
|
-
let manifest = { agents: {} };
|
|
106
|
-
for (let i = 0; i < 14; i++) {
|
|
107
|
-
putAgent(root, "alpha", {}, `v${i}\n`);
|
|
108
|
-
manifest = buildManifest({ maestroRoot: root, previous: manifest }).manifest;
|
|
109
|
-
}
|
|
110
|
-
const e = manifest.agents.alpha;
|
|
111
|
-
assert.equal(e.history.length, 10);
|
|
112
|
-
assert.equal(e.history.includes(e.sha256), false);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("buildManifest: empty directories are recorded on the manifest", () => {
|
|
116
|
-
const root = sdkRoot();
|
|
117
|
-
putAgent(root, "alpha");
|
|
118
|
-
mkdirSync(join(root, "agents", "hollow"), { recursive: true });
|
|
119
|
-
assert.deepEqual(buildManifest({ maestroRoot: root }).manifest.emptyDirs, ["hollow"]);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("writeManifest / readManifest round trip, and manifestSlugs is sorted", () => {
|
|
123
|
-
const root = sdkRoot();
|
|
124
|
-
putAgent(root, "zeta");
|
|
125
|
-
putAgent(root, "alpha");
|
|
126
|
-
const { manifest } = buildManifest({ maestroRoot: root, sdkVersion: "3.2.1" });
|
|
127
|
-
const p = writeManifest(root, manifest);
|
|
128
|
-
assert.equal(p, manifestPath(root));
|
|
129
|
-
assert.equal(p.endsWith(MANIFEST_REL), true);
|
|
130
|
-
const read = readManifest(root);
|
|
131
|
-
assert.equal(read.sdkVersion, "3.2.1");
|
|
132
|
-
assert.deepEqual(manifestSlugs(read), ["alpha", "zeta"]);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test("readManifest: fail-open on a missing OR corrupt manifest — degrades SAFELY", () => {
|
|
136
|
-
// Degrading to "layer 0 has no managed files" makes every on-disk file look
|
|
137
|
-
// locally edited, which means nothing is overwritten. Safe direction.
|
|
138
|
-
const root = sdkRoot();
|
|
139
|
-
assert.deepEqual(readManifest(root), { sdkVersion: "", agents: {} });
|
|
140
|
-
mkdirSync(join(root, "agents"), { recursive: true });
|
|
141
|
-
writeFileSync(manifestPath(root), "{ not json");
|
|
142
|
-
assert.deepEqual(readManifest(root), { sdkVersion: "", agents: {} });
|
|
143
|
-
assert.deepEqual([...manifestShas(readManifest(root))], []);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test("isManagedByManifest: exact file bytes only", () => {
|
|
147
|
-
const root = sdkRoot();
|
|
148
|
-
const text = putAgent(root, "alpha");
|
|
149
|
-
const { manifest } = buildManifest({ maestroRoot: root });
|
|
150
|
-
assert.equal(isManagedByManifest(manifest, text), true);
|
|
151
|
-
assert.equal(isManagedByManifest(manifest, text + "\n# edited\n"), false);
|
|
152
|
-
assert.equal(isManagedByManifest(manifest, ""), false);
|
|
153
|
-
assert.equal(isManagedByManifest(manifest, null), false);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("readSdkAgent: returns parsed text + fileHash, null when absent", () => {
|
|
157
|
-
const root = sdkRoot();
|
|
158
|
-
const text = putAgent(root, "alpha");
|
|
159
|
-
const a = readSdkAgent(root, "alpha");
|
|
160
|
-
assert.equal(a.slug, "alpha");
|
|
161
|
-
assert.equal(a.text, text);
|
|
162
|
-
assert.equal(a.fileHash, hashFile(text));
|
|
163
|
-
assert.equal(a.frontmatter.name, "alpha");
|
|
164
|
-
assert.equal(readSdkAgent(root, "nope"), null);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test("the REAL SDK tree builds a manifest with no invalid agents", () => {
|
|
168
|
-
const { manifest, invalid } = buildManifest({ maestroRoot: MAESTRO_ROOT, sdkVersion: "test" });
|
|
169
|
-
assert.deepEqual(invalid.map((i) => i.slug), []);
|
|
170
|
-
assert.ok(Object.keys(manifest.agents).length >= 1);
|
|
171
|
-
for (const [slug, e] of Object.entries(manifest.agents)) {
|
|
172
|
-
const text = readFileSync(join(MAESTRO_ROOT, "agents", slug, "agent.md"), "utf8");
|
|
173
|
-
assert.equal(e.sha256, hashFile(text), `${slug} sha must be the file hash`);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* refs.test.mjs — THE single sub-agent reference parser (§2.1).
|
|
3
|
-
* Run: node --test lib/subagents/refs.test.mjs
|
|
4
|
-
*/
|
|
5
|
-
"use strict";
|
|
6
|
-
|
|
7
|
-
import { test } from "node:test";
|
|
8
|
-
import assert from "node:assert/strict";
|
|
9
|
-
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
|
|
10
|
-
import { tmpdir } from "node:os";
|
|
11
|
-
import { join } from "node:path";
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
AGENT_REF_PATTERN,
|
|
15
|
-
AGENT_VIA,
|
|
16
|
-
SELF_SENTINELS,
|
|
17
|
-
agentRefRegex,
|
|
18
|
-
groupRefs,
|
|
19
|
-
parseAgentRefs,
|
|
20
|
-
parseEventRoutingRefs,
|
|
21
|
-
parseTeamRefs,
|
|
22
|
-
scanAgentRefs,
|
|
23
|
-
} from "./refs.mjs";
|
|
24
|
-
|
|
25
|
-
/** The two regexes this module replaces, verbatim, so the fixes are provable. */
|
|
26
|
-
const OLD_DOCTOR = () => /^\s+agent:\s*([\w-]+)\b/gm;
|
|
27
|
-
|
|
28
|
-
function repo() {
|
|
29
|
-
const root = mkdtempSync(join(tmpdir(), "subagent-refs-"));
|
|
30
|
-
mkdirSync(join(root, "workflows"), { recursive: true });
|
|
31
|
-
mkdirSync(join(root, "teams"), { recursive: true });
|
|
32
|
-
mkdirSync(join(root, "config"), { recursive: true });
|
|
33
|
-
return root;
|
|
34
|
-
}
|
|
35
|
-
const refsOf = (list) => list.map((r) => r.ref);
|
|
36
|
-
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
// parseAgentRefs — the grammar and the two bugs it fixes
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
test("parseAgentRefs: matches all four keys at any indent, including a list item", () => {
|
|
42
|
-
const text = [
|
|
43
|
-
"agent: top-level",
|
|
44
|
-
" agent: indented",
|
|
45
|
-
" - agent: in-a-list",
|
|
46
|
-
" delegate: delegated",
|
|
47
|
-
" subagent: subbed",
|
|
48
|
-
" handler: handled",
|
|
49
|
-
].join("\n");
|
|
50
|
-
assert.deepEqual(refsOf(parseAgentRefs(text, { kind: "workflow" })),
|
|
51
|
-
["top-level", "indented", "in-a-list", "delegated", "subbed", "handled"]);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("parseAgentRefs: a TOP-LEVEL `agent:` is visible (the old doctor regex missed it)", () => {
|
|
55
|
-
const text = "agent: top-level\n";
|
|
56
|
-
assert.equal(OLD_DOCTOR().exec(text), null, "sanity: the old regex really did miss it");
|
|
57
|
-
assert.deepEqual(refsOf(parseAgentRefs(text)), ["top-level"]);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("parseAgentRefs: a malformed value is INVISIBLE, not silently truncated", () => {
|
|
61
|
-
// The old `\b` regex matched `agent: foo bar` and reported `foo`, hiding the
|
|
62
|
-
// malformation. Anchoring to end-of-line makes a bad value a non-match, which
|
|
63
|
-
// is what lets a caller trust the set it gets.
|
|
64
|
-
const text = " agent: foo bar\n";
|
|
65
|
-
assert.equal(OLD_DOCTOR().exec(text)[1], "foo", "sanity: the old regex truncated");
|
|
66
|
-
assert.deepEqual(parseAgentRefs(text), []);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("parseAgentRefs: a trailing comment is allowed and stripped", () => {
|
|
70
|
-
assert.deepEqual(refsOf(parseAgentRefs(" agent: alpha # why\n")), ["alpha"]);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("parseAgentRefs: self sentinels are skipped", () => {
|
|
74
|
-
const text = [" agent: agent-self", " agent: agent_self", " agent: self", " agent: real"].join("\n");
|
|
75
|
-
assert.deepEqual(refsOf(parseAgentRefs(text)), ["real"]);
|
|
76
|
-
for (const s of ["agent-self", "agent_self", "self"]) assert.equal(SELF_SENTINELS.has(s), true);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("parseAgentRefs: reports 1-indexed line numbers and carries kind/file", () => {
|
|
80
|
-
const out = parseAgentRefs("x: 1\ny: 2\n agent: alpha\n", { kind: "workflow", file: "workflows/a.yaml" });
|
|
81
|
-
assert.deepEqual(out, [{ ref: "alpha", key: "agent", line: 3, kind: "workflow", file: "workflows/a.yaml" }]);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("agentRefRegex: a FRESH regex per call (a shared /g carries lastIndex)", () => {
|
|
85
|
-
const text = " agent: alpha\n agent: beta\n";
|
|
86
|
-
assert.deepEqual(refsOf(parseAgentRefs(text)), ["alpha", "beta"]);
|
|
87
|
-
assert.deepEqual(refsOf(parseAgentRefs(text)), ["alpha", "beta"], "second call must not resume mid-string");
|
|
88
|
-
assert.equal(agentRefRegex().source, AGENT_REF_PATTERN);
|
|
89
|
-
assert.equal(agentRefRegex().lastIndex, 0);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test("parseAgentRefs: non-string / empty input returns []", () => {
|
|
93
|
-
assert.deepEqual(parseAgentRefs(null), []);
|
|
94
|
-
assert.deepEqual(parseAgentRefs(""), []);
|
|
95
|
-
assert.deepEqual(parseAgentRefs(42), []);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
// teams/*.yaml — membership was never checked before
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
|
|
102
|
-
test("parseTeamRefs: reads `lead:` and the `members:` block sequence", () => {
|
|
103
|
-
const y = [
|
|
104
|
-
"id: growth",
|
|
105
|
-
"lead: growth-lead",
|
|
106
|
-
"members:",
|
|
107
|
-
" - desktop-operator # comment",
|
|
108
|
-
" - research-analyst",
|
|
109
|
-
"responsibilities:",
|
|
110
|
-
" - not-an-agent",
|
|
111
|
-
].join("\n");
|
|
112
|
-
const out = parseTeamRefs(y, { file: "teams/growth.yaml" });
|
|
113
|
-
assert.deepEqual(refsOf(out), ["growth-lead", "desktop-operator", "research-analyst"]);
|
|
114
|
-
assert.equal(out.every((r) => r.kind === "team" && r.file === "teams/growth.yaml"), true);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("parseTeamRefs: a sibling key ENDS the members block", () => {
|
|
118
|
-
const y = ["members:", " - alpha", "other:", " - beta"].join("\n");
|
|
119
|
-
assert.deepEqual(refsOf(parseTeamRefs(y)), ["alpha"]);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("parseTeamRefs: skips self sentinels", () => {
|
|
123
|
-
assert.deepEqual(refsOf(parseTeamRefs("members:\n - agent-self\n - alpha\n")), ["alpha"]);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// ---------------------------------------------------------------------------
|
|
127
|
-
// event-routing — `via:` is what stops every workflow id being reported
|
|
128
|
-
// ---------------------------------------------------------------------------
|
|
129
|
-
|
|
130
|
-
test("parseEventRoutingRefs: `via` decides whether a handler is an AGENT", () => {
|
|
131
|
-
const y = [
|
|
132
|
-
"routes:",
|
|
133
|
-
" - event: inbox.received",
|
|
134
|
-
" handler: triage-agent",
|
|
135
|
-
" via: standard-agent",
|
|
136
|
-
" - event: cadence.tick",
|
|
137
|
-
" handler: daily-brief",
|
|
138
|
-
" via: workflow",
|
|
139
|
-
" - event: x.y",
|
|
140
|
-
" handler: some-skill",
|
|
141
|
-
" via: skill",
|
|
142
|
-
].join("\n");
|
|
143
|
-
const out = parseEventRoutingRefs(y, { file: "config/event-routing.yaml" });
|
|
144
|
-
assert.deepEqual(out.map((r) => [r.ref, r.isAgent]), [
|
|
145
|
-
["triage-agent", true],
|
|
146
|
-
["daily-brief", false],
|
|
147
|
-
["some-skill", false],
|
|
148
|
-
]);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("parseEventRoutingRefs: a route with NO `via` is treated as an agent handler", () => {
|
|
152
|
-
const out = parseEventRoutingRefs(" - event: e\n handler: mystery\n");
|
|
153
|
-
assert.deepEqual(out.map((r) => [r.ref, r.isAgent, r.via]), [["mystery", true, ""]]);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("parseEventRoutingRefs: key order within a route does not matter", () => {
|
|
157
|
-
const y = [" - via: standard-agent", " handler: alpha", " - handler: beta", " via: workflow"].join("\n");
|
|
158
|
-
assert.deepEqual(parseEventRoutingRefs(y).map((r) => [r.ref, r.isAgent]), [["alpha", true], ["beta", false]]);
|
|
159
|
-
for (const v of ["standard-agent", "role-agent", "agent"]) assert.equal(AGENT_VIA.has(v), true);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
// scanAgentRefs — all three sources in one pass
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
|
|
166
|
-
test("scanAgentRefs: covers workflows + teams + event-routing, and filters non-agent routes", () => {
|
|
167
|
-
const root = repo();
|
|
168
|
-
mkdirSync(join(root, "workflows", "nested"), { recursive: true });
|
|
169
|
-
writeFileSync(join(root, "workflows", "a.yaml"), "steps:\n - agent: alpha\n - agent: agent-self\n");
|
|
170
|
-
writeFileSync(join(root, "workflows", "nested", "b.yml"), " agent: nested-one\n");
|
|
171
|
-
writeFileSync(join(root, "workflows", "notes.md"), " agent: ignored-non-yaml\n");
|
|
172
|
-
writeFileSync(join(root, "teams", "t.yaml"), "members:\n - team-member\n");
|
|
173
|
-
writeFileSync(join(root, "config", "event-routing.yaml"),
|
|
174
|
-
" - event: e\n handler: routed-agent\n via: standard-agent\n - event: f\n handler: some-workflow\n via: workflow\n");
|
|
175
|
-
|
|
176
|
-
const refs = scanAgentRefs({ agentRoot: root });
|
|
177
|
-
assert.deepEqual(refsOf(refs).sort(), ["alpha", "nested-one", "routed-agent", "team-member"]);
|
|
178
|
-
assert.deepEqual([...new Set(refs.map((r) => r.kind))].sort(), ["event-routing", "team", "workflow"]);
|
|
179
|
-
assert.equal(refs.every((r) => r.file && !r.file.startsWith("/")), true, "paths are agentRoot-relative");
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test("scanAgentRefs: includeNonAgentRouting surfaces workflow/skill handlers too", () => {
|
|
183
|
-
const root = repo();
|
|
184
|
-
writeFileSync(join(root, "config", "event-routing.yaml"), " - event: f\n handler: some-workflow\n via: workflow\n");
|
|
185
|
-
assert.deepEqual(refsOf(scanAgentRefs({ agentRoot: root })), []);
|
|
186
|
-
assert.deepEqual(refsOf(scanAgentRefs({ agentRoot: root, includeNonAgentRouting: true })), ["some-workflow"]);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test("scanAgentRefs: fail-open on a missing root / missing dirs", () => {
|
|
190
|
-
assert.deepEqual(scanAgentRefs({}), []);
|
|
191
|
-
assert.deepEqual(scanAgentRefs({ agentRoot: join(tmpdir(), "definitely-not-here-12345") }), []);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test("groupRefs: collapses to slug → locations", () => {
|
|
195
|
-
const m = groupRefs([
|
|
196
|
-
{ ref: "alpha", file: "w/a.yaml", line: 2, kind: "workflow" },
|
|
197
|
-
{ ref: "alpha", file: "w/b.yaml", line: 9, kind: "workflow" },
|
|
198
|
-
{ ref: "beta", file: "t/t.yaml", line: 1, kind: "team" },
|
|
199
|
-
null,
|
|
200
|
-
]);
|
|
201
|
-
assert.equal(m.get("alpha").length, 2);
|
|
202
|
-
assert.deepEqual(m.get("beta"), [{ file: "t/t.yaml", line: 1, kind: "team" }]);
|
|
203
|
-
assert.equal(m.size, 2);
|
|
204
|
-
});
|