@xhulz/nina 0.55.1 → 0.57.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/package.json +1 -1
- package/releases/0.56.0/core/GAPS.md +125 -0
- package/releases/0.56.0/core/answered.json +68 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/qa.md +7 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.56.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.56.0/core/tree/.claude/agents/architect.md +130 -0
- package/releases/0.56.0/core/tree/.claude/agents/dba.md +119 -0
- package/releases/0.56.0/core/tree/.claude/agents/design-reviewer.md +75 -0
- package/releases/0.56.0/core/tree/.claude/agents/devops.md +118 -0
- package/releases/0.56.0/core/tree/.claude/agents/implementer.md +163 -0
- package/releases/0.56.0/core/tree/.claude/agents/integration-tester.md +170 -0
- package/releases/0.56.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.56.0/core/tree/.claude/agents/qa.md +150 -0
- package/releases/0.56.0/core/tree/.claude/agents/reviewer.md +187 -0
- package/releases/0.56.0/core/tree/.claude/agents/secops.md +120 -0
- package/releases/0.56.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.56.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.56.0/core/tree/.claude/agents-overview.md +65 -0
- package/releases/0.56.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.56.0/core/tree/.claude/graph.md +79 -0
- package/releases/0.56.0/core/tree/.claude/patterns.md +251 -0
- package/releases/0.56.0/core/tree/.claude/pills/README.md +150 -0
- package/releases/0.56.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.56.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.56.0/core/tree/.claude/router.md +205 -0
- package/releases/0.56.0/core/tree/.claude/templates/integration.md +48 -0
- package/releases/0.56.0/core/tree/CLAUDE.md +166 -0
- package/releases/0.56.0/core/tree/scripts/cost-watch.mjs +25 -0
- package/releases/0.56.0/core/tree/scripts/edit-guard.mjs +24 -0
- package/releases/0.56.0/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.56.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.56.0/core/vocabulary.json +14 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.56.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.56.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.56.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
- package/releases/0.56.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
- package/releases/0.56.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.56.0/surfaces/d1/tree/.claude/patterns.md +8 -0
- package/releases/0.56.0/surfaces/d1/tree/CLAUDE.md +2 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/patterns.md +22 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.56.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.56.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.56.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
- package/releases/0.56.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
- package/releases/0.56.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.56.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.56.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.56.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.56.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.56.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.56.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.56.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.56.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.56.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.56.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.56.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.56.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.56.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.56.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.56.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.56.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/releases/0.57.0/core/GAPS.md +125 -0
- package/releases/0.57.0/core/answered.json +68 -0
- package/releases/0.57.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.57.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.57.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.57.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.57.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.57.0/core/defaults/tree/.claude/agents/qa.md +7 -0
- package/releases/0.57.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.57.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.57.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.57.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.57.0/core/tree/.claude/agents/architect.md +130 -0
- package/releases/0.57.0/core/tree/.claude/agents/dba.md +119 -0
- package/releases/0.57.0/core/tree/.claude/agents/design-reviewer.md +75 -0
- package/releases/0.57.0/core/tree/.claude/agents/devops.md +118 -0
- package/releases/0.57.0/core/tree/.claude/agents/implementer.md +163 -0
- package/releases/0.57.0/core/tree/.claude/agents/integration-tester.md +170 -0
- package/releases/0.57.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.57.0/core/tree/.claude/agents/qa.md +150 -0
- package/releases/0.57.0/core/tree/.claude/agents/reviewer.md +187 -0
- package/releases/0.57.0/core/tree/.claude/agents/secops.md +120 -0
- package/releases/0.57.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.57.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.57.0/core/tree/.claude/agents-overview.md +65 -0
- package/releases/0.57.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.57.0/core/tree/.claude/graph.md +79 -0
- package/releases/0.57.0/core/tree/.claude/patterns.md +251 -0
- package/releases/0.57.0/core/tree/.claude/pills/README.md +150 -0
- package/releases/0.57.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.57.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.57.0/core/tree/.claude/router.md +205 -0
- package/releases/0.57.0/core/tree/.claude/templates/integration.md +48 -0
- package/releases/0.57.0/core/tree/CLAUDE.md +166 -0
- package/releases/0.57.0/core/tree/scripts/cost-watch.mjs +25 -0
- package/releases/0.57.0/core/tree/scripts/edit-guard.mjs +24 -0
- package/releases/0.57.0/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.57.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.57.0/core/vocabulary.json +14 -0
- package/releases/0.57.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.57.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.57.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.57.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.57.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.57.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.57.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.57.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.57.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.57.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
- package/releases/0.57.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
- package/releases/0.57.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.57.0/surfaces/d1/tree/.claude/patterns.md +8 -0
- package/releases/0.57.0/surfaces/d1/tree/CLAUDE.md +2 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/patterns.md +22 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.57.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.57.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.57.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
- package/releases/0.57.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
- package/releases/0.57.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.57.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
- package/releases/0.57.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.57.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.57.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
- package/releases/0.57.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.57.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.57.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.57.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.57.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.57.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.57.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.57.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.57.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.57.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.57.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.57.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.57.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.57.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.57.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.57.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.57.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.57.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.57.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.57.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.57.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.57.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.57.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.57.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.57.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.57.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.57.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.57.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.57.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.57.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.57.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.57.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.57.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.57.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.57.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.57.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.57.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.57.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.57.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.57.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.57.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.57.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.57.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.57.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.57.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.57.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.57.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.57.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.57.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.57.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.57.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
- package/releases/0.57.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.57.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.57.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.57.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.57.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.57.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/src/commands/export.mjs +5 -5
- package/src/commands/langfuse.mjs +30 -1
- package/src/commands/stats.mjs +7 -0
- package/src/dashboard.html +2 -1
- package/src/transcripts.mjs +227 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<!-- nina:slot money.1 -->
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Monetary values & percentages
|
|
6
|
+
|
|
7
|
+
- **Money is an integer of the currency's minor unit**, end to end — {{MINOR_UNIT}}, never a float, never a type that can hold a fraction of one. Source of precision: `{{MONEY_MODULE}}`.
|
|
8
|
+
- **Percentages are basis points** (`40% → 4000`, `100% → 10000`) so a distribution is exact without floating point.
|
|
9
|
+
- **Parsing, formatting and percentage arithmetic live in one module and nowhere else.** A second place that turns a string into money is a second rounding rule, and the two will disagree on a value somebody is owed.
|
|
10
|
+
- **Never** convert to a floating-point type before formatting, and never let a display format round a stored value.
|
|
11
|
+
- Monetary database columns are integer. JSON responses serialize them as **strings**, not numbers, so no client parses them into a float. The service owns the conversion.
|
|
12
|
+
|
|
13
|
+
<!-- nina:slot money.2 -->
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Distribution invariants (enforced by reviewer + unit tests)
|
|
18
|
+
|
|
19
|
+
The distribution engine lives in `{{CORE_PKG}}`. It is pure and total over integer {{MINOR_UNIT}}:
|
|
20
|
+
|
|
21
|
+
- A destination is either a percentage (basis points of the amount) or a fixed value.
|
|
22
|
+
- `total = Σ destinations`, computed in {{MINOR_UNIT}}.
|
|
23
|
+
- `total > amount` → **reject.** Never clamp, never distribute what is not there.
|
|
24
|
+
- `total < amount` → **allow**, surface the shortfall, and leave the remainder **parked**. Silence is the defect here: an unexplained remainder is indistinguishable from a bug.
|
|
25
|
+
- `total = amount` → OK.
|
|
26
|
+
- A retention is just another destination — the same arithmetic applies to it, with no special case.
|
|
27
|
+
|
|
28
|
+
<!-- nina:slot money.3 -->
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## State machines & idempotency (enforced by reviewer)
|
|
33
|
+
|
|
34
|
+
- **Every money-moving entity is a state machine.** Its states and legal transitions are declared in `.claude/architecture.md`; the code follows the declaration, not the other way round. Illegal transitions throw; they are never silently coerced.
|
|
35
|
+
- **Every transition writes an audit row** (entity type, entity id, from, to, actor). No transition without a trail — the trail is what makes a money bug diagnosable after the money has moved.
|
|
36
|
+
- **Outbound transfers are idempotent.** Each carries a unique key, and dedupe happens before the side effect, at the serialization point.
|
|
37
|
+
- **One serialization point per balance.** Whatever computes and dispatches against a single balance never runs concurrently with itself. Two concurrent runs over one balance is how a double send happens even when every individual write is correct.
|
|
38
|
+
|
|
39
|
+
<!-- nina:slot money.4 -->
|
|
40
|
+
- **Money computation and dispatch go through the serialization point.** Routes, queue consumers and tools never run a distribution or a transfer synchronously in the request path.
|
|
41
|
+
|
|
42
|
+
<!-- nina:slot money.5 -->
|
|
43
|
+
- **The distribution engine lives in `{{CORE_PKG}}`.** Services call it as a pure function; they do not embed money logic inline.
|
|
44
|
+
|
|
45
|
+
<!-- nina:slot money.6 -->
|
|
46
|
+
- Idempotency on money-moving writes: the record carries an idempotency key, and the serialization point refuses the second attempt *before* the side effect.
|
|
47
|
+
|
|
48
|
+
<!-- nina:slot money.7 -->
|
|
49
|
+
- `{{CORE_PKG}}` is the most-tested package — pure functions over fixtures. Cover the distribution invariants exhaustively: over, under, exact, mixed percentage and fixed, and rounding.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!-- nina:slot money.1 -->
|
|
2
|
+
3. **An outbound transfer is idempotent — it is NEVER sent twice.** Every transfer carries an idempotency key, and deduplication happens at a single serialization point **before** the side effect, never after it. A retry, a re-delivered queue message, a double-click and a replayed webhook must all collapse to one transfer. This is the highest-blast-radius invariant in the system: almost everything else can be corrected afterwards, and a duplicated transfer cannot.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot money.2 -->
|
|
5
|
+
4. **Conservation — what comes in equals what goes out plus what is retained.** A distribution may NEVER exceed the amount it distributes → **block**. If it is **less**, **alert** and leave the remainder **parked** and visible — never silently distributed, never silently absorbed. All arithmetic is integer {{MINOR_UNIT}}; percentages are basis points. The LLM never computes a monetary value.
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot money.3 -->
|
|
8
|
+
5. **State-machine integrity.** Every money-moving entity has an explicit set of states and legal transitions, declared in `.claude/architecture.md` § *State machines* — not inferred from whatever code happens to exist. Only declared transitions are legal; an illegal one throws and is never silently coerced. **Every transition writes an audit row** (entity, id, from → to, actor, when). Skipping a state, inventing one, or landing a transition with no audit row is a hard reject.
|
|
9
|
+
|
|
10
|
+
<!-- nina:slot money.4 -->
|
|
11
|
+
- **money movement**: anything that creates, moves, settles or records a balance
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<!-- nina:slot pii.1 -->
|
|
2
|
+
- **Privacy.** No sensitive value in clear text where it must not be. The categories are declared in `.claude/architecture.md` § *Privacy* — they are not decided per query, and not decided by you. **Document bytes live only in the object store, referenced by key: the column holds the key, never the bytes.** Any column the architect's spec flagged for masking must be encrypted at rest. Reject a schema that widens what is stored in the clear.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<!-- nina:slot pii.1 -->
|
|
2
|
+
|
|
3
|
+
**Point it at an UNAUTHENTICATED route — a sign-in page is the right one.** Never sign in, and never
|
|
4
|
+
screenshot a signed-in page. Accounts on deployed environments hold real personal data, and a
|
|
5
|
+
screenshot or a console dump pulls it into agent transcripts and logs, where it persists and nobody
|
|
6
|
+
ever looks for it again. That is a privacy leak with no upside here: the sign-in page proves
|
|
7
|
+
everything this check is for. It renders from the same bundle, it exercises the same build-time env
|
|
8
|
+
inlining (a missing variable blanks it identically), and it needs no session.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<!-- nina:slot pii.1 -->
|
|
2
|
+
- Check for privacy regressions: **no sensitive value in a log, an error message — a library's own included — a URL or a test fixture**, per the categories in `.claude/architecture.md` § *Privacy*; document bytes only in the object store, never plaintext in the database; no leaked secret.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot pii.2 -->
|
|
5
|
+
- **privacy** — the privacy check above, in full: no sensitive value in a log, an error message, a
|
|
6
|
+
URL or a test fixture; document bytes only in the object store; no leaked secret. Where
|
|
7
|
+
**tenant-and-privacy** is dispatched, its reviewer owns this too and there is no separate one; where
|
|
8
|
+
it is not, this is a dimension of its own. Either way it is never nobody's.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<!-- nina:slot pii.1 -->
|
|
2
|
+
- **Privacy (Hard Rule #8)** — the categories declared in `.claude/architecture.md` § *Privacy* must never appear in logs, in responses that do not need them, in error paths, or in the database in clear text. Check every endpoint that returns a person's record for fields it includes by accident: a `select` that quietly grew a column is the usual way this leaks. Check the error paths too — they are written last and reviewed least — and what the libraries on them print or carry in their own errors, bound values included.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot pii.1 -->
|
|
2
|
+
- **Nothing personal reaches the chain.** What is written on-chain is public, permanent, and beyond
|
|
3
|
+
deletion by anyone — including by you, and including after a subject asks. A hash of a personal
|
|
4
|
+
identifier is not a protection: it is a lookup table for whoever holds the input set. Reject a
|
|
5
|
+
personal datum stored in state, emitted in an event, or passed as calldata.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot pii.1 -->
|
|
2
|
+
8. **Sensitive personal data is classified, and the classification is enforced.** What counts as sensitive here — identity documents, financial identifiers, anything a person would not expect to be readable by whoever can read a log — is declared in `.claude/architecture.md` § *Privacy*, along with the regime that governs it ({{PRIVACY_REGIME}}). Encrypted at rest. **No sensitive value in a log, an error message, a URL, an analytics event, or an agent transcript** — the places nobody thinks about are exactly the ones that persist. That includes what a library puts in its own errors and prints itself: a database client's error can carry the query and every bound value, and a framework can log a whole error past the logger it was given. A database error is mapped at the data layer to a typed error from a closed list before anything logs or returns it, and a test proves that a bound value it writes reaches no thrown error. Document bytes live only in the object store, referenced by key, never in the database in clear text.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot pii.2 -->
|
|
5
|
+
- **personal data**: where it is collected, stored, logged or sent
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!-- nina:slot prisma.1 -->
|
|
2
|
+
| `prisma-cli` | **before ANY `prisma` CLI command** — `validate`, `format`, `migrate dev`, `migrate diff`, `migrate deploy`. Flags differ between versions, and one of them (`migrate diff --shadow-database-url` against a live database) destroys the database it points at |
|
|
3
|
+
| `prisma-client-api` | auditing a new or changed query — filters, operators, `$transaction` semantics. Through a driver adapter, cite the adapter's installed source for what a transaction does: the skill describes the default engine, and an adapter can roll back less |
|
|
4
|
+
|
|
5
|
+
<!-- nina:slot prisma.2 -->
|
|
6
|
+
Neither skill covers a **caching or edge layer sitting on top of the client** — cache strategy,
|
|
7
|
+
edge-client behaviour and the rule about never caching a read that authorizes a write belong to that
|
|
8
|
+
layer's own integration doc, declared like any other integration. A skill does not replace it.
|
|
9
|
+
|
|
10
|
+
<!-- nina:slot prisma.3 -->
|
|
11
|
+
- **With Prisma, both offline.** Run `pnpm --filter {{DB_PKG_NAME}} exec prisma validate`; it must pass. Then run `prisma migrate diff` in its **offline, file-only form** and read the generated SQL, not just the Prisma delta. Its flags changed in Prisma 7, so read the installed major (`prisma --version`) before writing the command:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
# Prisma 6. Compare the PREVIOUS schema against the working one — pure file-to-file, no database.
|
|
15
|
+
git show HEAD:{{DB_PKG}}/prisma/schema.prisma > /tmp/schema-head.prisma
|
|
16
|
+
pnpm --filter {{DB_PKG_NAME}} exec prisma migrate diff \
|
|
17
|
+
--from-schema-datamodel /tmp/schema-head.prisma \
|
|
18
|
+
--to-schema-datamodel prisma/schema.prisma \
|
|
19
|
+
--script
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
From Prisma 7 the two flags are `--from-schema` and `--to-schema`, and the schema engine starts from the
|
|
23
|
+
datasource in `prisma.config.ts` even for this diff: with none there, it prints nothing and exits 0. For
|
|
24
|
+
SQLite a placeholder the diff never opens is enough, a `file:` URL under a gitignored directory; for any
|
|
25
|
+
other provider, find out what the diff opens before you give it one. Never a real database's URL. A first schema has nothing at `HEAD`: diff it `--from-empty`. Compare the output against the
|
|
26
|
+
hand-written `migration.sql` to confirm the declarative parts match.
|
|
27
|
+
|
|
28
|
+
⚠️ **Do NOT reach for `--from-migrations`.** It looks like the natural choice and it is a trap:
|
|
29
|
+
Prisma refuses it without a shadow database (`--shadow-database-url`, or from Prisma 7 a
|
|
30
|
+
`shadowDatabaseUrl` in `prisma.config.ts`), which pushes you toward supplying a database URL — the exact move that destroys databases (below). The file-to-file form above answers the
|
|
31
|
+
same question with no database involved.
|
|
32
|
+
|
|
33
|
+
⛔ **NEVER point `--shadow-database-url`, `--from-url`, `--to-url`, `--from-schema-datasource` — or, from Prisma 7, a `shadowDatabaseUrl` or the config datasource behind `--from-config-datasource` — at a real database, most of all not the one `{{SECRETS_LOCAL}}` names.** Prisma **executes** the entire migration history against whatever database those flags name, which **DESTROYS ALL DATA** in it. A shadow database must be a disposable, empty database and nothing else. It does not announce itself: the command completes with a reassuring "empty migration" result and no error, having already destroyed everything in the database it was pointed at. If the offline form above cannot answer your question, **STOP and report that to the orchestrator** instead of reaching for a database URL.
|
|
34
|
+
|
|
35
|
+
<!-- nina:slot prisma.4 -->
|
|
36
|
+
- **Where reads are cached through Prisma Accelerate,** every `findUnique` / `findMany` on a hot read path names its `cacheStrategy` (`ttl` / `swr`), and one that feeds a correctness-critical write names none.
|
|
37
|
+
|
|
38
|
+
<!-- nina:slot prisma.5 -->
|
|
39
|
+
- **On an edge runtime,** new Prisma code paths use the edge client of the installed major: `@prisma/client/edge` up to Prisma 6; from 7, the client the `prisma-client` generator writes with the platform's `runtime` set, imported from where it is generated — `@prisma/client/edge` fails there with `MODULE_NOT_FOUND`.
|
|
40
|
+
|
|
41
|
+
<!-- nina:slot prisma.6 -->
|
|
42
|
+
- Run a Prisma command that writes to a real database — `prisma migrate dev`, `migrate deploy`, `migrate reset`, `migrate resolve`, `db push`, `db execute`, `$executeRaw*` — or **any `prisma migrate diff` variant carrying a database URL**, which silently wipes the database it points at.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!-- nina:slot prisma.1 -->
|
|
2
|
+
- **`prisma-cli`** — before `migrate deploy` or any other Prisma CLI command against a real database.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot prisma.2 -->
|
|
5
|
+
- **Prisma migrations go out with `prisma migrate deploy`**, and nothing else changes a shared database's schema.
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot prisma.3 -->
|
|
8
|
+
- Run `prisma migrate diff --shadow-database-url <url>` against any live database. It **executes** the migration history against that database, destroying everything in it, and reports success. Use a disposable shadow database or don't run it.
|
|
9
|
+
|
|
10
|
+
<!-- nina:slot prisma.4 -->
|
|
11
|
+
- Run `prisma migrate reset` against a shared database, ever.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- nina:slot prisma.1 -->
|
|
2
|
+
- **Database table/column names** (Prisma `@map` / `@@map`): `snake_case` in SQL, `camelCase` in TS.
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot prisma.2 -->
|
|
5
|
+
- Where Prisma reads are cached through Accelerate, `cacheStrategy` lives in `{{DB_PKG}}`: services do not add, strip, or override cache policies.
|
|
6
|
+
|
|
7
|
+
<!-- nina:slot prisma.3 -->
|
|
8
|
+
|
|
9
|
+
### Prisma
|
|
10
|
+
|
|
11
|
+
- Schema lives in `{{DB_PKG}}/prisma/schema.prisma`. Nowhere else.
|
|
12
|
+
- **Never write raw SQL** unless justified in a code comment with perf data.
|
|
13
|
+
- N+1 is solved with `include` / `select`, chosen deliberately.
|
|
14
|
+
- Where reads are cached through Accelerate, every `findUnique` / `findMany` on a hot path defines its `cacheStrategy`, and no direct connection string is embedded in code.
|
|
15
|
+
- Tests run a real Prisma client against a real database; the client is never mocked.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- nina:slot prisma.1 -->
|
|
2
|
+
| Run any `prisma` CLI command (the database gate, devops, and the integration gate where there is one) | `prisma-cli` | flags differ between versions; `migrate diff --shadow-database-url` against a live DB wiped dev/staging once |
|
|
3
|
+
|
|
4
|
+
<!-- nina:slot prisma.2 -->
|
|
5
|
+
| Write or audit a Prisma query (architect, implementer, the database gate, reviewer) | `prisma-client-api` | filters, operators, `$transaction` semantics. a caching or edge layer on top of the client is **NOT covered** — its cache strategy belongs to that layer's own integration doc |
|
package/src/commands/export.mjs
CHANGED
|
@@ -29,7 +29,7 @@ import { BATCH, SETTLE_MINUTES, contextMode, postScore, postScores, postSpans, s
|
|
|
29
29
|
import { runOf, transcriptsOf } from '../transcripts.mjs';
|
|
30
30
|
import { exportsDir, snapshotsDir } from '../paths.mjs';
|
|
31
31
|
import { readStoreFile } from '../store.mjs';
|
|
32
|
-
import { readConfig,
|
|
32
|
+
import { readConfig, readStatus, targetOf, writeStatus } from './langfuse.mjs';
|
|
33
33
|
import { projectName } from './stats.mjs';
|
|
34
34
|
import { handsToModel } from '../detectors.mjs';
|
|
35
35
|
import { takeLock } from '../lock.mjs';
|
|
@@ -137,11 +137,11 @@ export async function autoExport(slug, records, now = new Date()) {
|
|
|
137
137
|
const setting = (await readConfig()).projects[slug];
|
|
138
138
|
if (!setting) return null;
|
|
139
139
|
let finding = null;
|
|
140
|
-
const last =
|
|
140
|
+
const last = await readStatus(slug);
|
|
141
141
|
if (last?.error && !last.reported) {
|
|
142
142
|
finding = `the export to Langfuse failed (${last.at}) — ${last.error}; \`nina langfuse status\` says where it stands`;
|
|
143
143
|
// Said on every run until the prompt hook hands it to the model, and not after (`handsToModel`).
|
|
144
|
-
if (handsToModel()) await
|
|
144
|
+
if (handsToModel()) await writeStatus(slug, { ...last, reported: true });
|
|
145
145
|
}
|
|
146
146
|
const sent = await sentOf(slug);
|
|
147
147
|
if (typeof sent === 'string') return finding ?? sent;
|
|
@@ -180,9 +180,9 @@ export async function exportCommand(argv, ctx = {}) {
|
|
|
180
180
|
if (result.spans > 0 || result.scores > 0 || result.error) {
|
|
181
181
|
// The same failure again is not news: it was reported once, and repeating it every turn it is
|
|
182
182
|
// retried would be the noise the detectors exist to remove.
|
|
183
|
-
const before =
|
|
183
|
+
const before = await readStatus(only);
|
|
184
184
|
const reported = Boolean(result.error && before?.error === result.error && before.reported);
|
|
185
|
-
await
|
|
185
|
+
await writeStatus(only, { at: new Date().toISOString(), spans: result.spans, scores: result.scores, error: result.error, reported });
|
|
186
186
|
}
|
|
187
187
|
return result.error ? 1 : 0;
|
|
188
188
|
} finally {
|
|
@@ -38,6 +38,35 @@ const SHORT = { full: 'with context', prompts: 'with prompts and reports', none:
|
|
|
38
38
|
/** Where the background export leaves what it last did, per project. */
|
|
39
39
|
export const statusPath = (slug) => join(exportsDir(), 'langfuse', `${slug}.status.json`);
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* What the background export last did for a project, or null. The export writes it while each turn's detector reads
|
|
43
|
+
* it, and a plain write empties the file before it fills it: read in between, it threw in the hook. It is written by
|
|
44
|
+
* rename now (`writeStatus`); one left unreadable some other way reads as nothing sent.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} slug
|
|
47
|
+
* @returns {Promise<object|null>}
|
|
48
|
+
*/
|
|
49
|
+
export async function readStatus(slug) {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(await readFile(statusPath(slug), 'utf8'));
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Writes it by rename, so a reader finds the last one whole or this one whole.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} slug
|
|
61
|
+
* @param {object} status
|
|
62
|
+
*/
|
|
63
|
+
export async function writeStatus(slug, status) {
|
|
64
|
+
await mkdir(join(exportsDir(), 'langfuse'), { recursive: true });
|
|
65
|
+
const tmp = `${statusPath(slug)}.${process.pid}.tmp`;
|
|
66
|
+
await writeFile(tmp, JSON.stringify(status));
|
|
67
|
+
await rename(tmp, statusPath(slug));
|
|
68
|
+
}
|
|
69
|
+
|
|
41
70
|
/**
|
|
42
71
|
* The configuration on disk: keys, host, and the projects that are on.
|
|
43
72
|
*
|
|
@@ -163,7 +192,7 @@ export async function langfuse(argv, ctx = {}) {
|
|
|
163
192
|
const on = Object.entries(config.projects);
|
|
164
193
|
if (on.length === 0) console.log(' no project sends its runs — `nina langfuse on` in one');
|
|
165
194
|
for (const [name, setting] of on) {
|
|
166
|
-
const last =
|
|
195
|
+
const last = await readStatus(name);
|
|
167
196
|
const said = !last ? 'nothing sent yet' : last.error ? `✗ ${last.at}: ${last.error}` : `last sent ${last.at}: ${last.spans} trace(s), ${last.scores} score(s)`;
|
|
168
197
|
console.log(` ${projectName(name)}: ${SHORT[contextMode(setting) ?? 'none']}, since ${setting.since.slice(0, 10)} — ${said}`);
|
|
169
198
|
}
|
package/src/commands/stats.mjs
CHANGED
|
@@ -592,6 +592,9 @@ function proportionReport(records, stepLimit = () => null, lineLimit = () => nul
|
|
|
592
592
|
),
|
|
593
593
|
);
|
|
594
594
|
}
|
|
595
|
+
if (d.unmeasured) {
|
|
596
|
+
console.log(note(`${d.unmeasured} implementer round(s) also wrote files through the shell, where no count of lines reaches: their size is not measured.`));
|
|
597
|
+
}
|
|
595
598
|
}
|
|
596
599
|
|
|
597
600
|
/**
|
|
@@ -646,6 +649,9 @@ export function proportionData(records, stepLimit = () => null, lineLimit = () =
|
|
|
646
649
|
return limit !== null && r.lines_written > limit;
|
|
647
650
|
});
|
|
648
651
|
const longest = longer.length ? longer.reduce((a, b) => (b.lines_written > a.lines_written ? b : a)) : null;
|
|
652
|
+
// A round that also wrote through the shell holds only what its edit tools wrote: under the limit, its size is not
|
|
653
|
+
// measured, not small. One project's implementers wrote most of their files so, and every such round read as 0 lines.
|
|
654
|
+
const unmeasured = records.filter((r) => r.role === 'implementer' && ranUnder(r, 'stepLines') && r.shell_writes > 0 && !longer.includes(r)).length;
|
|
649
655
|
return {
|
|
650
656
|
rows: rows.map(({ label, cycles: n, designed }) => ({ label, cycles: n, designed })),
|
|
651
657
|
built,
|
|
@@ -655,6 +661,7 @@ export function proportionData(records, stepLimit = () => null, lineLimit = () =
|
|
|
655
661
|
? { count: over.length, limit: stepLimit(largest.project), largest: largest.files_touched, readM: typeof largest.tokens?.read === 'number' ? Math.round(largest.tokens.read / 1e6) : null }
|
|
656
662
|
: null,
|
|
657
663
|
overLines: longest ? { count: longer.length, limit: lineLimit(longest.project), largest: longest.lines_written } : null,
|
|
664
|
+
unmeasured,
|
|
658
665
|
};
|
|
659
666
|
}
|
|
660
667
|
|
package/src/dashboard.html
CHANGED
|
@@ -359,7 +359,8 @@ const RENDER = {
|
|
|
359
359
|
d.proportion ? [el('h3', {}, 'proportion · the size of a change against its chain'),
|
|
360
360
|
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]),
|
|
361
361
|
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,
|
|
362
|
-
d.proportion.overLines ? el('p', { class: 'note warn' }, `${d.proportion.overLines.count} implementer round(s) wrote more lines than one step may (${d.proportion.overLines.limit}); the largest wrote ${d.proportion.overLines.largest}.`) : null
|
|
362
|
+
d.proportion.overLines ? el('p', { class: 'note warn' }, `${d.proportion.overLines.count} implementer round(s) wrote more lines than one step may (${d.proportion.overLines.limit}); the largest wrote ${d.proportion.overLines.largest}.`) : null,
|
|
363
|
+
d.proportion.unmeasured ? el('p', { class: 'note faint' }, `${d.proportion.unmeasured} implementer round(s) also wrote files through the shell, where no count of lines reaches: their size is not measured.`) : null] : null,
|
|
363
364
|
);
|
|
364
365
|
},
|
|
365
366
|
};
|
package/src/transcripts.mjs
CHANGED
|
@@ -124,6 +124,220 @@ const WRITES = { Edit: 'file_path', Write: 'file_path', MultiEdit: 'file_path',
|
|
|
124
124
|
/** How many lines a text written through an edit tool holds: none for an empty one, a last newline ending one. */
|
|
125
125
|
const linesIn = (text) => (typeof text === 'string' && text.length > 0 ? text.replace(/\n$/, '').split('\n').length : 0);
|
|
126
126
|
|
|
127
|
+
/** A temporary directory named in a command: what it writes there is scratch, not the tree. */
|
|
128
|
+
const TEMP = /(?:^|[^\w.-])(?:\/private)?\/tmp\/|\/var\/folders\/|\$\{?TMPDIR\b|\bmktemp\b|\/scratchpad\b/;
|
|
129
|
+
|
|
130
|
+
/** A script, in a heredoc or a quoted `-e`, that opens a file to write it. */
|
|
131
|
+
const SCRIPT_WRITES = /\bopen\([^)\n]*,\s*(?:mode\s*=\s*)?['"](?:w|a|x|r\+)[bt+]?['"]|\.write_(?:text|bytes)\(|\b(?:writeFileSync|appendFileSync|writeFile|appendFile|createWriteStream)\(/;
|
|
132
|
+
|
|
133
|
+
/** A script's write to a file it names as a literal: the path, judged as the shell's own writes are. */
|
|
134
|
+
const LITERAL_WRITES = [
|
|
135
|
+
/\bopen\(\s*(['"])([^'"\n]+)\1\s*,\s*(?:mode\s*=\s*)?['"](?:w|a|x|r\+)/g,
|
|
136
|
+
/\bPath\(\s*(['"])([^'"\n]+)\1\s*\)\.write_(?:text|bytes)\(/g,
|
|
137
|
+
/\b(?:writeFileSync|appendFileSync|writeFile|appendFile|createWriteStream)\(\s*(['"`])([^'"`\n]+)\1/g,
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
/** A redirect's own words, which are not a command's last argument. */
|
|
141
|
+
const REDIRECTS = /(?:^|\s)(?:\d?[<>]{1,2}\|?|&>>?)(?:&\d+|\s*\S+)/g;
|
|
142
|
+
|
|
143
|
+
/** What may stand before a simple command's own name: a function it opens, `do`, `then`, a group, `nohup`. */
|
|
144
|
+
const LEADS = String.raw`^\s*(?:[A-Za-z_][\w-]*\(\)\s*)?(?:(?:do|then|else|time|nohup|\{|\(|!)\s*)*`;
|
|
145
|
+
|
|
146
|
+
/** A simple command that opens a function's body. */
|
|
147
|
+
const FUNCTION = /^\s*(?:function\s+[A-Za-z_][\w-]*\s*(?:\(\))?|[A-Za-z_][\w-]*\s*\(\))\s*\{/;
|
|
148
|
+
|
|
149
|
+
/** A `cd` or `pushd`, and where it goes. */
|
|
150
|
+
const CD = new RegExp(String.raw`${LEADS}(?:cd|pushd)\b(?:\s+(\S+))?`);
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The shell's own ways to write a file, read in a simple command's words, each with where it writes: the redirect's
|
|
154
|
+
* or `tee`'s file, the last word of an in-place `sed` or `perl`, of a `cp` or a `mv`; a formatter's `--write` or
|
|
155
|
+
* `--fix`, `git apply` and `patch` write where the command stands.
|
|
156
|
+
*/
|
|
157
|
+
const SHELL_WRITES = [
|
|
158
|
+
[/(?:^|[\s;&|(){}])\d?>>?\|?\s*(?!&)(?!\/dev\/(?:null|stdout|stderr)\b)([^\s;&|<>()]+)/, 1],
|
|
159
|
+
[/(?:^|[\s;&|(])tee\s+(?:-a\s+)?(?!\/dev\/null\b)([^\s;&|<>()-][^\s;&|<>()]*)/, 1],
|
|
160
|
+
[new RegExp(String.raw`${LEADS}(?:sed|perl)\s+(?:-\S*\s+)*(?:-[a-zA-Z]*i\b|--in-place)`), 'last'],
|
|
161
|
+
[new RegExp(String.raw`${LEADS}(?:cp|mv)\s`), 'last'],
|
|
162
|
+
[/\s--(?:write|fix)(?=\s|=|$)/, null],
|
|
163
|
+
[new RegExp(String.raw`${LEADS}(?:git\s+apply|patch)\b`), null],
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
/** A simple command that runs an interpreter, whose script — a heredoc, a `-e` or `-c` — may open a file to write. */
|
|
167
|
+
const INTERPRETER = new RegExp(String.raw`${LEADS}(?:[A-Za-z_]\w*=\S*\s+)*(?:(?:pnpm|npx)\s+(?:exec\s+)?)?(?:python3?|node|deno|bun|perl|ruby|tsx)\b`);
|
|
168
|
+
|
|
169
|
+
/** A quoted word that is only a path or a variable, kept as a word rather than emptied as a string. */
|
|
170
|
+
const PLAIN = /^[\w$./{}~@:+,=-]*$/;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* A command's simple commands, in order, as the shell splits them at `&&`, `||`, `;`, `|`, `&` and line ends outside
|
|
174
|
+
* quotes: each with its text as written, a heredoc's body joined to the command that opened it, and its words — a
|
|
175
|
+
* quoted path kept, any other quoted string emptied, no body — so that `a > b` in a script, or an arrow function in
|
|
176
|
+
* `node -e "…"`, is no redirect.
|
|
177
|
+
*
|
|
178
|
+
* @param {string} command
|
|
179
|
+
* @returns {{raw: string, words: string}[]}
|
|
180
|
+
*/
|
|
181
|
+
function simpleCommands(command) {
|
|
182
|
+
const out = [];
|
|
183
|
+
let raw = '';
|
|
184
|
+
let words = '';
|
|
185
|
+
let quote = null;
|
|
186
|
+
let opened = 0;
|
|
187
|
+
const bodies = [];
|
|
188
|
+
const end = () => {
|
|
189
|
+
if (raw.trim()) out.push({ raw, words });
|
|
190
|
+
raw = '';
|
|
191
|
+
words = '';
|
|
192
|
+
};
|
|
193
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
194
|
+
const c = command[i];
|
|
195
|
+
const prev = command[i - 1];
|
|
196
|
+
const next = command[i + 1];
|
|
197
|
+
if (quote) {
|
|
198
|
+
raw += c;
|
|
199
|
+
if (c === '\\' && quote === '"' && next !== undefined) {
|
|
200
|
+
raw += next;
|
|
201
|
+
i += 1;
|
|
202
|
+
} else if (c === quote) {
|
|
203
|
+
const inside = raw.slice(opened, -1);
|
|
204
|
+
words += PLAIN.test(inside) ? inside : '""';
|
|
205
|
+
quote = null;
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (c === '\\' && next !== undefined) {
|
|
210
|
+
// A backslash before a line end joins the lines; before anything else, it keeps that character as it is.
|
|
211
|
+
raw += next === '\n' ? ' ' : c + next;
|
|
212
|
+
words += next === '\n' ? ' ' : c + next;
|
|
213
|
+
i += 1;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (c === '$' && next === '(' && command[i + 2] === '(') {
|
|
217
|
+
// Arithmetic, `$((1 << n))` or `$((a > b))`: neither a heredoc nor a redirect.
|
|
218
|
+
let depth = 0;
|
|
219
|
+
let j = i + 1;
|
|
220
|
+
for (; j < command.length; j += 1) {
|
|
221
|
+
if (command[j] === '(') depth += 1;
|
|
222
|
+
else if (command[j] === ')' && (depth -= 1) === 0) break;
|
|
223
|
+
}
|
|
224
|
+
raw += command.slice(i, j + 1);
|
|
225
|
+
words += '0';
|
|
226
|
+
i = j;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (c === "'" || c === '"') {
|
|
230
|
+
quote = c;
|
|
231
|
+
raw += c;
|
|
232
|
+
opened = raw.length;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
// A comment runs to the line's end: what it says is not run.
|
|
236
|
+
if (c === '#' && (prev === undefined || /[\s;&|(]/.test(prev))) {
|
|
237
|
+
while (i + 1 < command.length && command[i + 1] !== '\n') i += 1;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const heredoc = c === '<' && prev !== '<' ? /^<<(-?)\s*\\?(['"]?)([A-Za-z_][\w.-]*)\2/.exec(command.slice(i)) : null;
|
|
241
|
+
if (heredoc) {
|
|
242
|
+
bodies.push({ delimiter: heredoc[3], tabs: heredoc[1] === '-' });
|
|
243
|
+
raw += heredoc[0];
|
|
244
|
+
words += '<<';
|
|
245
|
+
i += heredoc[0].length - 1;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (c === '\n') {
|
|
249
|
+
// Each heredoc opened on the line: the lines up to its delimiter, kept with the command that opened it.
|
|
250
|
+
let at = i + 1;
|
|
251
|
+
for (const { delimiter, tabs } of bodies.splice(0)) {
|
|
252
|
+
while (at < command.length) {
|
|
253
|
+
const stop = command.indexOf('\n', at);
|
|
254
|
+
const line = command.slice(at, stop === -1 ? command.length : stop);
|
|
255
|
+
raw += `\n${line}`;
|
|
256
|
+
at = stop === -1 ? command.length : stop + 1;
|
|
257
|
+
if ((tabs ? line.replace(/^\t+/, '') : line) === delimiter) break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
i = at - 1;
|
|
261
|
+
end();
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const pair = (c === '&' || c === '|') && next === c;
|
|
265
|
+
const pipe = c === '|' && prev !== '>';
|
|
266
|
+
const background = c === '&' && prev !== '>' && prev !== '<' && next !== '>';
|
|
267
|
+
if (c === ';' || pair || pipe || background) {
|
|
268
|
+
end();
|
|
269
|
+
if (pair || (c === '|' && next === '&')) i += 1;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
raw += c;
|
|
273
|
+
words += c;
|
|
274
|
+
}
|
|
275
|
+
end();
|
|
276
|
+
return out;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Whether a shell command writes a file outside a temporary directory: a redirect into a file, `tee`, `sed -i` or
|
|
281
|
+
* `perl -i`, `cp` or `mv`, a formatter's `--write` or `--fix`, `git apply` or `patch`, or an interpreter's script that
|
|
282
|
+
* opens a file to write it. Each write is placed where it lands: a path, a variable the command bound — to a temporary
|
|
283
|
+
* directory or not — or, for a relative one, where the command stands after its `cd`s, which starts in the project
|
|
284
|
+
* and keeps its place through a relative `cd`. A variable the command did not bind, a function's `$1`, is nowhere
|
|
285
|
+
* known, and not counted. One project's implementers wrote their files so, through a script that kept an escape the
|
|
286
|
+
* edit tools decode, and the count of the edit tools read those rounds as having written nothing. Read from the
|
|
287
|
+
* command alone, it is a sign, not a count: a script run from a file is missed, and so is one that writes the tree
|
|
288
|
+
* through a variable while it names a temporary directory.
|
|
289
|
+
*
|
|
290
|
+
* @param {string} command - A Bash tool call's command.
|
|
291
|
+
* @returns {boolean}
|
|
292
|
+
*/
|
|
293
|
+
export function writesThroughShell(command) {
|
|
294
|
+
if (typeof command !== 'string') return false;
|
|
295
|
+
const vars = new Map();
|
|
296
|
+
let here = 'tree';
|
|
297
|
+
// Where each open subshell's `(` found the command standing: its `cd`s end with it.
|
|
298
|
+
const outside = [];
|
|
299
|
+
// A function's body runs where it is called, on what it is given: nowhere this command says.
|
|
300
|
+
let functions = 0;
|
|
301
|
+
let standing = here;
|
|
302
|
+
const place = (target) => {
|
|
303
|
+
if (typeof target !== 'string' || target === '') return standing;
|
|
304
|
+
const name = /^\$\{?(\w+|[@*#?$!-])/.exec(target);
|
|
305
|
+
if (name) return /^\$\{?TMPDIR\b/.test(target) ? 'scratch' : (vars.get(name[1]) ?? 'unknown');
|
|
306
|
+
if (TEMP.test(target) || target.startsWith('/dev/')) return 'scratch';
|
|
307
|
+
if (target.startsWith('~') || target.includes('""')) return 'unknown';
|
|
308
|
+
return target.startsWith('/') ? 'tree' : standing;
|
|
309
|
+
};
|
|
310
|
+
for (const { raw, words } of simpleCommands(command)) {
|
|
311
|
+
const groups = words.replace(/\$\([^()]*\)/g, '');
|
|
312
|
+
for (let n = /^\s*(\(*)/.exec(groups)[1].length; n > 0; n -= 1) outside.push(here);
|
|
313
|
+
for (let n = (groups.match(/\)/g) ?? []).length - (groups.match(/\(/g) ?? []).length; n > 0 && outside.length; n -= 1) here = outside.pop();
|
|
314
|
+
if (FUNCTION.test(words)) functions += 1;
|
|
315
|
+
standing = functions > 0 ? 'unknown' : here;
|
|
316
|
+
for (const [, name, value] of words.matchAll(/(?:^|\s)(?:export\s+)?([A-Za-z_]\w*)=(\S*)/g)) vars.set(name, place(value));
|
|
317
|
+
const cd = CD.exec(words);
|
|
318
|
+
if (cd) {
|
|
319
|
+
if (functions === 0) here = cd[1] === undefined ? 'unknown' : place(cd[1]);
|
|
320
|
+
} else {
|
|
321
|
+
for (const [rule, at] of SHELL_WRITES) {
|
|
322
|
+
const found = rule.exec(words);
|
|
323
|
+
if (!found) continue;
|
|
324
|
+
const target = at === 'last' ? words.replace(REDIRECTS, ' ').trim().split(/\s+/).at(-1) : at === null ? undefined : found[at];
|
|
325
|
+
if (place(target) === 'tree') return true;
|
|
326
|
+
}
|
|
327
|
+
if (INTERPRETER.test(raw) && SCRIPT_WRITES.test(raw)) {
|
|
328
|
+
// A script names its files inside it: a literal one is placed; with none, a script that names a temporary
|
|
329
|
+
// directory is taken to write there.
|
|
330
|
+
const literal = LITERAL_WRITES.flatMap((re) => [...raw.matchAll(re)].map((m) => m[2]));
|
|
331
|
+
if (literal.some((path) => place(path) === 'tree')) return true;
|
|
332
|
+
const names = TEMP.test(raw) || [...vars].some(([v, p]) => p !== 'tree' && new RegExp(`\\$\\{?${v}\\b`).test(raw));
|
|
333
|
+
if (literal.length === 0 && standing === 'tree' && !names) return true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (functions > 0 && /(?:^|[\s;])\}\s*$/.test(words)) functions -= 1;
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
|
|
127
341
|
/** The line under a declared verdict that names what the report sends back. */
|
|
128
342
|
const ISSUES = /^\s*ISSUES:(.*)$/;
|
|
129
343
|
|
|
@@ -646,7 +860,7 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
646
860
|
if (
|
|
647
861
|
run.agent_read_bytes === size &&
|
|
648
862
|
!('resumes' in run) &&
|
|
649
|
-
[...records, ...later].every((r) => settled(r) && 'round' in r && 'lessons_read' in r && 'tokens' in r && 'files_touched' in r && 'lines_written' in r && 'effort' in r && 'turns' in r && 'prompt_chars' in r)
|
|
863
|
+
[...records, ...later].every((r) => settled(r) && 'round' in r && 'lessons_read' in r && 'tokens' in r && 'files_touched' in r && 'lines_written' in r && 'shell_writes' in r && 'effort' in r && 'turns' in r && 'prompt_chars' in r)
|
|
650
864
|
) {
|
|
651
865
|
continue;
|
|
652
866
|
}
|
|
@@ -679,6 +893,8 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
679
893
|
* run wrote, not its diff: an edit made twice counts twice, and a file a shell command changed not at all.
|
|
680
894
|
*/
|
|
681
895
|
lines: new Map(),
|
|
896
|
+
/** Its shell commands that wrote a file outside a temporary directory, which no count of lines reaches. */
|
|
897
|
+
shellWrites: 0,
|
|
682
898
|
/** The tool calls the round made, by id: over its turns, how many calls each turn sent at once. */
|
|
683
899
|
calls: new Set(),
|
|
684
900
|
/** How long the prompt that opened the round was, in characters; never the prompt. */
|
|
@@ -726,6 +942,15 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
726
942
|
// A torn line loses one edit, not the run's.
|
|
727
943
|
}
|
|
728
944
|
}
|
|
945
|
+
if (line.includes('"name":"Bash"')) {
|
|
946
|
+
try {
|
|
947
|
+
for (const block of JSON.parse(line)?.message?.content ?? []) {
|
|
948
|
+
if (block?.type === 'tool_use' && block.name === 'Bash' && writesThroughShell(block.input?.command)) round.shellWrites += 1;
|
|
949
|
+
}
|
|
950
|
+
} catch {
|
|
951
|
+
// A torn line loses one command, not the run's.
|
|
952
|
+
}
|
|
953
|
+
}
|
|
729
954
|
if (line.includes('"usage"')) {
|
|
730
955
|
try {
|
|
731
956
|
const row = JSON.parse(line);
|
|
@@ -808,6 +1033,7 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
808
1033
|
record.effort = spent.effort;
|
|
809
1034
|
record.files_touched = round.written.size;
|
|
810
1035
|
record.lines_written = [...round.lines.values()].reduce((a, b) => a + b, 0);
|
|
1036
|
+
record.shell_writes = round.shellWrites;
|
|
811
1037
|
record.skills = [...round.skills];
|
|
812
1038
|
record.lessons_read = round.lessonsRead.size;
|
|
813
1039
|
record.lessons_listed = round.lessonsListed;
|