@xhulz/nina 0.35.0 → 0.37.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 +4 -1
- package/package.json +1 -1
- 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/releases/0.37.0/core/GAPS.md +125 -0
- package/releases/0.37.0/core/answered.json +29 -0
- package/releases/0.37.0/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.37.0/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.37.0/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.37.0/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.37.0/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.37.0/core/defaults/tree/.claude/agents/qa.md +2 -0
- package/releases/0.37.0/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.37.0/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.37.0/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.37.0/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.37.0/core/tree/.claude/agents/architect.md +127 -0
- package/releases/0.37.0/core/tree/.claude/agents/dba.md +110 -0
- package/releases/0.37.0/core/tree/.claude/agents/devops.md +114 -0
- package/releases/0.37.0/core/tree/.claude/agents/implementer.md +156 -0
- package/releases/0.37.0/core/tree/.claude/agents/integration-tester.md +168 -0
- package/releases/0.37.0/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.37.0/core/tree/.claude/agents/qa.md +153 -0
- package/releases/0.37.0/core/tree/.claude/agents/reviewer.md +183 -0
- package/releases/0.37.0/core/tree/.claude/agents/secops.md +121 -0
- package/releases/0.37.0/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.37.0/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.37.0/core/tree/.claude/agents-overview.md +64 -0
- package/releases/0.37.0/core/tree/.claude/frontend.md +10 -0
- package/releases/0.37.0/core/tree/.claude/graph.md +75 -0
- package/releases/0.37.0/core/tree/.claude/patterns.md +213 -0
- package/releases/0.37.0/core/tree/.claude/pills/README.md +146 -0
- package/releases/0.37.0/core/tree/.claude/pipeline.md +101 -0
- package/releases/0.37.0/core/tree/.claude/retrieval.md +103 -0
- package/releases/0.37.0/core/tree/.claude/router.md +198 -0
- package/releases/0.37.0/core/tree/.claude/templates/integration.md +45 -0
- package/releases/0.37.0/core/tree/CLAUDE.md +161 -0
- package/releases/0.37.0/core/tree/scripts/cost-watch.mjs +18 -0
- package/releases/0.37.0/core/tree/scripts/edit-guard.mjs +16 -0
- package/releases/0.37.0/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.37.0/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.37.0/core/vocabulary.json +13 -0
- package/releases/0.37.0/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.37.0/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.37.0/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.37.0/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.37.0/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.37.0/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.37.0/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.37.0/surfaces/blockchain/tree/.claude/router.md +6 -0
- package/releases/0.37.0/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/agents/architect.md +5 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/agents/dba.md +50 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/agents/devops.md +5 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/agents/integration-tester.md +10 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/agents/reviewer.md +22 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/patterns.md +19 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.37.0/surfaces/db/tree/.claude/router.md +4 -0
- package/releases/0.37.0/surfaces/db/tree/CLAUDE.md +17 -0
- package/releases/0.37.0/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.37.0/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.37.0/surfaces/edge-cf/tree/.claude/agents/devops.md +32 -0
- package/releases/0.37.0/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.37.0/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.37.0/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.37.0/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.37.0/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.37.0/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.37.0/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.37.0/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.37.0/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.37.0/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.37.0/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.37.0/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.37.0/surfaces/frontend/tree/.claude/agents/reviewer.md +56 -0
- package/releases/0.37.0/surfaces/frontend/tree/.claude/patterns.md +8 -0
- package/releases/0.37.0/surfaces/frontend/tree/.claude/retrieval.md +2 -0
- package/releases/0.37.0/surfaces/frontend/tree/.claude/router.md +14 -0
- package/releases/0.37.0/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.37.0/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.37.0/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.37.0/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.37.0/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.37.0/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.37.0/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.37.0/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.37.0/surfaces/integrations/tree/.claude/graph.md +10 -0
- package/releases/0.37.0/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.37.0/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.37.0/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.37.0/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.37.0/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.37.0/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.37.0/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.37.0/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.37.0/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.37.0/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.37.0/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.37.0/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.37.0/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.37.0/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.37.0/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.37.0/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.37.0/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.37.0/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.37.0/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.37.0/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.37.0/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.37.0/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.37.0/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.37.0/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.37.0/surfaces/prisma/tree/.claude/agents/architect.md +2 -0
- package/releases/0.37.0/surfaces/prisma/tree/.claude/agents/dba.md +39 -0
- package/releases/0.37.0/surfaces/prisma/tree/.claude/agents/devops.md +11 -0
- package/releases/0.37.0/surfaces/prisma/tree/.claude/agents/implementer.md +2 -0
- package/releases/0.37.0/surfaces/prisma/tree/.claude/agents/integration-tester.md +2 -0
- package/releases/0.37.0/surfaces/prisma/tree/.claude/agents/reviewer.md +2 -0
- package/releases/0.37.0/surfaces/prisma/tree/.claude/patterns.md +15 -0
- package/releases/0.37.0/surfaces/prisma/tree/CLAUDE.md +5 -0
- package/src/commands/export.mjs +8 -16
- package/src/commands/gate.mjs +40 -18
- package/src/commands/learn.mjs +5 -15
- package/src/commands/pipeline.mjs +4 -4
- package/src/commands/runs.mjs +14 -8
- package/src/commands/snapshot.mjs +63 -64
- package/src/commands/stats.mjs +46 -28
- package/src/commands/status.mjs +2 -2
- package/src/gate.mjs +48 -36
- package/src/graph.mjs +36 -0
- package/src/store.mjs +158 -0
- package/src/transcripts.mjs +16 -5
- package/src/wiring.mjs +1 -1
package/src/gate.mjs
CHANGED
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
* the verdict a dispatch answers may not be on disk when the dispatch is about to go out, and the file
|
|
10
10
|
* is mostly replay: a session resumed over a bridge rewrites its history, three quarters of the bytes
|
|
11
11
|
* in the one measured. The gate keeps its own ledger instead — one small file per session, metadata
|
|
12
|
-
* only, with the ids a report gave its issues
|
|
12
|
+
* only, with the ids a report gave its issues and the name an agent was launched with as the only things a
|
|
13
|
+
* model wrote — written by the hooks
|
|
13
14
|
* that see each fact as it happens:
|
|
14
15
|
*
|
|
15
16
|
* PostToolUse on SubagentHandback the report a stage handed back, verbatim — its verdict
|
|
16
17
|
* SubagentStop the same, for a stage that wrote its report as its last message
|
|
17
18
|
* PreToolUse / PostToolUse on Agent, Task, SendMessage
|
|
18
19
|
* a dispatch launched, and the agent it launched
|
|
19
|
-
* UserPromptSubmit (human text only),
|
|
20
|
-
* the owner spoke, and every count starts over
|
|
20
|
+
* UserPromptSubmit (human text only) the owner spoke, and every open loop gets one round more
|
|
21
21
|
* PreToolUse on Agent, Task, SendMessage
|
|
22
22
|
* the decision: let it through, or send the round past the cap to
|
|
23
23
|
* the owner to confirm
|
|
@@ -41,7 +41,13 @@
|
|
|
41
41
|
* belongs to that round; a pass cancels a rejection only if a fix went out between the two — a later
|
|
42
42
|
* review that saw the fix — and closes the loop only if it began after the latest fix with no rejection
|
|
43
43
|
* running beside it; a pass from the stage the source hands its work on to (qa, after the reviewer)
|
|
44
|
-
* closes the source's loops; and the owner speaking
|
|
44
|
+
* closes the source's loops; and the owner speaking gives every loop still open one round more than its cap —
|
|
45
|
+
* it used to empty everything, and since the owner speaks every few minutes no loop ever reached a cap: over
|
|
46
|
+
* two days of one project, sixty resets and not one question. Guessed verdicts never count.
|
|
47
|
+
*
|
|
48
|
+
* A loop is its source's verdict, whichever stage the orchestrator sends the fix to: a rejection routed to
|
|
49
|
+
* the architect one round and to the implementer the next was counted once on each edge, and went round
|
|
50
|
+
* twice its cap before anything asked. Each edge's own cap still decides the round that goes to it.
|
|
45
51
|
*
|
|
46
52
|
* It fails open, and it only acts where the project pins a version that ships it. Any error lets the
|
|
47
53
|
* call through and is logged where `nina gate --selftest`, a detector every project runs, reports it: a
|
|
@@ -53,7 +59,7 @@ import { dirname, join } from 'node:path';
|
|
|
53
59
|
import { fileURLToPath } from 'node:url';
|
|
54
60
|
import { TERMINALS, declaredTokens, parseGraph } from './graph.mjs';
|
|
55
61
|
import { gateDir, slugFor } from './paths.mjs';
|
|
56
|
-
import { declaredIssues, isLoopBack } from './transcripts.mjs';
|
|
62
|
+
import { declaredIssues, isLoopBack, saidToAgent } from './transcripts.mjs';
|
|
57
63
|
|
|
58
64
|
/** The package this runs from: its releases say whether a pinned version ships the gate at all. */
|
|
59
65
|
const PACKAGE = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
@@ -266,7 +272,9 @@ function append(path, entry) {
|
|
|
266
272
|
* too, the round's number is its most-repeated issue's instead — one more than the rounds that issue
|
|
267
273
|
* was in since then — and the edge's own number is kept beside it, against a ceiling of twice the cap.
|
|
268
274
|
* A late sibling's issues are counted in the round it belongs to;
|
|
269
|
-
* - a pass from the stage the source hands passing work on to closes the source's loops outright
|
|
275
|
+
* - a pass from the stage the source hands passing work on to closes the source's loops outright;
|
|
276
|
+
* - the owner speaking gives each loop still open one round more — its `grace` — and closes nothing. A
|
|
277
|
+
* model's own question to the owner (`AskUserQuestion`) is not the owner speaking.
|
|
270
278
|
*
|
|
271
279
|
* @param {object[]} entries - The ledger, oldest first.
|
|
272
280
|
* @param {ReturnType<typeof loopEdges>} loops - The capped loop-back edges.
|
|
@@ -307,6 +315,8 @@ export function replay(entries, loops, forward = new Map()) {
|
|
|
307
315
|
* @type {Map<string, {n: number, agents?: string[], at?: number, blind?: boolean, edge?: number}>}
|
|
308
316
|
*/
|
|
309
317
|
const streak = new Map();
|
|
318
|
+
/** `${source}|${token}` → the rounds the owner has given the loop past its cap, by speaking while it was open. */
|
|
319
|
+
const grace = new Map();
|
|
310
320
|
/** Where each source's latest round went out: a pass must begin after it to say the fix passed. */
|
|
311
321
|
const lastFix = new Map();
|
|
312
322
|
/** Completions a dispatch already acted on: a late copy of one is not new. */
|
|
@@ -315,7 +325,7 @@ export function replay(entries, loops, forward = new Map()) {
|
|
|
315
325
|
const reportedAt = new Map();
|
|
316
326
|
const made = [];
|
|
317
327
|
const close = (source) => {
|
|
318
|
-
for (const key of [...
|
|
328
|
+
for (const map of [streak, grace]) for (const key of [...map.keys()]) if (key.startsWith(`${source}|`)) map.delete(key);
|
|
319
329
|
};
|
|
320
330
|
|
|
321
331
|
const effect = (to, at) => {
|
|
@@ -329,7 +339,7 @@ export function replay(entries, loops, forward = new Map()) {
|
|
|
329
339
|
const closes = passes.some((p) => p.launch > (lastFix.get(source) ?? -1) && !live.some((v) => v.launch < p.at));
|
|
330
340
|
const routed = live.filter((v) => byToken.get(v.verdict)?.has(to));
|
|
331
341
|
const fresh = routed.filter((v) => {
|
|
332
|
-
const last = streak.get(`${source}|${
|
|
342
|
+
const last = streak.get(`${source}|${v.verdict}`);
|
|
333
343
|
return closes || !last || v.launch > last.at;
|
|
334
344
|
});
|
|
335
345
|
// A late sibling belongs to the round it was running in, so its issues do too: each is counted in
|
|
@@ -338,7 +348,7 @@ export function replay(entries, loops, forward = new Map()) {
|
|
|
338
348
|
const adopted = new Map();
|
|
339
349
|
const blinded = new Set();
|
|
340
350
|
for (const v of routed.filter((r) => !fresh.includes(r))) {
|
|
341
|
-
const key = `${source}|${
|
|
351
|
+
const key = `${source}|${v.verdict}`;
|
|
342
352
|
const edge = streak.get(key);
|
|
343
353
|
if (!edge) continue;
|
|
344
354
|
if (!(v.issues?.length > 0)) blinded.add(key);
|
|
@@ -349,21 +359,22 @@ export function replay(entries, loops, forward = new Map()) {
|
|
|
349
359
|
}
|
|
350
360
|
if (fresh.length > 0) {
|
|
351
361
|
const token = fresh.at(-1).verdict;
|
|
352
|
-
const key = `${source}|${
|
|
353
|
-
const
|
|
362
|
+
const key = `${source}|${token}`;
|
|
363
|
+
const given = grace.get(key) ?? 0;
|
|
364
|
+
const max = byToken.get(token).get(to) + given;
|
|
354
365
|
const before = closes ? { n: 0, agents: [] } : (streak.get(key) ?? { n: 0, agents: [] });
|
|
355
366
|
const agents = [...new Set([...(before.agents ?? []), ...fresh.map((v) => v.agent)])];
|
|
356
367
|
// Counted by issue only while every round since the loop last closed named its issues: a round
|
|
357
368
|
// that named nothing advanced no issue's count, so from then on only the edge's count is whole.
|
|
358
369
|
const named = !before.blind && !blinded.has(key) && fresh.every((v) => v.issues?.length > 0);
|
|
359
|
-
const round = { key, source, target: to, token, round: before.n + 1, max, edgeRound: before.n + 1, ceiling: max, blind: !named, agents, at };
|
|
370
|
+
const round = { key, source, target: to, token, round: before.n + 1, max, edgeRound: before.n + 1, ceiling: max, blind: !named, agents, at, given };
|
|
360
371
|
if (named) {
|
|
361
372
|
const issues = [...new Set(fresh.flatMap((v) => v.issues))].map((id) => ({
|
|
362
373
|
id,
|
|
363
374
|
round: (closes ? 0 : ((adopted.get(`${key}|${id}`) ?? streak.get(`${key}|${id}`))?.n ?? 0)) + 1,
|
|
364
375
|
}));
|
|
365
376
|
const worst = issues.reduce((a, b) => (b.round > a.round ? b : a));
|
|
366
|
-
Object.assign(round, { round: worst.round, issue: worst.id, issues, ceiling: max * EDGE_CEILING });
|
|
377
|
+
Object.assign(round, { round: worst.round, issue: worst.id, issues, ceiling: (max - given) * EDGE_CEILING + given });
|
|
367
378
|
}
|
|
368
379
|
rounds.push(round);
|
|
369
380
|
}
|
|
@@ -374,9 +385,9 @@ export function replay(entries, loops, forward = new Map()) {
|
|
|
374
385
|
|
|
375
386
|
entries.forEach((e, i) => {
|
|
376
387
|
if (e.k === 'reset') {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
388
|
+
// Ledgers written before the model's own questions stopped counting as the owner speaking hold those too.
|
|
389
|
+
if (e.why === 'ask') return;
|
|
390
|
+
for (const key of streak.keys()) if (key.split('|').length === 2) grace.set(key, (grace.get(key) ?? 0) + 1);
|
|
380
391
|
return;
|
|
381
392
|
}
|
|
382
393
|
if (e.k === 'verdict' && e.declared && e.agent) {
|
|
@@ -463,25 +474,25 @@ function withoutRef(to) {
|
|
|
463
474
|
}
|
|
464
475
|
|
|
465
476
|
/**
|
|
466
|
-
* Who a SendMessage goes to: an agent id the ledger has seen, or
|
|
467
|
-
*
|
|
468
|
-
*
|
|
477
|
+
* Who a SendMessage goes to: an agent id the ledger has seen, or a name, with or without the ` [ref]` a
|
|
478
|
+
* listing appends. A name is the one an agent was launched with, or — launched with none — its stage's; and,
|
|
479
|
+
* as Claude Code resolves it, the latest agent to go by it is the one reached. A fixer launched as `fixer` and
|
|
480
|
+
* resumed by that name was seen only after it went out, so no resume of it was ever asked about.
|
|
469
481
|
*
|
|
470
482
|
* @returns {{agent: string|null, role: string|null}}
|
|
471
483
|
*/
|
|
472
484
|
function recipient(entries, project, to) {
|
|
473
|
-
const
|
|
474
|
-
const
|
|
485
|
+
const said = withoutRef(to) || null;
|
|
486
|
+
const goesBy = (e) => e.name ?? (e.via === 'SendMessage' ? null : e.role);
|
|
487
|
+
const named = said ? entries.findLast((e) => e.k === 'dispatch' && e.agent && goesBy(e) === said) : null;
|
|
488
|
+
const agent = named?.agent ?? said;
|
|
489
|
+
const role = roleOf(entries, agent) ?? (said && project.graph.stages.has(said) ? said : null);
|
|
475
490
|
return { agent, role };
|
|
476
491
|
}
|
|
477
492
|
|
|
478
|
-
/** Where this agent's current completion starts in the ledger: after its latest launch. */
|
|
493
|
+
/** Where this agent's current completion starts in the ledger: after its latest launch. A `pre` names no agent. */
|
|
479
494
|
function currentLaunch(entries, agent) {
|
|
480
|
-
|
|
481
|
-
entries.forEach((e, i) => {
|
|
482
|
-
if ((e.k === 'dispatch' || e.k === 'pre') && e.agent === agent) at = i;
|
|
483
|
-
});
|
|
484
|
-
return at;
|
|
495
|
+
return entries.findLastIndex((e) => e.k === 'dispatch' && e.agent === agent);
|
|
485
496
|
}
|
|
486
497
|
|
|
487
498
|
/**
|
|
@@ -513,8 +524,10 @@ function handbackIn(path) {
|
|
|
513
524
|
continue;
|
|
514
525
|
}
|
|
515
526
|
const content = row?.message?.content;
|
|
516
|
-
// A new prompt — the resume of a finished agent — starts a new completion; its handback is later.
|
|
517
|
-
|
|
527
|
+
// A new prompt — the resume of a finished agent — starts a new completion; its handback is later. What
|
|
528
|
+
// counts as one is the parser's: a reminder Claude Code slips in after the handback is not, and read as one
|
|
529
|
+
// it threw away the handback the stop was looking for.
|
|
530
|
+
if (saidToAgent(row)) message = null;
|
|
518
531
|
if (!Array.isArray(content)) continue;
|
|
519
532
|
for (const block of content) {
|
|
520
533
|
if (block?.type === 'tool_use' && block.name === 'SubagentHandback' && typeof block.input?.message === 'string') message = block.input.message;
|
|
@@ -566,10 +579,6 @@ function onPost(input, project, path, at) {
|
|
|
566
579
|
}
|
|
567
580
|
// A dispatch made inside a subagent is not the pipeline's: its stages are dispatched by the orchestrator.
|
|
568
581
|
if (input.agent_id) return null;
|
|
569
|
-
if (tool === 'AskUserQuestion') {
|
|
570
|
-
append(path, { k: 'reset', at, why: 'ask' });
|
|
571
|
-
return null;
|
|
572
|
-
}
|
|
573
582
|
if (!DISPATCH.has(tool)) return null;
|
|
574
583
|
const response = input.tool_response && typeof input.tool_response === 'object' ? input.tool_response : {};
|
|
575
584
|
let role;
|
|
@@ -584,7 +593,9 @@ function onPost(input, project, path, at) {
|
|
|
584
593
|
}
|
|
585
594
|
if (!role || !project.graph.stages.has(role)) return null;
|
|
586
595
|
const resumed = tool === 'SendMessage' ? Boolean(response.resumedAgentId) : undefined;
|
|
587
|
-
|
|
596
|
+
// The name an agent was launched with, if any: a later SendMessage may reach it by that name alone.
|
|
597
|
+
const name = tool !== 'SendMessage' && typeof input.tool_input?.name === 'string' ? input.tool_input.name.slice(0, 80) : null;
|
|
598
|
+
append(path, { k: 'dispatch', at, role, agent, via: tool, id: input.tool_use_id ?? null, ...(name ? { name } : {}), ...(resumed === undefined ? {} : { resumed }) });
|
|
588
599
|
return null;
|
|
589
600
|
}
|
|
590
601
|
|
|
@@ -599,9 +610,10 @@ const excess = (r) => Math.max(r.round - r.max, r.edgeRound - r.ceiling);
|
|
|
599
610
|
function confirmation(r) {
|
|
600
611
|
const from = r.agents.length > 0 ? ` The rounds so far came from ${r.agents.join(', ')}.` : '';
|
|
601
612
|
const edge = `${r.source} → ${r.target} (${r.token})`;
|
|
602
|
-
|
|
613
|
+
const cap = r.given ? `${r.max - r.given}, and your ${r.given} repl${r.given === 1 ? 'y' : 'ies'} since it opened gave it ${r.given} more` : `${r.max}`;
|
|
614
|
+
let why = `this would be round ${r.round} on ${edge}, and .claude/graph.md caps that loop at ${cap}.`;
|
|
603
615
|
if (r.issue && r.round > r.max) {
|
|
604
|
-
why = `this would be round ${r.round} of the issue \`${r.issue}\` on ${edge}, and .claude/graph.md caps that loop at ${
|
|
616
|
+
why = `this would be round ${r.round} of the issue \`${r.issue}\` on ${edge}, and .claude/graph.md caps that loop at ${cap}.`;
|
|
605
617
|
} else if (r.issue) {
|
|
606
618
|
why =
|
|
607
619
|
`this would be round ${r.edgeRound} on ${edge} with no approval between them. No issue has passed the cap of ${r.max}, ` +
|
package/src/graph.mjs
CHANGED
|
@@ -158,8 +158,44 @@ export function validateGraph(graph, specs, roles = new Set()) {
|
|
|
158
158
|
if (isLoopBack(e.token) && !TERMINALS.has(e.to) && e.max === null) {
|
|
159
159
|
problems.push(`graph.md:${e.line}: loop-back \`${e.from}\` → \`${e.to}\` has no cap — add "· max N"`);
|
|
160
160
|
}
|
|
161
|
+
// A cap of none sends every round to the owner: the edge is one to `human`, said another way.
|
|
162
|
+
if (e.max !== null && e.max < 1) problems.push(`graph.md:${e.line}: \`${e.from}\` → \`${e.to}\` is capped at ${e.max} — a cap is one round or more`);
|
|
163
|
+
if (TERMINALS.has(e.from)) problems.push(`graph.md:${e.line}: an edge leaves \`${e.from}\`, where work ends`);
|
|
161
164
|
}
|
|
162
165
|
|
|
166
|
+
// The shape of the whole. Each of these held in every graph composed so far and nothing checked it: a
|
|
167
|
+
// cycle through verdicts that pass work on has no cap and never ends; a stage no edge touches is never
|
|
168
|
+
// dispatched; and one from which no chain of edges reaches `done` or `human` is where work stops unseen.
|
|
169
|
+
const forward = new Map();
|
|
170
|
+
for (const e of edges) if (!isLoopBack(e.token) && stages.has(e.from) && stages.has(e.to)) forward.set(e.from, [...(forward.get(e.from) ?? []), e.to]);
|
|
171
|
+
const state = new Map();
|
|
172
|
+
const cycle = (stage, path) => {
|
|
173
|
+
state.set(stage, 'open');
|
|
174
|
+
for (const next of forward.get(stage) ?? []) {
|
|
175
|
+
if (state.get(next) === 'open') return [...path.slice(path.indexOf(next)), next];
|
|
176
|
+
if (!state.has(next)) {
|
|
177
|
+
const found = cycle(next, [...path, next]);
|
|
178
|
+
if (found) return found;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
state.set(stage, 'done');
|
|
182
|
+
return null;
|
|
183
|
+
};
|
|
184
|
+
for (const s of stages) {
|
|
185
|
+
const found = state.has(s) ? null : cycle(s, [s]);
|
|
186
|
+
if (found) {
|
|
187
|
+
problems.push(`the graph goes round ${found.map((x) => `\`${x}\``).join(' → ')} on verdicts that pass work on — a loop with no cap`);
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
for (const s of stages) if (!edges.some((e) => e.from === s || e.to === s)) problems.push(`stage \`${s}\` has no edge in or out — nothing dispatches it`);
|
|
192
|
+
const ends = new Set(TERMINALS);
|
|
193
|
+
for (let grew = true; grew; ) {
|
|
194
|
+
grew = false;
|
|
195
|
+
for (const e of edges) if (ends.has(e.to) && !ends.has(e.from)) grew = ends.add(e.from) && true;
|
|
196
|
+
}
|
|
197
|
+
for (const s of stages) if (edges.some((e) => e.from === s) && !ends.has(s)) problems.push(`no chain of edges from \`${s}\` reaches \`done\` or \`human\` — work there never ends`);
|
|
198
|
+
|
|
163
199
|
// The loop gate reads one cap per edge. Two lines naming the same edge with different caps would
|
|
164
200
|
// leave it guessing which one holds; it would take the larger, but a graph should not need a
|
|
165
201
|
// tie-breaker.
|
package/src/store.mjs
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The measurement store, read one way by every report.
|
|
3
|
+
*
|
|
4
|
+
* Five readers each parsed `~/.nina/snapshots/<project>.jsonl` their own way — `stats`, `runs`, `learn`,
|
|
5
|
+
* `export` and `status` — and chose the project four different ways, so two reports on the same history
|
|
6
|
+
* disagreed and neither said why. Every report reads it here now. Only `snapshot`, which rewrites the file,
|
|
7
|
+
* reads it raw.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
11
|
+
import { join, resolve } from 'node:path';
|
|
12
|
+
import { slugFor, snapshotsDir } from './paths.mjs';
|
|
13
|
+
import { ROLE_TOKENS } from './transcripts.mjs';
|
|
14
|
+
|
|
15
|
+
/** What a record says when no verdict was read from its report. */
|
|
16
|
+
const UNREAD = new Set(['UNCLEAR', 'NONE', null, undefined]);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Records from a store file's text. A torn line loses one record, never the file.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} text - The file.
|
|
22
|
+
* @returns {object[]}
|
|
23
|
+
*/
|
|
24
|
+
export function parseStore(text) {
|
|
25
|
+
return String(text)
|
|
26
|
+
.split('\n')
|
|
27
|
+
.filter((l) => l.trim())
|
|
28
|
+
.flatMap((l) => {
|
|
29
|
+
try {
|
|
30
|
+
return [JSON.parse(l)];
|
|
31
|
+
} catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A record as the reports read it. A verdict outside the vocabulary of the role that gave it is not a
|
|
39
|
+
* verdict that role can give: records captured before the classifier knew the role read a dba's report
|
|
40
|
+
* as `PASS`, qa's token, and 21 of those made a gate that had stopped one change in fourteen look like a
|
|
41
|
+
* rubber stamp. Kept in the store as captured, and read as unreadable.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} record - As captured.
|
|
44
|
+
* @returns {object}
|
|
45
|
+
*/
|
|
46
|
+
export function asRead(record) {
|
|
47
|
+
const tokens = ROLE_TOKENS[record?.role];
|
|
48
|
+
if (!tokens || UNREAD.has(record.verdict) || tokens.includes(record.verdict)) return record;
|
|
49
|
+
return { ...record, verdict: 'UNCLEAR', verdict_source: 'outside-role' };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Every record in one store file.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} file - The file.
|
|
56
|
+
* @param {{raw?: boolean}} [options] - `raw`: as captured, for the one writer of the store.
|
|
57
|
+
* @returns {object[]} Empty when there is no such file.
|
|
58
|
+
*/
|
|
59
|
+
export function readStoreFile(file, { raw = false } = {}) {
|
|
60
|
+
let text;
|
|
61
|
+
try {
|
|
62
|
+
text = readFileSync(file, 'utf8');
|
|
63
|
+
} catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const records = parseStore(text);
|
|
67
|
+
return raw ? records : records.map(asRead);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The store names a project's records may be under: its path as given, and with its links resolved, since
|
|
72
|
+
* either may be the one its sessions ran in.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} dir - The project.
|
|
75
|
+
* @returns {string[]}
|
|
76
|
+
*/
|
|
77
|
+
export function storeNamesOf(dir) {
|
|
78
|
+
const names = new Set([slugFor(dir)]);
|
|
79
|
+
try {
|
|
80
|
+
names.add(slugFor(realpathSync(dir)));
|
|
81
|
+
} catch {
|
|
82
|
+
// A directory that cannot be resolved is looked up as given.
|
|
83
|
+
}
|
|
84
|
+
return [...names];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A project's records.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} dir - The project.
|
|
91
|
+
* @param {string} [store] - The store directory.
|
|
92
|
+
* @returns {object[]|null} Null when the store has nothing for it.
|
|
93
|
+
*/
|
|
94
|
+
export function projectRecords(dir, store = snapshotsDir()) {
|
|
95
|
+
for (const name of storeNamesOf(dir)) {
|
|
96
|
+
const file = join(store, `${name}.jsonl`);
|
|
97
|
+
if (existsSync(file)) return readStoreFile(file);
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The store names a `--project` value picks, one rule for every report. A directory is that project, by the
|
|
104
|
+
* name its sessions ran under; anything else is a store name, exactly, or else every name that contains it.
|
|
105
|
+
* `stats --project ../thing` read the directory as a piece of a name, found none, and said to run a snapshot.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} value - What was given.
|
|
108
|
+
* @param {string} [store] - The store directory.
|
|
109
|
+
* @returns {string[]}
|
|
110
|
+
*/
|
|
111
|
+
export function namesFor(value, store = snapshotsDir()) {
|
|
112
|
+
const names = (() => {
|
|
113
|
+
try {
|
|
114
|
+
return readdirSync(store).filter((f) => f.endsWith('.jsonl')).map((f) => f.slice(0, -'.jsonl'.length));
|
|
115
|
+
} catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
})();
|
|
119
|
+
let isDir = false;
|
|
120
|
+
try {
|
|
121
|
+
isDir = statSync(resolve(value)).isDirectory();
|
|
122
|
+
} catch {
|
|
123
|
+
// Not a directory here: a name.
|
|
124
|
+
}
|
|
125
|
+
if (isDir) return storeNamesOf(resolve(value)).filter((n) => names.includes(n));
|
|
126
|
+
if (names.includes(value)) return [value];
|
|
127
|
+
return names.filter((n) => n.includes(value));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Two digits. */
|
|
131
|
+
const two = (n) => String(n).padStart(2, '0');
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The day a moment fell on where this machine is, as `YYYY-MM-DD`. The store keeps UTC; the reports read
|
|
135
|
+
* the owner's day. Cut from the UTC string, `--since` and the days a report printed moved a round from the
|
|
136
|
+
* evening it ran in to the next morning for anyone west of Greenwich — 20 of one project's 130.
|
|
137
|
+
*
|
|
138
|
+
* @param {unknown} ts - An ISO timestamp.
|
|
139
|
+
* @returns {string} The day, or the first ten characters of whatever could not be read as a moment.
|
|
140
|
+
*/
|
|
141
|
+
export function localDay(ts) {
|
|
142
|
+
const d = new Date(String(ts ?? ''));
|
|
143
|
+
return Number.isNaN(d.getTime()) ? String(ts ?? '').slice(0, 10) : `${d.getFullYear()}-${two(d.getMonth() + 1)}-${two(d.getDate())}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* A moment where this machine is, as `YYYY-MM-DD HH:MM`.
|
|
148
|
+
*
|
|
149
|
+
* @param {unknown} ts - An ISO timestamp.
|
|
150
|
+
* @returns {string}
|
|
151
|
+
*/
|
|
152
|
+
export function localMinute(ts) {
|
|
153
|
+
const d = new Date(String(ts ?? ''));
|
|
154
|
+
return Number.isNaN(d.getTime()) ? String(ts ?? '') : `${localDay(ts)} ${two(d.getHours())}:${two(d.getMinutes())}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The zone the reports read days in, named where a report prints a time. */
|
|
158
|
+
export const zone = () => Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'local time';
|
package/src/transcripts.mjs
CHANGED
|
@@ -20,8 +20,15 @@ import { homedir } from 'node:os';
|
|
|
20
20
|
/** Root under which Claude Code stores one directory of transcripts per project. */
|
|
21
21
|
export const PROJECTS_ROOT = join(homedir(), '.claude', 'projects');
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Where the transcripts are read from: Claude Code's own directory, or `NINA_TRANSCRIPTS`, which moves it for
|
|
25
|
+
* a test. Every reader asks this: listing the projects read Claude Code's own whatever the variable said, so a
|
|
26
|
+
* snapshot meant to stay in a sandbox captured the machine's real projects into it.
|
|
27
|
+
*/
|
|
28
|
+
const transcriptsRoot = () => process.env.NINA_TRANSCRIPTS ?? PROJECTS_ROOT;
|
|
29
|
+
|
|
30
|
+
/** The transcript directory of a project, by its snapshot name. */
|
|
31
|
+
export const transcriptsOf = (slug) => join(transcriptsRoot(), slug);
|
|
25
32
|
|
|
26
33
|
/**
|
|
27
34
|
* The verdict tokens the agent specs mandate on a report's first line, per role.
|
|
@@ -310,7 +317,7 @@ const handsBack = (row) =>
|
|
|
310
317
|
* @param {object|null} row - A parsed transcript row.
|
|
311
318
|
* @returns {boolean}
|
|
312
319
|
*/
|
|
313
|
-
const saidToAgent = (row) => {
|
|
320
|
+
export const saidToAgent = (row) => {
|
|
314
321
|
if (row?.type !== 'user') return false;
|
|
315
322
|
const content = row.message?.content;
|
|
316
323
|
if (Array.isArray(content) && content.some((b) => b?.type === 'tool_result')) return false;
|
|
@@ -486,6 +493,9 @@ export async function scanProject(projectDir, prior = {}) {
|
|
|
486
493
|
model: block?.input?.model ?? row?.message?.model ?? null,
|
|
487
494
|
branch: row.gitBranch ?? null,
|
|
488
495
|
session: row.sessionId ?? null,
|
|
496
|
+
// The Claude Code that ran the dispatch: a change in how its transcripts are written first shows
|
|
497
|
+
// as a parser that stops finding something, and this says which version it stopped at.
|
|
498
|
+
claude_code: row.version ?? null,
|
|
489
499
|
verdict: null,
|
|
490
500
|
verdict_source: null,
|
|
491
501
|
issues: null,
|
|
@@ -757,6 +767,7 @@ async function attachAgentDetail(projectDir, dispatches) {
|
|
|
757
767
|
model: run.model,
|
|
758
768
|
branch: round.branch ?? run.branch,
|
|
759
769
|
session: run.session,
|
|
770
|
+
claude_code: run.claude_code ?? null,
|
|
760
771
|
verdict: null,
|
|
761
772
|
verdict_source: null,
|
|
762
773
|
issues: null,
|
|
@@ -884,10 +895,10 @@ export function tokensOf(usage) {
|
|
|
884
895
|
* @returns {Promise<{slug: string, dir: string, mtime: number}[]>} The projects found.
|
|
885
896
|
*/
|
|
886
897
|
export async function listProjects() {
|
|
887
|
-
const slugs = await readdir(
|
|
898
|
+
const slugs = await readdir(transcriptsRoot()).catch(() => []);
|
|
888
899
|
const out = [];
|
|
889
900
|
for (const slug of slugs) {
|
|
890
|
-
const dir =
|
|
901
|
+
const dir = transcriptsOf(slug);
|
|
891
902
|
const info = await stat(dir).catch(() => null);
|
|
892
903
|
if (info?.isDirectory()) out.push({ slug, dir, mtime: info.mtimeMs });
|
|
893
904
|
}
|
package/src/wiring.mjs
CHANGED
|
@@ -125,7 +125,7 @@ export const HOOKS = [
|
|
|
125
125
|
},
|
|
126
126
|
}),
|
|
127
127
|
},
|
|
128
|
-
{ event: 'UserPromptSubmit', script: GATE, timeout: 10, why: "the owner's reply never
|
|
128
|
+
{ event: 'UserPromptSubmit', script: GATE, timeout: 10, why: "the owner's reply never gives an open loop its round more" },
|
|
129
129
|
{
|
|
130
130
|
event: 'PreToolUse', matcher: 'Agent|Task|SendMessage', script: GATE, timeout: 10, why: 'the caps in .claude/graph.md stay instructions nothing holds',
|
|
131
131
|
crash: (cannot) => ({ systemMessage: `NINA: the loop gate could not start, so the caps in .claude/graph.md are not held — ${cannot}` }),
|