@xhulz/nina 0.34.0 → 0.36.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 +222 -48
- package/package.json +1 -1
- package/releases/0.35.0/core/GAPS.md +125 -0
- package/releases/0.35.0/core/answered.json +29 -0
- package/releases/0.35.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.35.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.35.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.35.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.35.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.35.0/core/defaults/tree/.claude/agents/qa.md +2 -0
- package/releases/0.35.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.35.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.35.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.35.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.35.0/core/tree/.claude/agents/architect.md +127 -0
- package/releases/0.35.0/core/tree/.claude/agents/dba.md +110 -0
- package/releases/0.35.0/core/tree/.claude/agents/devops.md +114 -0
- package/releases/0.35.0/core/tree/.claude/agents/implementer.md +156 -0
- package/releases/0.35.0/core/tree/.claude/agents/integration-tester.md +168 -0
- package/releases/0.35.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.35.0/core/tree/.claude/agents/qa.md +153 -0
- package/releases/0.35.0/core/tree/.claude/agents/reviewer.md +183 -0
- package/releases/0.35.0/core/tree/.claude/agents/secops.md +121 -0
- package/releases/0.35.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.35.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.35.0/core/tree/.claude/agents-overview.md +64 -0
- package/releases/0.35.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.35.0/core/tree/.claude/graph.md +75 -0
- package/releases/0.35.0/core/tree/.claude/patterns.md +213 -0
- package/releases/0.35.0/core/tree/.claude/pills/README.md +146 -0
- package/releases/0.35.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.35.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.35.0/core/tree/.claude/router.md +198 -0
- package/releases/0.35.0/core/tree/.claude/templates/integration.md +45 -0
- package/releases/0.35.0/core/tree/CLAUDE.md +161 -0
- package/releases/0.35.0/core/tree/scripts/cost-watch.mjs +18 -0
- package/releases/0.35.0/core/tree/scripts/edit-guard.mjs +16 -0
- package/releases/0.35.0/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.35.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.35.0/core/vocabulary.json +13 -0
- package/releases/0.35.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.35.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.35.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.35.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.35.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.35.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.35.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.35.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.35.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/agents/dba.md +50 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/patterns.md +19 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.35.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.35.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/devops.md +32 -0
- package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.35.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.35.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.35.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.35.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.35.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.35.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.35.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.35.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.35.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.35.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.35.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.35.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.35.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.35.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.35.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.35.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.35.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.35.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.35.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.35.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.35.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.35.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.35.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.35.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.35.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.35.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.35.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.35.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.35.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.35.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.35.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.35.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.35.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.35.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.35.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.35.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.35.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.35.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.35.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.35.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.35.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.35.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.35.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.35.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.35.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.35.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.35.0/surfaces/prisma/tree/.claude/agents/dba.md +39 -0
- package/releases/0.35.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.35.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.35.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.35.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.35.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.35.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/releases/0.36.0/core/GAPS.md +125 -0
- package/releases/0.36.0/core/answered.json +29 -0
- package/releases/0.36.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.36.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.36.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.36.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.36.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.36.0/core/defaults/tree/.claude/agents/qa.md +2 -0
- package/releases/0.36.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.36.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.36.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.36.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.36.0/core/tree/.claude/agents/architect.md +127 -0
- package/releases/0.36.0/core/tree/.claude/agents/dba.md +110 -0
- package/releases/0.36.0/core/tree/.claude/agents/devops.md +114 -0
- package/releases/0.36.0/core/tree/.claude/agents/implementer.md +156 -0
- package/releases/0.36.0/core/tree/.claude/agents/integration-tester.md +168 -0
- package/releases/0.36.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.36.0/core/tree/.claude/agents/qa.md +153 -0
- package/releases/0.36.0/core/tree/.claude/agents/reviewer.md +183 -0
- package/releases/0.36.0/core/tree/.claude/agents/secops.md +121 -0
- package/releases/0.36.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.36.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.36.0/core/tree/.claude/agents-overview.md +64 -0
- package/releases/0.36.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.36.0/core/tree/.claude/graph.md +75 -0
- package/releases/0.36.0/core/tree/.claude/patterns.md +213 -0
- package/releases/0.36.0/core/tree/.claude/pills/README.md +146 -0
- package/releases/0.36.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.36.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.36.0/core/tree/.claude/router.md +198 -0
- package/releases/0.36.0/core/tree/.claude/templates/integration.md +45 -0
- package/releases/0.36.0/core/tree/CLAUDE.md +161 -0
- package/releases/0.36.0/core/tree/scripts/cost-watch.mjs +18 -0
- package/releases/0.36.0/core/tree/scripts/edit-guard.mjs +16 -0
- package/releases/0.36.0/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.36.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.36.0/core/vocabulary.json +13 -0
- package/releases/0.36.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.36.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.36.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.36.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.36.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.36.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.36.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.36.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.36.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/agents/dba.md +50 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/patterns.md +19 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.36.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.36.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/devops.md +32 -0
- package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.36.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.36.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.36.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.36.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.36.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.36.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.36.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.36.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.36.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.36.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.36.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.36.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.36.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.36.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.36.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.36.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.36.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.36.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.36.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.36.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.36.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.36.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.36.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.36.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.36.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.36.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.36.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.36.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.36.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.36.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.36.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.36.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.36.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.36.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.36.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.36.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.36.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.36.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.36.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.36.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.36.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.36.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.36.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.36.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.36.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.36.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.36.0/surfaces/prisma/tree/.claude/agents/dba.md +39 -0
- package/releases/0.36.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.36.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.36.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.36.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.36.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.36.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/src/agentrun.mjs +89 -9
- package/src/commands/check.mjs +12 -8
- package/src/commands/compose.mjs +85 -9
- package/src/commands/export.mjs +8 -16
- package/src/commands/gate.mjs +19 -13
- package/src/commands/init.mjs +13 -4
- package/src/commands/learn.mjs +17 -18
- package/src/commands/pipeline.mjs +4 -4
- package/src/commands/release.mjs +21 -3
- package/src/commands/runs.mjs +14 -8
- package/src/commands/snapshot.mjs +63 -64
- package/src/commands/stats.mjs +98 -30
- package/src/commands/status.mjs +2 -2
- package/src/commands/upgrade.mjs +131 -25
- package/src/guard.mjs +7 -37
- package/src/paths.mjs +50 -1
- package/src/prices.mjs +7 -7
- package/src/store.mjs +158 -0
- package/src/transcripts.mjs +15 -4
- package/src/vocabulary.mjs +12 -1
- package/src/wiring.mjs +106 -41
package/src/commands/release.mjs
CHANGED
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* Releases are never rewritten. Fixing a release means cutting the next one.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { cp, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
12
|
+
import { cp, mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
13
13
|
import { existsSync } from 'node:fs';
|
|
14
14
|
import { join } from 'node:path';
|
|
15
|
+
import { byVersion } from './compose.mjs';
|
|
15
16
|
|
|
16
17
|
/** A release name: dotted numbers, so the directory sorts and reads like a version. */
|
|
17
18
|
const VERSION = /^\d+\.\d+\.\d+$/;
|
|
@@ -80,11 +81,28 @@ export async function release(argv, ctx) {
|
|
|
80
81
|
console.error(` ${version} already exists — a release is immutable; cut the next one.\n`);
|
|
81
82
|
return 1;
|
|
82
83
|
}
|
|
84
|
+
// Above the newest, or `init` pins something older than what the package says it is, and the package's
|
|
85
|
+
// version moves backwards with the cut.
|
|
86
|
+
const newest = (await readdir(join(ctx.root, 'releases'), { withFileTypes: true }).catch(() => []))
|
|
87
|
+
.filter((e) => e.isDirectory() && VERSION.test(e.name))
|
|
88
|
+
.map((e) => e.name)
|
|
89
|
+
.sort(byVersion)
|
|
90
|
+
.at(-1);
|
|
91
|
+
if (newest && byVersion(version, newest) < 0) {
|
|
92
|
+
console.error(` ${newest} is the newest release — a release is cut above it, not below.\n`);
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
83
95
|
|
|
84
|
-
|
|
96
|
+
// Frozen beside the releases and moved in whole: a cut stopped half-way used to leave a partial release,
|
|
97
|
+
// immutable by name, which `init` then pinned as the newest.
|
|
98
|
+
const partial = join(ctx.root, `.release-${version}.partial`);
|
|
99
|
+
await rm(partial, { recursive: true, force: true });
|
|
100
|
+
await mkdir(partial, { recursive: true });
|
|
85
101
|
for (const layer of ['core', 'surfaces']) {
|
|
86
|
-
await cp(join(ctx.root, layer), join(
|
|
102
|
+
await cp(join(ctx.root, layer), join(partial, layer), { recursive: true });
|
|
87
103
|
}
|
|
104
|
+
await mkdir(join(ctx.root, 'releases'), { recursive: true });
|
|
105
|
+
await rename(partial, dest);
|
|
88
106
|
|
|
89
107
|
const surfaces = (await readdir(join(dest, 'surfaces'), { withFileTypes: true }))
|
|
90
108
|
.filter((e) => e.isDirectory())
|
package/src/commands/runs.mjs
CHANGED
|
@@ -16,8 +16,8 @@ import { HARNESS } from '../paths.mjs';
|
|
|
16
16
|
import { costOf } from '../prices.mjs';
|
|
17
17
|
import { isLoopBack, runOf } from '../transcripts.mjs';
|
|
18
18
|
import { amber, bold, dim, pink } from '../look.mjs';
|
|
19
|
-
import {
|
|
20
|
-
import { cyclesOf, projectName } from './stats.mjs';
|
|
19
|
+
import { localDay, localMinute, projectRecords, zone } from '../store.mjs';
|
|
20
|
+
import { WRITERS, cyclesOf, projectName } from './stats.mjs';
|
|
21
21
|
|
|
22
22
|
/** What ended a cycle, in words. */
|
|
23
23
|
const ENDS = {
|
|
@@ -55,7 +55,8 @@ export function summarize(cycle) {
|
|
|
55
55
|
if (isLoopBack(r.verdict)) s.back += 1;
|
|
56
56
|
stages.set(r.role, s);
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
// The code a cycle wrote, as `stats` counts it: an architect's spec or a tester's report is not a write of code.
|
|
59
|
+
const files = rounds.filter((r) => WRITERS.has(r.role) && typeof r.files_touched === 'number').map((r) => r.files_touched);
|
|
59
60
|
const peaks = rounds.filter((r) => typeof r.context_peak === 'number').map((r) => r.context_peak);
|
|
60
61
|
return {
|
|
61
62
|
start,
|
|
@@ -92,9 +93,14 @@ export async function runs(argv) {
|
|
|
92
93
|
}
|
|
93
94
|
const since = arg('--since');
|
|
94
95
|
const last = Math.max(1, Number(arg('--last') ?? 10) || 10);
|
|
95
|
-
const
|
|
96
|
+
const stored = (projectRecords(dir) ?? []).filter((r) => r.status !== 'denied');
|
|
97
|
+
const records = stored.filter((r) => !since || localDay(r.ts) >= since);
|
|
96
98
|
if (records.length === 0) {
|
|
97
|
-
console.error(
|
|
99
|
+
console.error(
|
|
100
|
+
stored.length === 0
|
|
101
|
+
? ' no measured history for this project yet — `nina snapshot` captures it\n'
|
|
102
|
+
: ` nothing measured since ${since} (${zone()}) — the newest round is from ${localDay(stored.map((r) => String(r.ts)).sort().at(-1))}\n`,
|
|
103
|
+
);
|
|
98
104
|
return 1;
|
|
99
105
|
}
|
|
100
106
|
const cycles = cyclesOf(records)
|
|
@@ -103,10 +109,10 @@ export async function runs(argv) {
|
|
|
103
109
|
const shown = cycles.slice(0, last);
|
|
104
110
|
console.log(
|
|
105
111
|
` ${bold(pink(projectName(records[0].project)))} · ${cycles.length} cycle(s)` +
|
|
106
|
-
dim(` newest first${cycles.length > shown.length ? `, the last ${shown.length} — --last N for more` : ''}; a cycle runs up to the verdict that closes it`),
|
|
112
|
+
dim(` newest first${cycles.length > shown.length ? `, the last ${shown.length} — --last N for more` : ''}; a cycle runs up to the verdict that closes it; times in ${zone()}`),
|
|
107
113
|
);
|
|
108
114
|
for (const c of shown) {
|
|
109
|
-
const when =
|
|
115
|
+
const when = localMinute(c.start);
|
|
110
116
|
console.log(`\n ${pink('▌')} ${bold(when)} · ${span(c.minutes)} · ${bold(money(c.cost))}${c.unpriced ? dim(` + ${c.unpriced} unpriced`) : ''} · ${dim(c.how)}`);
|
|
111
117
|
console.log(` ${c.name.length > 96 ? `${c.name.slice(0, 95)}…` : c.name}`);
|
|
112
118
|
const chain = c.stages.map((s) => {
|
|
@@ -122,7 +128,7 @@ export async function runs(argv) {
|
|
|
122
128
|
const costliest = shown.reduce((a, c) => (c.cost > a.cost ? c : a), shown[0]);
|
|
123
129
|
console.log(
|
|
124
130
|
`\n ${shown.length} cycle(s): ${bold(money(total))} in all` +
|
|
125
|
-
(shown.length > 1 ? `; the costliest, ${money(costliest.cost)}, ${costliest.start
|
|
131
|
+
(shown.length > 1 ? `; the costliest, ${money(costliest.cost)}, ${localDay(costliest.start)} — ${costliest.name.slice(0, 60)}` : '') +
|
|
126
132
|
'\n',
|
|
127
133
|
);
|
|
128
134
|
return 0;
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { copyFile, mkdir, open, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
12
|
+
import { HARNESS, snapshotsDir } from '../paths.mjs';
|
|
13
|
+
import { decodeProjectDir } from './stats.mjs';
|
|
14
|
+
import { readStoreFile } from '../store.mjs';
|
|
15
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
15
16
|
import { join, resolve } from 'node:path';
|
|
16
17
|
import { listProjects, scanProject } from '../transcripts.mjs';
|
|
17
18
|
|
|
@@ -21,7 +22,8 @@ const STATE_FILE = '.state.json';
|
|
|
21
22
|
/**
|
|
22
23
|
* Reads a JSON file, or an empty object when it is missing or unreadable. A state file cut short
|
|
23
24
|
* by an interrupted write used to throw on every later snapshot, and observation stopped for every
|
|
24
|
-
* project on the machine with nothing saying so. An unreadable cursor costs one re-read
|
|
25
|
+
* project on the machine with nothing saying so. An unreadable cursor costs one re-read, which keeps what the
|
|
26
|
+
* store already holds (`snapshotProject`).
|
|
25
27
|
*
|
|
26
28
|
* @param {string} path - The file.
|
|
27
29
|
* @returns {Promise<object>}
|
|
@@ -42,35 +44,10 @@ async function readJson(path) {
|
|
|
42
44
|
*/
|
|
43
45
|
async function writeAtomicText(path, text) {
|
|
44
46
|
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
45
|
-
await writeFile(tmp, text);
|
|
47
|
+
await writeFile(tmp, text, { mode: 0o600 });
|
|
46
48
|
await rename(tmp, path);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
/**
|
|
50
|
-
* Reads the records already captured for a project.
|
|
51
|
-
*
|
|
52
|
-
* @param {string} file - Path to the snapshot JSONL.
|
|
53
|
-
* @returns {Promise<object[]>} The records, or an empty list.
|
|
54
|
-
*/
|
|
55
|
-
async function readRecords(file) {
|
|
56
|
-
const records = [];
|
|
57
|
-
try {
|
|
58
|
-
await readFile(file, { flag: 'r' });
|
|
59
|
-
} catch {
|
|
60
|
-
return records;
|
|
61
|
-
}
|
|
62
|
-
const rl = createInterface({ input: createReadStream(file), crlfDelay: Infinity });
|
|
63
|
-
for await (const line of rl) {
|
|
64
|
-
if (!line.trim()) continue;
|
|
65
|
-
try {
|
|
66
|
-
records.push(JSON.parse(line));
|
|
67
|
-
} catch {
|
|
68
|
-
/* a torn line loses one record, never the file */
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return records;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
51
|
/**
|
|
75
52
|
* Writes the snapshot through a temp file, so an interrupted run cannot leave a
|
|
76
53
|
* truncated one behind.
|
|
@@ -82,41 +59,11 @@ async function writeAtomic(file, records) {
|
|
|
82
59
|
// Unique per writer: two snapshots of one project at once used the same temporary name, and
|
|
83
60
|
// could interleave into it before either renamed it into place.
|
|
84
61
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
85
|
-
|
|
62
|
+
// Its owner's alone: it holds each dispatch's description, which the orchestrator wrote.
|
|
63
|
+
await writeFile(tmp, `${records.map((r) => JSON.stringify(r)).join('\n')}\n`, { mode: 0o600 });
|
|
86
64
|
await rename(tmp, file);
|
|
87
65
|
}
|
|
88
66
|
|
|
89
|
-
/**
|
|
90
|
-
* A project's records in the measurement store, under the name Claude Code gives its directory — as
|
|
91
|
-
* given, or with its links resolved, since either may be the one the sessions ran in.
|
|
92
|
-
*
|
|
93
|
-
* @param {string} dir - The project.
|
|
94
|
-
* @returns {object[]|null} Null when the store has nothing for it.
|
|
95
|
-
*/
|
|
96
|
-
export function storedRecords(dir) {
|
|
97
|
-
const names = new Set([slugFor(dir)]);
|
|
98
|
-
try {
|
|
99
|
-
names.add(slugFor(realpathSync(dir)));
|
|
100
|
-
} catch {
|
|
101
|
-
// A directory that cannot be resolved is looked up as given.
|
|
102
|
-
}
|
|
103
|
-
for (const name of names) {
|
|
104
|
-
const file = join(snapshotsDir(), `${name}.jsonl`);
|
|
105
|
-
if (!existsSync(file)) continue;
|
|
106
|
-
return readFileSync(file, 'utf8')
|
|
107
|
-
.split('\n')
|
|
108
|
-
.filter(Boolean)
|
|
109
|
-
.flatMap((l) => {
|
|
110
|
-
try {
|
|
111
|
-
return [JSON.parse(l)];
|
|
112
|
-
} catch {
|
|
113
|
-
return [];
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
67
|
/**
|
|
121
68
|
* Takes a project's lock, or says it is taken. The records and the cursors are written one after the other,
|
|
122
69
|
* and two snapshots of one project at once — a project's own detector and a global hook, both on every
|
|
@@ -204,6 +151,42 @@ export async function snapshot(argv, ctx) {
|
|
|
204
151
|
return 0;
|
|
205
152
|
}
|
|
206
153
|
|
|
154
|
+
/** Whether a round's verdict came from the report it handed back: the one reading nothing later corrects. */
|
|
155
|
+
const settledByHandback = (r) => r?.verdict_source === 'handback';
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A round as a re-read after lost cursors leaves it. What the store held stands, field by field, and the
|
|
159
|
+
* re-read fills only what it lacked — unless the re-read settled the round from the report its stage handed
|
|
160
|
+
* back and the store had not: a verdict read from a notification is the one a handback corrects, and kept
|
|
161
|
+
* over it, a round the store had as REJECTED stayed so after its stage had said APPROVED, and stayed so for
|
|
162
|
+
* good once that stage's transcript was pruned.
|
|
163
|
+
*
|
|
164
|
+
* @param {object} held - The round as the store held it.
|
|
165
|
+
* @param {object} reread - The round as the re-read found it.
|
|
166
|
+
* @returns {object}
|
|
167
|
+
*/
|
|
168
|
+
export function rereadOver(held, reread) {
|
|
169
|
+
const defined = (record) => Object.fromEntries(Object.entries(record).filter(([, v]) => v !== null && v !== undefined));
|
|
170
|
+
return settledByHandback(reread) && !settledByHandback(held) ? { ...held, ...defined(reread) } : { ...reread, ...defined(held) };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The release a project pins now, or null when its directory or profile cannot be found.
|
|
175
|
+
*
|
|
176
|
+
* @param {string} slug - Its store name.
|
|
177
|
+
* @returns {string|null}
|
|
178
|
+
*/
|
|
179
|
+
function pinOf(slug) {
|
|
180
|
+
const dir = decodeProjectDir(slug);
|
|
181
|
+
if (!dir) return null;
|
|
182
|
+
try {
|
|
183
|
+
const core = JSON.parse(readFileSync(join(dir, HARNESS, 'profile.json'), 'utf8')).core;
|
|
184
|
+
return typeof core === 'string' ? core : null;
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
207
190
|
/**
|
|
208
191
|
* Snapshots one project, under its lock.
|
|
209
192
|
*
|
|
@@ -214,12 +197,28 @@ async function snapshotProject(project, { outDir, rebuild, quiet, legacy }) {
|
|
|
214
197
|
// The store is the only copy of history older than the transcripts, and a rebuild re-reads only what is
|
|
215
198
|
// still on disk: the record it replaces is kept beside it first.
|
|
216
199
|
if (rebuild && existsSync(file)) await copyFile(file, `${file}.before-rebuild-${new Date().toISOString().replace(/[:.]/g, '-')}`);
|
|
217
|
-
const
|
|
200
|
+
const stored = readStoreFile(file, { raw: true });
|
|
201
|
+
const prior = rebuild ? [] : stored;
|
|
218
202
|
const own = rebuild ? {} : await readJson(join(outDir, `${project.slug}.state.json`));
|
|
219
203
|
const cursors = rebuild ? {} : (own.cursors ?? legacy[project.slug]?.cursors ?? {});
|
|
220
204
|
|
|
221
205
|
const scanned = await scanProject(project.dir, { cursors, records: prior });
|
|
222
|
-
|
|
206
|
+
// With its cursors gone — a state file deleted, or cut short — the transcripts are read again from the start
|
|
207
|
+
// over records the store already holds, and read that way a notification is taken a second time: one
|
|
208
|
+
// project had 34 verdicts, 639 return times and 79 durations rewritten. What the store held stands, field
|
|
209
|
+
// by field, and the re-read adds only what it lacked; the cursors it leaves hold from then on.
|
|
210
|
+
const reread = !rebuild && prior.length > 0 && Object.keys(cursors).length === 0;
|
|
211
|
+
const held = new Map(prior.map((r) => [r.dispatch_id, r]));
|
|
212
|
+
const kept = (r) => (reread && held.has(r.dispatch_id) ? rereadOver(held.get(r.dispatch_id), r) : r);
|
|
213
|
+
// The release the project pinned when a round was first captured, so a rule is judged only on the rounds
|
|
214
|
+
// that ran under it. A round already on record keeps what it had — a rebuild included, which would
|
|
215
|
+
// otherwise stamp today's pin on weeks of history — and only a new one takes today's.
|
|
216
|
+
const pinned = new Map(stored.map((r) => [r.dispatch_id, r.core ?? null]));
|
|
217
|
+
const pin = pinOf(project.slug);
|
|
218
|
+
const records = scanned.records.map(kept).map((r) => {
|
|
219
|
+
const core = pinned.has(r.dispatch_id) ? pinned.get(r.dispatch_id) : pin;
|
|
220
|
+
return { ...r, project: project.slug, ...(core ? { core } : {}) };
|
|
221
|
+
});
|
|
223
222
|
if (records.length === 0) return 0;
|
|
224
223
|
|
|
225
224
|
const fresh = records.length - prior.length;
|
package/src/commands/stats.mjs
CHANGED
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
import { amber, bar, bold, dim, heading, note, pink, stacked } from '../look.mjs';
|
|
15
15
|
import { readFile, readdir } from 'node:fs/promises';
|
|
16
16
|
import { spawnSync } from 'node:child_process';
|
|
17
|
-
import {
|
|
18
|
-
import { createInterface } from 'node:readline';
|
|
17
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
19
18
|
import { homedir } from 'node:os';
|
|
20
19
|
import { basename, join } from 'node:path';
|
|
21
|
-
import { ROLE_TOKENS, isLoopBack, runOf } from '../transcripts.mjs';
|
|
20
|
+
import { ROLE_TOKENS, isLoopBack, runOf, transcriptsOf } from '../transcripts.mjs';
|
|
21
|
+
import { localDay, namesFor, readStoreFile } from '../store.mjs';
|
|
22
22
|
import { frontmatter, pillFiles } from './pills.mjs';
|
|
23
|
-
import { HARNESS, snapshotsDir } from '../paths.mjs';
|
|
23
|
+
import { HARNESS, slugFor, snapshotsDir } from '../paths.mjs';
|
|
24
24
|
import { defaultVocabulary } from '../vocabulary.mjs';
|
|
25
|
-
import { layerRootFor } from './compose.mjs';
|
|
25
|
+
import { byVersion, layerRootFor } from './compose.mjs';
|
|
26
26
|
import { PRICES_AS_OF, costOf } from '../prices.mjs';
|
|
27
27
|
|
|
28
28
|
/**
|
|
@@ -56,41 +56,77 @@ const PIPELINE_ROLES = new Set(Object.keys(ROLE_TOKENS));
|
|
|
56
56
|
* Loads snapshot records, optionally filtered.
|
|
57
57
|
*
|
|
58
58
|
* @param {string} dir - Snapshot directory.
|
|
59
|
-
* @param {{since: string|null, project: string|null}} opts - Filters.
|
|
59
|
+
* @param {{since: string|null, project: string|null}} opts - Filters; `project` as every report reads it.
|
|
60
60
|
* @returns {Promise<object[]>} The matching records.
|
|
61
61
|
*/
|
|
62
62
|
async function load(dir, opts) {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
const names = opts.project
|
|
64
|
+
? namesFor(opts.project, dir)
|
|
65
|
+
: (await readdir(dir).catch(() => [])).filter((f) => f.endsWith('.jsonl')).map((f) => f.slice(0, -'.jsonl'.length));
|
|
66
|
+
return names
|
|
67
|
+
.flatMap((name) => readStoreFile(join(dir, `${name}.jsonl`)))
|
|
68
|
+
.filter((r) => !opts.since || localDay(r.ts) >= opts.since);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
/** Directories already found for an encoded name: `stats` asks for the same project many times over. */
|
|
73
|
+
const decoded = new Map();
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The directory a project's sessions ran in, as its own transcripts record it: every line carries the
|
|
77
|
+
* session's `cwd`. Taken only when it encodes back to the same name and is still there.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} encoded - A snapshot's project name, which is also its transcript directory's.
|
|
80
|
+
* @returns {string|null}
|
|
81
|
+
*/
|
|
82
|
+
function recordedDir(encoded) {
|
|
83
|
+
const dir = transcriptsOf(encoded);
|
|
84
|
+
let files = [];
|
|
85
|
+
try {
|
|
86
|
+
files = readdirSync(dir).filter((f) => f.endsWith('.jsonl'));
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
for (const file of files.slice(0, 5)) {
|
|
91
|
+
let head = '';
|
|
92
|
+
try {
|
|
93
|
+
const fd = openSync(join(dir, file), 'r');
|
|
94
|
+
try {
|
|
95
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
96
|
+
head = buffer.toString('utf8', 0, readSync(fd, buffer, 0, buffer.length, 0));
|
|
97
|
+
} finally {
|
|
98
|
+
closeSync(fd);
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
for (const line of head.split('\n')) {
|
|
104
|
+
let cwd;
|
|
70
105
|
try {
|
|
71
|
-
|
|
72
|
-
if (opts.since && String(r.ts).slice(0, 10) < opts.since) continue;
|
|
73
|
-
out.push(r);
|
|
106
|
+
cwd = JSON.parse(line)?.cwd;
|
|
74
107
|
} catch {
|
|
75
|
-
|
|
108
|
+
continue;
|
|
76
109
|
}
|
|
110
|
+
if (typeof cwd === 'string' && slugFor(cwd) === encoded && existsSync(cwd)) return cwd;
|
|
77
111
|
}
|
|
78
112
|
}
|
|
79
|
-
return
|
|
113
|
+
return null;
|
|
80
114
|
}
|
|
81
115
|
|
|
82
|
-
|
|
83
116
|
/**
|
|
84
117
|
* Turns a snapshot's encoded project name back into a directory.
|
|
85
118
|
*
|
|
86
|
-
* The encoding
|
|
87
|
-
*
|
|
88
|
-
*
|
|
119
|
+
* The encoding makes every character that is not a letter or a digit a dash, so the name alone cannot say
|
|
120
|
+
* what it was. The project's transcripts can: each line records the directory its session ran in. Failing
|
|
121
|
+
* that, the split is resolved against the file system — which can only rebuild `/` from a dash, so a
|
|
122
|
+
* project whose path held a `.`, a space or an `_` was never found, and its pills, models and requests went
|
|
123
|
+
* missing from every report without a word.
|
|
89
124
|
*
|
|
90
125
|
* @param {string} encoded - The `project` field of a snapshot record.
|
|
91
126
|
* @returns {string | null} The directory, or `null` when no split of the name exists.
|
|
92
127
|
*/
|
|
93
128
|
export function decodeProjectDir(encoded) {
|
|
129
|
+
if (decoded.has(encoded)) return decoded.get(encoded);
|
|
94
130
|
const parts = encoded.replace(/^-/, '').split('-');
|
|
95
131
|
const walk = (base, i) => {
|
|
96
132
|
if (i === parts.length) return base;
|
|
@@ -102,7 +138,10 @@ export function decodeProjectDir(encoded) {
|
|
|
102
138
|
}
|
|
103
139
|
return null;
|
|
104
140
|
};
|
|
105
|
-
|
|
141
|
+
const found = recordedDir(encoded) ?? walk('/', 0);
|
|
142
|
+
// Only what was found: a directory made after a miss is found the next time it is asked for.
|
|
143
|
+
if (found) decoded.set(encoded, found);
|
|
144
|
+
return found;
|
|
106
145
|
}
|
|
107
146
|
|
|
108
147
|
/**
|
|
@@ -335,7 +374,7 @@ function modelReport(records) {
|
|
|
335
374
|
.map((rs) => rs.map((r) => (r.tokens ? costOf(r.tokens, r.usage_model) : null)).filter((c) => c !== null))
|
|
336
375
|
.filter((cs) => cs.length > 0)
|
|
337
376
|
.map((cs) => cs.reduce((a, b) => a + b, 0));
|
|
338
|
-
const dates = rounds.map((r) =>
|
|
377
|
+
const dates = rounds.map((r) => localDay(r.ts)).sort();
|
|
339
378
|
lines.push(
|
|
340
379
|
` ${i === 0 ? `${pink(role)}${' '.repeat(Math.max(20 - role.length, 1))}` : ''.padEnd(20)}${model.padEnd(26)}${String(runs.size).padStart(5)} run(s) ${dim(`${dates[0]} → ${dates.at(-1)}`)}` +
|
|
341
380
|
(clear.length ? ` loop-back ${pct(loops, clear.length)} of ${clear.length}` : ' no readable verdict') +
|
|
@@ -377,8 +416,25 @@ function modelReport(records) {
|
|
|
377
416
|
/** The verdicts that close a pipeline cycle: the change passed its tests, reached preview, or cleared the audit. */
|
|
378
417
|
const CYCLE_ENDS = { qa: 'PASS', devops: 'DEPLOYED', secops: 'SECURE' };
|
|
379
418
|
|
|
419
|
+
/**
|
|
420
|
+
* The release each rule this report judges first shipped in. A round is judged on a rule only when the pin it
|
|
421
|
+
* was captured under (`core`) carries it: over every round, "0 of 16 loop-backs named their issues" read
|
|
422
|
+
* reports written before the rule existed as breaking it. Releases are never rewritten, so neither is this.
|
|
423
|
+
*/
|
|
424
|
+
const RULE_SINCE = { issues: '0.22.0', stepFiles: '0.28.8' };
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Whether a round ran under a release that carries a rule. One captured before rounds recorded their pin
|
|
428
|
+
* cannot say, and is left out rather than judged.
|
|
429
|
+
*
|
|
430
|
+
* @param {object} record - A round.
|
|
431
|
+
* @param {keyof RULE_SINCE} rule - The rule.
|
|
432
|
+
* @returns {boolean}
|
|
433
|
+
*/
|
|
434
|
+
export const ranUnder = (record, rule) => typeof record.core === 'string' && (record.core === 'dev' || byVersion(record.core, RULE_SINCE[rule]) >= 0);
|
|
435
|
+
|
|
380
436
|
/** The stages that write code, whose runs' file counts size a cycle. */
|
|
381
|
-
const WRITERS = new Set(['implementer', 'solidity-dev']);
|
|
437
|
+
export const WRITERS = new Set(['implementer', 'solidity-dev']);
|
|
382
438
|
|
|
383
439
|
/** The stages whose presence makes a cycle a designed one. */
|
|
384
440
|
const DESIGNERS = new Set(['planner', 'architect']);
|
|
@@ -489,7 +545,7 @@ function proportionReport(records, stepLimit = () => null) {
|
|
|
489
545
|
// cache read per file held near 1M up to 19 files and was 6.9M in a 52-file run. Asked only of a project
|
|
490
546
|
// whose pinned release states the limit.
|
|
491
547
|
const over = records.filter((r) => {
|
|
492
|
-
if (r.role !== 'implementer' || typeof r.files_touched !== 'number') return false;
|
|
548
|
+
if (r.role !== 'implementer' || typeof r.files_touched !== 'number' || !ranUnder(r, 'stepFiles')) return false;
|
|
493
549
|
const limit = stepLimit(r.project);
|
|
494
550
|
return limit !== null && r.files_touched > limit;
|
|
495
551
|
});
|
|
@@ -622,7 +678,14 @@ export async function stats(argv, ctx) {
|
|
|
622
678
|
// A dispatch a hook denied never ran, so it is neither a run nor a missing verdict.
|
|
623
679
|
const held = records.filter((r) => r.status === 'denied').length;
|
|
624
680
|
const now = Date.now();
|
|
681
|
+
// Agents outside the pipeline — Claude Code's own, a project's own — have no verdict to give. Read as
|
|
682
|
+
// stages they were 100% unreadable and were told to add a verdict token their spec never asked for.
|
|
683
|
+
const outside = new Map();
|
|
625
684
|
for (const r of records.filter((r) => r.status !== 'denied')) {
|
|
685
|
+
if (!PIPELINE_ROLES.has(r.role)) {
|
|
686
|
+
outside.set(r.role, (outside.get(r.role) ?? 0) + 1);
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
626
689
|
if (!byRole.has(r.role)) {
|
|
627
690
|
byRole.set(r.role, { n: 0, runs: new Set(), done: 0, lost: 0, clear: 0, loop: 0, unclear: 0, declared: 0, durations: [] });
|
|
628
691
|
}
|
|
@@ -640,7 +703,7 @@ export async function stats(argv, ctx) {
|
|
|
640
703
|
if (typeof r.duration_s === 'number') s.durations.push(r.duration_s);
|
|
641
704
|
}
|
|
642
705
|
|
|
643
|
-
const span = [records[0]?.ts, records.at(-1)?.ts].map(
|
|
706
|
+
const span = [records[0]?.ts, records.at(-1)?.ts].map(localDay);
|
|
644
707
|
const projectCount = new Set(records.map((r) => r.project)).size;
|
|
645
708
|
const run = byRun(records.filter((r) => r.status !== 'denied')).size;
|
|
646
709
|
console.log(
|
|
@@ -688,9 +751,14 @@ export async function stats(argv, ctx) {
|
|
|
688
751
|
const lost = rows.reduce((a, [, s]) => a + s.lost, 0);
|
|
689
752
|
const done = rows.reduce((a, [, s]) => a + s.done, 0);
|
|
690
753
|
console.log('');
|
|
754
|
+
if (outside.size > 0) {
|
|
755
|
+
const named = [...outside.entries()].sort((a, b) => b[1] - a[1]).map(([role, n]) => `${role} ×${n}`).join(', ');
|
|
756
|
+
console.log(note(`${[...outside.values()].reduce((a, b) => a + b, 0)} round(s) by agents outside the pipeline (${named}) — not stages, so no verdict is asked of them.`, 'info'));
|
|
757
|
+
}
|
|
691
758
|
if (unreadable === 0 && lost === 0) console.log(note("every finished round's verdict could be read.", 'ok'));
|
|
759
|
+
// The same share as the table's column: rounds that finished, or should have, with no verdict read.
|
|
692
760
|
if (unreadable > 0) {
|
|
693
|
-
console.log(note(`${pct(unreadable, done + lost)} of finished rounds
|
|
761
|
+
console.log(note(`${pct(unreadable + lost, done + lost)} of finished rounds have no verdict that can be read. Those stages need a verdict token on the report's first line.`, 'warn'));
|
|
694
762
|
}
|
|
695
763
|
if (lost > 0) {
|
|
696
764
|
console.log(
|
|
@@ -704,7 +772,7 @@ export async function stats(argv, ctx) {
|
|
|
704
772
|
|
|
705
773
|
// Whether the stages that send work back say what they send back. Counted only over reports read
|
|
706
774
|
// since the record learned the field, and only over declared loop-backs, which are all it asks of.
|
|
707
|
-
const named = records.filter((r) => typeof r.issues === 'number');
|
|
775
|
+
const named = records.filter((r) => typeof r.issues === 'number' && ranUnder(r, 'issues'));
|
|
708
776
|
if (named.length > 0) {
|
|
709
777
|
const withIds = named.filter((r) => r.issues > 0).length;
|
|
710
778
|
console.log(
|
|
@@ -788,7 +856,7 @@ export async function stats(argv, ctx) {
|
|
|
788
856
|
: note(`${pills.retired} of ${pills.total} pill(s) retired into a rule.`, 'ok'),
|
|
789
857
|
);
|
|
790
858
|
if (newest) {
|
|
791
|
-
const since = records.filter((r) =>
|
|
859
|
+
const since = records.filter((r) => localDay(r.ts) > newest).length;
|
|
792
860
|
if (since > 0) console.log(note(`${since} round(s) since the newest pill (${newest}).`));
|
|
793
861
|
}
|
|
794
862
|
}
|
package/src/commands/status.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import { amber, bold, dim, green, pink } from '../look.mjs';
|
|
|
22
22
|
import { byVersion, layerRootFor } from './compose.mjs';
|
|
23
23
|
import { filedRequests } from './learn.mjs';
|
|
24
24
|
import { frontmatter, pillFiles } from './pills.mjs';
|
|
25
|
-
import {
|
|
25
|
+
import { projectRecords } from '../store.mjs';
|
|
26
26
|
|
|
27
27
|
/** How long the registry gets to say which version is newest; a status is not worth waiting on. */
|
|
28
28
|
const REGISTRY_MS = 3000;
|
|
@@ -123,7 +123,7 @@ export async function status(argv, ctx) {
|
|
|
123
123
|
const check = verdictOf(bin, ['check', '--project', target]);
|
|
124
124
|
row('check', check.ok ? green(check.line) : warn(`${check.line} — \`nina check\` lists them`));
|
|
125
125
|
|
|
126
|
-
const records =
|
|
126
|
+
const records = projectRecords(target) ?? [];
|
|
127
127
|
if (records.length === 0) {
|
|
128
128
|
row('measured', dim('nothing yet — `nina snapshot` captures it'));
|
|
129
129
|
} else {
|