@xhulz/nina 0.79.0 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/nina.mjs +3 -0
- package/package.json +1 -1
- package/releases/0.80.0/core/GAPS.md +125 -0
- package/releases/0.80.0/core/answered.json +128 -0
- package/releases/0.80.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.80.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.80.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.80.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.80.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.80.0/core/defaults/tree/.claude/agents/qa.md +7 -0
- package/releases/0.80.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.80.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.80.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.80.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.80.0/core/tree/.claude/agents/architect.md +140 -0
- package/releases/0.80.0/core/tree/.claude/agents/dba.md +123 -0
- package/releases/0.80.0/core/tree/.claude/agents/design-reviewer.md +82 -0
- package/releases/0.80.0/core/tree/.claude/agents/devops.md +122 -0
- package/releases/0.80.0/core/tree/.claude/agents/implementer.md +208 -0
- package/releases/0.80.0/core/tree/.claude/agents/integration-tester.md +174 -0
- package/releases/0.80.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.80.0/core/tree/.claude/agents/qa.md +154 -0
- package/releases/0.80.0/core/tree/.claude/agents/reviewer.md +191 -0
- package/releases/0.80.0/core/tree/.claude/agents/secops.md +124 -0
- package/releases/0.80.0/core/tree/.claude/agents/solidity-auditor.md +130 -0
- package/releases/0.80.0/core/tree/.claude/agents/solidity-dev.md +127 -0
- package/releases/0.80.0/core/tree/.claude/agents-overview.md +65 -0
- package/releases/0.80.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.80.0/core/tree/.claude/graph.md +79 -0
- package/releases/0.80.0/core/tree/.claude/patterns.md +256 -0
- package/releases/0.80.0/core/tree/.claude/pills/README.md +150 -0
- package/releases/0.80.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.80.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.80.0/core/tree/.claude/router.md +224 -0
- package/releases/0.80.0/core/tree/.claude/templates/integration.md +48 -0
- package/releases/0.80.0/core/tree/CLAUDE.md +167 -0
- package/releases/0.80.0/core/tree/scripts/cost-watch.mjs +27 -0
- package/releases/0.80.0/core/tree/scripts/edit-guard.mjs +24 -0
- package/releases/0.80.0/core/tree/scripts/harness-check.mjs +84 -0
- package/releases/0.80.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.80.0/core/vocabulary.json +15 -0
- package/releases/0.80.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.80.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.80.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.80.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.80.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.80.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.80.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.80.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.80.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.80.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
- package/releases/0.80.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
- package/releases/0.80.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.80.0/surfaces/d1/tree/.claude/patterns.md +8 -0
- package/releases/0.80.0/surfaces/d1/tree/CLAUDE.md +2 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/patterns.md +22 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.80.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.80.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.80.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
- package/releases/0.80.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
- package/releases/0.80.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.80.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
- package/releases/0.80.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.80.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.80.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
- package/releases/0.80.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.80.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.80.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.80.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.80.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.80.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.80.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.80.0/surfaces/edge-cf/tree/CLAUDE.md +29 -0
- package/releases/0.80.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.80.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.80.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.80.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.80.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.80.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.80.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.80.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.80.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.80.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.80.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.80.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.80.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.80.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.80.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.80.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.80.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.80.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.80.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.80.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.80.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.80.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.80.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.80.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.80.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.80.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.80.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.80.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.80.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.80.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.80.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.80.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.80.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.80.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.80.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.80.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.80.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.80.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.80.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.80.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.80.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.80.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.80.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
- package/releases/0.80.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.80.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.80.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.80.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.80.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.80.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/releases/0.81.0/core/GAPS.md +125 -0
- package/releases/0.81.0/core/answered.json +128 -0
- package/releases/0.81.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.81.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.81.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.81.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.81.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.81.0/core/defaults/tree/.claude/agents/qa.md +7 -0
- package/releases/0.81.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.81.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.81.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.81.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.81.0/core/tiers.json +5 -0
- package/releases/0.81.0/core/tree/.claude/agents/architect.md +140 -0
- package/releases/0.81.0/core/tree/.claude/agents/dba.md +123 -0
- package/releases/0.81.0/core/tree/.claude/agents/design-reviewer.md +82 -0
- package/releases/0.81.0/core/tree/.claude/agents/devops.md +122 -0
- package/releases/0.81.0/core/tree/.claude/agents/implementer.md +208 -0
- package/releases/0.81.0/core/tree/.claude/agents/integration-tester.md +174 -0
- package/releases/0.81.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.81.0/core/tree/.claude/agents/qa.md +154 -0
- package/releases/0.81.0/core/tree/.claude/agents/reviewer.md +191 -0
- package/releases/0.81.0/core/tree/.claude/agents/secops.md +124 -0
- package/releases/0.81.0/core/tree/.claude/agents/solidity-auditor.md +130 -0
- package/releases/0.81.0/core/tree/.claude/agents/solidity-dev.md +127 -0
- package/releases/0.81.0/core/tree/.claude/agents-overview.md +65 -0
- package/releases/0.81.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.81.0/core/tree/.claude/graph.md +79 -0
- package/releases/0.81.0/core/tree/.claude/patterns.md +256 -0
- package/releases/0.81.0/core/tree/.claude/pills/README.md +150 -0
- package/releases/0.81.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.81.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.81.0/core/tree/.claude/router.md +224 -0
- package/releases/0.81.0/core/tree/.claude/templates/integration.md +48 -0
- package/releases/0.81.0/core/tree/CLAUDE.md +167 -0
- package/releases/0.81.0/core/tree/scripts/cost-watch.mjs +27 -0
- package/releases/0.81.0/core/tree/scripts/edit-guard.mjs +24 -0
- package/releases/0.81.0/core/tree/scripts/harness-check.mjs +84 -0
- package/releases/0.81.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.81.0/core/vocabulary.json +18 -0
- package/releases/0.81.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.81.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.81.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.81.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.81.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.81.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.81.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.81.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.81.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.81.0/surfaces/d1/tree/.claude/agents/dba.md +15 -0
- package/releases/0.81.0/surfaces/d1/tree/.claude/agents/devops.md +2 -0
- package/releases/0.81.0/surfaces/d1/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.81.0/surfaces/d1/tree/.claude/patterns.md +8 -0
- package/releases/0.81.0/surfaces/d1/tree/CLAUDE.md +2 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/agents/dba.md +54 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/patterns.md +22 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.81.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.81.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.81.0/surfaces/drizzle/tree/.claude/agents/dba.md +9 -0
- package/releases/0.81.0/surfaces/drizzle/tree/.claude/agents/devops.md +5 -0
- package/releases/0.81.0/surfaces/drizzle/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.81.0/surfaces/drizzle/tree/.claude/patterns.md +7 -0
- package/releases/0.81.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.81.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.81.0/surfaces/edge-cf/tree/.claude/agents/devops.md +29 -0
- package/releases/0.81.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.81.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.81.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.81.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.81.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.81.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.81.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.81.0/surfaces/edge-cf/tree/CLAUDE.md +29 -0
- package/releases/0.81.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.81.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.81.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.81.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.81.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.81.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.81.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.81.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.81.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.81.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.81.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.81.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.81.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.81.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.81.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.81.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.81.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.81.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.81.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.81.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.81.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.81.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.81.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.81.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.81.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.81.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.81.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.81.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.81.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.81.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.81.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.81.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.81.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.81.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.81.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.81.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.81.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.81.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.81.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.81.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.81.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.81.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.81.0/surfaces/prisma/tree/.claude/agents/dba.md +42 -0
- package/releases/0.81.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.81.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.81.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.81.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.81.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.81.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/src/commands/check.mjs +4 -2
- package/src/commands/compose.mjs +58 -4
- package/src/commands/eval.mjs +35 -24
- package/src/commands/init.mjs +14 -4
- package/src/commands/status.mjs +11 -1
- package/src/commands/upgrade.mjs +17 -2
- package/src/vocabulary.mjs +76 -2
|
@@ -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. **A test that proves a value stays out matches by containment** (`String(v).includes(value)`), never by equality: a leak arrives inside an id, a message or a key. It gives each log call in the source a run, counts the console's calls by method and asserts each method the path must not use at zero, and reads at least what the console prints of each entry and each error the runs produce, recursively: every own key, symbols and non-enumerable ones too (`Reflect.ownKeys`), and the entries of every `Map`, `Set`, `Headers` and `URLSearchParams`, unless the spec rules one out in writing. Its controls put their value where the real emitter puts its own, past a call's first argument and among several, and the spec names the window its spy covers and why nothing logs after it. Its forbidden values are the run's own that § *Privacy* keeps out — where it lists what may be logged, every other value the run carries — each with a control that embeds it in a longer string under an allowed key and must fail the check, and no fixture value inside another's.<!-- nina:why --> In one project an allowlist check that refused only equal values passed a mutant logging `${id}:${amount}`; the next read `Object.keys` and missed an amount on a non-enumerable property and an info log no row inspected; the one after left out a class and four log paths.<!-- /nina:why --> Where what it claims to keep out names a class — an attribute, an amount — it covers every member: a surface with open-ended slots is read whole, never through a list of them — every attribute of every element a rendered page carries, just as every property of an error is — and a sweep that read nothing fails; each forbidden value is looked for in every form the code can give it, formatted and raw; and each state that shows, hides or replaces a value is swept on its own, the one built to withhold it most of all. One mutant puts the value in a slot the check does not name — `data-x` on a page; on an object or an error, a non-enumerable key, a symbol key, a nested object, a `Map` entry, `cause`, the end of `message` or `stack` — and one per field puts that field's raw value in each such slot, on every error the code throws, not only one it builds: one left green is a slot or a form the check misses. The implementer runs them on its mirror, as one block it reuses, before `DIFF-READY`, whatever the step lists, and reports one left green as the row the spec lacks (Hard Rule #17), adding it only in a file the step authorizes.<!-- nina:why --> In one project a walk of own keys missed a `Map`, a symbol key and a nested object, and every control put its value in a call's first argument, where the runtime's loggers put theirs second; then three steps in two days went back from review for a slot no listed mutant tried — an error's `cause`, its `stack`, a hidden key.<!-- /nina:why --><!-- nina:why --> In one project a sweep of `data-testid`, `aria-label` and `title`, proven by mutants in those three, stayed green with the label in a `data-label`; the next swept every attribute for the formatted amounts and missed their raw centavos; the one after swept only the state that shows the figure, and the row built to show none carried it in a `title`.<!-- /nina:why --> 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/check.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { HOOK_SCRIPTS, missingWiring, shippedScripts } from '../wiring.mjs';
|
|
|
17
17
|
import { existsSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
18
18
|
import { basename, dirname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
19
19
|
import { SLOT, closedGate, defaultedSlots, layerRootFor, noticeOf, readLayerText, stripWhy, walk } from './compose.mjs';
|
|
20
|
-
import { PLACEHOLDER, defaultVocabulary } from '../vocabulary.mjs';
|
|
20
|
+
import { PLACEHOLDER, defaultVocabulary, tierProblem } from '../vocabulary.mjs';
|
|
21
21
|
import { missingNeeds } from '../surfaces.mjs';
|
|
22
22
|
import { resolveCitation } from './pills.mjs';
|
|
23
23
|
|
|
@@ -489,7 +489,9 @@ export async function check(argv, ctx) {
|
|
|
489
489
|
|
|
490
490
|
const referenced = await referencedVocabulary(resolved.dir, surfaces.filter((s) => available.includes(s)), target);
|
|
491
491
|
const declared = profile.vocabulary ?? {};
|
|
492
|
-
const
|
|
492
|
+
const tierError = tierProblem(resolved.dir, profile.tier, profile.core);
|
|
493
|
+
if (tierError) problems.push(tierError);
|
|
494
|
+
const defaults = defaultVocabulary(resolved.dir, profile.tier);
|
|
493
495
|
for (const name of [...referenced].sort()) {
|
|
494
496
|
const unanswered = !(name in declared) ? !(name in defaults) : declared[name] === null || declared[name] === '';
|
|
495
497
|
if (unanswered && expected.has(`{{${name}}}`)) notes.push(`vocabulary {{${name}}} is new in this core and still to fill`);
|
package/src/commands/compose.mjs
CHANGED
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
20
20
|
import { HARNESS, deferredWhy, layerRootFor, readProfile } from '../paths.mjs';
|
|
21
21
|
import { expectedUnfilled } from '../expected.mjs';
|
|
22
|
-
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { existsSync, lstatSync, readFileSync, writeFileSync } from 'node:fs';
|
|
23
23
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
24
|
-
import { NAME, PLACEHOLDER, defaultVocabulary } from '../vocabulary.mjs';
|
|
24
|
+
import { NAME, PLACEHOLDER, defaultVocabulary, modelsLine, tierProblem } from '../vocabulary.mjs';
|
|
25
25
|
|
|
26
26
|
export { layerRootFor };
|
|
27
27
|
|
|
@@ -426,7 +426,11 @@ export async function composeProject(target, ctx, options = {}) {
|
|
|
426
426
|
const resolved = layerRootFor(ctx.root, profile.core);
|
|
427
427
|
if (resolved.error) return { error: resolved.error, written, differ, unfilled: [], skipped };
|
|
428
428
|
const layerRoot = resolved.dir;
|
|
429
|
-
|
|
429
|
+
// A tier the pinned release does not have is refused rather than composed as the release's default: the
|
|
430
|
+
// project asked for other models, and composing these without a word would run every stage on the wrong one.
|
|
431
|
+
const tierError = tierProblem(layerRoot, profile.tier, profile.core);
|
|
432
|
+
if (tierError) return { error: tierError, written, differ, unfilled: [], skipped };
|
|
433
|
+
const defaults = defaultVocabulary(layerRoot, profile.tier);
|
|
430
434
|
|
|
431
435
|
const coreTree = join(layerRoot, 'core', 'tree');
|
|
432
436
|
const layers = [
|
|
@@ -579,16 +583,60 @@ export async function composeProject(target, ctx, options = {}) {
|
|
|
579
583
|
return { written, differ, unfilled, skipped, malformed, removed };
|
|
580
584
|
}
|
|
581
585
|
|
|
586
|
+
/**
|
|
587
|
+
* Records a tier in a project's profile, once the release it pins is known to have it.
|
|
588
|
+
*
|
|
589
|
+
* The tier lives in the profile, not on the command line: a flag that only composed would be undone by the
|
|
590
|
+
* next `compose` without it, and every hook that recomposes runs without it. `tier` goes after `core`, the
|
|
591
|
+
* release whose tiers it names; the rest of the profile keeps its order.
|
|
592
|
+
*
|
|
593
|
+
* @param {string} target - The project directory.
|
|
594
|
+
* @param {string} tier - The level asked for.
|
|
595
|
+
* @param {{root: string}} ctx - CLI context.
|
|
596
|
+
* @returns {{error: string} | {tier: string, from: string|undefined, line: string}}
|
|
597
|
+
*/
|
|
598
|
+
export function setTier(target, tier, ctx) {
|
|
599
|
+
const { path, profile, error } = readProfile(target);
|
|
600
|
+
if (error) return { error };
|
|
601
|
+
const layers = layerRootFor(ctx.root, profile.core);
|
|
602
|
+
if (layers.error) return { error: layers.error };
|
|
603
|
+
const problem = tierProblem(layers.dir, tier, profile.core);
|
|
604
|
+
if (problem) return { error: problem };
|
|
605
|
+
const from = profile.tier;
|
|
606
|
+
if (from !== tier) {
|
|
607
|
+
const entries = Object.entries(profile).filter(([key]) => key !== 'tier');
|
|
608
|
+
const at = entries.findIndex(([key]) => key === 'core') + 1;
|
|
609
|
+
entries.splice(at, 0, ['tier', tier]);
|
|
610
|
+
writeFileSync(path, `${JSON.stringify(Object.fromEntries(entries), null, 2)}\n`);
|
|
611
|
+
}
|
|
612
|
+
const declared = Object.fromEntries(Object.entries(profile.vocabulary ?? {}).filter(([, value]) => typeof value === 'string'));
|
|
613
|
+
return { tier, from, line: modelsLine({ ...defaultVocabulary(layers.dir, tier), ...declared }) };
|
|
614
|
+
}
|
|
615
|
+
|
|
582
616
|
/**
|
|
583
617
|
* `nina compose` — the CLI wrapper around {@link composeProject}.
|
|
584
618
|
*
|
|
585
|
-
* @param {string[]} argv - Command arguments; `--project <dir>` selects the target
|
|
619
|
+
* @param {string[]} argv - Command arguments; `--project <dir>` selects the target, `--tier <level>` records the
|
|
620
|
+
* project's tier in its profile before composing.
|
|
586
621
|
* @param {{root: string}} ctx - CLI context; `root` is the NINA install directory.
|
|
587
622
|
* @returns {Promise<number>} Process exit code.
|
|
588
623
|
*/
|
|
589
624
|
export async function compose(argv, ctx) {
|
|
590
625
|
const target = resolve(argv.includes('--project') ? argv[argv.indexOf('--project') + 1] : '.');
|
|
591
626
|
const check = argv.includes('--check');
|
|
627
|
+
const tier = argv.includes('--tier') ? argv[argv.indexOf('--tier') + 1] : undefined;
|
|
628
|
+
let moved = null;
|
|
629
|
+
if (tier !== undefined) {
|
|
630
|
+
if (check) {
|
|
631
|
+
console.error(' --tier writes the profile, and --check writes nothing: run them one at a time');
|
|
632
|
+
return 2;
|
|
633
|
+
}
|
|
634
|
+
moved = setTier(target, tier, ctx);
|
|
635
|
+
if (moved.error) {
|
|
636
|
+
console.error(` ${moved.error}`);
|
|
637
|
+
return 1;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
592
640
|
const result = await composeProject(target, ctx, { check });
|
|
593
641
|
|
|
594
642
|
if (result.error) {
|
|
@@ -639,6 +687,12 @@ export async function compose(argv, ctx) {
|
|
|
639
687
|
return bad === 0 ? 0 : 1;
|
|
640
688
|
}
|
|
641
689
|
console.log(`composed ${result.written.length} file(s) → ${relative(process.cwd(), target) || '.'}`);
|
|
690
|
+
if (moved) {
|
|
691
|
+
console.log(` tier ${moved.tier}${moved.from === moved.tier ? ', as it was' : `, was ${moved.from ?? 'not declared'}`} — ${moved.line}`);
|
|
692
|
+
// Claude Code registers each agent, its frontmatter included, when a session starts; a session left open may
|
|
693
|
+
// keep the models it registered.
|
|
694
|
+
if (moved.from !== moved.tier) console.log(' open a new Claude Code session before the next dispatch: one already open may keep the models it started with');
|
|
695
|
+
}
|
|
642
696
|
if (result.removed.length > 0) {
|
|
643
697
|
console.log(` removed ${result.removed.length} file(s) composed for a surface this profile no longer declares — a copy of each is in ${REMOVED}/:`);
|
|
644
698
|
for (const rel of result.removed) console.log(` ${rel}`);
|
package/src/commands/eval.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* `nina eval` — does a release's reviewer catch more of a planted set of defects than another's?
|
|
3
3
|
*
|
|
4
4
|
* nina eval --release 0.23.0 --release 0.24.0 [--repeat 2] [--model <m>] [--judge] [--keep]
|
|
5
|
+
* nina eval --release 0.81.0 --tier low --tier high [--repeat 2]
|
|
5
6
|
* nina eval --release 0.24.0 --dry-run
|
|
6
7
|
* nina eval --regrade <reports dir> [--judge]
|
|
7
8
|
*
|
|
@@ -57,9 +58,10 @@ const IGNORE = ['.claude/*', '!.claude/plans/', '!.claude/code-map.md', '!.claud
|
|
|
57
58
|
/** How far a cited line may sit from a defect's anchor and still be that defect. */
|
|
58
59
|
const TOLERANCE = 2;
|
|
59
60
|
|
|
60
|
-
/** The vocabulary the fixture composes with: a plain project, no surfaces. */
|
|
61
|
-
const PROFILE = (core) => ({
|
|
61
|
+
/** The vocabulary the fixture composes with: a plain project, no surfaces, at the tier asked for if any. */
|
|
62
|
+
const PROFILE = (core, tier) => ({
|
|
62
63
|
core,
|
|
64
|
+
...(tier !== undefined ? { tier } : {}),
|
|
63
65
|
surfaces: [],
|
|
64
66
|
vocabulary: { OWNER: 'the maintainer', API_DIR: 'src/server', APP_DIR: 'src/web', PKG_SCOPE: '@notes', EMITTING_PKGS: '`core`' },
|
|
65
67
|
});
|
|
@@ -171,12 +173,13 @@ export function grade(report, defects) {
|
|
|
171
173
|
* @param {string} fixture - The eval's directory.
|
|
172
174
|
* @param {string} core - The release to compose.
|
|
173
175
|
* @param {{root: string}} ctx - CLI context.
|
|
176
|
+
* @param {string} [tier] - The tier to compose it at; its own models when absent.
|
|
174
177
|
* @returns {Promise<string>} The scratch project.
|
|
175
178
|
*/
|
|
176
|
-
async function stage(fixture, core, ctx) {
|
|
179
|
+
async function stage(fixture, core, ctx, tier) {
|
|
177
180
|
const dir = mkdtempSync(join(tmpdir(), 'nina-eval-'));
|
|
178
181
|
try {
|
|
179
|
-
return await build(dir, fixture, core, ctx);
|
|
182
|
+
return await build(dir, fixture, core, ctx, tier);
|
|
180
183
|
} catch (error) {
|
|
181
184
|
rmSync(dir, { recursive: true, force: true });
|
|
182
185
|
throw error;
|
|
@@ -184,7 +187,7 @@ async function stage(fixture, core, ctx) {
|
|
|
184
187
|
}
|
|
185
188
|
|
|
186
189
|
/** Fills a scratch directory with the reviewer's world; `stage` removes it if this throws. */
|
|
187
|
-
async function build(dir, fixture, core, ctx) {
|
|
190
|
+
async function build(dir, fixture, core, ctx, tier) {
|
|
188
191
|
cpSync(join(fixture, 'base'), dir, { recursive: true });
|
|
189
192
|
writeFileSync(join(dir, '.gitignore'), IGNORE);
|
|
190
193
|
const git = (...args) => spawnSync('git', ['-c', 'user.name=nina-eval', '-c', 'user.email=eval@nina.invalid', ...args], { cwd: dir, encoding: 'utf8' });
|
|
@@ -195,7 +198,7 @@ async function build(dir, fixture, core, ctx) {
|
|
|
195
198
|
// reviewer reads is the change alone.
|
|
196
199
|
cpSync(join(fixture, 'after'), dir, { recursive: true });
|
|
197
200
|
mkdirSync(join(dir, '.nina'), { recursive: true });
|
|
198
|
-
writeFileSync(join(dir, '.nina', 'profile.json'), JSON.stringify(PROFILE(core), null, 2));
|
|
201
|
+
writeFileSync(join(dir, '.nina', 'profile.json'), JSON.stringify(PROFILE(core, tier), null, 2));
|
|
199
202
|
const result = await composeProject(dir, ctx);
|
|
200
203
|
if (result.error) throw new Error(result.error);
|
|
201
204
|
if (!existsSync(join(dir, '.claude', 'agents', 'reviewer.md'))) throw new Error(`core ${core} composed no reviewer into ${dir}`);
|
|
@@ -472,6 +475,9 @@ export function reviewerCommand({ model, api }) {
|
|
|
472
475
|
*/
|
|
473
476
|
export async function evalCommand(argv, ctx) {
|
|
474
477
|
const releases = argv.flatMap((a, i) => (a === '--release' ? [argv[i + 1]] : []));
|
|
478
|
+
// Each release at each tier asked for, or at its own models when none is: an arm is one composition to measure.
|
|
479
|
+
const levels = argv.flatMap((a, i) => (a === '--tier' ? [argv[i + 1]] : []));
|
|
480
|
+
const arms = releases.flatMap((core) => (levels.length ? levels.map((tier) => ({ core, tier, label: `${core}@${tier}` })) : [{ core, label: core }]));
|
|
475
481
|
const repeat = Number(argv.includes('--repeat') ? argv[argv.indexOf('--repeat') + 1] : 1);
|
|
476
482
|
const options = {
|
|
477
483
|
model: argv.includes('--model') ? argv[argv.indexOf('--model') + 1] : undefined,
|
|
@@ -483,10 +489,15 @@ export async function evalCommand(argv, ctx) {
|
|
|
483
489
|
const reportsAt = argv.includes('--reports') ? argv[argv.indexOf('--reports') + 1] : null;
|
|
484
490
|
const regrade = argv.includes('--regrade') ? argv[argv.indexOf('--regrade') + 1] : null;
|
|
485
491
|
if ((releases.length === 0 && !regrade) || !Number.isInteger(repeat) || repeat < 1) {
|
|
486
|
-
console.error(' usage: nina eval --release <version> [--release <version>] [--repeat N] [--model m] [--judge] [--dry-run] [--keep] [--reports <dir>]');
|
|
492
|
+
console.error(' usage: nina eval --release <version> [--release <version>] [--tier <level>] [--repeat N] [--model m] [--judge] [--dry-run] [--keep] [--reports <dir>]');
|
|
487
493
|
console.error(' nina eval --regrade <reports dir> [--judge]\n');
|
|
488
494
|
return 2;
|
|
489
495
|
}
|
|
496
|
+
// --model is passed to the reviewer over its spec's model line, so under it two tiers would differ in effort alone.
|
|
497
|
+
if (levels.length > 0 && options.model) {
|
|
498
|
+
console.error(' --model runs every reviewer on one model, which is half of what --tier compares: give one or the other\n');
|
|
499
|
+
return 2;
|
|
500
|
+
}
|
|
490
501
|
|
|
491
502
|
const fixture = join(ctx.root, 'evals', 'reviewer');
|
|
492
503
|
const defects = plantedDefects(fixture);
|
|
@@ -525,7 +536,7 @@ export async function evalCommand(argv, ctx) {
|
|
|
525
536
|
return 0;
|
|
526
537
|
}
|
|
527
538
|
control();
|
|
528
|
-
console.log(` ${defects.length} planted defects · ${releases.length} release(s) × ${repeat} run(s) · reviewer via \`claude -p\`` +
|
|
539
|
+
console.log(` ${defects.length} planted defects · ${releases.length} release(s)${levels.length ? ` × ${levels.length} tier(s)` : ''} × ${repeat} run(s) · reviewer via \`claude -p\`` +
|
|
529
540
|
(options.api ? ' with the API credentials in the environment' : ' on the login, never a per-token key') + '\n');
|
|
530
541
|
|
|
531
542
|
// Every report is kept, whatever the grade said: a number is only as good as the text behind it, and
|
|
@@ -541,13 +552,13 @@ export async function evalCommand(argv, ctx) {
|
|
|
541
552
|
writeFileSync(join(reports, name), text);
|
|
542
553
|
};
|
|
543
554
|
const results = [];
|
|
544
|
-
for (const core of
|
|
555
|
+
for (const { core, tier, label } of arms) {
|
|
545
556
|
for (let n = 1; n <= repeat; n += 1) {
|
|
546
557
|
let dir;
|
|
547
558
|
try {
|
|
548
|
-
dir = await stage(fixture, core, ctx);
|
|
559
|
+
dir = await stage(fixture, core, ctx, tier);
|
|
549
560
|
} catch (error) {
|
|
550
|
-
console.error(` ✗ ${
|
|
561
|
+
console.error(` ✗ ${label}: ${error.message}`);
|
|
551
562
|
return 1;
|
|
552
563
|
}
|
|
553
564
|
try {
|
|
@@ -555,7 +566,7 @@ export async function evalCommand(argv, ctx) {
|
|
|
555
566
|
let report;
|
|
556
567
|
let cost = null;
|
|
557
568
|
if (dry) {
|
|
558
|
-
console.log(` dry run — ${
|
|
569
|
+
console.log(` dry run — ${label} staged in ${dir}; would run: claude ${args.map((a) => (/\s/.test(a) ? JSON.stringify(a) : a)).join(' ')}`);
|
|
559
570
|
report = readFileSync(join(fixture, 'sample-report.md'), 'utf8');
|
|
560
571
|
} else {
|
|
561
572
|
const run = spawnSync('claude', args, { cwd: dir, env, encoding: 'utf8', timeout: 1_800_000, maxBuffer: 64 * 1024 * 1024 });
|
|
@@ -563,18 +574,18 @@ export async function evalCommand(argv, ctx) {
|
|
|
563
574
|
// A run that did not review — not logged in, rate-limited, out of turns — is a failure to report,
|
|
564
575
|
// not a review that caught nothing: graded, it would drag its release's mean down.
|
|
565
576
|
if (failed) {
|
|
566
|
-
console.error(` ✗ ${
|
|
577
|
+
console.error(` ✗ ${label} run ${n}: ${failed}`);
|
|
567
578
|
return 1;
|
|
568
579
|
}
|
|
569
580
|
const out = JSON.parse(run.stdout);
|
|
570
581
|
report = out.result ?? '';
|
|
571
582
|
cost = typeof out.total_cost_usd === 'number' ? out.total_cost_usd : null;
|
|
572
583
|
}
|
|
573
|
-
keepReport(`${
|
|
584
|
+
keepReport(`${label}-${n}.md`, report);
|
|
574
585
|
const graded = grade(report, defects);
|
|
575
586
|
const judged = judging ? judge(defects, report, diff, options) : unjudged;
|
|
576
|
-
results.push({
|
|
577
|
-
console.log(describe(`${
|
|
587
|
+
results.push({ arm: label, n, ...graded, cost, judged });
|
|
588
|
+
console.log(describe(`${label} run ${n}`, graded, defects.length, cost, judged));
|
|
578
589
|
} finally {
|
|
579
590
|
if (!dry) forgetProject(dir);
|
|
580
591
|
if (!keep) rmSync(dir, { recursive: true, force: true });
|
|
@@ -584,28 +595,28 @@ export async function evalCommand(argv, ctx) {
|
|
|
584
595
|
|
|
585
596
|
if (reports) console.log(`\n reports: ${reports}`);
|
|
586
597
|
else console.log('');
|
|
587
|
-
for (const
|
|
588
|
-
const runs = results.filter((r) => r.
|
|
598
|
+
for (const { label } of arms) {
|
|
599
|
+
const runs = results.filter((r) => r.arm === label);
|
|
589
600
|
const mean = runs.reduce((a, r) => a + r.caught.length, 0) / runs.length;
|
|
590
601
|
const always = defects.filter((d) => runs.every((r) => !r.caught.includes(d.id))).map((d) => d.id);
|
|
591
602
|
const judged = runs.filter((r) => r.judged && !r.judged.failed && !r.judged.skipped);
|
|
592
603
|
console.log(
|
|
593
|
-
` ${
|
|
604
|
+
` ${label}: caught ${mean.toFixed(1)}/${defects.length} on average over ${runs.length} run(s)` +
|
|
594
605
|
(judged.length
|
|
595
606
|
? `, ${(judged.reduce((a, r) => a + r.judged.found.length, 0) / judged.length).toFixed(1)} identified by the judge (${judged.length} of ${runs.length} judged${suspect ? ', suspect' : ''})`
|
|
596
607
|
: '') +
|
|
597
608
|
(always.length ? ` — never caught: ${always.join(', ')}` : ''),
|
|
598
609
|
);
|
|
599
610
|
}
|
|
600
|
-
// Each
|
|
601
|
-
const counts =
|
|
611
|
+
// Each arm against the first, with the widest spread any arm showed between its own runs.
|
|
612
|
+
const counts = arms.map(({ label }) => results.filter((r) => r.arm === label).map((r) => r.caught.length));
|
|
602
613
|
const mean = (xs) => xs.reduce((x, y) => x + y, 0) / xs.length;
|
|
603
614
|
const spread = Math.max(...counts.map((xs) => Math.max(...xs) - Math.min(...xs)));
|
|
604
|
-
for (let i = 1; i <
|
|
615
|
+
for (let i = 1; i < arms.length; i += 1) {
|
|
605
616
|
const delta = mean(counts[i]) - mean(counts[0]);
|
|
606
617
|
console.log(
|
|
607
|
-
`\n ${
|
|
608
|
-
(repeat > 1 ? `, against a run-to-run spread of ${spread} within
|
|
618
|
+
`\n ${arms[i].label} − ${arms[0].label}: ${delta >= 0 ? '+' : ''}${delta.toFixed(1)} defect(s)` +
|
|
619
|
+
(repeat > 1 ? `, against a run-to-run spread of ${spread} within one` : ' — one run each cannot tell a difference from noise'),
|
|
609
620
|
);
|
|
610
621
|
}
|
|
611
622
|
return 0;
|
package/src/commands/init.mjs
CHANGED
|
@@ -20,7 +20,7 @@ import { existsSync } from 'node:fs';
|
|
|
20
20
|
import { join, resolve } from 'node:path';
|
|
21
21
|
import { REQUIRES, SLOT, byVersion, closedGate, composedPaths, composeProject, defaultedSlots, layerRootFor, projectOwned, walk } from './compose.mjs';
|
|
22
22
|
import { owedDocuments } from './check.mjs';
|
|
23
|
-
import { PLACEHOLDER, defaultVocabulary } from '../vocabulary.mjs';
|
|
23
|
+
import { PLACEHOLDER, defaultVocabulary, tierProblem, tiers } from '../vocabulary.mjs';
|
|
24
24
|
import { DETECTABLE, missingNeeds } from '../surfaces.mjs';
|
|
25
25
|
import { bold, dim, green, pink } from '../look.mjs';
|
|
26
26
|
|
|
@@ -309,6 +309,15 @@ export async function init(argv, ctx) {
|
|
|
309
309
|
console.error(` ${resolved.error}\n`);
|
|
310
310
|
return 1;
|
|
311
311
|
}
|
|
312
|
+
// Asked for, or kept from the profile it replaces; refused before anything is written when the pin has no such tier.
|
|
313
|
+
const tier = arg('--tier') ?? previous?.tier;
|
|
314
|
+
const tierError = tierProblem(resolved.dir, tier, core);
|
|
315
|
+
if (tierError) {
|
|
316
|
+
// One kept from the profile being replaced is not one the command named, so it says where it came from.
|
|
317
|
+
const kept = arg('--tier') === null ? ` — the profile being replaced holds it: ${Object.keys(tiers(resolved.dir)).length > 0 ? 'pass --tier with one it has, or ' : ''}take "tier" out of ${HARNESS}/profile.json` : '';
|
|
318
|
+
console.error(` ${tierError}${kept}\n`);
|
|
319
|
+
return 1;
|
|
320
|
+
}
|
|
312
321
|
|
|
313
322
|
// Everything below is read from the version this profile will pin — the surfaces that
|
|
314
323
|
// exist, what each one costs, and the slots to fill — so the checklist describes the
|
|
@@ -377,7 +386,7 @@ export async function init(argv, ctx) {
|
|
|
377
386
|
|
|
378
387
|
// A name the release supplies a default for is not asked: it composes as the default until the
|
|
379
388
|
// project declares it, and the TODO says what the default is so a project on another stack can.
|
|
380
|
-
const defaults = defaultVocabulary(resolved.dir);
|
|
389
|
+
const defaults = defaultVocabulary(resolved.dir, tier);
|
|
381
390
|
const defaulted = [...vocabulary].filter((name) => name in defaults).sort();
|
|
382
391
|
for (const name of defaulted) vocabulary.delete(name);
|
|
383
392
|
// What the profile being replaced had filled in, for a name these layers still use or one the release
|
|
@@ -388,11 +397,12 @@ export async function init(argv, ctx) {
|
|
|
388
397
|
|
|
389
398
|
const profile = {
|
|
390
399
|
core,
|
|
400
|
+
...(tier !== undefined ? { tier } : {}),
|
|
391
401
|
surfaces,
|
|
392
402
|
...(surfaces.includes('integrations') ? { integrations: keptIntegrations } : {}),
|
|
393
403
|
vocabulary: Object.fromEntries([...[...vocabulary].map((name) => [name, null]), ...kept].sort(([a], [b]) => a.localeCompare(b))),
|
|
394
404
|
// Anything else the profile said is the project's, and stays.
|
|
395
|
-
...Object.fromEntries(Object.entries(previous ?? {}).filter(([key]) => !['core', 'surfaces', 'integrations', 'vocabulary'].includes(key))),
|
|
405
|
+
...Object.fromEntries(Object.entries(previous ?? {}).filter(([key]) => !['core', 'tier', 'surfaces', 'integrations', 'vocabulary'].includes(key))),
|
|
396
406
|
};
|
|
397
407
|
|
|
398
408
|
await mkdir(join(harness, 'project', 'tree'), { recursive: true });
|
|
@@ -596,7 +606,7 @@ export async function init(argv, ctx) {
|
|
|
596
606
|
for (const w of unwired) console.log(` to wire ${w}`);
|
|
597
607
|
if (wired.length || unwired.length) console.log('');
|
|
598
608
|
console.log(` surfaces: ${surfaces.join(', ') || '(none)'}`);
|
|
599
|
-
console.log(` core: ${profile.core}`);
|
|
609
|
+
console.log(` core: ${profile.core}${tier !== undefined ? `, tier ${tier}` : ''}`);
|
|
600
610
|
console.log(` to fill: ${vocabulary.size} vocabulary entries, ${slots.length} project slots, ${owed.size} document(s)`);
|
|
601
611
|
if (inTheWay.length > 0) {
|
|
602
612
|
console.log(` composed: nothing — ${inTheWay.length} file(s) of yours sit where the harness composes one, and would be lost:`);
|
package/src/commands/status.mjs
CHANGED
|
@@ -14,10 +14,11 @@ import { spawnSync } from 'node:child_process';
|
|
|
14
14
|
import { existsSync, readFileSync } from 'node:fs';
|
|
15
15
|
import { createRequire } from 'node:module';
|
|
16
16
|
import { join, resolve } from 'node:path';
|
|
17
|
-
import { readProfile } from '../paths.mjs';
|
|
17
|
+
import { HARNESS, readProfile } from '../paths.mjs';
|
|
18
18
|
import { costOf } from '../prices.mjs';
|
|
19
19
|
import { runOf } from '../transcripts.mjs';
|
|
20
20
|
import { missingWiring, shippedScripts } from '../wiring.mjs';
|
|
21
|
+
import { defaultVocabulary, modelsLine, tierProblem, tiers } from '../vocabulary.mjs';
|
|
21
22
|
import { amber, bold, dim, green, pink } from '../look.mjs';
|
|
22
23
|
import { byVersion, layerRootFor } from './compose.mjs';
|
|
23
24
|
import { filedRequests } from './learn.mjs';
|
|
@@ -114,6 +115,15 @@ export async function status(argv, ctx) {
|
|
|
114
115
|
if (!layers.error) {
|
|
115
116
|
const unwired = await missingWiring(target, await shippedScripts(layers.dir, profile.surfaces ?? []));
|
|
116
117
|
row('hooks', unwired.length === 0 ? green('every hook its release needs is wired') : warn(`${unwired.length} missing — \`nina wire --apply\` merges them`));
|
|
118
|
+
// What each kind of stage runs on: the tier names it, and a model the project declared itself wins over it.
|
|
119
|
+
const tierError = tierProblem(layers.dir, profile.tier, pinned);
|
|
120
|
+
const declared = Object.fromEntries(Object.entries(profile.vocabulary ?? {}).filter(([, value]) => typeof value === 'string'));
|
|
121
|
+
row(
|
|
122
|
+
'models',
|
|
123
|
+
tierError
|
|
124
|
+
? warn(`${tierError} — ${Object.keys(tiers(layers.dir)).length > 0 ? '`nina compose --tier <level>` sets one it has' : `take "tier" out of ${HARNESS}/profile.json`}`)
|
|
125
|
+
: `${profile.tier === undefined ? "the release's own" : `tier ${bold(profile.tier)}`} · ${modelsLine({ ...defaultVocabulary(layers.dir, profile.tier), ...declared })}`,
|
|
126
|
+
);
|
|
117
127
|
} else {
|
|
118
128
|
row('hooks', warn(layers.error));
|
|
119
129
|
}
|