@xhulz/nina 0.51.0 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/bin/nina.mjs +29 -5
- package/package.json +1 -1
- package/releases/0.52.0/core/GAPS.md +125 -0
- package/releases/0.52.0/core/answered.json +68 -0
- package/releases/0.52.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.52.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.52.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.52.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.52.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.52.0/core/defaults/tree/.claude/agents/qa.md +7 -0
- package/releases/0.52.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.52.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.52.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.52.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.52.0/core/tree/.claude/agents/architect.md +129 -0
- package/releases/0.52.0/core/tree/.claude/agents/dba.md +117 -0
- package/releases/0.52.0/core/tree/.claude/agents/devops.md +118 -0
- package/releases/0.52.0/core/tree/.claude/agents/implementer.md +163 -0
- package/releases/0.52.0/core/tree/.claude/agents/integration-tester.md +168 -0
- package/releases/0.52.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.52.0/core/tree/.claude/agents/qa.md +150 -0
- package/releases/0.52.0/core/tree/.claude/agents/reviewer.md +184 -0
- package/releases/0.52.0/core/tree/.claude/agents/secops.md +120 -0
- package/releases/0.52.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.52.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.52.0/core/tree/.claude/agents-overview.md +64 -0
- package/releases/0.52.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.52.0/core/tree/.claude/graph.md +75 -0
- package/releases/0.52.0/core/tree/.claude/patterns.md +251 -0
- package/releases/0.52.0/core/tree/.claude/pills/README.md +150 -0
- package/releases/0.52.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.52.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.52.0/core/tree/.claude/router.md +202 -0
- package/releases/0.52.0/core/tree/.claude/templates/integration.md +46 -0
- package/releases/0.52.0/core/tree/CLAUDE.md +164 -0
- package/releases/0.52.0/core/tree/scripts/cost-watch.mjs +25 -0
- package/releases/0.52.0/core/tree/scripts/edit-guard.mjs +24 -0
- package/releases/0.52.0/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.52.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.52.0/core/vocabulary.json +13 -0
- package/releases/0.52.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.52.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.52.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.52.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.52.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.52.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.52.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.52.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.52.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.52.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
- package/releases/0.52.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
- package/releases/0.52.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.52.0/surfaces/d1/tree/.claude/patterns.md +8 -0
- package/releases/0.52.0/surfaces/d1/tree/CLAUDE.md +2 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/patterns.md +22 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.52.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.52.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.52.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
- package/releases/0.52.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
- package/releases/0.52.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.52.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
- package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
- package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.52.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.52.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.52.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.52.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.52.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.52.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.52.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.52.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.52.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.52.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.52.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.52.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.52.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.52.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.52.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.52.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.52.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.52.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.52.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.52.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.52.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.52.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.52.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.52.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.52.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.52.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.52.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.52.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.52.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.52.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.52.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.52.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.52.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.52.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.52.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.52.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.52.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.52.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.52.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.52.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.52.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.52.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.52.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.52.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.52.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.52.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.52.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
- package/releases/0.52.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.52.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.52.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.52.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.52.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.52.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/releases/0.53.0/core/GAPS.md +125 -0
- package/releases/0.53.0/core/answered.json +68 -0
- package/releases/0.53.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.53.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.53.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.53.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.53.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.53.0/core/defaults/tree/.claude/agents/qa.md +7 -0
- package/releases/0.53.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.53.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.53.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.53.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.53.0/core/tree/.claude/agents/architect.md +129 -0
- package/releases/0.53.0/core/tree/.claude/agents/dba.md +117 -0
- package/releases/0.53.0/core/tree/.claude/agents/devops.md +118 -0
- package/releases/0.53.0/core/tree/.claude/agents/implementer.md +163 -0
- package/releases/0.53.0/core/tree/.claude/agents/integration-tester.md +168 -0
- package/releases/0.53.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.53.0/core/tree/.claude/agents/qa.md +150 -0
- package/releases/0.53.0/core/tree/.claude/agents/reviewer.md +184 -0
- package/releases/0.53.0/core/tree/.claude/agents/secops.md +120 -0
- package/releases/0.53.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.53.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.53.0/core/tree/.claude/agents-overview.md +64 -0
- package/releases/0.53.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.53.0/core/tree/.claude/graph.md +75 -0
- package/releases/0.53.0/core/tree/.claude/patterns.md +251 -0
- package/releases/0.53.0/core/tree/.claude/pills/README.md +150 -0
- package/releases/0.53.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.53.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.53.0/core/tree/.claude/router.md +202 -0
- package/releases/0.53.0/core/tree/.claude/templates/integration.md +46 -0
- package/releases/0.53.0/core/tree/CLAUDE.md +164 -0
- package/releases/0.53.0/core/tree/scripts/cost-watch.mjs +25 -0
- package/releases/0.53.0/core/tree/scripts/edit-guard.mjs +24 -0
- package/releases/0.53.0/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.53.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.53.0/core/vocabulary.json +13 -0
- package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.53.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.53.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.53.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.53.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.53.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
- package/releases/0.53.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
- package/releases/0.53.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.53.0/surfaces/d1/tree/.claude/patterns.md +8 -0
- package/releases/0.53.0/surfaces/d1/tree/CLAUDE.md +2 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/patterns.md +22 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.53.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.53.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.53.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
- package/releases/0.53.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
- package/releases/0.53.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.53.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
- package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
- package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.53.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.53.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.53.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.53.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.53.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.53.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.53.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.53.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.53.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.53.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.53.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.53.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.53.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.53.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.53.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.53.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.53.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.53.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.53.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.53.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.53.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.53.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.53.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.53.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.53.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.53.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.53.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.53.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.53.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.53.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.53.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.53.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.53.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.53.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.53.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.53.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
- package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.53.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.53.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.53.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/src/commands/dashboard.mjs +33 -0
- package/src/commands/learn.mjs +51 -19
- package/src/commands/pills.mjs +55 -28
- package/src/commands/pipeline.mjs +62 -33
- package/src/commands/runs.mjs +14 -3
- package/src/commands/stats.mjs +227 -115
- package/src/dashboard.html +328 -0
- package/src/dashboard.mjs +185 -0
- package/src/gate.mjs +14 -2
- package/src/office.html +142 -4
- package/src/office.mjs +192 -104
- package/src/serve.mjs +108 -0
- package/src/store.mjs +13 -1
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>NINA dashboard</title>
|
|
7
|
+
<link rel="icon" href="data:,">
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
color-scheme: dark;
|
|
11
|
+
--bg: #1b1d26; --panel: #22202b; --panel2: #2a2836; --line: #34324a; --ink: #e6e8ee; --soft: #a9adbf; --faint: #7d8199;
|
|
12
|
+
--accent: #ffd34d; --pink: #ff7ab8; --green: #3ec74d; --amber: #f2a93b; --red: #e45b5b; --blue: #6ea8ff;
|
|
13
|
+
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
14
|
+
--sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
15
|
+
}
|
|
16
|
+
* { box-sizing: border-box; }
|
|
17
|
+
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink); font: 14px/1.45 var(--sans); }
|
|
18
|
+
body { display: flex; flex-direction: column; }
|
|
19
|
+
header { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 10px 16px; border-bottom: 2px solid var(--line); background: var(--panel); }
|
|
20
|
+
header h1 { margin: 0; font: 600 15px var(--mono); color: var(--accent); letter-spacing: 0.04em; }
|
|
21
|
+
header .what { color: var(--soft); font-size: 13px; }
|
|
22
|
+
nav { display: flex; gap: 4px; flex-wrap: wrap; margin-left: auto; }
|
|
23
|
+
nav a { padding: 6px 12px; border-radius: 4px; color: var(--soft); text-decoration: none; font: 600 13px var(--mono); border: 2px solid transparent; }
|
|
24
|
+
nav a:hover { color: var(--ink); background: var(--panel2); }
|
|
25
|
+
nav a[aria-current="page"] { color: var(--accent); border-color: var(--line); background: var(--bg); }
|
|
26
|
+
main { flex: 1; min-height: 0; position: relative; }
|
|
27
|
+
#office { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
|
|
28
|
+
#view { position: absolute; inset: 0; overflow-y: auto; padding: 20px 16px 40px; }
|
|
29
|
+
.wrap { max-width: 1100px; margin: 0 auto; }
|
|
30
|
+
h2 { margin: 0 0 4px; font: 600 18px var(--sans); }
|
|
31
|
+
h3 { margin: 28px 0 10px; font: 600 12px var(--mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent); }
|
|
32
|
+
.sub { color: var(--faint); font-size: 13px; margin: 0 0 16px; }
|
|
33
|
+
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; }
|
|
34
|
+
.card { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 12px 14px; }
|
|
35
|
+
.card .k { color: var(--faint); font-size: 12px; }
|
|
36
|
+
.card .v { font: 600 22px var(--mono); margin-top: 2px; }
|
|
37
|
+
.card .n { color: var(--soft); font-size: 12px; margin-top: 2px; }
|
|
38
|
+
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
39
|
+
th { text-align: left; font: 600 11px var(--mono); color: var(--faint); text-transform: uppercase; letter-spacing: 0.05em; padding: 6px 8px; border-bottom: 1px solid var(--line); }
|
|
40
|
+
td { padding: 7px 8px; border-bottom: 1px solid #2a2838; vertical-align: top; }
|
|
41
|
+
td.num, th.num { text-align: right; font-family: var(--mono); white-space: nowrap; }
|
|
42
|
+
.scroll { overflow-x: auto; }
|
|
43
|
+
.role { color: var(--pink); font-family: var(--mono); }
|
|
44
|
+
.mono { font-family: var(--mono); }
|
|
45
|
+
code.mono { font-size: 0.92em; color: var(--ink); }
|
|
46
|
+
.soft { color: var(--soft); } .faint { color: var(--faint); }
|
|
47
|
+
.ok { color: var(--green); } .warn { color: var(--amber); } .bad { color: var(--red); }
|
|
48
|
+
.chips { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
49
|
+
.chip { padding: 1px 7px; border-radius: 10px; background: var(--panel2); border: 1px solid var(--line); font: 12px var(--mono); color: var(--soft); white-space: nowrap; }
|
|
50
|
+
.chip.back { border-color: #6b4a1f; color: var(--amber); }
|
|
51
|
+
.bar { display: flex; height: 10px; border-radius: 3px; overflow: hidden; background: var(--panel2); min-width: 60px; }
|
|
52
|
+
.bar span { display: block; height: 100%; }
|
|
53
|
+
.list { margin: 0; padding-left: 18px; } .list li { margin: 3px 0; }
|
|
54
|
+
.flow { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
|
|
55
|
+
.node { padding: 6px 10px; border-radius: 5px; background: var(--panel); border: 1px solid var(--line); font: 600 13px var(--mono); color: var(--pink); }
|
|
56
|
+
.node.end { color: var(--soft); }
|
|
57
|
+
.arrow { color: var(--faint); font-family: var(--mono); }
|
|
58
|
+
.stage { display: grid; grid-template-columns: 170px 1fr; gap: 4px 14px; padding: 12px 0; border-bottom: 1px solid #2a2838; }
|
|
59
|
+
.empty { color: var(--faint); padding: 30px 0; text-align: center; }
|
|
60
|
+
.note { margin: 4px 0; font-size: 13px; }
|
|
61
|
+
@media (max-width: 640px) { .stage { grid-template-columns: 1fr; } nav { margin-left: 0; } }
|
|
62
|
+
</style>
|
|
63
|
+
</head>
|
|
64
|
+
<body>
|
|
65
|
+
<header>
|
|
66
|
+
<h1>NINA</h1>
|
|
67
|
+
<span class="what" id="what"></span>
|
|
68
|
+
<nav id="tabs" aria-label="tabs"></nav>
|
|
69
|
+
</header>
|
|
70
|
+
<main>
|
|
71
|
+
<iframe id="office" title="the office" hidden></iframe>
|
|
72
|
+
<section id="view" aria-live="polite"></section>
|
|
73
|
+
</main>
|
|
74
|
+
<script>
|
|
75
|
+
'use strict';
|
|
76
|
+
/*
|
|
77
|
+
* The dashboard: the reports a person reads, a tab each, drawn from what `nina dashboard` serves at `api/<tab>` —
|
|
78
|
+
* the same data each command prints from. Nothing is fetched from anywhere else, and nothing a report says is put
|
|
79
|
+
* into the page as markup: every text goes in as text.
|
|
80
|
+
*/
|
|
81
|
+
const ROOM = /*NINA_DASHBOARD*/null || { project: 'demo' };
|
|
82
|
+
const TABS = [
|
|
83
|
+
['office', 'Office'],
|
|
84
|
+
['tasks', 'Tasks'],
|
|
85
|
+
['pipeline', 'Pipeline'],
|
|
86
|
+
['pills', 'Pills'],
|
|
87
|
+
['learning', 'Learning'],
|
|
88
|
+
['stats', 'Stats'],
|
|
89
|
+
];
|
|
90
|
+
/** How often an open tab is read again. The office streams on its own. */
|
|
91
|
+
const REFRESH_MS = 15_000;
|
|
92
|
+
|
|
93
|
+
/** An element, its attributes, and its children — strings go in as text. */
|
|
94
|
+
function el(tag, attrs = {}, ...children) {
|
|
95
|
+
const node = document.createElement(tag);
|
|
96
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
97
|
+
if (v === null || v === undefined || v === false) continue;
|
|
98
|
+
if (k === 'class') node.className = v;
|
|
99
|
+
else if (k === 'style') node.setAttribute('style', v);
|
|
100
|
+
else node.setAttribute(k, v === true ? '' : v);
|
|
101
|
+
}
|
|
102
|
+
for (const child of children.flat(Infinity)) {
|
|
103
|
+
if (child === null || child === undefined || child === false) continue;
|
|
104
|
+
node.append(child instanceof Node ? child : document.createTextNode(String(child)));
|
|
105
|
+
}
|
|
106
|
+
return node;
|
|
107
|
+
}
|
|
108
|
+
/** A sentence whose `quoted` words are commands, set in the monospace face. */
|
|
109
|
+
const said = (text) => String(text).split(/`([^`]+)`/).map((part, i) => (i % 2 ? el('code', { class: 'mono' }, part) : part));
|
|
110
|
+
const card = (k, v, n) => el('div', { class: 'card' }, el('div', { class: 'k' }, k), el('div', { class: 'v' }, v), n ? el('div', { class: 'n' }, n) : null);
|
|
111
|
+
const pct = (a, b) => (b > 0 ? `${Math.round((100 * a) / b)}%` : '—');
|
|
112
|
+
const money = (n) => (n === null || n === undefined ? '—' : `$${n >= 100 ? n.toFixed(0) : n.toFixed(2)}`);
|
|
113
|
+
const span = (m) => (m === null || m === undefined ? '—' : m >= 60 ? `${Math.floor(m / 60)}h${String(m % 60).padStart(2, '0')}` : `${m}m`);
|
|
114
|
+
const when = (iso) => {
|
|
115
|
+
const t = new Date(iso);
|
|
116
|
+
return Number.isNaN(t.getTime()) ? '—' : t.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
117
|
+
};
|
|
118
|
+
const kTokens = (n) => (typeof n === 'number' ? `${Math.round(n / 1000)}k` : '—');
|
|
119
|
+
/** A bar of parts, each `[share, color]`, as wide as its container. */
|
|
120
|
+
const bar = (parts) => el('div', { class: 'bar' }, parts.filter(([n]) => n > 0).map(([n, color]) => el('span', { style: `width:${(n * 100).toFixed(2)}%;background:${color}` })));
|
|
121
|
+
const table = (head, rows, numeric = []) =>
|
|
122
|
+
el('div', { class: 'scroll' }, el('table', {}, el('thead', {}, el('tr', {}, head.map((h, i) => el('th', { class: numeric.includes(i) ? 'num' : null }, h)))), el('tbody', {}, rows.map((r) => el('tr', {}, r.map((c, i) => el('td', { class: numeric.includes(i) ? 'num' : null }, c)))))));
|
|
123
|
+
|
|
124
|
+
// ─── the tabs ─────────────────────────────────────────────────────────────────────────────────
|
|
125
|
+
const RENDER = {
|
|
126
|
+
tasks(d) {
|
|
127
|
+
if (!d || d.tasks.length === 0) return el('p', { class: 'empty' }, 'No task measured yet: the first dispatch the snapshot records starts one.');
|
|
128
|
+
const shown = d.tasks;
|
|
129
|
+
const cost = shown.reduce((a, t) => a + (t.cost ?? 0), 0);
|
|
130
|
+
const done = shown.filter((t) => t.minutes !== null).map((t) => t.minutes).sort((a, b) => a - b);
|
|
131
|
+
return el('div', {},
|
|
132
|
+
el('h2', {}, 'Tasks'),
|
|
133
|
+
el('p', { class: 'sub' }, said(`Each task is a cycle: the dispatches up to the verdict that closes them, named by the first one's description — as \`nina runs\` cuts them. Newest first${d.total > shown.length ? `, the last ${shown.length} of ${d.total}` : ''}.`)),
|
|
134
|
+
el('div', { class: 'cards' },
|
|
135
|
+
card('tasks', String(d.total)),
|
|
136
|
+
card('cost shown', money(cost), 'at API list prices'),
|
|
137
|
+
card('median length', span(done.length ? done[Math.floor(done.length / 2)] : null), 'from first dispatch to last verdict'),
|
|
138
|
+
),
|
|
139
|
+
el('h3', {}, 'every task'),
|
|
140
|
+
table(['started', 'task', 'who worked on it', 'length', 'cost', 'end'],
|
|
141
|
+
shown.map((t) => [
|
|
142
|
+
el('span', { class: 'mono soft' }, when(t.start)),
|
|
143
|
+
el('div', {}, el('div', {}, t.name), t.files !== null || t.peak !== null ? el('div', { class: 'faint' }, [t.files !== null ? `largest write ${t.files} file(s)` : null, t.peak !== null ? `context up to ${kTokens(t.peak)}` : null].filter(Boolean).join(' · ')) : null),
|
|
144
|
+
el('div', { class: 'chips' }, t.stages.map((s) => el('span', { class: s.back ? 'chip back' : 'chip' }, `${s.role}${s.rounds > 1 ? ` ×${s.rounds}` : ''}${s.back ? ` · ${s.back} back` : ''}`))),
|
|
145
|
+
span(t.minutes),
|
|
146
|
+
money(t.cost),
|
|
147
|
+
el('span', { class: t.end_kind === 'closed' ? 'ok' : t.end_kind === 'open' ? 'warn' : 'faint' }, t.end_kind === 'closed' ? '✓ ' + t.how.replace(/^ended at /, '') : t.how),
|
|
148
|
+
]), [3, 4]),
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
pipeline(d) {
|
|
153
|
+
if (!d) return el('p', { class: 'empty' }, said('No composed pipeline here: `nina compose` writes .claude/graph.md.'));
|
|
154
|
+
const node = (s) => el('span', { class: ['done', 'human'].includes(s) ? 'node end' : 'node' }, `${s}${d.many.some((m) => m.stage === s) ? ' ×n' : ''}`);
|
|
155
|
+
const chain = (stages) => el('div', { class: 'flow' }, stages.flatMap((s, i) => (i === 0 ? [node(s)] : [el('span', { class: 'arrow' }, '→'), node(s)])));
|
|
156
|
+
return el('div', {},
|
|
157
|
+
el('h2', {}, 'Pipeline'),
|
|
158
|
+
el('p', { class: 'sub' }, `${d.project}${d.core ? ` · core ${d.core}` : ''}${d.surfaces.length ? ` · surfaces ${d.surfaces.join(', ')}` : ''}`),
|
|
159
|
+
el('h3', {}, 'the line'),
|
|
160
|
+
chain(d.line),
|
|
161
|
+
d.ends.length ? el('div', { style: 'margin-top:10px' }, d.ends.map((e) => el('div', { class: 'flow', style: 'margin:6px 0 0 24px' }, el('span', { class: 'arrow' }, '↳'), chain(e.path), el('span', { class: 'faint' }, e.when)))) : null,
|
|
162
|
+
d.gates.length ? [el('h3', {}, 'gates, when the diff calls for them'), d.gates.map((g) => el('div', { class: 'flow', style: 'margin:6px 0' }, chain([g.from, g.gate, g.to]), el('span', { class: 'faint' }, g.beside.length ? `beside the ${g.beside.join(', ')} · ${g.when}` : g.when)))] : null,
|
|
163
|
+
d.many.length ? [el('h3', {}, 'several agents at once'), table(['stage', 'when'], d.many.map((m) => [el('span', { class: 'role' }, m.stage), m.when]))] : null,
|
|
164
|
+
d.back.length ? [el('h3', {}, 'sent back: to whom, on which verdict, rounds per issue'),
|
|
165
|
+
table(['from', 'to', 'verdict', 'rounds', 'why'], d.back.map((e) => [el('span', { class: 'role' }, e.from), el('span', { class: 'role' }, e.to), el('span', { class: 'mono' }, e.token), e.to === 'human' ? '—' : e.max === null ? '?' : String(e.max), el('span', { class: 'soft' }, e.when ?? '')]), [3])] : null,
|
|
166
|
+
el('h3', {}, d.measured ? `stages · what each ran since ${d.since}` : 'stages · no measured history yet'),
|
|
167
|
+
d.stages.map((s) => el('div', { class: 'stage' },
|
|
168
|
+
el('div', {}, el('div', { class: 'role' }, s.stage), el('div', { class: 'faint mono' }, [s.model, s.effort].filter(Boolean).join(' · ') || 'no spec')),
|
|
169
|
+
el('div', {},
|
|
170
|
+
el('div', {}, s.words),
|
|
171
|
+
s.history ? el('div', { class: 'soft' }, `${s.history.runs} run(s)${s.history.rounds > s.history.runs ? ` in ${s.history.rounds} rounds` : ''}${s.history.read === 0 ? ', no verdict read' : `, sent back ${s.history.back} of ${s.history.read} (${pct(s.history.back, s.history.read)})`}`) : d.measured ? el('div', { class: 'faint' }, 'no run') : null,
|
|
172
|
+
s.skills.length ? el('div', { class: 'chips', style: 'margin-top:6px' }, s.skills.map((k) => el('span', { class: 'chip' }, k))) : null,
|
|
173
|
+
),
|
|
174
|
+
)),
|
|
175
|
+
d.own.length ? [el('h3', {}, "the project's own agents"), table(['agent', 'what it does'], d.own.map((o) => [el('span', { class: 'role' }, o.role), o.description]))] : null,
|
|
176
|
+
d.shapes.length ? [el('h3', {}, 'which chain a task takes, by its shape'), table(['shape', 'chain'], d.shapes.map((s) => [s.shape, el('span', { class: 'mono soft' }, s.chain)]))] : null,
|
|
177
|
+
);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
pills(d) {
|
|
181
|
+
if (!d) return el('p', { class: 'empty' }, said('No composed agents here: `nina compose` first.'));
|
|
182
|
+
if (d.files === 0) return el('p', { class: 'empty' }, 'No pill written yet: either nothing has been learned the expensive way, or nothing was written down.');
|
|
183
|
+
const order = [...d.pills].sort((a, b) => (a.status === 'retired') - (b.status === 'retired') || b.occurrences - a.occurrences || String(b.last_seen ?? b.date).localeCompare(String(a.last_seen ?? a.date)));
|
|
184
|
+
return el('div', {},
|
|
185
|
+
el('h2', {}, 'Pills'),
|
|
186
|
+
el('p', { class: 'sub' }, said('The corrections the pipeline wrote about itself, checked as `nina pills` checks them. A lesson learned three times is a rule: it graduates here or to the harness.')),
|
|
187
|
+
el('div', { class: 'cards' },
|
|
188
|
+
card('pills', String(d.files)),
|
|
189
|
+
card('active', String(d.active)),
|
|
190
|
+
card('retired', String(d.retired), 'made rules, here or in the harness'),
|
|
191
|
+
card('problems', String(d.problems.length), d.problems.length ? 'see below' : 'every pill well formed'),
|
|
192
|
+
),
|
|
193
|
+
d.problems.length ? [el('h3', {}, 'problems'), d.problems.map((p) => el('p', { class: 'note bad' }, '✗ ', said(p)))] : null,
|
|
194
|
+
el('h3', {}, 'every pill'),
|
|
195
|
+
table(['pill', 'for', 'times', 'last seen', 'status', 'when it applies'],
|
|
196
|
+
order.map((p) => [
|
|
197
|
+
el('span', { class: 'mono' }, p.label),
|
|
198
|
+
el('div', { class: 'chips' }, p.roles.map((r) => el('span', { class: 'chip' }, r))),
|
|
199
|
+
el('span', { class: p.occurrences >= 3 && p.status !== 'retired' ? 'warn' : null }, String(p.occurrences)),
|
|
200
|
+
el('span', { class: 'mono soft' }, p.last_seen ?? p.date ?? '—'),
|
|
201
|
+
el('span', { class: p.status === 'retired' ? 'faint' : 'ok' }, p.status ?? '—'),
|
|
202
|
+
el('span', { class: 'soft' }, p.trigger ?? ''),
|
|
203
|
+
]), [2]),
|
|
204
|
+
d.notes.length ? el('details', { style: 'margin-top:24px' }, el('summary', { class: 'soft', style: 'cursor:pointer' }, `${d.notes.length} note(s): citations that moved, lessons that recur`), d.notes.map((n) => el('p', { class: 'note soft' }, '· ', said(n)))) : null,
|
|
205
|
+
);
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
learning(d) {
|
|
209
|
+
if (!d) return el('p', { class: 'empty' }, 'No profile here to read the learning cycle of.');
|
|
210
|
+
const measured = d.checks.filter((c) => c.before);
|
|
211
|
+
const unmeasured = d.checks.filter((c) => !c.before);
|
|
212
|
+
return el('div', {},
|
|
213
|
+
el('h2', {}, 'Learning'),
|
|
214
|
+
el('p', { class: 'sub' }, said('Whether the pipeline learns from its own runs, link by link, as `nina learn` reads it.')),
|
|
215
|
+
el('div', { class: 'cards' },
|
|
216
|
+
card('observe', d.rounds ? `${d.runs} runs` : '—', d.rounds ? `${d.rounds} rounds, ${d.span[0]} → ${d.span[1]}` : 'nothing recorded'),
|
|
217
|
+
card('capture', d.late.length ? `${d.late.length} owed` : '✓', d.late.length ? 'stages sent back with nothing written down' : `no stage sent back 3+ times without a lesson in ${d.days} days`),
|
|
218
|
+
card('apply', d.use.measured ? pct(d.use.read, d.use.measured) : '—', d.use.measured ? `of ${d.use.measured} runs read a lesson` : 'not measured yet'),
|
|
219
|
+
card('graduate', `${d.open.length} open`, `${d.ready.length} at 3+ · ${d.madeHere} made a rule here`),
|
|
220
|
+
card('loops', d.gated ? (d.activity ? String(d.activity.rounds) : '—') : 'no gate', d.activity ? `rounds on capped edges · ${d.activity.held} sent to you` : d.gated ? 'no ledger yet' : 'caps are instructions'),
|
|
221
|
+
),
|
|
222
|
+
d.late.length ? [el('h3', {}, 'capture · sent back with nothing written down'), d.late.map((o) => el('div', { class: 'note' }, el('span', { class: 'role' }, o.role), ` ${o.count} loop-back(s) ${o.since ? `since its newest lesson (${o.since})` : 'and no lesson at all'}`, el('ul', { class: 'list soft' }, o.recent.map((r) => el('li', {}, `${when(r.ts)} · ${r.desc ?? '(no description)'}`)))))] : null,
|
|
223
|
+
d.owed.length ? [el('h3', {}, 'to graduate · here or in the harness'), d.owed.map((o) => el('p', { class: 'note' }, el('span', { class: 'mono' }, o.rel), o.declined ? el('span', { class: 'soft' }, ` — declined by the harness${o.declined.in ? ` in ${o.declined.in}` : ''}: ${o.declined.why}`) : el('span', { class: 'soft' }, ` — recurred ${o.occurrences} times`)))] : null,
|
|
224
|
+
d.open.length ? [el('h3', {}, 'requests open in the harness'), table(['since', 'pill', 'proposed for'], d.open.map((r) => [el('span', { class: 'mono soft' }, r.date), el('span', { class: 'mono' }, r.pill), r.target]))] : null,
|
|
225
|
+
d.relapses.length ? [el('h3', {}, 'came back after graduating'), d.relapses.map((r) => el('p', { class: `note ${r.due ? 'bad' : 'soft'}` }, `${r.due ? '✗' : '·'} ${r.rel} came back ${r.recurred} time(s) after ${r.target === 'project' ? `it became a rule here in ${r.rule_in}` : `its rule shipped in ${r.answered_in || 'a release'}`}${r.due ? ' — the rule did not hold' : ' — watched'}`))] : null,
|
|
226
|
+
el('h3', {}, 'verify · is each lesson sent back less since?'),
|
|
227
|
+
measured.length
|
|
228
|
+
? table(['lesson', 'stage', 'before → after', 'n', 'change', 'other stages'], measured.map((c) => [
|
|
229
|
+
el('span', { class: 'mono' }, c.rel.replace(/^\.claude\/pills\//, '')),
|
|
230
|
+
el('span', { class: 'role' }, c.role),
|
|
231
|
+
`${pct(c.before.loops, c.before.n)} → ${pct(c.after.loops, c.after.n)}`,
|
|
232
|
+
`${c.before.n} → ${c.after.n}`,
|
|
233
|
+
el('span', { class: c.change === 'lower' ? 'ok' : c.change === 'higher' ? 'bad' : 'faint' }, { lower: 'sent back less', higher: 'sent back more', unclear: 'no clear change' }[c.change]),
|
|
234
|
+
el('span', { class: 'soft' }, `${pct(c.control.before.loops, c.control.before.n)} → ${pct(c.control.after.loops, c.control.after.n)}`),
|
|
235
|
+
]), [3])
|
|
236
|
+
: el('p', { class: 'note faint' }, 'No lesson could be measured yet.'),
|
|
237
|
+
unmeasured.length ? el('p', { class: 'note faint' }, `${unmeasured.length} more could not be measured.`) : null,
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
stats(d) {
|
|
242
|
+
if (!d) return el('p', { class: 'empty' }, 'Nothing measured for this project yet: its hooks snapshot it every turn.');
|
|
243
|
+
const rows = d.stages.rows;
|
|
244
|
+
const busiest = Math.max(1, ...rows.map((s) => s.rounds));
|
|
245
|
+
return el('div', {},
|
|
246
|
+
el('h2', {}, 'Stats'),
|
|
247
|
+
el('p', { class: 'sub' }, said(`${d.span[0]} → ${d.span[1]}, as \`nina stats\` measures it in this project${d.denied ? ` · ${d.denied} dispatch(es) denied by a hook, not counted` : ''}.`)),
|
|
248
|
+
el('div', { class: 'cards' },
|
|
249
|
+
card('runs', String(d.runs), `${d.rounds} rounds`),
|
|
250
|
+
d.cost ? card('cost', money(d.cost.total), `at API list prices of ${d.cost.pricesAsOf}`) : null,
|
|
251
|
+
d.proportion ? card('cycles that wrote code', String(d.proportion.built)) : null,
|
|
252
|
+
),
|
|
253
|
+
el('h3', {}, 'stages · what each ran, and what came of it'),
|
|
254
|
+
table(['stage', 'runs', 'rounds', 'sent back', 'rate', 'median', ''],
|
|
255
|
+
rows.map((s) => [
|
|
256
|
+
el('span', { class: 'role' }, s.role),
|
|
257
|
+
String(s.runs),
|
|
258
|
+
String(s.rounds),
|
|
259
|
+
String(s.loop),
|
|
260
|
+
pct(s.loop, s.clear),
|
|
261
|
+
s.median_s === null ? '—' : `${(s.median_s / 60).toFixed(1)}m`,
|
|
262
|
+
el('div', { style: `width:${Math.max(8, (100 * s.rounds) / busiest)}%` }, bar([[(s.clear - s.loop) / s.rounds, 'var(--pink)'], [s.loop / s.rounds, 'var(--amber)'], [(s.rounds - s.clear) / s.rounds, '#4a4860']])),
|
|
263
|
+
]), [1, 2, 3, 4, 5]),
|
|
264
|
+
el('p', { class: 'note faint' }, 'pink: went forward · amber: sent back · grey: no verdict read'),
|
|
265
|
+
d.stages.outside.length ? el('p', { class: 'note faint' }, `Agents outside the pipeline: ${d.stages.outside.map(([r, n]) => `${r} ×${n}`).join(', ')}.`) : null,
|
|
266
|
+
d.cost ? [el('h3', {}, 'cost · by stage'),
|
|
267
|
+
table(['stage', 'runs', 'median', 'total', 'share', ''], d.cost.rows.map((r) => [el('span', { class: 'role' }, r.role), String(r.runs), money(r.median), money(r.total), pct(r.total, d.cost.total), bar([[r.total / d.cost.total, 'var(--pink)']])]), [1, 2, 3, 4]),
|
|
268
|
+
d.cost.unpriced ? el('p', { class: 'note faint' }, `${d.cost.unpriced} run(s) on a model the price table does not know.`) : null] : null,
|
|
269
|
+
d.context ? [el('h3', {}, 'context · every turn re-reads it all'),
|
|
270
|
+
table(['stage', 'rounds', 'n', 'turns', 'calls/turn', 'starts at', 'peak', 'prompt'], d.context.rows.map((r, i, all) => [i > 0 && all[i - 1].role === r.role ? '' : el('span', { class: 'role' }, r.role), el('span', { class: 'faint' }, r.label), String(r.n), String(r.turns), r.callsPerTurn.toFixed(2), kTokens(r.start), kTokens(r.peak), r.prompt === null ? '—' : r.prompt.toLocaleString('en-US')]), [2, 3, 4, 5, 6, 7])] : null,
|
|
271
|
+
d.proportion ? [el('h3', {}, 'proportion · the size of a change against its chain'),
|
|
272
|
+
table(['largest write', 'cycles', 'with a planner or architect', ''], d.proportion.rows.map((r) => [r.label, String(r.cycles), r.cycles ? `${r.designed} (${pct(r.designed, r.cycles)})` : '—', bar([[r.cycles ? r.designed / r.cycles : 0, 'var(--pink)']])]), [1, 2]),
|
|
273
|
+
d.proportion.over ? el('p', { class: 'note warn' }, `${d.proportion.over.count} implementer round(s) wrote more files than one step may (${d.proportion.over.limit}); the largest wrote ${d.proportion.over.largest}.`) : null] : null,
|
|
274
|
+
);
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// ─── the frame ────────────────────────────────────────────────────────────────────────────────
|
|
279
|
+
const view = document.getElementById('view');
|
|
280
|
+
const office = document.getElementById('office');
|
|
281
|
+
let current = null;
|
|
282
|
+
let timer = null;
|
|
283
|
+
|
|
284
|
+
async function load(tab) {
|
|
285
|
+
try {
|
|
286
|
+
const res = await fetch(`api/${tab}`, { cache: 'no-store' });
|
|
287
|
+
const data = await res.json();
|
|
288
|
+
if (tab !== current) return;
|
|
289
|
+
const scroll = view.scrollTop;
|
|
290
|
+
view.replaceChildren(el('div', { class: 'wrap' }, res.ok ? RENDER[tab](data) : el('p', { class: 'empty bad' }, data?.error ?? `${tab} could not be read`)));
|
|
291
|
+
view.scrollTop = scroll;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (tab === current) view.replaceChildren(el('div', { class: 'wrap' }, el('p', { class: 'empty bad' }, said(`${tab} could not be read — is \`nina dashboard\` still running? ${error.message}`))));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function show(tab) {
|
|
298
|
+
if (!TABS.some(([key]) => key === tab)) tab = 'office';
|
|
299
|
+
current = tab;
|
|
300
|
+
clearInterval(timer);
|
|
301
|
+
for (const a of document.querySelectorAll('nav a')) {
|
|
302
|
+
if (a.dataset.tab === tab) a.setAttribute('aria-current', 'page');
|
|
303
|
+
else a.removeAttribute('aria-current');
|
|
304
|
+
}
|
|
305
|
+
const isOffice = tab === 'office';
|
|
306
|
+
office.hidden = !isOffice;
|
|
307
|
+
view.hidden = isOffice;
|
|
308
|
+
// The office keeps its own stream; loaded once, it stays connected while other tabs are read.
|
|
309
|
+
if (isOffice && !office.getAttribute('src')) office.setAttribute('src', 'office/');
|
|
310
|
+
if (isOffice) return;
|
|
311
|
+
view.replaceChildren(el('div', { class: 'wrap' }, el('p', { class: 'empty' }, 'Reading…')));
|
|
312
|
+
load(tab);
|
|
313
|
+
timer = setInterval(() => document.visibilityState === 'visible' && load(tab), REFRESH_MS);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
document.getElementById('tabs').append(...TABS.map(([key, label]) => el('a', { href: `#${key}`, 'data-tab': key }, label)));
|
|
317
|
+
window.addEventListener('hashchange', () => show(location.hash.slice(1)));
|
|
318
|
+
fetch('api/overview', { cache: 'no-store' })
|
|
319
|
+
.then((r) => r.json())
|
|
320
|
+
.then((o) => {
|
|
321
|
+
document.getElementById('what').textContent = `${o.project}${o.core ? ` · core ${o.core}` : ''}${o.surfaces?.length ? ` · ${o.surfaces.join(', ')}` : ''}`;
|
|
322
|
+
document.title = `NINA dashboard · ${o.project}`;
|
|
323
|
+
})
|
|
324
|
+
.catch(() => (document.getElementById('what').textContent = ROOM.project));
|
|
325
|
+
show(location.hash.slice(1) || 'office');
|
|
326
|
+
</script>
|
|
327
|
+
</body>
|
|
328
|
+
</html>
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nina dashboard` — the reports a person reads, in one page on this machine: the office, the tasks, the pipeline,
|
|
3
|
+
* the pills, the learning cycle and the stats, each a tab.
|
|
4
|
+
*
|
|
5
|
+
* The commands stay what they are: the hooks and CI run them, and an agent reads their text, which it cannot do with a
|
|
6
|
+
* page. So every tab is served from the function its command prints from — `taskList`, `pipelineData`, `pillsData`,
|
|
7
|
+
* `learningData`, `statsData` — and nothing here counts anything of its own: a second copy of a count is the one
|
|
8
|
+
* thing this repository has shipped drifting more than once. The office is the office, its routes mounted at
|
|
9
|
+
* `/office/` (`officeRoutes`). The page is served on the loopback address only, the way the office is (`serve.mjs`).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
import { createServer } from 'node:http';
|
|
14
|
+
import { basename, dirname, join } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { readProfile } from './paths.mjs';
|
|
17
|
+
import { projectRecords } from './store.mjs';
|
|
18
|
+
import { officeRoutes } from './office.mjs';
|
|
19
|
+
import { listenHere, openInBrowser, refuseElsewhere, untilStopped } from './serve.mjs';
|
|
20
|
+
import { layerRootFor } from './commands/compose.mjs';
|
|
21
|
+
import { taskList } from './commands/runs.mjs';
|
|
22
|
+
import { pipelineData } from './commands/pipeline.mjs';
|
|
23
|
+
import { pillsData } from './commands/pills.mjs';
|
|
24
|
+
import { learningData } from './commands/learn.mjs';
|
|
25
|
+
import { statsData } from './commands/stats.mjs';
|
|
26
|
+
|
|
27
|
+
/** The page: one self-contained file, nothing fetched from anywhere else. */
|
|
28
|
+
const PAGE = join(dirname(fileURLToPath(import.meta.url)), 'dashboard.html');
|
|
29
|
+
|
|
30
|
+
/** The port it asks for first, one past the office's. Taken, any free one. */
|
|
31
|
+
export const DASHBOARD_PORT = 6463;
|
|
32
|
+
|
|
33
|
+
/** How many tasks the tasks tab lists, newest first: a long project's history would otherwise fill the page. */
|
|
34
|
+
const TASKS_SHOWN = 60;
|
|
35
|
+
|
|
36
|
+
/** A project's measured rounds, oldest first, or none. */
|
|
37
|
+
const roundsOf = (target) => (projectRecords(target) ?? []).sort((a, b) => String(a.ts).localeCompare(String(b.ts)));
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* What each tab is served, by its name. Each reads the project as it stands at the request, from the same function the
|
|
41
|
+
* command prints from, and carries what that command prints — a pill's name, stage, count and trigger, the notes and
|
|
42
|
+
* problems `nina pills` lists, the words the orchestrator gave a dispatch, each stage's words and the task shapes
|
|
43
|
+
* `nina pipeline` draws — and never a pill's body, a report, a prompt
|
|
44
|
+
* or a whole record. Nothing leaves the machine: the page is served on the loopback address only.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} target - The project directory.
|
|
47
|
+
* @param {{root: string}} ctx - CLI context: where the releases are.
|
|
48
|
+
* @returns {Record<string, () => Promise<object|null>>}
|
|
49
|
+
*/
|
|
50
|
+
export function dashboardTabs(target, ctx) {
|
|
51
|
+
return {
|
|
52
|
+
overview: async () => {
|
|
53
|
+
const profile = readProfile(target).profile ?? {};
|
|
54
|
+
return { project: basename(target), core: profile.core ?? null, surfaces: (profile.surfaces ?? []).slice().sort() };
|
|
55
|
+
},
|
|
56
|
+
tasks: async () => {
|
|
57
|
+
const tasks = taskList(roundsOf(target));
|
|
58
|
+
return { total: tasks.length, tasks: tasks.slice(0, TASKS_SHOWN) };
|
|
59
|
+
},
|
|
60
|
+
pipeline: async () => {
|
|
61
|
+
const data = pipelineData(target);
|
|
62
|
+
if (!data) return null;
|
|
63
|
+
const { graph, ...rest } = data;
|
|
64
|
+
return { ...rest, edges: graph.edges.map(({ from, to, token }) => ({ from, to, token })) };
|
|
65
|
+
},
|
|
66
|
+
pills: async () => pillsData(target, ctx),
|
|
67
|
+
learning: async () => {
|
|
68
|
+
const { profile, error } = readProfile(target);
|
|
69
|
+
if (error) return null;
|
|
70
|
+
const layers = layerRootFor(ctx.root, profile.core);
|
|
71
|
+
if (layers.error) return null;
|
|
72
|
+
const d = await learningData(target, layers.dir, profile, { records: roundsOf(target) });
|
|
73
|
+
return {
|
|
74
|
+
days: d.days,
|
|
75
|
+
runs: d.runs,
|
|
76
|
+
rounds: d.rounds,
|
|
77
|
+
span: d.span,
|
|
78
|
+
late: d.late.map(({ role, count, since, recent }) => ({ role, count, since, recent: recent.map(({ ts, desc }) => ({ ts, desc: desc ?? null })) })),
|
|
79
|
+
active: d.active.length,
|
|
80
|
+
ready: d.ready.map((l) => ({ rel: l.rel, occurrences: l.occurrences })),
|
|
81
|
+
open: d.open.map(({ date, pill, target: layer }) => ({ date, pill, target: layer })),
|
|
82
|
+
owed: d.owed.map(({ lesson, declined }) => ({ rel: lesson.rel, occurrences: lesson.occurrences, declined: declined ? { why: declined.declined, in: declined.answered_in } : null })),
|
|
83
|
+
madeHere: d.madeHere,
|
|
84
|
+
relapses: d.relapses.map(({ lesson, request, recurred, due }) => ({ rel: lesson.rel, recurred, due, target: request.target, rule_in: request.rule_in, answered_in: request.answered_in })),
|
|
85
|
+
use: d.use,
|
|
86
|
+
checks: d.checks.map((c) => (c.comparable ? { rel: c.rel, role: c.role, date: c.date, on: c.on ?? null, before: c.before, after: c.after, change: c.change, control: c.control } : { rel: c.rel, role: c.role, why: c.why })),
|
|
87
|
+
activity: d.activity,
|
|
88
|
+
gated: d.gated,
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
stats: async () => statsData(roundsOf(target), ctx),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Serves the dashboard: the page at `/`, each tab's data at `/api/<tab>`, and the office at `/office/`.
|
|
97
|
+
*
|
|
98
|
+
* @param {{target: string, ctx: {root: string}, port?: number}} options
|
|
99
|
+
* @returns {Promise<{url: string, close: () => Promise<void>}>}
|
|
100
|
+
*/
|
|
101
|
+
export async function serveDashboard({ target, ctx, port = DASHBOARD_PORT }) {
|
|
102
|
+
const tabs = dashboardTabs(target, ctx);
|
|
103
|
+
const room = JSON.stringify({ project: basename(target) }).replace(/</g, '\\u003c');
|
|
104
|
+
const page = readFileSync(PAGE, 'utf8').replace('/*NINA_DASHBOARD*/null', () => room);
|
|
105
|
+
// The office polls its ledger while anyone watches it: made when its tab is first opened, not before.
|
|
106
|
+
let office = null;
|
|
107
|
+
const officeFor = () => {
|
|
108
|
+
if (!office) office = officeRoutes({ target });
|
|
109
|
+
return office;
|
|
110
|
+
};
|
|
111
|
+
const json = (res, status, body) => {
|
|
112
|
+
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' });
|
|
113
|
+
res.end(JSON.stringify(body));
|
|
114
|
+
};
|
|
115
|
+
const server = createServer(async (req, res) => {
|
|
116
|
+
if (refuseElsewhere(req, res)) return;
|
|
117
|
+
const path = new URL(req.url ?? '/', 'http://localhost').pathname;
|
|
118
|
+
if (path === '/') {
|
|
119
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
|
|
120
|
+
res.end(page);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// The office page reaches its stream by a relative `events`, so it is served under a directory.
|
|
124
|
+
if (path === '/office') {
|
|
125
|
+
res.writeHead(301, { location: '/office/' });
|
|
126
|
+
res.end();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (path.startsWith('/office/')) {
|
|
130
|
+
let routes;
|
|
131
|
+
try {
|
|
132
|
+
routes = officeFor();
|
|
133
|
+
} catch (error) {
|
|
134
|
+
res.writeHead(404, { 'content-type': 'text/plain' });
|
|
135
|
+
res.end(`no office here — ${error.message}\n`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (routes.handle(req, res, path.slice('/office'.length))) return;
|
|
139
|
+
}
|
|
140
|
+
const tab = /^\/api\/([a-z]+)$/.exec(path)?.[1];
|
|
141
|
+
if (tab && Object.hasOwn(tabs, tab)) {
|
|
142
|
+
try {
|
|
143
|
+
json(res, 200, await tabs[tab]());
|
|
144
|
+
} catch (error) {
|
|
145
|
+
json(res, 500, { error: error?.message ?? String(error) });
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
res.writeHead(404, { 'content-type': 'text/plain' });
|
|
150
|
+
res.end('not here\n');
|
|
151
|
+
});
|
|
152
|
+
const url = await listenHere(server, port);
|
|
153
|
+
const close = () =>
|
|
154
|
+
new Promise((resolveClose) => {
|
|
155
|
+
office?.close();
|
|
156
|
+
server.close(() => resolveClose());
|
|
157
|
+
});
|
|
158
|
+
return { url, close };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Runs the dashboard until Ctrl+C: serves it, says where for a click, and opens it in the system's browser when asked.
|
|
163
|
+
*
|
|
164
|
+
* @param {{target: string, ctx: {root: string}, port?: number, open?: boolean}} options
|
|
165
|
+
* @returns {Promise<number>} Process exit code.
|
|
166
|
+
*/
|
|
167
|
+
export async function viewDashboard({ target, ctx, port = DASHBOARD_PORT, open = false }) {
|
|
168
|
+
let dashboard;
|
|
169
|
+
try {
|
|
170
|
+
dashboard = await serveDashboard({ target, ctx, port });
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error(` the dashboard could not open a port — ${error?.message ?? error}\n`);
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
const stopped = untilStopped();
|
|
176
|
+
const opened = open ? await openInBrowser(dashboard.url) : false;
|
|
177
|
+
// The address ends its line, with nothing after it, so a terminal makes it a link.
|
|
178
|
+
console.log(` the dashboard · ${basename(target)}`);
|
|
179
|
+
if (open && !opened) console.log(' No browser could be opened from here.');
|
|
180
|
+
console.log(` ${opened ? 'Opened in your browser' : 'Click to open it'}: ${dashboard.url}`);
|
|
181
|
+
console.log(' Ctrl+C closes the dashboard.\n');
|
|
182
|
+
await stopped;
|
|
183
|
+
await dashboard.close();
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
package/src/gate.mjs
CHANGED
|
@@ -628,7 +628,7 @@ function onPost(input, project, path, at) {
|
|
|
628
628
|
const resumed = tool === 'SendMessage' ? Boolean(response.resumedAgentId) : undefined;
|
|
629
629
|
// The name an agent was launched with, if any: a later SendMessage may reach it by that name alone.
|
|
630
630
|
const name = tool !== 'SendMessage' && typeof input.tool_input?.name === 'string' ? input.tool_input.name.slice(0, 80) : null;
|
|
631
|
-
append(path, { k: 'dispatch', at, role, agent, via: tool, id: input.tool_use_id ?? null, ...(name ? { name } : {}), ...(resumed === undefined ? {} : { resumed }) });
|
|
631
|
+
append(path, { k: 'dispatch', at, role, agent, via: tool, id: input.tool_use_id ?? null, ...describedAs(input), ...(name ? { name } : {}), ...(resumed === undefined ? {} : { resumed }) });
|
|
632
632
|
return null;
|
|
633
633
|
}
|
|
634
634
|
|
|
@@ -663,6 +663,18 @@ function confirmation(r) {
|
|
|
663
663
|
};
|
|
664
664
|
}
|
|
665
665
|
|
|
666
|
+
/**
|
|
667
|
+
* The few words the orchestrator gave a dispatch — the `description` the measurement store keeps as `desc` — which
|
|
668
|
+
* the office names a piece of work by. A message to an agent carries none: it adds to work already named.
|
|
669
|
+
*
|
|
670
|
+
* @param {object} input - The hook's input.
|
|
671
|
+
* @returns {{desc?: string}}
|
|
672
|
+
*/
|
|
673
|
+
const describedAs = (input) => {
|
|
674
|
+
const desc = input.tool_name === 'SendMessage' ? null : input.tool_input?.description;
|
|
675
|
+
return typeof desc === 'string' && desc.trim() ? { desc: desc.trim().slice(0, 200) } : {};
|
|
676
|
+
};
|
|
677
|
+
|
|
666
678
|
/** A dispatch is about to go out: note when, and send the round past its edge's cap to the owner to confirm. */
|
|
667
679
|
function onPre(input, project, path, at) {
|
|
668
680
|
// The orchestrator asking the owner, as it asks: when it was, and nothing it said. Nothing counts it; the office
|
|
@@ -681,7 +693,7 @@ function onPre(input, project, path, at) {
|
|
|
681
693
|
// names its agent when the ledger already knows it — never a name the model wrote that nothing launched — so the
|
|
682
694
|
// office can tell a message to a running agent from a resume before the `dispatch` entry is written.
|
|
683
695
|
const known = to?.agent && entries.some((e) => e.k === 'dispatch' && e.agent === to.agent);
|
|
684
|
-
append(path, { k: 'pre', at, role, id: input.tool_use_id ?? null, ...(to ? { via: 'SendMessage', ...(known ? { agent: to.agent } : {}) } : {}) });
|
|
696
|
+
append(path, { k: 'pre', at, role, id: input.tool_use_id ?? null, ...describedAs(input), ...(to ? { via: 'SendMessage', ...(known ? { agent: to.agent } : {}) } : {}) });
|
|
685
697
|
const over = roundsFor(entries, role, project.loops, project.forward)
|
|
686
698
|
.filter((r) => excess(r) > 0)
|
|
687
699
|
.sort((a, b) => excess(b) - excess(a));
|