@xhulz/nina 0.82.0 → 0.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/bin/nina.mjs +6 -1
- package/package.json +1 -1
- package/releases/0.83.0/core/GAPS.md +125 -0
- package/releases/0.83.0/core/answered.json +134 -0
- package/releases/0.83.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.83.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.83.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.83.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.83.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.83.0/core/defaults/tree/.claude/agents/qa.md +7 -0
- package/releases/0.83.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.83.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.83.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.83.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.83.0/core/tiers.json +5 -0
- package/releases/0.83.0/core/tree/.claude/agents/architect.md +140 -0
- package/releases/0.83.0/core/tree/.claude/agents/dba.md +123 -0
- package/releases/0.83.0/core/tree/.claude/agents/design-reviewer.md +82 -0
- package/releases/0.83.0/core/tree/.claude/agents/devops.md +122 -0
- package/releases/0.83.0/core/tree/.claude/agents/implementer.md +208 -0
- package/releases/0.83.0/core/tree/.claude/agents/integration-tester.md +174 -0
- package/releases/0.83.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.83.0/core/tree/.claude/agents/qa.md +154 -0
- package/releases/0.83.0/core/tree/.claude/agents/reviewer.md +191 -0
- package/releases/0.83.0/core/tree/.claude/agents/secops.md +124 -0
- package/releases/0.83.0/core/tree/.claude/agents/solidity-auditor.md +130 -0
- package/releases/0.83.0/core/tree/.claude/agents/solidity-dev.md +127 -0
- package/releases/0.83.0/core/tree/.claude/agents-overview.md +65 -0
- package/releases/0.83.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.83.0/core/tree/.claude/graph.md +79 -0
- package/releases/0.83.0/core/tree/.claude/patterns.md +256 -0
- package/releases/0.83.0/core/tree/.claude/pills/README.md +150 -0
- package/releases/0.83.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.83.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.83.0/core/tree/.claude/router.md +224 -0
- package/releases/0.83.0/core/tree/.claude/templates/integration.md +48 -0
- package/releases/0.83.0/core/tree/CLAUDE.md +167 -0
- package/releases/0.83.0/core/tree/scripts/cost-watch.mjs +27 -0
- package/releases/0.83.0/core/tree/scripts/edit-guard.mjs +24 -0
- package/releases/0.83.0/core/tree/scripts/harness-check.mjs +84 -0
- package/releases/0.83.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.83.0/core/vocabulary.json +18 -0
- package/releases/0.83.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.83.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.83.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.83.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.83.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.83.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.83.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.83.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.83.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.83.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
- package/releases/0.83.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
- package/releases/0.83.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.83.0/surfaces/d1/tree/.claude/patterns.md +8 -0
- package/releases/0.83.0/surfaces/d1/tree/CLAUDE.md +2 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/patterns.md +22 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.83.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.83.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.83.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
- package/releases/0.83.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
- package/releases/0.83.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.83.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
- package/releases/0.83.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.83.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.83.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
- package/releases/0.83.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.83.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.83.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.83.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.83.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.83.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.83.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.83.0/surfaces/edge-cf/tree/CLAUDE.md +29 -0
- package/releases/0.83.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.83.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.83.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.83.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.83.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.83.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.83.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.83.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.83.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.83.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.83.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.83.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.83.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.83.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.83.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.83.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.83.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.83.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.83.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.83.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.83.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.83.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.83.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.83.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.83.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.83.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.83.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.83.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.83.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.83.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.83.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.83.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.83.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.83.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.83.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.83.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.83.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.83.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.83.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.83.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.83.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.83.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.83.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
- package/releases/0.83.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.83.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.83.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.83.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.83.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.83.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/releases/0.84.0/core/GAPS.md +125 -0
- package/releases/0.84.0/core/answered.json +134 -0
- package/releases/0.84.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.84.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.84.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.84.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.84.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.84.0/core/defaults/tree/.claude/agents/qa.md +7 -0
- package/releases/0.84.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.84.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.84.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.84.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.84.0/core/tiers.json +5 -0
- package/releases/0.84.0/core/tree/.claude/agents/architect.md +140 -0
- package/releases/0.84.0/core/tree/.claude/agents/dba.md +123 -0
- package/releases/0.84.0/core/tree/.claude/agents/design-reviewer.md +82 -0
- package/releases/0.84.0/core/tree/.claude/agents/devops.md +122 -0
- package/releases/0.84.0/core/tree/.claude/agents/implementer.md +208 -0
- package/releases/0.84.0/core/tree/.claude/agents/integration-tester.md +174 -0
- package/releases/0.84.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.84.0/core/tree/.claude/agents/qa.md +154 -0
- package/releases/0.84.0/core/tree/.claude/agents/reviewer.md +191 -0
- package/releases/0.84.0/core/tree/.claude/agents/secops.md +124 -0
- package/releases/0.84.0/core/tree/.claude/agents/solidity-auditor.md +130 -0
- package/releases/0.84.0/core/tree/.claude/agents/solidity-dev.md +127 -0
- package/releases/0.84.0/core/tree/.claude/agents-overview.md +65 -0
- package/releases/0.84.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.84.0/core/tree/.claude/graph.md +79 -0
- package/releases/0.84.0/core/tree/.claude/patterns.md +256 -0
- package/releases/0.84.0/core/tree/.claude/pills/README.md +150 -0
- package/releases/0.84.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.84.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.84.0/core/tree/.claude/router.md +224 -0
- package/releases/0.84.0/core/tree/.claude/templates/integration.md +48 -0
- package/releases/0.84.0/core/tree/CLAUDE.md +167 -0
- package/releases/0.84.0/core/tree/scripts/cost-watch.mjs +27 -0
- package/releases/0.84.0/core/tree/scripts/edit-guard.mjs +24 -0
- package/releases/0.84.0/core/tree/scripts/harness-check.mjs +84 -0
- package/releases/0.84.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.84.0/core/vocabulary.json +18 -0
- package/releases/0.84.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.84.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.84.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.84.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.84.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.84.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.84.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.84.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.84.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.84.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
- package/releases/0.84.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
- package/releases/0.84.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.84.0/surfaces/d1/tree/.claude/patterns.md +8 -0
- package/releases/0.84.0/surfaces/d1/tree/CLAUDE.md +2 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/patterns.md +22 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.84.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.84.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.84.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
- package/releases/0.84.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
- package/releases/0.84.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.84.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
- package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
- package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.84.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.84.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.84.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.84.0/surfaces/edge-cf/tree/CLAUDE.md +29 -0
- package/releases/0.84.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.84.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.84.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.84.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.84.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.84.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.84.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.84.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.84.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.84.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.84.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.84.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.84.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.84.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.84.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.84.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.84.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.84.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.84.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.84.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.84.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.84.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.84.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.84.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.84.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.84.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.84.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.84.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.84.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.84.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.84.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.84.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.84.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.84.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.84.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.84.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.84.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.84.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.84.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.84.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.84.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.84.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.84.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
- package/releases/0.84.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.84.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.84.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.84.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.84.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.84.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/src/commands/dashboard.mjs +31 -6
- package/src/commands/eval.mjs +9 -3
- package/src/dashboard.mjs +13 -8
- package/src/demo.mjs +417 -0
- package/src/office.mjs +16 -3
package/src/dashboard.mjs
CHANGED
|
@@ -30,6 +30,9 @@ const PAGE = join(dirname(fileURLToPath(import.meta.url)), 'dashboard.html');
|
|
|
30
30
|
/** The port it asks for first, one past the office's. Taken, any free one. */
|
|
31
31
|
export const DASHBOARD_PORT = 6463;
|
|
32
32
|
|
|
33
|
+
/** How the demo's office plays (`nina dashboard --demo`): its one session, over and over, for a screen left on. */
|
|
34
|
+
export const DEMO_OFFICE = { replay: true, loop: true };
|
|
35
|
+
|
|
33
36
|
/** How many tasks the tasks tab lists, newest first: a long project's history would otherwise fill the page. */
|
|
34
37
|
const TASKS_SHOWN = 60;
|
|
35
38
|
|
|
@@ -100,18 +103,19 @@ export function dashboardTabs(target, ctx) {
|
|
|
100
103
|
/**
|
|
101
104
|
* Serves the dashboard: the page at `/`, each tab's data at `/api/<tab>`, and the office at `/office/`.
|
|
102
105
|
*
|
|
103
|
-
* @param {{target: string, ctx: {root: string}, port?: number, tell?: (line: string) => void}} options -
|
|
104
|
-
* a failure is said: the terminal by default.
|
|
106
|
+
* @param {{target: string, ctx: {root: string}, port?: number, tell?: (line: string) => void, replay?: boolean, loop?: boolean, rest?: number}} options -
|
|
107
|
+
* `tell` is where a failure is said: the terminal by default. `replay`, `loop` and `rest` are the office's
|
|
108
|
+
* (`officeRoutes`): the demo's plays its one session over and over.
|
|
105
109
|
* @returns {Promise<{url: string, close: () => Promise<void>}>}
|
|
106
110
|
*/
|
|
107
|
-
export async function serveDashboard({ target, ctx, port = DASHBOARD_PORT, tell = console.error }) {
|
|
111
|
+
export async function serveDashboard({ target, ctx, port = DASHBOARD_PORT, tell = console.error, replay = false, loop = false, rest }) {
|
|
108
112
|
const tabs = dashboardTabs(target, ctx);
|
|
109
113
|
const room = JSON.stringify({ project: basename(target) }).replace(/</g, '\\u003c');
|
|
110
114
|
const page = readFileSync(PAGE, 'utf8').replace('/*NINA_DASHBOARD*/null', () => room);
|
|
111
115
|
// The office polls its ledger while anyone watches it: made when its tab is first opened, not before.
|
|
112
116
|
let office = null;
|
|
113
117
|
const officeFor = () => {
|
|
114
|
-
if (!office) office = officeRoutes({ target });
|
|
118
|
+
if (!office) office = officeRoutes({ target, replay, loop, ...(rest === undefined ? {} : { rest }) });
|
|
115
119
|
return office;
|
|
116
120
|
};
|
|
117
121
|
// What failed is said in the terminal that runs the dashboard, and never in the response: an error's message carries
|
|
@@ -172,13 +176,14 @@ export async function serveDashboard({ target, ctx, port = DASHBOARD_PORT, tell
|
|
|
172
176
|
/**
|
|
173
177
|
* Runs the dashboard until Ctrl+C: serves it, says where for a click, and opens it in the system's browser when asked.
|
|
174
178
|
*
|
|
175
|
-
* @param {{target: string, ctx: {root: string}, port?: number, open?: boolean}} options
|
|
179
|
+
* @param {{target: string, ctx: {root: string}, port?: number, open?: boolean, demo?: boolean}} options - `demo`: the
|
|
180
|
+
* project is the demo's, its office a replay in a loop, and the terminal says the data is invented.
|
|
176
181
|
* @returns {Promise<number>} Process exit code.
|
|
177
182
|
*/
|
|
178
|
-
export async function viewDashboard({ target, ctx, port = DASHBOARD_PORT, open = false }) {
|
|
183
|
+
export async function viewDashboard({ target, ctx, port = DASHBOARD_PORT, open = false, demo = false }) {
|
|
179
184
|
let dashboard;
|
|
180
185
|
try {
|
|
181
|
-
dashboard = await serveDashboard({ target, ctx, port });
|
|
186
|
+
dashboard = await serveDashboard({ target, ctx, port, ...(demo ? DEMO_OFFICE : {}) });
|
|
182
187
|
} catch (error) {
|
|
183
188
|
console.error(` the dashboard could not open a port — ${error?.message ?? error}\n`);
|
|
184
189
|
return 1;
|
|
@@ -186,7 +191,7 @@ export async function viewDashboard({ target, ctx, port = DASHBOARD_PORT, open =
|
|
|
186
191
|
const stopped = untilStopped();
|
|
187
192
|
const opened = open ? await openInBrowser(dashboard.url) : false;
|
|
188
193
|
// The address ends its line, with nothing after it, so a terminal makes it a link.
|
|
189
|
-
console.log(` the dashboard · ${basename(target)}`);
|
|
194
|
+
console.log(demo ? ' the dashboard · a demo: an invented notes app, three weeks of made-up work, no real project' : ` the dashboard · ${basename(target)}`);
|
|
190
195
|
if (open && !opened) console.log(' No browser could be opened from here.');
|
|
191
196
|
console.log(` ${opened ? 'Opened in your browser' : 'Click to open it'}: ${dashboard.url}`);
|
|
192
197
|
console.log(' Ctrl+C closes the dashboard.\n');
|
package/src/demo.mjs
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nina dashboard --demo` — the dashboard on a project that does not exist: for showing someone how NINA works without
|
|
3
|
+
* opening a real project, running Claude or spending a token.
|
|
4
|
+
*
|
|
5
|
+
* A notes app is composed into a scratch directory at the release this package carries, and given three weeks of
|
|
6
|
+
* invented history: each session's transcripts, written the way Claude Code writes them, and today's session through
|
|
7
|
+
* the loop gate's own hook handler. The snapshot then reads the transcripts into the store as it reads any project's.
|
|
8
|
+
* Nothing here writes a ledger entry or a record of its own: a demo built from hand-written JSON would drift from the
|
|
9
|
+
* format the first time either changed, and the test that runs it would still pass (one mechanism per fact). Every
|
|
10
|
+
* word in it — the features, the issues, the reports — is invented, and nothing is read from a real project.
|
|
11
|
+
*
|
|
12
|
+
* `NINA_DATA` and `NINA_TRANSCRIPTS` point into the scratch directory for the rest of the process, so the store, the
|
|
13
|
+
* gate's ledgers and the transcripts every tab and the office read are the demo's; the command's process ends with the
|
|
14
|
+
* demo, which removes the directory.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { tmpdir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { handle } from './gate.mjs';
|
|
21
|
+
import { slugFor } from './paths.mjs';
|
|
22
|
+
import { byVersion, composeProject } from './commands/compose.mjs';
|
|
23
|
+
import { frontmatter } from './commands/pills.mjs';
|
|
24
|
+
import { snapshot } from './commands/snapshot.mjs';
|
|
25
|
+
|
|
26
|
+
/** The project's name, which the dashboard and the office show. */
|
|
27
|
+
export const DEMO_NAME = 'notes';
|
|
28
|
+
|
|
29
|
+
/** The stages that run beside the reviewer on a designed change's diff: the database gate, since the notes have a table. */
|
|
30
|
+
const BESIDE_REVIEW = ['dba'];
|
|
31
|
+
|
|
32
|
+
/** A minute. */
|
|
33
|
+
const MIN = 60_000;
|
|
34
|
+
|
|
35
|
+
/** A day. */
|
|
36
|
+
const DAY = 86_400_000;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The release the demo composes: the one this package carries as its own version, or its newest.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} root - The NINA install directory.
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
function ownRelease(root) {
|
|
45
|
+
const version = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version;
|
|
46
|
+
const releases = readdirSync(join(root, 'releases')).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
|
|
47
|
+
if (releases.includes(version)) return version;
|
|
48
|
+
return releases.sort(byVersion).at(-1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The demo's profile: a notes app with a database and a web page, its vocabulary invented. */
|
|
52
|
+
const profileAt = (core) => ({
|
|
53
|
+
core,
|
|
54
|
+
surfaces: ['db', 'frontend'],
|
|
55
|
+
vocabulary: {
|
|
56
|
+
PROJECT: 'Notes',
|
|
57
|
+
OWNER: 'the maintainer',
|
|
58
|
+
API_DIR: 'src/server',
|
|
59
|
+
APP_DIR: 'src/web',
|
|
60
|
+
PKG_SCOPE: '@notes',
|
|
61
|
+
EMITTING_PKGS: '`core`',
|
|
62
|
+
DB_PKG_NAME: '@notes/db',
|
|
63
|
+
TENANT_KEY: 'workspaceId',
|
|
64
|
+
SECRETS_LOCAL: '.dev.vars',
|
|
65
|
+
SECRETS_PROD: "the host's secret store",
|
|
66
|
+
VISUAL_SERVE: 'pnpm dev',
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Two lessons the pipeline wrote: the implementer's, ten days ago, after which the reviewer sent its work back far less
|
|
72
|
+
* often, and the architect's, six days ago. Where each lives, when it was written, and its text.
|
|
73
|
+
*
|
|
74
|
+
* @param {number} now
|
|
75
|
+
* @returns {{path: string, since: number, text: string}[]}
|
|
76
|
+
*/
|
|
77
|
+
const PILLS = (now) => {
|
|
78
|
+
const ago = (n) => {
|
|
79
|
+
const d = new Date(now - n * DAY);
|
|
80
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
81
|
+
};
|
|
82
|
+
return [
|
|
83
|
+
{
|
|
84
|
+
path: 'implementer/a-call-that-sets-something-sets-it.md',
|
|
85
|
+
since: now - 10 * DAY,
|
|
86
|
+
text: `---
|
|
87
|
+
id: implementer-a-call-that-sets-something-sets-it
|
|
88
|
+
applies_to: [implementer]
|
|
89
|
+
severity: medium
|
|
90
|
+
status: active
|
|
91
|
+
date: ${ago(10)}
|
|
92
|
+
last_seen: ${ago(3)}
|
|
93
|
+
occurrences: 2
|
|
94
|
+
trigger: writing a call that sets a flag, a state or a field on a stored row
|
|
95
|
+
---
|
|
96
|
+
**What went wrong:** an endpoint meant to archive a note flipped the flag, so a second call unarchived it.
|
|
97
|
+
**Rule:** a call that sets something sets it: the same call twice leaves the row as once.
|
|
98
|
+
**Why:** a retry, a double click and a replayed request all call twice.
|
|
99
|
+
**How to apply:** write a test that calls it twice and reads the row after.
|
|
100
|
+
source: an invented review, for the demo
|
|
101
|
+
`,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
path: 'architect/say-what-a-second-call-does.md',
|
|
105
|
+
since: now - 6 * DAY,
|
|
106
|
+
text: `---
|
|
107
|
+
id: architect-say-what-a-second-call-does
|
|
108
|
+
applies_to: [architect]
|
|
109
|
+
severity: low
|
|
110
|
+
status: active
|
|
111
|
+
date: ${ago(6)}
|
|
112
|
+
occurrences: 1
|
|
113
|
+
trigger: specifying an endpoint that changes a stored row
|
|
114
|
+
---
|
|
115
|
+
**What went wrong:** a spec left open what a second call does, and the design-reviewer sent it back.
|
|
116
|
+
**Rule:** a spec that changes a row says what the same call does the second time.
|
|
117
|
+
**Why:** the implementer picks one if the spec does not, and the review finds out a round later.
|
|
118
|
+
**How to apply:** one line under the endpoint: a second call is a no-op, an error, or a new row.
|
|
119
|
+
source: an invented review, for the demo
|
|
120
|
+
`,
|
|
121
|
+
},
|
|
122
|
+
];
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/** The features of three weeks of invented work, one a task: what the task is called, and what its steps call it. */
|
|
126
|
+
const FEATURES = [
|
|
127
|
+
['Add tags to notes', 'tags'],
|
|
128
|
+
['Search notes by title', 'title search'],
|
|
129
|
+
['Share a note by link', 'share link'],
|
|
130
|
+
['Paginate the notes list', 'pagination'],
|
|
131
|
+
['Export notes as Markdown', 'Markdown export'],
|
|
132
|
+
['Pin a note to the top', 'pinning'],
|
|
133
|
+
['Undo a deleted note', 'undo'],
|
|
134
|
+
['Show a word count', 'word count'],
|
|
135
|
+
['Sort notes by date', 'date sort'],
|
|
136
|
+
['Rename a notebook', 'notebook rename'],
|
|
137
|
+
['Color a notebook', 'notebook colors'],
|
|
138
|
+
['Move a note between notebooks', 'note move'],
|
|
139
|
+
['Restore an old version of a note', 'versions'],
|
|
140
|
+
['Limit a note to 100 KB', 'size limit'],
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
/** How long each stage works, in minutes, at least and at most. */
|
|
144
|
+
const WORKS = {
|
|
145
|
+
planner: [3, 5],
|
|
146
|
+
architect: [8, 14],
|
|
147
|
+
'design-reviewer': [4, 7],
|
|
148
|
+
implementer: [10, 20],
|
|
149
|
+
reviewer: [4, 7],
|
|
150
|
+
dba: [3, 5],
|
|
151
|
+
qa: [3, 6],
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/** What each stage does in its own transcript, as the office says it: a shell command's description, or a file it works on. */
|
|
155
|
+
const TOOLS = {
|
|
156
|
+
planner: [['Read', { file_path: '.claude/architecture.md' }], ['Grep', { pattern: 'notes' }], ['Write', { file_path: '.claude/plans/plan.md' }]],
|
|
157
|
+
architect: [['Read', { file_path: '.claude/architecture.md' }], ['Read', { file_path: 'src/server/notes.ts' }], ['Bash', { description: 'Check how the notes table is indexed' }], ['Write', { file_path: '.claude/plans/specs/spec.md' }]],
|
|
158
|
+
'design-reviewer': [['Read', { file_path: '.claude/plans/specs/spec.md' }], ['Bash', { description: 'Try the spec on a second call' }]],
|
|
159
|
+
implementer: [['Read', { file_path: '.claude/plans/specs/spec.md' }], ['Edit', { file_path: 'src/server/notes.ts' }], ['Edit', { file_path: 'src/server/notes.test.ts' }], ['Bash', { description: 'Run the typecheck' }], ['Bash', { description: 'Run the mutations on a copy' }]],
|
|
160
|
+
reviewer: [['Bash', { description: 'Show the diff' }], ['Read', { file_path: 'src/server/notes.ts' }], ['Bash', { description: 'Run the lint' }]],
|
|
161
|
+
dba: [['Read', { file_path: 'src/server/db/schema.ts' }], ['Bash', { description: 'Read the migration the change adds' }]],
|
|
162
|
+
qa: [['Bash', { description: 'Run the notes tests' }], ['Bash', { description: 'Run the whole suite once' }]],
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/** Tokens a run writes, in thousands, at least and at most; what it reads back from its cache is ten to forty times that. */
|
|
166
|
+
const WRITES = { planner: [2, 5], architect: [12, 30], 'design-reviewer': [4, 9], implementer: [10, 25], reviewer: [5, 12], dba: [2, 5], qa: [1, 3] };
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* A small generator of numbers that is the same on every run, so the demo, and the test that reads it, never vary.
|
|
170
|
+
*
|
|
171
|
+
* @param {number} seed
|
|
172
|
+
* @returns {() => number} A number in [0, 1).
|
|
173
|
+
*/
|
|
174
|
+
function seeded(seed) {
|
|
175
|
+
let s = seed >>> 0;
|
|
176
|
+
return () => {
|
|
177
|
+
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
178
|
+
return s / 2 ** 32;
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The runs of one task, from `start`: who works, on what words, when, and what each reports. `sendBack` says which
|
|
184
|
+
* gate sends the work back once; `design` whether it goes through the planner, the architect and the design-reviewer.
|
|
185
|
+
*
|
|
186
|
+
* @returns {{runs: object[], end: number}}
|
|
187
|
+
*/
|
|
188
|
+
function taskRuns(random, start, { name, noun, design, sendBack = null, between = 0.2 }) {
|
|
189
|
+
const runs = [];
|
|
190
|
+
const slug = noun.toLowerCase().replace(/\s+/g, '-');
|
|
191
|
+
const span = (role) => {
|
|
192
|
+
const [lo, hi] = WORKS[role];
|
|
193
|
+
return Math.round((lo + random() * (hi - lo)) * MIN);
|
|
194
|
+
};
|
|
195
|
+
let at = start;
|
|
196
|
+
const run = (role, desc, report, { from = at, beside = false } = {}) => {
|
|
197
|
+
const r = { role, desc, report, start: from, end: from + span(role) };
|
|
198
|
+
runs.push(r);
|
|
199
|
+
if (!beside) at = r.end + between * MIN;
|
|
200
|
+
return r;
|
|
201
|
+
};
|
|
202
|
+
if (design) {
|
|
203
|
+
run('planner', name, 'VERDICT: PLAN-READY\n\nTwo steps: the endpoint, then the list.');
|
|
204
|
+
run('architect', `Spec the ${noun}`, 'VERDICT: SPEC-READY\n\nThe design is the question: the design-reviewer reads it first.');
|
|
205
|
+
if (sendBack === 'design-reviewer') {
|
|
206
|
+
run('design-reviewer', `Check the ${noun} design`, `VERDICT: REJECTED\nISSUES: ${slug}-second-call-undefined\n\nThe spec does not say what a second call does.`);
|
|
207
|
+
run('architect', `Correct the ${noun} spec`, 'VERDICT: SPEC-READY\n\nA second call is now a no-op, and a case reads it.');
|
|
208
|
+
}
|
|
209
|
+
run('design-reviewer', `Check the ${noun} design`, 'VERDICT: APPROVED\n\nThe design holds.');
|
|
210
|
+
}
|
|
211
|
+
run('implementer', design ? `Build the ${noun}` : name, 'VERDICT: DIFF-READY\n\nThe change and its tests, each with the mutation that turns it red.');
|
|
212
|
+
// The reviewer and the gates beside it go out together on the diff, and qa once each has approved.
|
|
213
|
+
const review = (round) => {
|
|
214
|
+
const from = at;
|
|
215
|
+
const sent = sendBack === 'reviewer' && round === 1;
|
|
216
|
+
const reviewer = run('reviewer', round === 1 ? `Review the ${noun} change` : `Review the ${noun} fix`, sent ? `VERDICT: REJECTED\nISSUES: ${slug}-not-idempotent\n\nA second call undoes the first.` : 'VERDICT: APPROVED\n\nThe diff matches its spec.', { from, beside: true });
|
|
217
|
+
const gates = round === 1 && design ? BESIDE_REVIEW.map((role) => run(role, `Check the ${noun} migration`, 'VERDICT: APPROVED\n\nThe migration is additive and indexed.', { from, beside: true })) : [];
|
|
218
|
+
at = Math.max(reviewer.end, ...gates.map((g) => g.end)) + between * MIN;
|
|
219
|
+
return sent;
|
|
220
|
+
};
|
|
221
|
+
if (review(1)) {
|
|
222
|
+
run('implementer', `Fix the ${noun} second call`, 'VERDICT: DIFF-READY\n\nA second call is a no-op now, and a test calls it twice.');
|
|
223
|
+
review(2);
|
|
224
|
+
}
|
|
225
|
+
if (sendBack === 'qa') {
|
|
226
|
+
run('qa', `Test the ${noun}`, `VERDICT: FAIL\nISSUES: ${slug}-test-order\n\nOne test reads a row another wrote.`);
|
|
227
|
+
run('implementer', `Fix the ${noun} test order`, 'VERDICT: DIFF-READY\n\nEach test makes its own rows.');
|
|
228
|
+
}
|
|
229
|
+
run('qa', `Test the ${noun}`, 'VERDICT: PASS\n\nThe tests the diff touches pass, and the suite once.');
|
|
230
|
+
return { runs, end: at };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Three weeks of invented sessions, and today's: each a list of runs. Today's is the one the office plays. Its first
|
|
235
|
+
* task goes the whole way, sent back once by the reviewer while the database gate approves beside it; its second
|
|
236
|
+
* starts while the first one's qa is at work, so two pieces of work are on the floor at once.
|
|
237
|
+
*
|
|
238
|
+
* @param {number} now - The moment the demo is made; today's last report came in three minutes before it.
|
|
239
|
+
* @returns {{id: string, prompt: string, at: number, runs: object[], asked?: [number, number]}[]}
|
|
240
|
+
*/
|
|
241
|
+
export function demoSessions(now) {
|
|
242
|
+
const random = seeded(2026);
|
|
243
|
+
const sessions = [];
|
|
244
|
+
const days = [21, 20, 19, 18, 17, 15, 14, 13, 12, 11, 9, 8, 7, 6, 5, 3, 2, 1];
|
|
245
|
+
let feature = 0;
|
|
246
|
+
for (const back of days) {
|
|
247
|
+
const morning = new Date(now - back * DAY);
|
|
248
|
+
morning.setHours(9 + Math.floor(random() * 3), Math.floor(random() * 60), 0, 0);
|
|
249
|
+
const runs = [];
|
|
250
|
+
let at = morning.getTime();
|
|
251
|
+
for (let n = 0; n < 1 + Math.floor(random() * 3); n += 1) {
|
|
252
|
+
const [name, noun] = FEATURES[feature % FEATURES.length];
|
|
253
|
+
feature += 1;
|
|
254
|
+
// The reviewer sent the implementer's work back more than half the time before its lesson, ten days ago, and seldom after.
|
|
255
|
+
const roll = random();
|
|
256
|
+
const sendBack = roll < (back > 10 ? 0.6 : 0.1) ? 'reviewer' : roll > 0.92 ? 'qa' : roll > 0.85 ? 'design-reviewer' : null;
|
|
257
|
+
const task = taskRuns(random, at, { name, noun, design: random() < 0.7 || sendBack === 'design-reviewer', sendBack });
|
|
258
|
+
runs.push(...task.runs);
|
|
259
|
+
at = task.end + (5 + random() * 20) * MIN;
|
|
260
|
+
}
|
|
261
|
+
sessions.push({ id: sessionId(random), prompt: 'Next feature, please.', at: morning.getTime() - MIN, runs });
|
|
262
|
+
}
|
|
263
|
+
// Today's: the archive, sent back once, and the list's dates, begun while the archive's tests run. Laid out from zero,
|
|
264
|
+
// then moved so its last report came in three minutes ago: nothing in it happens after `now`.
|
|
265
|
+
const archive = taskRuns(seeded(7), MIN, { name: 'Add archiving to notes', noun: 'archive', design: true, sendBack: 'reviewer' });
|
|
266
|
+
const qa = archive.runs.at(-1);
|
|
267
|
+
const dates = taskRuns(seeded(11), qa.start + 1.5 * MIN, { name: 'Fix the dates on the notes list', noun: 'list dates', design: false });
|
|
268
|
+
const runs = [...archive.runs, ...dates.runs].sort((a, b) => a.start - b.start);
|
|
269
|
+
const shift = now - 3 * MIN - Math.max(...runs.map((r) => r.end));
|
|
270
|
+
for (const r of runs) Object.assign(r, { start: r.start + shift, end: r.end + shift });
|
|
271
|
+
const planned = archive.runs[0];
|
|
272
|
+
sessions.push({
|
|
273
|
+
id: sessionId(random),
|
|
274
|
+
prompt: 'Add archiving to notes, and fix the dates on the list.',
|
|
275
|
+
at: shift,
|
|
276
|
+
runs,
|
|
277
|
+
// The orchestrator asks the owner which of the planner's two steps goes first, and is answered before the architect goes.
|
|
278
|
+
asked: [planned.end + 0.05 * MIN, planned.end + 0.15 * MIN],
|
|
279
|
+
});
|
|
280
|
+
return sessions;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** A session's id, shaped as Claude Code's are. */
|
|
284
|
+
function sessionId(random) {
|
|
285
|
+
const hex = (n) => Array.from({ length: n }, () => Math.floor(random() * 16).toString(16)).join('');
|
|
286
|
+
return `${hex(8)}-${hex(4)}-4${hex(3)}-a${hex(3)}-${hex(12)}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** An agent's id, as hexadecimal as Claude Code's, which the transcript parser reads by that. */
|
|
290
|
+
const agentId = (random) => `a${Array.from({ length: 16 }, () => Math.floor(random() * 16).toString(16)).join('')}`;
|
|
291
|
+
|
|
292
|
+
/** What a stage runs on: the model and effort its composed spec names. */
|
|
293
|
+
function ranOn(project, role) {
|
|
294
|
+
const fields = frontmatter(readFileSync(join(project, '.claude', 'agents', `${role}.md`), 'utf8')) ?? {};
|
|
295
|
+
return { model: fields.model ?? null, effort: fields.effort ?? null };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Writes one session as Claude Code writes it: the orchestrator's transcript, with each dispatch launched in the
|
|
300
|
+
* background and its notification when it ends, and each run's own transcript under `subagents/`, with its tool
|
|
301
|
+
* calls, what each message spent, and the report it handed back.
|
|
302
|
+
*/
|
|
303
|
+
function writeTranscripts(dir, project, session, random, pills) {
|
|
304
|
+
const stamp = (t) => new Date(t).toISOString();
|
|
305
|
+
const main = [];
|
|
306
|
+
const row = (fields) => main.push({ ...fields, cwd: project, sessionId: session.id, version: '2.1.312' });
|
|
307
|
+
row({ type: 'user', uuid: `${session.id}-u0`, timestamp: stamp(session.at), message: { role: 'user', content: session.prompt } });
|
|
308
|
+
const subagents = join(dir, session.id, 'subagents');
|
|
309
|
+
mkdirSync(subagents, { recursive: true });
|
|
310
|
+
for (const [i, run] of session.runs.entries()) {
|
|
311
|
+
run.toolUseId = `toolu_${session.id.slice(0, 8)}${String(i).padStart(3, '0')}`;
|
|
312
|
+
run.agent = agentId(random);
|
|
313
|
+
row({ type: 'assistant', uuid: `${run.toolUseId}-d`, timestamp: stamp(run.start), message: { role: 'assistant', content: [{ type: 'tool_use', id: run.toolUseId, name: 'Agent', input: { subagent_type: run.role, description: run.desc, prompt: `${run.desc}.`, run_in_background: true } }] } });
|
|
314
|
+
row({ type: 'user', uuid: `${run.toolUseId}-l`, timestamp: stamp(run.start + 1000), message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: run.toolUseId, content: `Async agent launched successfully.\nagentId: ${run.agent}` }] } });
|
|
315
|
+
row({
|
|
316
|
+
type: 'user',
|
|
317
|
+
uuid: `${run.toolUseId}-n`,
|
|
318
|
+
timestamp: stamp(run.end + 2000),
|
|
319
|
+
message: { role: 'user', content: `<task-notification>\n<task-id>${run.agent}</task-id>\n<tool-use-id>${run.toolUseId}</tool-use-id>\n<status>completed</status>\n<result>${run.report}</result>\n<duration_ms>${run.end - run.start}</duration_ms>\n</task-notification>` },
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const { model, effort } = ranOn(project, run.role);
|
|
323
|
+
// Each stage opens the lessons its directory held when it was dispatched, as its spec tells it to.
|
|
324
|
+
const lessons = pills.filter((p) => p.path.startsWith(`${run.role}/`) && p.since <= run.start).map((p) => ['Read', { file_path: join(project, '.claude', 'pills', p.path) }]);
|
|
325
|
+
const tools = [...lessons, ...TOOLS[run.role]];
|
|
326
|
+
const [lo, hi] = WRITES[run.role];
|
|
327
|
+
const written = Math.round((lo + random() * (hi - lo)) * 1000);
|
|
328
|
+
const steps = tools.length + 1;
|
|
329
|
+
const own = [{ type: 'user', uuid: `${run.agent}-p`, timestamp: stamp(run.start), agentId: run.agent, sessionId: session.id, message: { role: 'user', content: `${run.desc}.` } }];
|
|
330
|
+
const said = (k, content, share) => {
|
|
331
|
+
const usage = { input_tokens: 4 + k, output_tokens: Math.round(written * share), cache_read_input_tokens: Math.round(written * (8 + random() * 24) * share), cache_creation_input_tokens: Math.round(written * (0.5 + random()) * share) };
|
|
332
|
+
own.push({ type: 'assistant', uuid: `${run.agent}-m${k}`, timestamp: stamp(run.start + ((run.end - run.start) * (k + 1)) / (steps + 1)), agentId: run.agent, sessionId: session.id, effort, message: { id: `msg_${run.agent}${k}`, role: 'assistant', model, usage, content } });
|
|
333
|
+
};
|
|
334
|
+
tools.forEach(([name, input], k) => {
|
|
335
|
+
const lines = name === 'Edit' ? { old_string: 'export {};', new_string: Array.from({ length: 12 + Math.floor(random() * 40) }, (_, n) => `// line ${n + 1}`).join('\n') } : name === 'Write' ? { content: Array.from({ length: 20 + Math.floor(random() * 30) }, (_, n) => `line ${n + 1}`).join('\n') } : {};
|
|
336
|
+
said(k, [{ type: 'tool_use', id: `${run.agent}-t${k}`, name, input: { ...input, ...lines } }], 1 / steps);
|
|
337
|
+
});
|
|
338
|
+
said(tools.length, [{ type: 'tool_use', id: `${run.agent}-h`, name: 'SubagentHandback', input: { message: run.report } }], 1 / steps);
|
|
339
|
+
writeFileSync(join(subagents, `agent-${run.agent}.jsonl`), `${own.map((r) => JSON.stringify(r)).join('\n')}\n`);
|
|
340
|
+
writeFileSync(join(subagents, `agent-${run.agent}.meta.json`), JSON.stringify({ agentType: run.role, toolUseId: run.toolUseId }));
|
|
341
|
+
}
|
|
342
|
+
writeFileSync(join(dir, `${session.id}.jsonl`), `${main.map((r) => JSON.stringify(r)).join('\n')}\n`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Today's session through the loop gate, event by event, each at its moment: what its hooks would have been handed.
|
|
347
|
+
* The ledger it writes is the one the office plays.
|
|
348
|
+
*/
|
|
349
|
+
function throughTheGate(project, session, subagents) {
|
|
350
|
+
const events = [{ at: session.at, input: { hook_event_name: 'UserPromptSubmit', prompt: session.prompt } }];
|
|
351
|
+
if (session.asked) {
|
|
352
|
+
events.push({ at: session.asked[0], input: { hook_event_name: 'PreToolUse', tool_name: 'AskUserQuestion', tool_input: {} } });
|
|
353
|
+
events.push({ at: session.asked[1], input: { hook_event_name: 'PostToolUse', tool_name: 'AskUserQuestion', tool_input: {} } });
|
|
354
|
+
}
|
|
355
|
+
for (const run of session.runs) {
|
|
356
|
+
const tool_input = { subagent_type: run.role, description: run.desc, prompt: `${run.desc}.`, run_in_background: true };
|
|
357
|
+
events.push({ at: run.start, input: { hook_event_name: 'PreToolUse', tool_name: 'Agent', tool_input, tool_use_id: run.toolUseId } });
|
|
358
|
+
events.push({ at: run.start + 1000, input: { hook_event_name: 'PostToolUse', tool_name: 'Agent', tool_input, tool_use_id: run.toolUseId, tool_response: { agentId: run.agent, isAsync: true, status: 'async_launched' } } });
|
|
359
|
+
events.push({ at: run.end, input: { hook_event_name: 'PostToolUse', tool_name: 'SubagentHandback', agent_id: run.agent, agent_type: run.role, tool_input: { message: run.report } } });
|
|
360
|
+
events.push({ at: run.end + 500, input: { hook_event_name: 'SubagentStop', agent_id: run.agent, agent_type: run.role, last_assistant_message: 'Handed back.', agent_transcript_path: join(subagents, `agent-${run.agent}.jsonl`), stop_hook_active: false } });
|
|
361
|
+
events.push({ at: run.end + 2000, input: { hook_event_name: 'UserPromptSubmit', prompt: `<task-notification>\n<task-id>${run.agent}</task-id>\n<tool-use-id>${run.toolUseId}</tool-use-id>\n<status>completed</status>\n</task-notification>` } });
|
|
362
|
+
}
|
|
363
|
+
events.sort((a, b) => a.at - b.at);
|
|
364
|
+
for (const { at, input } of events) handle({ ...input, session_id: session.id }, { root: project, now: new Date(at) });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Makes the demo: the project, its history, and the environment that points every reader at them.
|
|
369
|
+
*
|
|
370
|
+
* @param {{root: string}} ctx - CLI context: where the releases are.
|
|
371
|
+
* @param {{now?: number}} [options]
|
|
372
|
+
* @returns {Promise<{project: string, dir: string, session: string, remove: () => void}>} `dir` holds all of it, and
|
|
373
|
+
* `remove` takes it away.
|
|
374
|
+
*/
|
|
375
|
+
export async function makeDemo(ctx, { now = Date.now() } = {}) {
|
|
376
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'nina-demo-')));
|
|
377
|
+
const remove = () => rmSync(dir, { recursive: true, force: true });
|
|
378
|
+
const before = { NINA_DATA: process.env.NINA_DATA, NINA_TRANSCRIPTS: process.env.NINA_TRANSCRIPTS };
|
|
379
|
+
try {
|
|
380
|
+
const project = join(dir, DEMO_NAME);
|
|
381
|
+
const data = join(dir, 'data');
|
|
382
|
+
const transcripts = join(dir, 'transcripts');
|
|
383
|
+
mkdirSync(join(project, '.nina'), { recursive: true });
|
|
384
|
+
writeFileSync(join(project, '.nina', 'profile.json'), `${JSON.stringify(profileAt(ownRelease(ctx.root)), null, 2)}\n`);
|
|
385
|
+
const composed = await composeProject(project, ctx);
|
|
386
|
+
if (composed.error) throw new Error(composed.error);
|
|
387
|
+
|
|
388
|
+
const pills = PILLS(now);
|
|
389
|
+
for (const pill of pills) {
|
|
390
|
+
const path = join(project, '.claude', 'pills', pill.path);
|
|
391
|
+
mkdirSync(join(path, '..'), { recursive: true });
|
|
392
|
+
writeFileSync(path, pill.text);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
process.env.NINA_DATA = data;
|
|
396
|
+
process.env.NINA_TRANSCRIPTS = transcripts;
|
|
397
|
+
const slug = slugFor(project);
|
|
398
|
+
const own = join(transcripts, slug);
|
|
399
|
+
mkdirSync(own, { recursive: true });
|
|
400
|
+
const random = seeded(29);
|
|
401
|
+
const sessions = demoSessions(now);
|
|
402
|
+
for (const session of sessions) writeTranscripts(own, project, session, random, pills);
|
|
403
|
+
const today = sessions.at(-1);
|
|
404
|
+
throughTheGate(project, today, join(own, today.id, 'subagents'));
|
|
405
|
+
const captured = await snapshot(['--project', slug, '--exact', '--quiet'], ctx);
|
|
406
|
+
if (captured !== 0) throw new Error('the snapshot could not read the demo');
|
|
407
|
+
return { project, dir, session: today.id, remove };
|
|
408
|
+
} catch (error) {
|
|
409
|
+
// Pointed at a directory about to be gone, every reader after this would find nothing.
|
|
410
|
+
for (const [name, value] of Object.entries(before)) {
|
|
411
|
+
if (value === undefined) delete process.env[name];
|
|
412
|
+
else process.env[name] = value;
|
|
413
|
+
}
|
|
414
|
+
remove();
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
}
|
package/src/office.mjs
CHANGED
|
@@ -302,6 +302,9 @@ export function busyFor(starts) {
|
|
|
302
302
|
const REPLAY_MAX_GAP_MS = 3000;
|
|
303
303
|
const REPLAY_MIN_GAP_MS = 500;
|
|
304
304
|
|
|
305
|
+
/** How long a replay in a loop rests on its last beat before it plays again from the start: the party is seen out. */
|
|
306
|
+
export const LOOP_REST_MS = 8000;
|
|
307
|
+
|
|
305
308
|
/**
|
|
306
309
|
* Each run the ledger saw start, in order: when, at which stage, by which agent once known, whether it was a message
|
|
307
310
|
* to a running agent, when it was refused, and when its verdict ended it. The beats and the timeline read the same.
|
|
@@ -626,10 +629,11 @@ const send = (res, event, data) => res.write(`event: ${event}\ndata: ${JSON.stri
|
|
|
626
629
|
* anyone watches live. `serveOffice` mounts them at the root; the dashboard (`nina dashboard`) at `/office/`, where the
|
|
627
630
|
* page's relative `events` reaches `/office/events`.
|
|
628
631
|
*
|
|
629
|
-
* @param {{target: string, wanted?: string, replay?: boolean}} options
|
|
632
|
+
* @param {{target: string, wanted?: string, replay?: boolean, loop?: boolean, rest?: number}} options - `loop`: a replay
|
|
633
|
+
* that ends plays again from the start, for an office left on a screen, after resting `rest` ms on its last beat.
|
|
630
634
|
* @returns {{handle: (req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse, path: string) => boolean, close: () => void}}
|
|
631
635
|
*/
|
|
632
|
-
export function officeRoutes({ target, wanted, replay = false }) {
|
|
636
|
+
export function officeRoutes({ target, wanted, replay = false, loop = false, rest = LOOP_REST_MS }) {
|
|
633
637
|
const room = officeRoom(target);
|
|
634
638
|
const graph = parseGraph(readFileSync(join(target, '.claude', 'graph.md'), 'utf8'));
|
|
635
639
|
// A function, so a `$&` or `$'` in a project's name or its owner's is text and not a replacement pattern.
|
|
@@ -726,7 +730,16 @@ export function officeRoutes({ target, wanted, replay = false }) {
|
|
|
726
730
|
const next = () => {
|
|
727
731
|
if (!clients.has(client)) return;
|
|
728
732
|
if (i >= beats.length) {
|
|
729
|
-
|
|
733
|
+
if (!loop) {
|
|
734
|
+
send(client.res, 'end', { beats: beats.length });
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
// Played again from its `hello`, which empties the room the page drew.
|
|
738
|
+
const timer = setTimeout(() => {
|
|
739
|
+
timers.delete(timer);
|
|
740
|
+
if (clients.has(client)) play(client);
|
|
741
|
+
}, rest);
|
|
742
|
+
timers.add(timer);
|
|
730
743
|
return;
|
|
731
744
|
}
|
|
732
745
|
const beat = beats[i];
|