@xhulz/nina 0.69.0 → 0.71.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/bin/nina.mjs +2 -1
- package/package.json +1 -1
- package/releases/0.70.0/core/GAPS.md +125 -0
- package/releases/0.70.0/core/answered.json +89 -0
- package/releases/0.70.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.70.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.70.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.70.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.70.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.70.0/core/defaults/tree/.claude/agents/qa.md +7 -0
- package/releases/0.70.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.70.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.70.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.70.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.70.0/core/tree/.claude/agents/architect.md +135 -0
- package/releases/0.70.0/core/tree/.claude/agents/dba.md +119 -0
- package/releases/0.70.0/core/tree/.claude/agents/design-reviewer.md +78 -0
- package/releases/0.70.0/core/tree/.claude/agents/devops.md +118 -0
- package/releases/0.70.0/core/tree/.claude/agents/implementer.md +178 -0
- package/releases/0.70.0/core/tree/.claude/agents/integration-tester.md +170 -0
- package/releases/0.70.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.70.0/core/tree/.claude/agents/qa.md +150 -0
- package/releases/0.70.0/core/tree/.claude/agents/reviewer.md +187 -0
- package/releases/0.70.0/core/tree/.claude/agents/secops.md +120 -0
- package/releases/0.70.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.70.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.70.0/core/tree/.claude/agents-overview.md +65 -0
- package/releases/0.70.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.70.0/core/tree/.claude/graph.md +79 -0
- package/releases/0.70.0/core/tree/.claude/patterns.md +252 -0
- package/releases/0.70.0/core/tree/.claude/pills/README.md +150 -0
- package/releases/0.70.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.70.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.70.0/core/tree/.claude/router.md +221 -0
- package/releases/0.70.0/core/tree/.claude/templates/integration.md +48 -0
- package/releases/0.70.0/core/tree/CLAUDE.md +167 -0
- package/releases/0.70.0/core/tree/scripts/cost-watch.mjs +25 -0
- package/releases/0.70.0/core/tree/scripts/edit-guard.mjs +24 -0
- package/releases/0.70.0/core/tree/scripts/harness-check.mjs +82 -0
- package/releases/0.70.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.70.0/core/vocabulary.json +14 -0
- package/releases/0.70.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.70.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.70.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.70.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.70.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.70.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.70.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.70.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.70.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.70.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
- package/releases/0.70.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
- package/releases/0.70.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.70.0/surfaces/d1/tree/.claude/patterns.md +8 -0
- package/releases/0.70.0/surfaces/d1/tree/CLAUDE.md +2 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/patterns.md +22 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.70.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.70.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.70.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
- package/releases/0.70.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
- package/releases/0.70.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.70.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
- package/releases/0.70.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.70.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.70.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
- package/releases/0.70.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.70.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.70.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.70.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.70.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.70.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.70.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.70.0/surfaces/edge-cf/tree/CLAUDE.md +29 -0
- package/releases/0.70.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.70.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.70.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.70.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.70.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.70.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.70.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.70.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.70.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.70.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.70.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.70.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.70.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.70.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.70.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.70.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.70.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.70.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.70.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.70.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.70.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.70.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.70.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.70.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.70.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.70.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.70.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.70.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.70.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.70.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.70.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.70.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.70.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.70.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.70.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.70.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.70.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.70.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.70.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.70.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.70.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.70.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.70.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
- package/releases/0.70.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.70.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.70.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.70.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.70.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.70.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/releases/0.71.0/core/GAPS.md +125 -0
- package/releases/0.71.0/core/answered.json +89 -0
- package/releases/0.71.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.71.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.71.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.71.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.71.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.71.0/core/defaults/tree/.claude/agents/qa.md +7 -0
- package/releases/0.71.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.71.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.71.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.71.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.71.0/core/tree/.claude/agents/architect.md +135 -0
- package/releases/0.71.0/core/tree/.claude/agents/dba.md +119 -0
- package/releases/0.71.0/core/tree/.claude/agents/design-reviewer.md +78 -0
- package/releases/0.71.0/core/tree/.claude/agents/devops.md +118 -0
- package/releases/0.71.0/core/tree/.claude/agents/implementer.md +178 -0
- package/releases/0.71.0/core/tree/.claude/agents/integration-tester.md +170 -0
- package/releases/0.71.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.71.0/core/tree/.claude/agents/qa.md +150 -0
- package/releases/0.71.0/core/tree/.claude/agents/reviewer.md +187 -0
- package/releases/0.71.0/core/tree/.claude/agents/secops.md +120 -0
- package/releases/0.71.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.71.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.71.0/core/tree/.claude/agents-overview.md +65 -0
- package/releases/0.71.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.71.0/core/tree/.claude/graph.md +79 -0
- package/releases/0.71.0/core/tree/.claude/patterns.md +252 -0
- package/releases/0.71.0/core/tree/.claude/pills/README.md +150 -0
- package/releases/0.71.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.71.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.71.0/core/tree/.claude/router.md +221 -0
- package/releases/0.71.0/core/tree/.claude/templates/integration.md +48 -0
- package/releases/0.71.0/core/tree/CLAUDE.md +167 -0
- package/releases/0.71.0/core/tree/scripts/cost-watch.mjs +27 -0
- package/releases/0.71.0/core/tree/scripts/edit-guard.mjs +24 -0
- package/releases/0.71.0/core/tree/scripts/harness-check.mjs +82 -0
- package/releases/0.71.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.71.0/core/vocabulary.json +15 -0
- package/releases/0.71.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.71.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.71.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.71.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.71.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.71.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.71.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.71.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.71.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.71.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
- package/releases/0.71.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
- package/releases/0.71.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.71.0/surfaces/d1/tree/.claude/patterns.md +8 -0
- package/releases/0.71.0/surfaces/d1/tree/CLAUDE.md +2 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/patterns.md +22 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.71.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.71.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.71.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
- package/releases/0.71.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
- package/releases/0.71.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.71.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
- package/releases/0.71.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.71.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.71.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
- package/releases/0.71.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.71.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.71.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.71.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.71.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.71.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.71.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.71.0/surfaces/edge-cf/tree/CLAUDE.md +29 -0
- package/releases/0.71.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.71.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.71.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.71.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.71.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.71.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.71.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.71.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.71.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.71.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.71.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.71.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.71.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.71.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.71.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.71.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.71.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.71.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.71.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.71.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.71.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.71.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.71.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.71.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.71.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.71.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.71.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.71.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.71.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.71.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.71.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.71.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.71.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.71.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.71.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.71.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.71.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.71.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.71.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.71.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.71.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.71.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.71.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
- package/releases/0.71.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.71.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.71.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.71.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.71.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.71.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/src/commands/learn.mjs +44 -13
- package/src/commands/sizes.mjs +94 -24
- package/src/commands/stats.mjs +57 -1
- package/src/commands/upgrade.mjs +3 -3
- package/src/cost.mjs +174 -19
- package/src/dashboard.html +3 -1
- package/src/gate.mjs +10 -4
- package/src/langfuse.mjs +5 -2
- package/src/transcripts.mjs +45 -7
- package/src/wiring.mjs +33 -5
package/src/commands/sizes.mjs
CHANGED
|
@@ -75,6 +75,23 @@ export function forecastIn(text) {
|
|
|
75
75
|
return { lines: Number(m[1].replace(/,/g, '')), files: files ? Number(files[1]) : null };
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* What a step file's `Size:` line says when {@link forecastIn} reads no forecast from it, or null when it reads one or
|
|
80
|
+
* the file has no such line. A step whose line was rewritten into prose — "round 1 landed +498/−95 against ~272" — was
|
|
81
|
+
* left out of every rate without a word. The label opens its line, as `Kind:` does: `font-size:` in a step's CSS and a
|
|
82
|
+
* sentence that names the `Size:` line are no line of their own.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} text - A step file.
|
|
85
|
+
* @returns {string|null} The line after `Size:`, shortened.
|
|
86
|
+
*/
|
|
87
|
+
export function unreadSize(text) {
|
|
88
|
+
if (forecastIn(text)) return null;
|
|
89
|
+
const m = /^[\s>*_-]*Size:[*_ \t]*([^\n]*)/im.exec(text);
|
|
90
|
+
if (!m) return null;
|
|
91
|
+
const said = m[1].trim();
|
|
92
|
+
return said.length > 72 ? `${said.slice(0, 71)}…` : said;
|
|
93
|
+
}
|
|
94
|
+
|
|
78
95
|
/** Runs git in the project, or says it could not. */
|
|
79
96
|
function git(dir, args) {
|
|
80
97
|
const r = spawnSync('git', ['-C', dir, ...args], { encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 });
|
|
@@ -178,7 +195,8 @@ const median = (xs) => {
|
|
|
178
195
|
* Every step with a forecast, landed or not: what it was forecast before it was built, and what its commits added.
|
|
179
196
|
*
|
|
180
197
|
* @param {string} dir - The project.
|
|
181
|
-
* @returns {{steps: object[], limit: number}|null} Null outside a git repository.
|
|
198
|
+
* @returns {{steps: object[], limit: number, unread: object[]}|null} Null outside a git repository. `unread` is each step
|
|
199
|
+
* file whose `Size:` line reads as no forecast, with what it says (`said`).
|
|
182
200
|
*/
|
|
183
201
|
export function measure(dir) {
|
|
184
202
|
const profile = (() => {
|
|
@@ -191,7 +209,7 @@ export function measure(dir) {
|
|
|
191
209
|
const limit = Number(profile?.vocabulary?.STEP_LINES) || Number(defaultVocabulary(PACKAGE).STEP_LINES) || 400;
|
|
192
210
|
// It runs every turn: a project with no step file does not pay for its history.
|
|
193
211
|
const files = stepFiles(dir);
|
|
194
|
-
if (files.length === 0) return git(dir, ['rev-parse', '--verify', '--quiet', 'HEAD']) === null ? null : { steps: [], limit };
|
|
212
|
+
if (files.length === 0) return git(dir, ['rev-parse', '--verify', '--quiet', 'HEAD']) === null ? null : { steps: [], limit, unread: [] };
|
|
195
213
|
const log = git(dir, ['log', '--format=%H%x09%ct%x09%cI%x09%s', 'HEAD']);
|
|
196
214
|
if (log === null) return null;
|
|
197
215
|
// Times as instants: an ISO time carries its zone, and two zones compared as text order wrongly.
|
|
@@ -227,23 +245,36 @@ export function measure(dir) {
|
|
|
227
245
|
return when;
|
|
228
246
|
};
|
|
229
247
|
const steps = [];
|
|
248
|
+
// Step files whose `Size:` line reads as no forecast: left out of every rate, so said rather than skipped.
|
|
249
|
+
const unread = [];
|
|
230
250
|
for (const file of files) {
|
|
231
251
|
const current = readFileSync(join(dir, file.path), 'utf8');
|
|
232
252
|
const now = forecastIn(current);
|
|
233
253
|
const kind = kindIn(current);
|
|
234
254
|
const landings = named.get(file.path).filter((c) => code.get(c.hash)?.size > 0);
|
|
235
255
|
if (landings.length === 0) {
|
|
236
|
-
if (!now)
|
|
256
|
+
if (!now) {
|
|
257
|
+
const said = unreadSize(current);
|
|
258
|
+
if (said !== null) unread.push({ ...file, landed: false, said });
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
237
261
|
// Forecast again in the working tree, it is newer than every commit.
|
|
238
262
|
const since = at('HEAD', file.path)?.lines === now.lines ? writtenAt(file.path, Infinity, now.lines) : Infinity;
|
|
239
|
-
|
|
263
|
+
// What it was first forecast at: a forecast restated at a rate carries that rate over it, and rates multiplied
|
|
264
|
+
// onto one another — another kind's among them — put one project's steps at ×2.54 of their count.
|
|
265
|
+
const original = changes.get(file.path).map((c) => at(c.hash, file.path)).filter(Boolean).at(-1) ?? null;
|
|
266
|
+
steps.push({ ...file, kind, landed: false, forecast: now, since, original: original && original.lines !== now.lines ? original : null });
|
|
240
267
|
continue;
|
|
241
268
|
}
|
|
242
269
|
// The forecast the step was built to: its file before its first commit, or as that commit wrote it.
|
|
243
270
|
const first = landings[0];
|
|
244
271
|
const before = at(`${first.hash}^`, file.path);
|
|
245
272
|
const forecast = before ?? at(first.hash, file.path);
|
|
246
|
-
if (!forecast)
|
|
273
|
+
if (!forecast) {
|
|
274
|
+
const said = unreadSize(text.get(`${first.hash}^:${file.path}`) ?? '') ?? unreadSize(text.get(`${first.hash}:${file.path}`) ?? '');
|
|
275
|
+
if (said !== null) unread.push({ ...file, landed: true, said, hash: first.hash });
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
247
278
|
const since = (before ? writtenAt(file.path, order.get(first.hash), forecast.lines) : null) ?? first.t;
|
|
248
279
|
const lines = new Map();
|
|
249
280
|
for (const c of landings) for (const [path, added] of code.get(c.hash)) lines.set(path, (lines.get(path) ?? 0) + added);
|
|
@@ -272,7 +303,7 @@ export function measure(dir) {
|
|
|
272
303
|
hash: last.hash,
|
|
273
304
|
});
|
|
274
305
|
}
|
|
275
|
-
return { steps, limit };
|
|
306
|
+
return { steps, limit, unread };
|
|
276
307
|
}
|
|
277
308
|
|
|
278
309
|
/**
|
|
@@ -294,6 +325,20 @@ export function rateOf(steps, { spec, since, kind = null }) {
|
|
|
294
325
|
return over.length ? { rate: median(over.map((s) => s.measured ?? s.ratio)), over: over.length, kind: mine.length ? kind : null } : null;
|
|
295
326
|
}
|
|
296
327
|
|
|
328
|
+
/**
|
|
329
|
+
* What a spec's newest landed steps of one kind missed by, whenever they were forecast: set beside a step to land that
|
|
330
|
+
* carries more over its first forecast than its kind ever missed by, which reads as rates carried from elsewhere.
|
|
331
|
+
*
|
|
332
|
+
* @param {object[]} steps
|
|
333
|
+
* @param {{spec: string, kind?: string|null}} step - Its spec and kind.
|
|
334
|
+
* @returns {number|null}
|
|
335
|
+
*/
|
|
336
|
+
export function kindRate(steps, { spec, kind = null }) {
|
|
337
|
+
if (!kind) return null;
|
|
338
|
+
const over = steps.filter((s) => s.landed && s.spec === spec && s.kind === kind).sort((a, b) => a.t - b.t).slice(-RATE_OVER);
|
|
339
|
+
return over.length ? median(over.map((s) => s.measured ?? s.ratio)) : null;
|
|
340
|
+
}
|
|
341
|
+
|
|
297
342
|
/**
|
|
298
343
|
* What the detector says: the newest landed step, when it overran, and the steps of its spec still forecast as they
|
|
299
344
|
* were when it landed — until one of them is forecast again. The next step to go out is forecast again at its turn, and
|
|
@@ -336,7 +381,7 @@ export async function sizes(argv) {
|
|
|
336
381
|
else console.error(' not a git repository, or one with no commit yet: a step is measured by its commits\n');
|
|
337
382
|
return check ? 0 : 1;
|
|
338
383
|
}
|
|
339
|
-
const { steps, limit } = measured;
|
|
384
|
+
const { steps, limit, unread } = measured;
|
|
340
385
|
if (check) {
|
|
341
386
|
// During `nina upgrade --apply`: a step that overran is the project's history, not the move — and the release that
|
|
342
387
|
// first shipped this detector rolled itself back in the project that asked for it. The move says it after.
|
|
@@ -344,30 +389,50 @@ export async function sizes(argv) {
|
|
|
344
389
|
console.log('sizes: current');
|
|
345
390
|
return 0;
|
|
346
391
|
}
|
|
392
|
+
const said = [];
|
|
347
393
|
const found = overrun(measured);
|
|
348
|
-
if (
|
|
394
|
+
if (found) {
|
|
395
|
+
const { step, stale } = found;
|
|
396
|
+
const again = stale.slice(0, 4).map((s) => {
|
|
397
|
+
const rate = rateOf(steps, s);
|
|
398
|
+
const next = Math.round(s.forecast.lines * (rate?.rate ?? 1));
|
|
399
|
+
return `${s.step} ~${s.forecast.lines} → ~${next}${rate?.kind ? ` (${rate.kind})` : ''}${next > limit ? ` past ${limit}` : ''}`;
|
|
400
|
+
});
|
|
401
|
+
const first = step.first === null ? '' : `, ${step.first} in its first round (${times(step.measured)})`;
|
|
402
|
+
said.push(
|
|
403
|
+
`step ${step.step} of ${step.spec} landed ${step.lines} lines against ~${step.forecast.lines} (${times(step.ratio)}${first}); ` +
|
|
404
|
+
`before the next of its ${stale.length} step(s) forecast when it was goes out, forecast that one again — a \`Size:\` line at the rate \`nina sizes\` gives it — ` +
|
|
405
|
+
`and split it if that puts it past ${limit}; the rest at their turn: ${again.join(', ')}${stale.length > again.length ? ', …' : ''}`,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
// Only a step still to land: its line can be written again now, and once it lands, the line it carried is history.
|
|
409
|
+
for (const u of unread.filter((x) => !x.landed)) {
|
|
410
|
+
// "~215 raw lines" opens with a number too: what reads is the forecast it is measured by, followed by `lines`.
|
|
411
|
+
said.push(`step ${u.step} of ${u.spec}: no forecast it can read in its \`Size:\` line ("${u.said}") — write it as \`Size: ~N lines in M files\`, the forecast the step is measured by followed by \`lines\`, before the step lands, or no rate counts it`);
|
|
412
|
+
}
|
|
413
|
+
if (said.length === 0) {
|
|
349
414
|
console.log('sizes: current');
|
|
350
415
|
return 0;
|
|
351
416
|
}
|
|
352
|
-
const
|
|
353
|
-
const again = stale.slice(0, 4).map((s) => {
|
|
354
|
-
const rate = rateOf(steps, s);
|
|
355
|
-
const next = Math.round(s.forecast.lines * (rate?.rate ?? 1));
|
|
356
|
-
return `${s.step} ~${s.forecast.lines} → ~${next}${rate?.kind ? ` (${rate.kind})` : ''}${next > limit ? ` past ${limit}` : ''}`;
|
|
357
|
-
});
|
|
358
|
-
const first = step.first === null ? '' : `, ${step.first} in its first round (${times(step.measured)})`;
|
|
359
|
-
console.log(
|
|
360
|
-
`step ${step.step} of ${step.spec} landed ${step.lines} lines against ~${step.forecast.lines} (${times(step.ratio)}${first}); ` +
|
|
361
|
-
`before the next of its ${stale.length} step(s) forecast when it was goes out, forecast that one again — a \`Size:\` line at the rate \`nina sizes\` gives it — ` +
|
|
362
|
-
`and split it if that puts it past ${limit}; the rest at their turn: ${again.join(', ')}${stale.length > again.length ? ', …' : ''}`,
|
|
363
|
-
);
|
|
417
|
+
for (const line of said) console.log(line);
|
|
364
418
|
return 1;
|
|
365
419
|
}
|
|
366
|
-
if (steps.length === 0) {
|
|
420
|
+
if (steps.length === 0 && unread.length === 0) {
|
|
367
421
|
console.log(` no step file under ${SPECS} states a \`Size:\` line yet\n`);
|
|
368
422
|
return 0;
|
|
369
423
|
}
|
|
370
|
-
|
|
424
|
+
// What a step to land carries over its first forecast, beside what its kind's landed steps missed by: rates multiplied
|
|
425
|
+
// onto one another, another kind's among them, read as one step's own.
|
|
426
|
+
const carried = (s) => {
|
|
427
|
+
if (!s.original) return '';
|
|
428
|
+
const factor = s.forecast.lines / s.original.lines;
|
|
429
|
+
const own = kindRate(steps, s);
|
|
430
|
+
const note = ` · first forecast ~${s.original.lines}, ${times(factor)} since`;
|
|
431
|
+
if (own === null) return dim(note);
|
|
432
|
+
const beside = `${note}; its ${s.kind} steps missed by ${times(own)}`;
|
|
433
|
+
return factor > own * OVERRUN ? amber(beside) : dim(beside);
|
|
434
|
+
};
|
|
435
|
+
for (const spec of [...new Set([...steps, ...unread].map((s) => s.spec))]) {
|
|
371
436
|
const mine = steps.filter((s) => s.spec === spec);
|
|
372
437
|
console.log(`\n ${bold(pink(spec))}${dim(` landed against the forecast it was built to; limit ${limit} lines`)}`);
|
|
373
438
|
for (const s of mine.filter((x) => x.landed).sort((a, b) => a.t - b.t)) {
|
|
@@ -382,16 +447,21 @@ export async function sizes(argv) {
|
|
|
382
447
|
for (const s of mine.filter((x) => !x.landed)) {
|
|
383
448
|
const rate = rateOf(steps, s);
|
|
384
449
|
if (!rate) {
|
|
385
|
-
console.log(` ${s.step.padEnd(10)} ~${String(s.forecast.lines).padEnd(5)} ${dim('to land — forecast after every step that landed, so none measures it yet')}`);
|
|
450
|
+
console.log(` ${s.step.padEnd(10)} ~${String(s.forecast.lines).padEnd(5)} ${dim('to land — forecast after every step that landed, so none measures it yet')}${carried(s)}`);
|
|
386
451
|
continue;
|
|
387
452
|
}
|
|
388
453
|
const next = Math.round(s.forecast.lines * rate.rate);
|
|
389
454
|
console.log(
|
|
390
455
|
` ${s.step.padEnd(10)} ~${String(s.forecast.lines).padEnd(5)} → ~${String(next).padEnd(5)} ` +
|
|
391
456
|
dim(`to land, at ${times(rate.rate)} over ${rate.over} ${rate.kind ? `${rate.kind} ` : ''}step(s) forecast as it was`) +
|
|
392
|
-
(next > limit ? amber(` past ${limit}: split`) : '')
|
|
457
|
+
(next > limit ? amber(` past ${limit}: split`) : '') +
|
|
458
|
+
carried(s),
|
|
393
459
|
);
|
|
394
460
|
}
|
|
461
|
+
for (const u of unread.filter((x) => x.spec === spec)) {
|
|
462
|
+
const where = u.landed ? `before ${u.hash.slice(0, 7)}, the commit that landed it,` : 'to land,';
|
|
463
|
+
console.log(` ${u.step.padEnd(10)} ${amber('no forecast it can read')} ${dim(`— its \`Size:\` line ${where} says "${u.said}", so no rate counts it`)}`);
|
|
464
|
+
}
|
|
395
465
|
}
|
|
396
466
|
console.log('');
|
|
397
467
|
return 0;
|
package/src/commands/stats.mjs
CHANGED
|
@@ -299,6 +299,17 @@ function contextReport(records) {
|
|
|
299
299
|
`${(row.prompt === null ? '—' : row.prompt.toLocaleString('en-US')).padStart(9)}`,
|
|
300
300
|
);
|
|
301
301
|
}
|
|
302
|
+
if (d.reread.length === 0) return;
|
|
303
|
+
const pct = (x) => (x === null ? '—' : `${Math.round(x * 100)}%`);
|
|
304
|
+
console.log('');
|
|
305
|
+
console.log(dim(' what the turns re-read: the context each round opened with, the expired cache written again, the outputs clipped to a file'));
|
|
306
|
+
console.log(dim(` ${'stage'.padEnd(20)}${'rounds'.padStart(6)}${'opened with'.padStart(13)}${'rewrites'.padStart(10)}${'of spend'.padStart(10)}${'clipped'.padStart(9)}`));
|
|
307
|
+
for (const row of d.reread) {
|
|
308
|
+
console.log(
|
|
309
|
+
` ${pink(row.role)}${' '.repeat(Math.max(20 - row.role.length, 1))}${String(row.n).padStart(6)}${pct(row.fixed).padStart(13)}` +
|
|
310
|
+
`${String(row.rewrites).padStart(10)}${pct(row.rewriteShare).padStart(10)}${String(row.clipped).padStart(9)}`,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
302
313
|
}
|
|
303
314
|
|
|
304
315
|
/**
|
|
@@ -334,7 +345,52 @@ export function contextData(records) {
|
|
|
334
345
|
});
|
|
335
346
|
}
|
|
336
347
|
}
|
|
337
|
-
return { rows };
|
|
348
|
+
return { rows, reread: rereadData(shaped) };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Where each stage's re-read went, as data: the share of the context its turns re-read that was what each
|
|
353
|
+
* round opened with — the system prompt, CLAUDE.md, the spec, the dispatch — the share of what it spent that
|
|
354
|
+
* went on writing an expired cache again, and the outputs the cost watch clipped to a file. Rounds measured
|
|
355
|
+
* before the fields existed are left out of the shares rather than counted as none; a stage with no
|
|
356
|
+
* measured round has no row.
|
|
357
|
+
*
|
|
358
|
+
* @param {object[]} shaped - The rounds that recorded their turns.
|
|
359
|
+
* @returns {{role: string, n: number, fixed: number|null, rewrites: number, rewriteShare: number|null, clipped: number}[]}
|
|
360
|
+
*/
|
|
361
|
+
export function rereadData(shaped) {
|
|
362
|
+
const measured = shaped.filter((r) => typeof r.cache_rewrites === 'number');
|
|
363
|
+
const byRole = new Map();
|
|
364
|
+
for (const r of measured) byRole.set(r.role, [...(byRole.get(r.role) ?? []), r]);
|
|
365
|
+
const t = (n) => (typeof n === 'number' ? n : 0);
|
|
366
|
+
const rows = [];
|
|
367
|
+
for (const [role, rounds] of byRole) {
|
|
368
|
+
let opened = 0;
|
|
369
|
+
let reread = 0;
|
|
370
|
+
let rewriteCost = 0;
|
|
371
|
+
let spent = 0;
|
|
372
|
+
for (const r of rounds) {
|
|
373
|
+
const total = t(r.tokens?.input) + t(r.tokens?.read) + t(r.tokens?.write_5m) + t(r.tokens?.write_1h);
|
|
374
|
+
if (total > 0 && typeof r.context_start === 'number') {
|
|
375
|
+
opened += Math.min(r.context_start * r.turns, total);
|
|
376
|
+
reread += total;
|
|
377
|
+
}
|
|
378
|
+
const cost = costOf(r.tokens, r.usage_model);
|
|
379
|
+
if (cost !== null && cost > 0) {
|
|
380
|
+
spent += cost;
|
|
381
|
+
rewriteCost += costOf({ write_5m: t(r.cache_rewritten) }, r.usage_model);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
rows.push({
|
|
385
|
+
role,
|
|
386
|
+
n: rounds.length,
|
|
387
|
+
fixed: reread > 0 ? opened / reread : null,
|
|
388
|
+
rewrites: rounds.reduce((a, r) => a + t(r.cache_rewrites), 0),
|
|
389
|
+
rewriteShare: spent > 0 ? rewriteCost / spent : null,
|
|
390
|
+
clipped: rounds.reduce((a, r) => a + t(r.clipped), 0),
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return rows.sort((a, b) => b.n - a.n);
|
|
338
394
|
}
|
|
339
395
|
|
|
340
396
|
/**
|
package/src/commands/upgrade.mjs
CHANGED
|
@@ -366,9 +366,9 @@ export async function upgrade(argv, ctx) {
|
|
|
366
366
|
const newScripts = new Set([...(await shippedScripts(onto.dir, kept))].filter((s) => !scriptsBefore.has(s)));
|
|
367
367
|
const unwired = newScripts.size > 0 ? await missingWiring(target, newScripts) : [];
|
|
368
368
|
// A script already run whose hooks this NINA needs more of — a matcher that grew — holds what it held, so it does
|
|
369
|
-
// not stop the move; it is said, with what fixes it.
|
|
369
|
+
// not stop the move; it is said, with what fixes it. So is a setting it now ships beside them.
|
|
370
370
|
const stillRun = new Set([...(await shippedScripts(onto.dir, kept))].filter((s) => scriptsBefore.has(s)));
|
|
371
|
-
const widened = stillRun.size > 0 ? (await missingWiring(target, stillRun)).filter((w) => / hook running `[^`]+` does not match /.test(w)) : [];
|
|
371
|
+
const widened = stillRun.size > 0 ? (await missingWiring(target, stillRun)).filter((w) => / hook running `[^`]+` does not match /.test(w) || /^\.claude\/settings\.json has no "/.test(w)) : [];
|
|
372
372
|
if (widened.length > 0) {
|
|
373
373
|
for (const w of widened) console.log(` · ${w}`);
|
|
374
374
|
console.log('');
|
|
@@ -633,7 +633,7 @@ export async function upgrade(argv, ctx) {
|
|
|
633
633
|
}
|
|
634
634
|
const forecasts = nina(ctx, target, ['sizes', '--project', target, '--check']);
|
|
635
635
|
if (!forecasts.ok && forecasts.out.trim()) {
|
|
636
|
-
console.log('\n
|
|
636
|
+
console.log('\n the step forecasts have something to act on — not caused by this move, now visible:');
|
|
637
637
|
for (const line of forecasts.out.trim().split('\n')) console.log(` ${line}`);
|
|
638
638
|
}
|
|
639
639
|
// The move is done and the tree is sound, but it is not finished: until these are written
|
package/src/cost.mjs
CHANGED
|
@@ -11,11 +11,19 @@
|
|
|
11
11
|
*
|
|
12
12
|
* It reads only what the run's transcript gained since its last call, keeps counts per message and
|
|
13
13
|
* nothing else, under the project's gate directory, and lets every call through if anything goes wrong.
|
|
14
|
+
*
|
|
15
|
+
* Two things a run pays for on every turn after are settled at the call itself. A shell command's long
|
|
16
|
+
* output stays in the context and is re-read on every turn — outputs longer than a screen were a fifth of
|
|
17
|
+
* what two projects' stages re-read — so past `clipAt` characters it goes to a file beside the ones Claude
|
|
18
|
+
* Code keeps for the session, and the run sees its first and last lines and where the rest is. And a call
|
|
19
|
+
* that ran past the five minutes a subagent's cache lives makes the next turn write the whole context again,
|
|
20
|
+
* at twelve times a read's price — a quarter of what one project's stages spent — so the run is told then.
|
|
14
21
|
*/
|
|
15
22
|
|
|
16
23
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
17
24
|
import { dirname, join } from 'node:path';
|
|
18
25
|
import { projectGateDir } from './gate.mjs';
|
|
26
|
+
import { CLIP_MARK } from './transcripts.mjs';
|
|
19
27
|
|
|
20
28
|
/** Where a run's counts are kept between calls, one file per agent. */
|
|
21
29
|
const stateDir = (root) => join(projectGateDir(root), 'cost');
|
|
@@ -45,18 +53,168 @@ function gained(path, from) {
|
|
|
45
53
|
return { text: buffer.subarray(0, end + 1).toString('utf8'), to: from + end + 1 };
|
|
46
54
|
}
|
|
47
55
|
|
|
56
|
+
/** How long a subagent's prompt cache lives without a turn: a call that runs past it is paid for again in full. */
|
|
57
|
+
const CACHE_MS = 5 * 60_000;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Whether a shell command reads a file or a diff rather than producing output: what it prints is what the
|
|
61
|
+
* run asked to read, so clipping it would only send the run to read the file it was cut to. What a command
|
|
62
|
+
* line prints last is its last command's, and of a pipe its last stage's, so that is the one judged:
|
|
63
|
+
* `sed -i … && node run` produces output, and `cd p && cat f` or `node run && tail -n 50 log` reads.
|
|
64
|
+
*
|
|
65
|
+
* @param {unknown} command - The Bash call's command.
|
|
66
|
+
* @returns {boolean}
|
|
67
|
+
*/
|
|
68
|
+
export function readsByShell(command) {
|
|
69
|
+
if (typeof command !== 'string') return false;
|
|
70
|
+
// Read as words, never by a pattern that backtracks: the command is the model's, of any length.
|
|
71
|
+
const assignment = (word) => /^\w+=/.test(word);
|
|
72
|
+
// A subshell's parentheses are not part of the words they hold.
|
|
73
|
+
const unwrap = (word) => {
|
|
74
|
+
let from = 0;
|
|
75
|
+
let to = word.length;
|
|
76
|
+
while (from < to && word[from] === '(') from += 1;
|
|
77
|
+
while (to > from && word[to - 1] === ')') to -= 1;
|
|
78
|
+
return word.slice(from, to);
|
|
79
|
+
};
|
|
80
|
+
const segments = command
|
|
81
|
+
.split(/&&|\|\||;|\n/)
|
|
82
|
+
.map((segment) => segment.split(/\s+/).map(unwrap).filter(Boolean))
|
|
83
|
+
// A segment of assignments alone prints nothing.
|
|
84
|
+
.filter((words) => words.length && !words.every(assignment));
|
|
85
|
+
const piped = (segments.at(-1) ?? []).join(' ').split('|').at(-1);
|
|
86
|
+
const words = piped.split(/\s+/).filter(Boolean);
|
|
87
|
+
// Less what runs a command rather than being it: assignments, `export NAME=…`, `timeout N`, `nice`, `command`.
|
|
88
|
+
let i = 0;
|
|
89
|
+
while (i < words.length) {
|
|
90
|
+
if (assignment(words[i]) || words[i] === 'nice' || words[i] === 'command') i += 1;
|
|
91
|
+
else if ((words[i] === 'export' && assignment(words[i + 1] ?? '')) || (words[i] === 'timeout' && words[i + 1])) i += 2;
|
|
92
|
+
else break;
|
|
93
|
+
}
|
|
94
|
+
if (['cat', 'sed', 'head', 'tail', 'nl', 'awk', 'less', 'more'].includes(words[i])) return true;
|
|
95
|
+
if (words[i] !== 'git') return false;
|
|
96
|
+
// `git diff` and `git show`, past the options that come before a subcommand.
|
|
97
|
+
i += 1;
|
|
98
|
+
while (words[i] === '--no-pager' || words[i] === '-C' || words[i] === '-c') i += words[i] === '--no-pager' ? 1 : 2;
|
|
99
|
+
return words[i] === 'diff' || words[i] === 'show';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The first lines of a text up to a size, or its last ones; a single line longer than the size is cut.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} text - The output.
|
|
106
|
+
* @param {number} size - Characters at most.
|
|
107
|
+
* @param {boolean} fromEnd - The last lines rather than the first.
|
|
108
|
+
* @returns {string}
|
|
109
|
+
*/
|
|
110
|
+
function lines(text, size, fromEnd) {
|
|
111
|
+
const all = text.split('\n');
|
|
112
|
+
const kept = [];
|
|
113
|
+
let length = 0;
|
|
114
|
+
for (const line of fromEnd ? all.reverse() : all) {
|
|
115
|
+
if (length + line.length + 1 > size) break;
|
|
116
|
+
kept.push(line);
|
|
117
|
+
length += line.length + 1;
|
|
118
|
+
}
|
|
119
|
+
if (kept.length === 0) return fromEnd ? text.slice(-size) : text.slice(0, size);
|
|
120
|
+
return (fromEnd ? kept.reverse() : kept).join('\n');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* A long Bash output clipped: the whole of it written to a file in the session's `tool-results` directory,
|
|
125
|
+
* where Claude Code keeps the outputs it moves out itself and a stage may read, and in its place the first
|
|
126
|
+
* lines, the last ones — where a test runner or a battery prints its summary — and where the rest is.
|
|
127
|
+
* Null when the output is short, the command reads a file, or the file cannot be written: the run then sees
|
|
128
|
+
* the output as it was.
|
|
129
|
+
*
|
|
130
|
+
* @param {object} input - The PostToolUse hook's input.
|
|
131
|
+
* @param {number} clipAt - The characters past which an output is clipped; 0 clips nothing.
|
|
132
|
+
* @returns {{updatedToolOutput: object, chars: number}|null}
|
|
133
|
+
*/
|
|
134
|
+
export function clipOutput(input, clipAt) {
|
|
135
|
+
const response = input?.tool_response;
|
|
136
|
+
if (input?.tool_name !== 'Bash' || !(clipAt > 0) || response === null || typeof response !== 'object' || typeof response.stdout !== 'string') return null;
|
|
137
|
+
const stderr = typeof response.stderr === 'string' ? response.stderr : '';
|
|
138
|
+
const chars = response.stdout.length + stderr.length;
|
|
139
|
+
if (chars <= clipAt || response.isImage || readsByShell(input.tool_input?.command)) return null;
|
|
140
|
+
const id = /^[A-Za-z0-9_-]{1,80}$/.test(String(input.tool_use_id ?? '')) ? input.tool_use_id : `${Date.now()}`;
|
|
141
|
+
const file = join(dirname(input.transcript_path), input.session_id, 'tool-results', `nina-${id}.txt`);
|
|
142
|
+
const whole = stderr ? `${response.stdout}\n--- stderr ---\n${stderr}` : response.stdout;
|
|
143
|
+
try {
|
|
144
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
145
|
+
writeFileSync(file, whole);
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const said =
|
|
150
|
+
`${CLIP_MARK}: ${whole.split('\n').length.toLocaleString('en-US')} lines and ${chars.toLocaleString('en-US')} characters, of which the first and last lines are shown. ` +
|
|
151
|
+
`The whole of it${stderr ? ', stderr after stdout,' : ''} is in ${file} — grep it, or read the range you need: read whole, it all comes back into your context and is re-read on every turn after.]`;
|
|
152
|
+
// A stdout short enough to show whole is shown once, the notice after it: only what is long is cut.
|
|
153
|
+
const head = Math.round(clipAt / 8);
|
|
154
|
+
const tail = Math.round((clipAt * 3) / 8);
|
|
155
|
+
const stdout = response.stdout.length > head + tail ? `${lines(response.stdout, head, false)}\n…\n${said}\n…\n${lines(response.stdout, tail, true)}` : `${response.stdout}\n${said}`;
|
|
156
|
+
return {
|
|
157
|
+
chars,
|
|
158
|
+
updatedToolOutput: { ...response, stdout, stderr: stderr ? lines(stderr, head, true) : stderr },
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
48
162
|
/**
|
|
49
163
|
* What one call of the watch says, or null: the counts are brought up to date from the run's transcript,
|
|
50
164
|
* and a warning is due each time the total re-read crosses `warnAt`, then twice it, four times it, and on.
|
|
165
|
+
* A long Bash output is clipped to a file ({@link clipOutput}), and a call that outlived the cache is named.
|
|
51
166
|
*
|
|
52
167
|
* @param {object} input - The PostToolUse hook's input.
|
|
53
|
-
* @param {{root: string, warnAt: number, now?: number}} options
|
|
168
|
+
* @param {{root: string, warnAt: number, clipAt?: number, now?: number}} options
|
|
54
169
|
* @returns {object|null} The hook's answer.
|
|
55
170
|
*/
|
|
56
|
-
export function watch(input, { root, warnAt, now = Date.now() }) {
|
|
171
|
+
export function watch(input, { root, warnAt, clipAt = 0, now = Date.now() }) {
|
|
57
172
|
// Only inside a subagent, which is where `agent_id` is set; and an id that could not be a file name is not one.
|
|
58
173
|
if (input?.hook_event_name !== 'PostToolUse' || !/^[A-Za-z0-9_-]{1,64}$/.test(String(input.agent_id ?? ''))) return null;
|
|
59
|
-
|
|
174
|
+
// The session's id becomes a directory name below, in the clip's path and the transcript's.
|
|
175
|
+
if (typeof input.transcript_path !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(String(input.session_id ?? ''))) return null;
|
|
176
|
+
const clipped = clipOutput(input, clipAt);
|
|
177
|
+
const counted = warnAt > 0 ? count(input, { root, warnAt, now }) : null;
|
|
178
|
+
const ran = typeof input.duration_ms === 'number' && input.duration_ms > CACHE_MS ? input.duration_ms : null;
|
|
179
|
+
if (!clipped && !counted?.due && ran === null) return null;
|
|
180
|
+
const size = (n) => (n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : `${Math.round(n / 1000)}k`);
|
|
181
|
+
const context = [];
|
|
182
|
+
const person = [];
|
|
183
|
+
if (counted?.due) {
|
|
184
|
+
context.push(
|
|
185
|
+
`NINA cost watch: this run has re-read ${counted.total} tokens of its own context${counted.turn}; every further turn costs more than the last. ` +
|
|
186
|
+
'If what is left is more than one run should carry, stop where your work is whole, hand back what you have, and name what is left, so the orchestrator can send it as its own step. Otherwise finish without widening the work.',
|
|
187
|
+
);
|
|
188
|
+
person.push(`${input.agent_type ?? 'a subagent'} ${input.agent_id.slice(0, 8)} has re-read ${counted.total} tokens of its own context${counted.turn}.`);
|
|
189
|
+
}
|
|
190
|
+
if (ran !== null) {
|
|
191
|
+
const minutes = Math.round(ran / 60_000);
|
|
192
|
+
const whole = counted?.context ? `, about ${size(counted.context)} tokens,` : '';
|
|
193
|
+
context.push(
|
|
194
|
+
`NINA cost watch: that call ran ${minutes} minutes, past the five your context stays cached between turns, so your next turn writes all of it${whole} again — at twelve times what re-reading it costs. ` +
|
|
195
|
+
'Wait in calls that each end within three minutes: run the rest in shares, or start it in the background and check on it no more often than every three minutes (Hard Rule #19).',
|
|
196
|
+
);
|
|
197
|
+
person.push(`${input.agent_type ?? 'a subagent'} ${input.agent_id.slice(0, 8)} waited ${minutes} min in one call; its next turn writes its whole context again.`);
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
...(person.length ? { systemMessage: `NINA: ${person.join(' ')}` } : {}),
|
|
201
|
+
hookSpecificOutput: {
|
|
202
|
+
hookEventName: 'PostToolUse',
|
|
203
|
+
...(context.length ? { additionalContext: context.join('\n\n') } : {}),
|
|
204
|
+
...(clipped ? { updatedToolOutput: clipped.updatedToolOutput } : {}),
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The run's re-read brought up to date from its transcript, and whether a warning is due; null when the
|
|
211
|
+
* transcript is not there.
|
|
212
|
+
*
|
|
213
|
+
* @param {object} input - The PostToolUse hook's input.
|
|
214
|
+
* @param {{root: string, warnAt: number, now: number}} options
|
|
215
|
+
* @returns {{due: boolean, total: string, turn: string, context: number|null}|null}
|
|
216
|
+
*/
|
|
217
|
+
function count(input, { root, warnAt, now }) {
|
|
60
218
|
const transcript = join(dirname(input.transcript_path), input.session_id, 'subagents', `agent-${input.agent_id}.jsonl`);
|
|
61
219
|
if (!existsSync(transcript)) return null;
|
|
62
220
|
const dir = stateDir(root);
|
|
@@ -69,6 +227,7 @@ export function watch(input, { root, warnAt, now = Date.now() }) {
|
|
|
69
227
|
}
|
|
70
228
|
const { text, to } = gained(transcript, state.offset);
|
|
71
229
|
let last = null;
|
|
230
|
+
const n = (v) => (typeof v === 'number' ? v : 0);
|
|
72
231
|
for (const line of text.split('\n')) {
|
|
73
232
|
if (!line.includes('"cache_read_input_tokens"')) continue;
|
|
74
233
|
try {
|
|
@@ -78,6 +237,8 @@ export function watch(input, { root, warnAt, now = Date.now() }) {
|
|
|
78
237
|
if (message?.id && typeof read === 'number') {
|
|
79
238
|
state.read[message.id] = read;
|
|
80
239
|
last = read;
|
|
240
|
+
// The whole context that turn held, which a turn after an expired cache writes again.
|
|
241
|
+
state.context = read + n(message.usage.cache_creation_input_tokens) + n(message.usage.input_tokens);
|
|
81
242
|
}
|
|
82
243
|
} catch {
|
|
83
244
|
// A torn line loses one message's count.
|
|
@@ -92,18 +253,12 @@ export function watch(input, { root, warnAt, now = Date.now() }) {
|
|
|
92
253
|
state.warned = crossed;
|
|
93
254
|
mkdirSync(dir, { recursive: true });
|
|
94
255
|
writeFileSync(file, JSON.stringify(state));
|
|
95
|
-
|
|
96
|
-
const m = (n) => `${Math.round(n / 1e6)}M`;
|
|
97
|
-
const size = (n) => (n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : `${Math.round(n / 1000)}k`);
|
|
98
|
-
const turn = last === null ? '' : `, and each turn now re-reads about ${size(last)} tokens more`;
|
|
256
|
+
const size = (v) => (v >= 1e6 ? `${(v / 1e6).toFixed(1)}M` : `${Math.round(v / 1000)}k`);
|
|
99
257
|
return {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
`NINA cost watch: this run has re-read ${m(total)} tokens of its own context${turn}; every further turn costs more than the last. ` +
|
|
105
|
-
'If what is left is more than one run should carry, stop where your work is whole, hand back what you have, and name what is left, so the orchestrator can send it as its own step. Otherwise finish without widening the work.',
|
|
106
|
-
},
|
|
258
|
+
due,
|
|
259
|
+
total: `${Math.round(total / 1e6)}M`,
|
|
260
|
+
turn: last === null ? '' : `, and each turn now re-reads about ${size(last)} tokens more`,
|
|
261
|
+
context: typeof state.context === 'number' ? state.context : null,
|
|
107
262
|
};
|
|
108
263
|
}
|
|
109
264
|
|
|
@@ -120,18 +275,18 @@ function prune(root, now) {
|
|
|
120
275
|
}
|
|
121
276
|
|
|
122
277
|
/**
|
|
123
|
-
* The hook: reads the event from stdin, answers on stdout when a warning is due
|
|
124
|
-
* happens — a hook that fails must not stop the run it watches.
|
|
278
|
+
* The hook: reads the event from stdin, answers on stdout when a warning is due or an output is clipped,
|
|
279
|
+
* and exits 0 whatever happens — a hook that fails must not stop the run it watches.
|
|
125
280
|
*
|
|
126
|
-
* @param {{root: string, warnAt: number, stdin?: NodeJS.ReadableStream, stdout?: NodeJS.WritableStream}} options
|
|
281
|
+
* @param {{root: string, warnAt: number, clipAt?: number, stdin?: NodeJS.ReadableStream, stdout?: NodeJS.WritableStream}} options
|
|
127
282
|
* @returns {Promise<number>}
|
|
128
283
|
*/
|
|
129
|
-
export async function runCostWatch({ root, warnAt, stdin = process.stdin, stdout = process.stdout }) {
|
|
284
|
+
export async function runCostWatch({ root, warnAt, clipAt = 0, stdin = process.stdin, stdout = process.stdout }) {
|
|
130
285
|
try {
|
|
131
286
|
const chunks = [];
|
|
132
287
|
for await (const chunk of stdin) chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
133
288
|
const now = Date.now();
|
|
134
|
-
const answer = watch(JSON.parse(Buffer.concat(chunks).toString('utf8')), { root, warnAt, now });
|
|
289
|
+
const answer = watch(JSON.parse(Buffer.concat(chunks).toString('utf8')), { root, warnAt, clipAt, now });
|
|
135
290
|
if (answer) stdout.write(`${JSON.stringify(answer)}\n`);
|
|
136
291
|
if (Math.random() < 0.01) prune(root, now);
|
|
137
292
|
} catch {
|
package/src/dashboard.html
CHANGED
|
@@ -362,7 +362,9 @@ const RENDER = {
|
|
|
362
362
|
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]),
|
|
363
363
|
d.cost.unpriced ? el('p', { class: 'note faint' }, `${d.cost.unpriced} run(s) on a model the price table does not know.`) : null] : null,
|
|
364
364
|
d.context ? [el('h3', {}, 'context · every turn re-reads it all'),
|
|
365
|
-
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])
|
|
365
|
+
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]),
|
|
366
|
+
d.context.reread?.length ? el('h3', {}, 'what the turns re-read') : null,
|
|
367
|
+
d.context.reread?.length ? table(['stage', 'rounds', 'opened with', 'rewrites', 'of spend', 'clipped'], d.context.reread.map((r) => [el('span', { class: 'role' }, r.role), String(r.n), r.fixed === null ? '—' : `${Math.round(r.fixed * 100)}%`, String(r.rewrites), r.rewriteShare === null ? '—' : `${Math.round(r.rewriteShare * 100)}%`, String(r.clipped)]), [1, 2, 3, 4, 5]) : null] : null,
|
|
366
368
|
d.proportion ? [el('h3', {}, 'proportion · the size of a change against its chain'),
|
|
367
369
|
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]),
|
|
368
370
|
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,
|
package/src/gate.mjs
CHANGED
|
@@ -268,6 +268,8 @@ function append(path, entry) {
|
|
|
268
268
|
*
|
|
269
269
|
* - a rejection is cancelled by a pass launched after a fix went out for it: that review saw the fix. A
|
|
270
270
|
* sibling launched with it, or a reviewer launched after it that saw no fix, cancels nothing;
|
|
271
|
+
* - a rejection is withdrawn by a later completion of the agent that made it, passing with no fix sent between:
|
|
272
|
+
* the same review judged again, which nothing else would settle;
|
|
271
273
|
* - a rejection from a review that was already running when the edge's last round went out belongs to
|
|
272
274
|
* that round, and makes no new one;
|
|
273
275
|
* - the rest make this dispatch a round on the edge: one more than the rounds since the loop last
|
|
@@ -335,12 +337,15 @@ export function replay(entries, loops, forward = new Map()) {
|
|
|
335
337
|
};
|
|
336
338
|
|
|
337
339
|
/**
|
|
338
|
-
* A source's waiting verdicts, settled: the rejections no later review of a fix cancelled
|
|
339
|
-
* closes its loops — one that began after the latest fix, with no
|
|
340
|
+
* A source's waiting verdicts, settled: the rejections no later review of a fix cancelled and no later completion of
|
|
341
|
+
* their own agent withdrew, and whether a pass closes its loops — one that began after the latest fix, with no
|
|
342
|
+
* rejection running beside it.
|
|
340
343
|
*/
|
|
341
344
|
const settle = (source, batch) => {
|
|
342
345
|
const passes = batch.filter((v) => !isLoopBack(v.verdict));
|
|
343
|
-
const live = batch.filter(
|
|
346
|
+
const live = batch.filter(
|
|
347
|
+
(v) => isLoopBack(v.verdict) && !passes.some((p) => p.launch > v.at && (p.agent === v.agent || fixBetween(source, v.at, p.launch))),
|
|
348
|
+
);
|
|
344
349
|
return { live, closes: passes.some((p) => p.launch > (lastFix.get(source) ?? -1) && !live.some((v) => v.launch < p.at)) };
|
|
345
350
|
};
|
|
346
351
|
|
|
@@ -461,7 +466,8 @@ export function replay(entries, loops, forward = new Map()) {
|
|
|
461
466
|
const onward = [...forward].filter(([, sources]) => sources.has(source)).map(([stage]) => stage);
|
|
462
467
|
return { source, token, rounds: s.n, cap, given: grace.get(key) ?? 0, issues, closing, targets: [...targetsOf(source)], onward };
|
|
463
468
|
});
|
|
464
|
-
|
|
469
|
+
// A rejection its own agent withdrew waits on no fix: counted as waiting, the next session inherited it.
|
|
470
|
+
const waiting = [...pending.entries()].flatMap(([source, batch]) => settle(source, batch).live.map((v) => ({ source, verdict: v.verdict, issues: v.issues ?? [], index: v.at })));
|
|
465
471
|
return { effect: (to) => effect(to, entries.length), rounds: made, open, waiting };
|
|
466
472
|
}
|
|
467
473
|
|
package/src/langfuse.mjs
CHANGED
|
@@ -112,8 +112,11 @@ export function contextMode(setting) {
|
|
|
112
112
|
return setting?.content === 'prompts' ? 'prompts' : setting?.content ? 'full' : null;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
/**
|
|
116
|
-
|
|
115
|
+
/**
|
|
116
|
+
* A round's shape, as metadata on its root: how many turns and calls it took, how large its context and prompt
|
|
117
|
+
* were, how often it wrote an expired cache again and how much, and how many outputs the cost watch clipped.
|
|
118
|
+
*/
|
|
119
|
+
const SHAPE = ['turns', 'tool_calls', 'context_start', 'context_peak', 'prompt_chars', 'cache_rewrites', 'cache_rewritten', 'clipped'];
|
|
117
120
|
|
|
118
121
|
/**
|
|
119
122
|
* The OTLP span at the root of a dispatch's trace. Without its context, a run that spent tokens is a
|