@xhulz/nina 0.28.18 → 0.28.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/releases/0.28.19/core/GAPS.md +125 -0
- package/releases/0.28.19/core/answered.json +29 -0
- package/releases/0.28.19/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.28.19/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.28.19/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.28.19/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.28.19/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.28.19/core/defaults/tree/.claude/agents/qa.md +2 -0
- package/releases/0.28.19/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.28.19/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.28.19/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.28.19/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.28.19/core/tree/.claude/agents/architect.md +125 -0
- package/releases/0.28.19/core/tree/.claude/agents/dba.md +107 -0
- package/releases/0.28.19/core/tree/.claude/agents/devops.md +113 -0
- package/releases/0.28.19/core/tree/.claude/agents/implementer.md +155 -0
- package/releases/0.28.19/core/tree/.claude/agents/integration-tester.md +160 -0
- package/releases/0.28.19/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.28.19/core/tree/.claude/agents/qa.md +146 -0
- package/releases/0.28.19/core/tree/.claude/agents/reviewer.md +180 -0
- package/releases/0.28.19/core/tree/.claude/agents/secops.md +121 -0
- package/releases/0.28.19/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.28.19/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.28.19/core/tree/.claude/agents-overview.md +64 -0
- package/releases/0.28.19/core/tree/.claude/graph.md +73 -0
- package/releases/0.28.19/core/tree/.claude/patterns.md +212 -0
- package/releases/0.28.19/core/tree/.claude/pills/README.md +145 -0
- package/releases/0.28.19/core/tree/.claude/pipeline.md +102 -0
- package/releases/0.28.19/core/tree/.claude/retrieval.md +104 -0
- package/releases/0.28.19/core/tree/.claude/router.md +201 -0
- package/releases/0.28.19/core/tree/.claude/templates/integration.md +45 -0
- package/releases/0.28.19/core/tree/CLAUDE.md +155 -0
- package/releases/0.28.19/core/tree/scripts/edit-guard.mjs +16 -0
- package/releases/0.28.19/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.28.19/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.28.19/core/vocabulary.json +12 -0
- package/releases/0.28.19/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.28.19/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.28.19/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.28.19/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.28.19/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.28.19/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.28.19/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.28.19/surfaces/blockchain/tree/.claude/router.md +9 -0
- package/releases/0.28.19/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/agents/architect.md +8 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/agents/dba.md +84 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/agents/devops.md +14 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/agents/implementer.md +5 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/agents/integration-tester.md +13 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/agents/reviewer.md +25 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/patterns.md +29 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.28.19/surfaces/db/tree/.claude/router.md +10 -0
- package/releases/0.28.19/surfaces/db/tree/CLAUDE.md +23 -0
- package/releases/0.28.19/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.28.19/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.28.19/surfaces/edge-cf/tree/.claude/agents/devops.md +31 -0
- package/releases/0.28.19/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.28.19/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.28.19/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.28.19/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.28.19/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.28.19/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.28.19/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.28.19/surfaces/edge-cf/tree/.claude/router.md +21 -0
- package/releases/0.28.19/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.28.19/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.28.19/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.28.19/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.28.19/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.28.19/surfaces/frontend/tree/.claude/agents/reviewer.md +62 -0
- package/releases/0.28.19/surfaces/frontend/tree/.claude/patterns.md +15 -0
- package/releases/0.28.19/surfaces/frontend/tree/.claude/retrieval.md +5 -0
- package/releases/0.28.19/surfaces/frontend/tree/.claude/router.md +17 -0
- package/releases/0.28.19/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.28.19/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.28.19/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.28.19/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.28.19/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.28.19/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.28.19/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.28.19/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.28.19/surfaces/integrations/tree/.claude/graph.md +9 -0
- package/releases/0.28.19/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.28.19/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.28.19/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.28.19/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.28.19/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.28.19/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.28.19/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.28.19/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.28.19/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.28.19/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.28.19/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.28.19/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.28.19/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.28.19/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.28.19/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.28.19/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.28.19/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.28.19/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.28.19/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.28.19/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.28.19/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.28.19/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.28.19/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.28.19/surfaces/pii/tree/CLAUDE.md +5 -0
- package/releases/0.28.20/core/GAPS.md +125 -0
- package/releases/0.28.20/core/answered.json +29 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/architect.md +1 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/dba.md +3 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/devops.md +1 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/implementer.md +1 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/qa.md +2 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/reviewer.md +3 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/secops.md +3 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/solidity-auditor.md +3 -0
- package/releases/0.28.20/core/defaults/tree/.claude/agents/solidity-dev.md +3 -0
- package/releases/0.28.20/core/tree/.claude/agents/architect.md +125 -0
- package/releases/0.28.20/core/tree/.claude/agents/dba.md +107 -0
- package/releases/0.28.20/core/tree/.claude/agents/devops.md +113 -0
- package/releases/0.28.20/core/tree/.claude/agents/implementer.md +155 -0
- package/releases/0.28.20/core/tree/.claude/agents/integration-tester.md +160 -0
- package/releases/0.28.20/core/tree/.claude/agents/planner.md +132 -0
- package/releases/0.28.20/core/tree/.claude/agents/qa.md +146 -0
- package/releases/0.28.20/core/tree/.claude/agents/reviewer.md +180 -0
- package/releases/0.28.20/core/tree/.claude/agents/secops.md +121 -0
- package/releases/0.28.20/core/tree/.claude/agents/solidity-auditor.md +126 -0
- package/releases/0.28.20/core/tree/.claude/agents/solidity-dev.md +123 -0
- package/releases/0.28.20/core/tree/.claude/agents-overview.md +64 -0
- package/releases/0.28.20/core/tree/.claude/graph.md +73 -0
- package/releases/0.28.20/core/tree/.claude/patterns.md +212 -0
- package/releases/0.28.20/core/tree/.claude/pills/README.md +145 -0
- package/releases/0.28.20/core/tree/.claude/pipeline.md +102 -0
- package/releases/0.28.20/core/tree/.claude/retrieval.md +104 -0
- package/releases/0.28.20/core/tree/.claude/router.md +201 -0
- package/releases/0.28.20/core/tree/.claude/templates/integration.md +45 -0
- package/releases/0.28.20/core/tree/CLAUDE.md +155 -0
- package/releases/0.28.20/core/tree/scripts/edit-guard.mjs +16 -0
- package/releases/0.28.20/core/tree/scripts/harness-check.mjs +71 -0
- package/releases/0.28.20/core/tree/scripts/loop-gate.mjs +19 -0
- package/releases/0.28.20/core/vocabulary.json +12 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/architect.md +5 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/devops.md +5 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/reviewer.md +5 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/solidity-auditor.md +18 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents/solidity-dev.md +16 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/agents-overview.md +6 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/graph.md +11 -0
- package/releases/0.28.20/surfaces/blockchain/tree/.claude/router.md +9 -0
- package/releases/0.28.20/surfaces/blockchain/tree/CLAUDE.md +12 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/architect.md +8 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/dba.md +84 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/devops.md +14 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/implementer.md +5 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/integration-tester.md +13 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/planner.md +2 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/reviewer.md +25 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents/secops.md +2 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/agents-overview.md +5 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/graph.md +8 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/patterns.md +29 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/pipeline.md +13 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/retrieval.md +5 -0
- package/releases/0.28.20/surfaces/db/tree/.claude/router.md +10 -0
- package/releases/0.28.20/surfaces/db/tree/CLAUDE.md +23 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/architect.md +14 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/dba.md +2 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/devops.md +31 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/implementer.md +14 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/integration-tester.md +11 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/qa.md +3 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/agents/secops.md +8 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/patterns.md +17 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/retrieval.md +8 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/.claude/router.md +21 -0
- package/releases/0.28.20/surfaces/edge-cf/tree/CLAUDE.md +27 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/architect.md +20 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/devops.md +28 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/implementer.md +25 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/qa.md +17 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/agents/reviewer.md +62 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/patterns.md +15 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/retrieval.md +5 -0
- package/releases/0.28.20/surfaces/frontend/tree/.claude/router.md +17 -0
- package/releases/0.28.20/surfaces/frontend/tree/CLAUDE.md +5 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/architect.md +16 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/implementer.md +12 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/integration-tester.md +20 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/planner.md +2 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/reviewer.md +21 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents/secops.md +2 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/agents-overview.md +8 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/graph.md +9 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/patterns.md +38 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/pipeline.md +16 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/retrieval.md +16 -0
- package/releases/0.28.20/surfaces/integrations/tree/.claude/router.md +6 -0
- package/releases/0.28.20/surfaces/integrations/tree/CLAUDE.md +17 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/architect.md +12 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/dba.md +14 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/implementer.md +11 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/integration-tester.md +3 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/reviewer.md +23 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/secops.md +5 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/solidity-auditor.md +4 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/agents/solidity-dev.md +4 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/graph.md +2 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/patterns.md +49 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/pipeline.md +2 -0
- package/releases/0.28.20/surfaces/money/tree/.claude/retrieval.md +2 -0
- package/releases/0.28.20/surfaces/money/tree/CLAUDE.md +11 -0
- package/releases/0.28.20/surfaces/pii/tree/.claude/agents/dba.md +2 -0
- package/releases/0.28.20/surfaces/pii/tree/.claude/agents/devops.md +8 -0
- package/releases/0.28.20/surfaces/pii/tree/.claude/agents/reviewer.md +8 -0
- package/releases/0.28.20/surfaces/pii/tree/.claude/agents/secops.md +2 -0
- package/releases/0.28.20/surfaces/pii/tree/.claude/agents/solidity-auditor.md +5 -0
- package/releases/0.28.20/surfaces/pii/tree/CLAUDE.md +5 -0
- package/src/commands/pipeline.mjs +192 -59
- package/src/commands/stats.mjs +83 -48
- package/src/graph.mjs +16 -1
- package/src/look.mjs +118 -0
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `nina pipeline` — draws a project's agent chain: the line work moves along, the gates it passes on the
|
|
3
|
-
* way, where each stage sends work back and how many rounds
|
|
4
|
-
* and
|
|
3
|
+
* way, the stages that run as several agents at once, where each stage sends work back and how many rounds
|
|
4
|
+
* it may, each stage's model, effort and skills with what it did over the last thirty days, and which chain
|
|
5
|
+
* a task of each shape takes. `--for` draws one shape's chain alone.
|
|
5
6
|
*
|
|
6
7
|
* Opening a project, the pipeline was a graph, a router, ten specs and a table in CLAUDE.md to read before
|
|
7
8
|
* anyone could say which stage came after which, on which model, with which skills. Every fact here is
|
|
8
|
-
* already composed, and `nina check` already holds it to the graph; this only reads it
|
|
9
|
+
* already composed, and `nina check` already holds it to the graph, or already measured; this only reads it
|
|
10
|
+
* back and draws it.
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
|
-
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
13
|
+
import { existsSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
12
14
|
import { basename, join, resolve } from 'node:path';
|
|
13
|
-
import {
|
|
14
|
-
import { HARNESS } from '../paths.mjs';
|
|
15
|
+
import { dim, pink } from '../look.mjs';
|
|
16
|
+
import { HARNESS, slugFor, snapshotsDir } from '../paths.mjs';
|
|
15
17
|
import { TERMINALS, parseGraph } from '../graph.mjs';
|
|
16
18
|
import { required } from '../tools.mjs';
|
|
17
19
|
import { isLoopBack } from '../transcripts.mjs';
|
|
@@ -20,6 +22,9 @@ import { frontmatter } from './pills.mjs';
|
|
|
20
22
|
/** A stage line under "## Stages": "- `name` — what it does". */
|
|
21
23
|
const STAGE_LINE = /^- `([a-z][a-z-]*)` — (.*)$/;
|
|
22
24
|
|
|
25
|
+
/** How far back the history beside each stage looks, unless `--since` says. */
|
|
26
|
+
const HISTORY_DAYS = 30;
|
|
27
|
+
|
|
23
28
|
/**
|
|
24
29
|
* What each stage does, in the graph's own words: shorter than a spec's `description:`, which is written
|
|
25
30
|
* for Claude Code to choose an agent by.
|
|
@@ -48,7 +53,8 @@ export function stageWords(text) {
|
|
|
48
53
|
* - The gates: a stage off the line that a line stage sends work to on a condition, and that hands it on
|
|
49
54
|
* to the next stage of the line.
|
|
50
55
|
* - The ends: where the line's last stage sends work on a condition, followed forward to a terminal.
|
|
51
|
-
* - The loop-backs: every edge on a verdict that sends work back, to a stage or to the owner
|
|
56
|
+
* - The loop-backs: every edge on a verdict that sends work back, to a stage or to the owner, grouped by
|
|
57
|
+
* the stage that sends it.
|
|
52
58
|
*
|
|
53
59
|
* @param {{stages: Set<string>, edges: object[]}} graph - A parsed graph.
|
|
54
60
|
* @returns {{line: string[], gates: object[], ends: object[], back: object[]}}
|
|
@@ -81,17 +87,18 @@ export function shapeOf(graph) {
|
|
|
81
87
|
}
|
|
82
88
|
return { path, when: e.when };
|
|
83
89
|
});
|
|
84
|
-
// Grouped by the stage that sends the work back, in the order the stages are listed.
|
|
85
90
|
const back = graph.edges.filter((e) => isLoopBack(e.token)).sort((a, b) => order.indexOf(a.from) - order.indexOf(b.from));
|
|
86
91
|
return { line, gates, ends, back };
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
/**
|
|
90
95
|
* The rows of the "Required chain by task shape" table in a composed CLAUDE.md: the shape, and the chain it
|
|
91
|
-
* takes — the part the table sets in bold, where it does.
|
|
96
|
+
* takes — the part the table sets in bold, where it does. A row whose chain runs stage → stage, or is
|
|
97
|
+
* "none", is a chain of its own; any other (`+ dba before reviewer`) is an add-on to whichever chain the
|
|
98
|
+
* task takes.
|
|
92
99
|
*
|
|
93
100
|
* @param {string} text - The composed CLAUDE.md.
|
|
94
|
-
* @returns {{shape: string, chain: string}[]}
|
|
101
|
+
* @returns {{shape: string, chain: string, addOn: boolean}[]}
|
|
95
102
|
*/
|
|
96
103
|
export function chainsByShape(text) {
|
|
97
104
|
const at = text.indexOf('| Task shape |');
|
|
@@ -102,34 +109,127 @@ export function chainsByShape(text) {
|
|
|
102
109
|
const [shape = '', chain = ''] = row.split('|').slice(1).map((c) => c.trim());
|
|
103
110
|
const bold = /\*\*(.+?)\*\*/.exec(chain)?.[1];
|
|
104
111
|
const plain = (s) => s.replace(/\*\*/g, '').replace(/`/g, '').trim();
|
|
105
|
-
|
|
112
|
+
const taken = plain(bold ?? chain);
|
|
113
|
+
rows.push({ shape: plain(shape), chain: taken, addOn: !taken.includes('→') && !/^none\b/i.test(taken) });
|
|
106
114
|
}
|
|
107
115
|
return rows;
|
|
108
116
|
}
|
|
109
117
|
|
|
118
|
+
/**
|
|
119
|
+
* The task shapes a `--for` names: by its number in the list, or by every word it gives.
|
|
120
|
+
*
|
|
121
|
+
* @param {{shape: string}[]} shapes - The chain table's rows.
|
|
122
|
+
* @param {string} query - A number, or words.
|
|
123
|
+
* @returns {object[]}
|
|
124
|
+
*/
|
|
125
|
+
export function pickShape(shapes, query) {
|
|
126
|
+
if (/^\d+$/.test(query.trim())) {
|
|
127
|
+
const row = shapes[Number(query) - 1];
|
|
128
|
+
return row ? [row] : [];
|
|
129
|
+
}
|
|
130
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
131
|
+
if (words.length === 0) return [];
|
|
132
|
+
return shapes.filter((r) => words.every((w) => r.shape.toLowerCase().includes(w)));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* What each stage did since a date, from the measurement store: its runs, the verdicts that could be read,
|
|
137
|
+
* and how many of those sent work back. A run a hook denied never ran, and is not counted.
|
|
138
|
+
*
|
|
139
|
+
* @param {object[]} records - The project's records.
|
|
140
|
+
* @param {string} since - An ISO date.
|
|
141
|
+
* @returns {Map<string, {runs: number, read: number, back: number}>}
|
|
142
|
+
*/
|
|
143
|
+
export function historyOf(records, since) {
|
|
144
|
+
const byRole = new Map();
|
|
145
|
+
for (const r of records) {
|
|
146
|
+
if (!r.role || r.status === 'denied' || String(r.ts ?? '') < since) continue;
|
|
147
|
+
const s = byRole.get(r.role) ?? { runs: 0, read: 0, back: 0 };
|
|
148
|
+
s.runs += 1;
|
|
149
|
+
if (r.verdict && r.verdict !== 'UNCLEAR' && r.verdict !== 'NONE') {
|
|
150
|
+
s.read += 1;
|
|
151
|
+
if (isLoopBack(r.verdict)) s.back += 1;
|
|
152
|
+
}
|
|
153
|
+
byRole.set(r.role, s);
|
|
154
|
+
}
|
|
155
|
+
return byRole;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The project's records in the measurement store, under the name Claude Code gives its directory — as
|
|
160
|
+
* given, or with its links resolved, since either may be the one the sessions ran in.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} dir - The project.
|
|
163
|
+
* @returns {object[]|null} Null when the store has nothing for it.
|
|
164
|
+
*/
|
|
165
|
+
function storedRuns(dir) {
|
|
166
|
+
const names = new Set([slugFor(dir)]);
|
|
167
|
+
try {
|
|
168
|
+
names.add(slugFor(realpathSync(dir)));
|
|
169
|
+
} catch {
|
|
170
|
+
// A directory that cannot be resolved is looked up as given.
|
|
171
|
+
}
|
|
172
|
+
for (const name of names) {
|
|
173
|
+
const file = join(snapshotsDir(), `${name}.jsonl`);
|
|
174
|
+
if (!existsSync(file)) continue;
|
|
175
|
+
return readFileSync(file, 'utf8')
|
|
176
|
+
.split('\n')
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
.flatMap((l) => {
|
|
179
|
+
try {
|
|
180
|
+
return [JSON.parse(l)];
|
|
181
|
+
} catch {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
110
189
|
/**
|
|
111
190
|
* Runs the command.
|
|
112
191
|
*
|
|
113
|
-
* @param {string[]} argv - `[--project <dir>]`.
|
|
192
|
+
* @param {string[]} argv - `[--project <dir>] [--for <number or words>] [--since <YYYY-MM-DD>]`.
|
|
114
193
|
* @returns {Promise<number>} Process exit code.
|
|
115
194
|
*/
|
|
116
195
|
export async function pipeline(argv) {
|
|
117
|
-
const
|
|
196
|
+
const arg = (flag) => (argv.includes(flag) ? argv[argv.indexOf(flag) + 1] ?? '' : null);
|
|
197
|
+
const dir = resolve(arg('--project') ?? process.cwd());
|
|
118
198
|
const graphPath = join(dir, '.claude', 'graph.md');
|
|
119
199
|
if (!existsSync(graphPath)) {
|
|
120
200
|
console.error(` no .claude/graph.md in ${dir} — this draws a composed pipeline: \`nina compose\` writes one`);
|
|
121
201
|
return 1;
|
|
122
202
|
}
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
const
|
|
203
|
+
const graphText = readFileSync(graphPath, 'utf8');
|
|
204
|
+
const graph = parseGraph(graphText);
|
|
205
|
+
const words = stageWords(graphText);
|
|
206
|
+
const shape = shapeOf(graph);
|
|
207
|
+
const claude = join(dir, 'CLAUDE.md');
|
|
208
|
+
const shapes = existsSync(claude) ? chainsByShape(readFileSync(claude, 'utf8')) : [];
|
|
209
|
+
|
|
210
|
+
// `--for`: one shape's chain, and only the stages on it.
|
|
211
|
+
const query = arg('--for');
|
|
212
|
+
let chosen = null;
|
|
213
|
+
if (query !== null) {
|
|
214
|
+
const picked = pickShape(shapes, query);
|
|
215
|
+
if (picked.length !== 1) {
|
|
216
|
+
console.error(picked.length === 0 ? ` no task shape matches "${query}" — by number or by words:` : ` "${query}" matches ${picked.length} task shapes — narrow it, or give the number:`);
|
|
217
|
+
for (const [i, r] of shapes.entries()) if (picked.length === 0 || picked.includes(r)) console.error(` ${String(i + 1).padStart(2)} ${r.shape}`);
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
chosen = picked[0];
|
|
221
|
+
}
|
|
126
222
|
|
|
127
|
-
const
|
|
128
|
-
const name = (s) => (color && !TERMINALS.has(s) ? `\x1b[38;2;${PINK.join(';')}m${s}\x1b[0m` : s);
|
|
129
|
-
const dim = (s) => (color ? `\x1b[2m${s}\x1b[0m` : s);
|
|
223
|
+
const name = (s) => (TERMINALS.has(s) ? s : pink(s));
|
|
130
224
|
const width = Math.max(80, Math.min(process.stdout.columns ?? 110, 140));
|
|
131
225
|
const fit = (s, room) => (s.length > room ? `${s.slice(0, Math.max(room - 1, 1))}…` : s);
|
|
132
226
|
const arrow = ' ─▶ ';
|
|
227
|
+
const many = graph.many ?? new Map();
|
|
228
|
+
// A stage that may run as several agents at once carries ×n wherever the line draws it. Alignment is
|
|
229
|
+
// counted on the plain text, and the names are painted after.
|
|
230
|
+
const plainStage = (s) => `${s}${many.has(s) ? ' ×n' : ''}`;
|
|
231
|
+
const shownStage = (s) => `${name(s)}${many.has(s) ? ' ×n' : ''}`;
|
|
232
|
+
const padName = (s, n) => `${name(s)}${' '.repeat(Math.max(n - s.length, 1))}`;
|
|
133
233
|
|
|
134
234
|
let profile = {};
|
|
135
235
|
try {
|
|
@@ -138,32 +238,54 @@ export async function pipeline(argv) {
|
|
|
138
238
|
// A pipeline composed by hand draws all the same; only the header has less to say.
|
|
139
239
|
}
|
|
140
240
|
const surfaces = (profile.surfaces ?? []).slice().sort();
|
|
141
|
-
console.log(` pipeline · ${basename(dir)}${profile.core ? ` · core ${profile.core}` : ''}${surfaces.length ? ` · surfaces ${surfaces.join(', ')}` : ''}
|
|
241
|
+
console.log(` pipeline · ${basename(dir)}${profile.core ? ` · core ${profile.core}` : ''}${surfaces.length ? ` · surfaces ${surfaces.join(', ')}` : ''}`);
|
|
142
242
|
|
|
143
|
-
//
|
|
144
|
-
const
|
|
243
|
+
// The stages this drawing covers: all of them, or those the chosen shape's chain names.
|
|
244
|
+
const onChain = chosen ? (chosen.chain.match(/[a-z][a-z-]*/g) ?? []).filter((w) => graph.stages.has(w)) : null;
|
|
245
|
+
const covered = new Set(onChain ?? graph.stages);
|
|
145
246
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
247
|
+
if (chosen) {
|
|
248
|
+
console.log(` for: ${chosen.shape}\n`);
|
|
249
|
+
if (onChain.length === 0) console.log(` ${chosen.chain}`);
|
|
250
|
+
else console.log(` ${onChain.map(shownStage).join(arrow)}`);
|
|
251
|
+
const addOns = shapes.filter((r) => r.addOn && r !== chosen);
|
|
252
|
+
if (addOns.length > 0 && onChain.length > 0) {
|
|
253
|
+
console.log(`\n ${dim('and, when the change calls for them')}`);
|
|
254
|
+
const col = Math.min(Math.max(...addOns.map((r) => r.chain.length)), 44) + 4;
|
|
255
|
+
for (const r of addOns) console.log(` ${fit(r.chain, col - 4).padEnd(col)}${dim(fit(r.shape, width - col - 6))}`);
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
console.log('');
|
|
259
|
+
// The line, and where its last stage sends work.
|
|
260
|
+
const plainHead = ` ${shape.line.map(plainStage).join(arrow)}`;
|
|
261
|
+
const head = ` ${shape.line.map(shownStage).join(arrow)}`;
|
|
262
|
+
if (shape.ends.length === 0) console.log(head);
|
|
263
|
+
const lead = ' '.repeat(plainHead.length + 1);
|
|
264
|
+
const joint = (i) => (shape.ends.length === 1 ? '──▶ ' : i === 0 ? '─┬─▶ ' : i === shape.ends.length - 1 ? ' └─▶ ' : ' ├─▶ ');
|
|
265
|
+
const endCol = Math.max(0, ...shape.ends.map((end, i) => `${lead}${joint(i)}${end.path.join(arrow)}`.length)) + 3;
|
|
266
|
+
shape.ends.forEach((end, i) => {
|
|
267
|
+
const plain = `${lead}${joint(i)}${end.path.join(arrow)}`;
|
|
268
|
+
console.log(`${i === 0 ? `${head} ` : lead}${joint(i)}${end.path.map(name).join(arrow)}${' '.repeat(endCol - plain.length)}${dim(fit(end.when, width - endCol))}`);
|
|
269
|
+
});
|
|
157
270
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
271
|
+
if (shape.gates.length > 0) {
|
|
272
|
+
console.log(`\n ${dim('gates on the way, when the diff calls for them')}`);
|
|
273
|
+
const chain = (g) => `${g.from}${arrow}${g.gate}${arrow}${g.to}`;
|
|
274
|
+
const col = Math.max(...shape.gates.map((g) => chain(g).length)) + 4;
|
|
275
|
+
for (const g of shape.gates) {
|
|
276
|
+
console.log(` ${name(g.from)}${arrow}${name(g.gate)}${arrow}${name(g.to)}${' '.repeat(col - chain(g).length)}${dim(fit(g.when, width - col - 6))}`);
|
|
277
|
+
}
|
|
164
278
|
}
|
|
165
279
|
}
|
|
166
280
|
|
|
281
|
+
const drawnMany = [...many].filter(([s]) => covered.has(s));
|
|
282
|
+
if (drawnMany.length > 0) {
|
|
283
|
+
console.log(`\n ${dim('×n runs as several agents at once, each on its own share of the work')}`);
|
|
284
|
+
const col = Math.max(...drawnMany.map(([s]) => s.length)) + 3;
|
|
285
|
+
for (const [s, { when }] of drawnMany) console.log(` ${padName(s, col)}${dim(fit(when, width - col - 8))}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const back = shape.back.filter((e) => covered.has(e.from) && (covered.has(e.to) || TERMINALS.has(e.to)));
|
|
167
289
|
if (back.length > 0) {
|
|
168
290
|
console.log(`\n ${dim('sent back: to whom, on which verdict, and how many rounds per issue')}`);
|
|
169
291
|
const fromCol = Math.max(...back.map((e) => e.from.length)) + 2;
|
|
@@ -180,20 +302,30 @@ export async function pipeline(argv) {
|
|
|
180
302
|
}
|
|
181
303
|
}
|
|
182
304
|
|
|
183
|
-
// Each stage: what it does, on which model and effort, with which skills.
|
|
305
|
+
// Each stage: what it does, on which model and effort, with which skills, and what it did lately.
|
|
184
306
|
const agentsDir = join(dir, '.claude', 'agents');
|
|
185
307
|
const specs = new Map();
|
|
186
308
|
for (const file of existsSync(agentsDir) ? readdirSync(agentsDir).filter((f) => f.endsWith('.md')).sort() : []) {
|
|
187
309
|
specs.set(file.replace(/\.md$/, ''), readFileSync(join(agentsDir, file), 'utf8'));
|
|
188
310
|
}
|
|
189
|
-
const
|
|
311
|
+
const since = arg('--since') || new Date(Date.now() - HISTORY_DAYS * 86_400_000).toISOString().slice(0, 10);
|
|
312
|
+
const runs = storedRuns(dir);
|
|
313
|
+
const history = runs ? historyOf(runs, since) : null;
|
|
314
|
+
const stages = [...graph.stages].filter((s) => covered.has(s));
|
|
190
315
|
const col = Math.max(...stages.map((s) => s.length), 10) + 2;
|
|
191
|
-
console.log(`\n ${dim('stages')}`);
|
|
316
|
+
console.log(`\n ${dim(history ? `stages · what each ran since ${since}, from the measured history` : 'stages · no measured history for this project yet')}`);
|
|
192
317
|
for (const stage of stages) {
|
|
193
318
|
const spec = specs.get(stage) ?? '';
|
|
194
319
|
const front = frontmatter(spec) ?? {};
|
|
195
|
-
const
|
|
196
|
-
console.log(` ${padName(stage, col)}${
|
|
320
|
+
const on = [front.model, front.effort].filter(Boolean).join(' · ') || 'no spec';
|
|
321
|
+
console.log(` ${padName(stage, col)}${on.padEnd(26)}${dim(fit(words.get(stage) ?? '', width - col - 32))}`);
|
|
322
|
+
if (history) {
|
|
323
|
+
const h = history.get(stage);
|
|
324
|
+
const said = !h
|
|
325
|
+
? 'no run'
|
|
326
|
+
: `${h.runs} run(s)${h.read === 0 ? ', no verdict read' : `, sent back ${h.back} of ${h.read} (${Math.round((100 * h.back) / h.read)}%)`}`;
|
|
327
|
+
console.log(` ${' '.repeat(col)}${dim('ran ')}${said}`);
|
|
328
|
+
}
|
|
197
329
|
const skills = [...required(spec).skills].sort();
|
|
198
330
|
if (skills.length > 0) {
|
|
199
331
|
const lines = [];
|
|
@@ -210,22 +342,23 @@ export async function pipeline(argv) {
|
|
|
210
342
|
}
|
|
211
343
|
}
|
|
212
344
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
345
|
+
if (!chosen) {
|
|
346
|
+
// An agent the project wrote for itself is not a stage, and the graph does not route to it.
|
|
347
|
+
const own = [...specs.keys()].filter((role) => !graph.stages.has(role));
|
|
348
|
+
if (own.length > 0) {
|
|
349
|
+
console.log(`\n ${dim("the project's own agents, outside the pipeline")}`);
|
|
350
|
+
for (const role of own) {
|
|
351
|
+
const front = frontmatter(specs.get(role)) ?? {};
|
|
352
|
+
console.log(` ${role}${' '.repeat(Math.max(col - role.length, 2))}${dim(fit(front.description ?? '', width - col - 6))}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (shapes.length > 0) {
|
|
356
|
+
console.log(`\n ${dim('which chain a task takes, by its shape (CLAUDE.md) — `nina pipeline --for <number or words>` draws one')}`);
|
|
357
|
+
const shapeCol = Math.min(Math.max(...shapes.map((r) => r.shape.length)), 56) + 2;
|
|
358
|
+
for (const [i, r] of shapes.entries()) {
|
|
359
|
+
console.log(` ${String(i + 1).padStart(2)} ${fit(r.shape, shapeCol - 2).padEnd(shapeCol)}${fit(r.chain, width - shapeCol - 10)}`);
|
|
360
|
+
}
|
|
220
361
|
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const claude = join(dir, 'CLAUDE.md');
|
|
224
|
-
const shapes = existsSync(claude) ? chainsByShape(readFileSync(claude, 'utf8')) : [];
|
|
225
|
-
if (shapes.length > 0) {
|
|
226
|
-
console.log(`\n ${dim('which chain a task takes, by its shape (CLAUDE.md)')}`);
|
|
227
|
-
const shapeCol = Math.min(Math.max(...shapes.map((r) => r.shape.length)), 58) + 2;
|
|
228
|
-
for (const r of shapes) console.log(` ${fit(r.shape, shapeCol - 2).padEnd(shapeCol)}${fit(r.chain, width - shapeCol - 6)}`);
|
|
229
362
|
}
|
|
230
363
|
console.log('');
|
|
231
364
|
return 0;
|
package/src/commands/stats.mjs
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* loop-backs it was supposed to harvest were being counted right here.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { amber, bar, bold, dim, heading, note, pink, stacked } from '../look.mjs';
|
|
14
15
|
import { readFile, readdir } from 'node:fs/promises';
|
|
15
16
|
import { spawnSync } from 'node:child_process';
|
|
16
17
|
import { createReadStream, existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
@@ -183,18 +184,21 @@ function costReport(records) {
|
|
|
183
184
|
// The same convention as the duration column beside it: the upper middle of an even count.
|
|
184
185
|
const median = (xs) => [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)];
|
|
185
186
|
console.log(
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
heading(
|
|
188
|
+
'cost',
|
|
189
|
+
`at API list prices of ${PRICES_AS_OF}; ${measured.length} of ${records.length} runs have a token record` +
|
|
190
|
+
(unpriced > 0 ? `, ${unpriced} on a model the price table does not know` : ''),
|
|
191
|
+
),
|
|
188
192
|
);
|
|
189
|
-
console.log(` ${'stage'.padEnd(20)}${'runs'.padStart(6)}${'median'.padStart(10)}${'total'.padStart(10)}${'share'.padStart(8)}`);
|
|
193
|
+
console.log(dim(` ${'stage'.padEnd(20)}${'runs'.padStart(6)}${'median'.padStart(10)}${'total'.padStart(10)}${'share'.padStart(8)}`));
|
|
190
194
|
for (const [role, costs] of [...byRole].sort((a, b) => b[1].reduce((x, y) => x + y, 0) - a[1].reduce((x, y) => x + y, 0))) {
|
|
191
195
|
const sum = costs.reduce((a, b) => a + b, 0);
|
|
192
196
|
console.log(
|
|
193
|
-
` ${role.
|
|
194
|
-
`${money(sum).padStart(10)}${pct(sum, total).padStart(8)}`,
|
|
197
|
+
` ${pink(role)}${' '.repeat(Math.max(20 - role.length, 1))}${String(costs.length).padStart(6)}${money(median(costs)).padStart(10)}` +
|
|
198
|
+
`${bold(money(sum).padStart(10))}${pct(sum, total).padStart(8)} ${pink(bar(sum / total, 20))}`,
|
|
195
199
|
);
|
|
196
200
|
}
|
|
197
|
-
console.log(` ${'all stages'.padEnd(20)}${String([...byRole.values()].flat().length).padStart(6)}${''.padStart(10)}${money(total).padStart(10)}`);
|
|
201
|
+
console.log(bold(` ${'all stages'.padEnd(20)}${String([...byRole.values()].flat().length).padStart(6)}${''.padStart(10)}${money(total).padStart(10)}`));
|
|
198
202
|
}
|
|
199
203
|
|
|
200
204
|
/**
|
|
@@ -271,7 +275,7 @@ function modelReport(records) {
|
|
|
271
275
|
const costs = runs.map((r) => (r.tokens ? costOf(r.tokens, r.usage_model) : null)).filter((c) => c !== null);
|
|
272
276
|
const dates = runs.map((r) => String(r.ts).slice(0, 10)).sort();
|
|
273
277
|
lines.push(
|
|
274
|
-
` ${
|
|
278
|
+
` ${i === 0 ? `${pink(role)}${' '.repeat(Math.max(20 - role.length, 1))}` : ''.padEnd(20)}${model.padEnd(26)}${String(runs.length).padStart(5)} run(s) ${dim(`${dates[0]} → ${dates.at(-1)}`)}` +
|
|
275
279
|
(clear.length ? ` loop-back ${pct(loops, clear.length)} of ${clear.length}` : ' no readable verdict') +
|
|
276
280
|
(costs.length ? ` median ${money(median(costs))}` : ''),
|
|
277
281
|
);
|
|
@@ -303,9 +307,9 @@ function modelReport(records) {
|
|
|
303
307
|
}
|
|
304
308
|
|
|
305
309
|
if (lines.length === 0 && drift.length === 0) return;
|
|
306
|
-
console.log('
|
|
310
|
+
console.log(heading('models', 'a stage that ran on more than one model or effort level, one line each; different weeks, not an experiment'));
|
|
307
311
|
for (const line of lines) console.log(line);
|
|
308
|
-
for (const line of drift) console.log(line);
|
|
312
|
+
for (const line of drift) console.log(amber(line));
|
|
309
313
|
}
|
|
310
314
|
|
|
311
315
|
/** The verdicts that close a pipeline cycle: the change passed its tests, reached preview, or cleared the audit. */
|
|
@@ -390,15 +394,19 @@ function proportionReport(records, stepLimit = () => null) {
|
|
|
390
394
|
}
|
|
391
395
|
if (built === 0) return;
|
|
392
396
|
console.log(
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
(
|
|
397
|
+
heading(
|
|
398
|
+
'proportion',
|
|
399
|
+
`${built} cycle(s) that wrote code; a cycle closes at qa PASS, devops DEPLOYED or secops SECURE, or at the next design after code` +
|
|
400
|
+
(unreadable > 0 ? `; ${unreadable} closed by a run whose verdict could not be read` : '') +
|
|
401
|
+
(open > 0 ? `; ${open} still open when their session's record ends` : ''),
|
|
402
|
+
),
|
|
396
403
|
);
|
|
397
|
-
console.log(` ${'largest write'.padEnd(16)}${'cycles'.padStart(8)}${'with a planner or architect'.padStart(30)}`);
|
|
404
|
+
console.log(dim(` ${'largest write'.padEnd(16)}${'cycles'.padStart(8)}${'with a planner or architect'.padStart(30)}`));
|
|
398
405
|
for (const row of rows) {
|
|
399
|
-
|
|
406
|
+
const designed = `${row.designed} (${pct(row.designed, row.cycles)})`.padStart(30);
|
|
407
|
+
console.log(` ${row.label.padEnd(16)}${String(row.cycles).padStart(8)}${row.cycles ? designed : dim(designed)} ${pink(bar(row.cycles ? row.designed / row.cycles : 0, 10))}`);
|
|
400
408
|
}
|
|
401
|
-
console.log(' A shape, not a verdict: a critical path is gated in full at any size, and one spec may cover sibling steps.');
|
|
409
|
+
console.log(dim(' A shape, not a verdict: a critical path is gated in full at any size, and one spec may cover sibling steps.'));
|
|
402
410
|
|
|
403
411
|
// The one size the harness does limit: what one implementer run writes. A run re-reads the context it
|
|
404
412
|
// has built on every turn, so its cost grows faster than its size — measured over two projects, the
|
|
@@ -413,7 +421,10 @@ function proportionReport(records, stepLimit = () => null) {
|
|
|
413
421
|
const largest = over.reduce((a, b) => (b.files_touched > a.files_touched ? b : a));
|
|
414
422
|
const read = typeof largest.tokens?.read === 'number' ? `, ${Math.round(largest.tokens.read / 1e6)}M tokens read from cache` : '';
|
|
415
423
|
console.log(
|
|
416
|
-
|
|
424
|
+
note(
|
|
425
|
+
`${over.length} implementer run(s) wrote more files than one step may (${stepLimit(largest.project)}); the largest wrote ${largest.files_touched}${read}. The architect splits such a spec into steps.`,
|
|
426
|
+
'warn',
|
|
427
|
+
),
|
|
417
428
|
);
|
|
418
429
|
}
|
|
419
430
|
}
|
|
@@ -551,34 +562,53 @@ export async function stats(argv, ctx) {
|
|
|
551
562
|
const span = [records[0]?.ts, records.at(-1)?.ts].map((t) => String(t).slice(0, 10));
|
|
552
563
|
const projectCount = new Set(records.map((r) => r.project)).size;
|
|
553
564
|
console.log(
|
|
554
|
-
` ${records.length} dispatches · ${projectCount} project${projectCount === 1 ? '' : 's'} · ` +
|
|
565
|
+
` ${projectCount === 1 ? `${bold(pink(projectName(records[0].project)))} · ` : ''}${bold(records.length)} dispatches · ${projectCount} project${projectCount === 1 ? '' : 's'} · ` +
|
|
555
566
|
`${span[0]} → ${span[1]}` +
|
|
556
|
-
(here ? ' (this project; --all for every project)' : '') +
|
|
557
|
-
(skipped > 0 ? ` (${skipped} non-harness project${skipped === 1 ? '' : 's'} hidden, --all to include)` : '') +
|
|
558
|
-
(held > 0 ? ` · ${held} dispatch(es) denied by a hook, not counted as runs` : '')
|
|
559
|
-
'\n',
|
|
567
|
+
(here ? dim(' (this project; --all for every project)') : '') +
|
|
568
|
+
(skipped > 0 ? dim(` (${skipped} non-harness project${skipped === 1 ? '' : 's'} hidden, --all to include)`) : '') +
|
|
569
|
+
(held > 0 ? ` · ${held} dispatch(es) denied by a hook, not counted as runs` : ''),
|
|
560
570
|
);
|
|
571
|
+
console.log(heading('stages', 'what each ran, and what came of it'));
|
|
561
572
|
console.log(
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
573
|
+
dim(
|
|
574
|
+
` ${'stage'.padEnd(20)}${'runs'.padStart(6)}${'verdict'.padStart(9)}${'loop-back'.padStart(11)}` +
|
|
575
|
+
`${'rate'.padStart(7)}${'declared'.padStart(10)}${'unreadable'.padStart(12)}` +
|
|
576
|
+
`${'median'.padStart(9)}`,
|
|
577
|
+
),
|
|
565
578
|
);
|
|
566
579
|
|
|
567
580
|
const rows = [...byRole.entries()].sort((a, b) => b[1].n - a[1].n);
|
|
581
|
+
// Each stage's runs as one bar, as long as its share of the busiest stage's: what went forward, what
|
|
582
|
+
// was sent back, and what said nothing readable (or is still running).
|
|
583
|
+
const busiest = Math.max(...rows.map(([, s]) => s.n), 1);
|
|
584
|
+
const zero = (text, n) => (n === 0 ? dim(text) : text);
|
|
568
585
|
for (const [role, s] of rows) {
|
|
586
|
+
const runsBar = stacked(
|
|
587
|
+
[
|
|
588
|
+
{ n: s.clear - s.loop, char: '█', paint: pink },
|
|
589
|
+
{ n: s.loop, char: '▓', paint: amber },
|
|
590
|
+
{ n: s.n - s.clear, char: '░', paint: dim },
|
|
591
|
+
],
|
|
592
|
+
Math.max(1, Math.round((16 * s.n) / busiest)),
|
|
593
|
+
);
|
|
594
|
+
const declared = pct(s.declared, s.done).padStart(10);
|
|
595
|
+
const unread = pct(s.unclear, s.done).padStart(12);
|
|
569
596
|
console.log(
|
|
570
|
-
`
|
|
571
|
-
`${String(s.loop).padStart(11)}${pct(s.loop, s.clear).padStart(7)}` +
|
|
572
|
-
`${
|
|
573
|
-
`${medianMin(s.durations).padStart(9)}`,
|
|
597
|
+
` ${pink(role)}${' '.repeat(Math.max(20 - role.length, 1))}${String(s.n).padStart(6)}${String(s.clear).padStart(9)}` +
|
|
598
|
+
`${zero(String(s.loop).padStart(11), s.loop)}${zero(pct(s.loop, s.clear).padStart(7), s.loop)}` +
|
|
599
|
+
`${s.declared < s.done ? amber(declared) : dim(declared)}${s.unclear > 0 ? amber(unread) : dim(unread)}` +
|
|
600
|
+
`${medianMin(s.durations).padStart(9)} ${runsBar}`,
|
|
574
601
|
);
|
|
575
602
|
}
|
|
603
|
+
console.log(dim(` ${' '.repeat(86)}█ forward ▓ sent back ░ no verdict read`));
|
|
576
604
|
|
|
577
605
|
const unreadable = rows.reduce((a, [, s]) => a + s.unclear, 0);
|
|
578
606
|
const done = rows.reduce((a, [, s]) => a + s.done, 0);
|
|
607
|
+
console.log('');
|
|
579
608
|
console.log(
|
|
580
|
-
|
|
581
|
-
|
|
609
|
+
unreadable === 0
|
|
610
|
+
? note("every finished run's verdict could be read.", 'ok')
|
|
611
|
+
: note(`${pct(unreadable, done)} of finished runs report no machine-readable verdict. Those stages need a verdict token on the report's first line.`, 'warn'),
|
|
582
612
|
);
|
|
583
613
|
|
|
584
614
|
// Whether the stages that send work back say what they send back. Counted only over reports read
|
|
@@ -587,8 +617,7 @@ export async function stats(argv, ctx) {
|
|
|
587
617
|
if (named.length > 0) {
|
|
588
618
|
const withIds = named.filter((r) => r.issues > 0).length;
|
|
589
619
|
console.log(
|
|
590
|
-
|
|
591
|
-
' on the `ISSUES` line under the verdict.',
|
|
620
|
+
note(`${withIds} of ${named.length} declared loop-back(s) (${pct(withIds, named.length)}) named their issues on the \`ISSUES\` line under the verdict.`, withIds < named.length ? 'warn' : 'ok'),
|
|
592
621
|
);
|
|
593
622
|
}
|
|
594
623
|
|
|
@@ -600,9 +629,11 @@ export async function stats(argv, ctx) {
|
|
|
600
629
|
for (const r of withSkills) for (const k of r.skills) tally.set(k, (tally.get(k) ?? 0) + 1);
|
|
601
630
|
const list = [...tally].sort((a, b) => b[1] - a[1]).map(([k, n]) => `${k} ×${n}`);
|
|
602
631
|
console.log(
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
632
|
+
note(
|
|
633
|
+
`${withSkills.length} of ${observable} runs with a readable transcript (${pct(withSkills.length, observable)}) invoked a Skill` +
|
|
634
|
+
(list.length > 0 ? ` — ${list.join(', ')}` : ' — none at all'),
|
|
635
|
+
withSkills.length === 0 ? 'warn' : 'info',
|
|
636
|
+
),
|
|
606
637
|
);
|
|
607
638
|
}
|
|
608
639
|
|
|
@@ -617,7 +648,8 @@ export async function stats(argv, ctx) {
|
|
|
617
648
|
others.reduce((a, [, o]) => a + o.clear, 0),
|
|
618
649
|
);
|
|
619
650
|
console.log(
|
|
620
|
-
|
|
651
|
+
note(
|
|
652
|
+
`${role}: ${s.loop} loop-back(s) in ${s.clear} readable verdict(s) (${pct(s.loop, s.clear)}),` +
|
|
621
653
|
` against ${elsewhere} across the other gates — check whether it still gates anything.` +
|
|
622
654
|
(s.n - s.clear > 0
|
|
623
655
|
? ` ${s.n - s.clear} further run(s) produced no readable verdict at all, so the rate may understate it.`
|
|
@@ -627,6 +659,8 @@ export async function stats(argv, ctx) {
|
|
|
627
659
|
(s.declared < s.clear
|
|
628
660
|
? ` Only ${s.declared} of the ${s.clear} were declared by the stage; the rest were inferred from its report.`
|
|
629
661
|
: ''),
|
|
662
|
+
'warn',
|
|
663
|
+
),
|
|
630
664
|
);
|
|
631
665
|
}
|
|
632
666
|
|
|
@@ -642,30 +676,31 @@ export async function stats(argv, ctx) {
|
|
|
642
676
|
const newest = pills.dates.at(-1);
|
|
643
677
|
const inWindow = pills.dates.filter((d) => d >= span[0] && d <= span[1]).length;
|
|
644
678
|
|
|
645
|
-
console.log('
|
|
679
|
+
console.log(heading('learning', 'what the loop-backs taught, and what became a rule'));
|
|
646
680
|
if (pills.total === 0) {
|
|
647
|
-
console.log(
|
|
648
|
-
` ${loopBacks} loop-back(s) and not one pill — either nothing was learned, or nothing was written down.`,
|
|
649
|
-
);
|
|
681
|
+
console.log(note(`${loopBacks} loop-back(s) and not one pill — either nothing was learned, or nothing was written down.`, loopBacks > 0 ? 'warn' : 'info'));
|
|
650
682
|
} else {
|
|
651
683
|
console.log(
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
684
|
+
note(
|
|
685
|
+
// A share over 100% says nothing: the pills were written for something other than these loop-backs.
|
|
686
|
+
`${loopBacks} loop-back(s) in the window → ${inWindow} pill(s) written${inWindow <= loopBacks ? ` (${pct(inWindow, loopBacks)})` : ''}` +
|
|
687
|
+
' — every loop-back is looked at for a lesson; `nina learn` shows which roles are owed one.' +
|
|
688
|
+
(pills.undated > 0 ? ` ${pills.undated} pill(s) carry no date and cannot be placed.` : ''),
|
|
689
|
+
),
|
|
656
690
|
);
|
|
657
691
|
console.log(
|
|
658
692
|
pills.retired === 0
|
|
659
|
-
? `
|
|
660
|
-
:
|
|
693
|
+
? note(`0 of ${pills.total} pill(s) retired — no correction has ever graduated into a rule.`, 'warn')
|
|
694
|
+
: note(`${pills.retired} of ${pills.total} pill(s) retired into a rule.`, 'ok'),
|
|
661
695
|
);
|
|
662
696
|
if (newest) {
|
|
663
697
|
const since = records.filter((r) => String(r.ts).slice(0, 10) > newest).length;
|
|
664
|
-
if (since > 0) console.log(
|
|
698
|
+
if (since > 0) console.log(note(`${since} dispatch(es) since the newest pill (${newest}).`));
|
|
665
699
|
}
|
|
666
700
|
}
|
|
667
701
|
for (const name of pills.unresolved) {
|
|
668
|
-
console.log(
|
|
702
|
+
console.log(note(`${name} could not be located on disk, so its pills are not counted.`, 'warn'));
|
|
669
703
|
}
|
|
704
|
+
console.log('');
|
|
670
705
|
return 0;
|
|
671
706
|
}
|