neurastream 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +21 -21
- package/src/adapters/omp/install.ts +5 -1
- package/src/core/agent-score.ts +78 -4
- package/templates/help/agent-score.txt +2 -0
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ neurastream config --json
|
|
|
23
23
|
|
|
24
24
|
Compatibility bins keep today's names (`agent-score`, `agent-ledger`, `brief-lint`, `oc-run`, `wip-check`, `wip-backup`, `wip-snapshot`, `pm-fail-facts`, `main-health`, `queue-check`, `queue-close`, `queue-cancel`, `main-start`, `machine-guard`, `glm-reset`, `priority-watch`, `resource-alerts`, `tmp-snapshot`, `heavy-run`). They are the same program, selected by the bin name.
|
|
25
25
|
|
|
26
|
+
Scoring: `agent-score accept` records accepted work by commit; `agent-score validate <Agent> --plan <plan.md> --phase <P> --evidence <report.md> --section "<heading prefix>"` records a validation phase (checks/tests ran, report section written, no commits) with proof Main cannot fake by typing: the plan line must be ticked `- [x]`, the cited section body must name a heavy-run label run by that agent (from `paths.heavy_log`), and the phase must not be scored already. The ranking board counts it in `Accepted (diag/val)` as `N (d/v)`.
|
|
27
|
+
|
|
26
28
|
Green is `bun run typecheck` and `bun test`, both exit 0.
|
|
27
29
|
|
|
28
30
|
## Changing a script
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neurastream",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Harness-agnostic agent orchestration: queue, guards, blockers, scoring, ledger.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,26 +20,26 @@
|
|
|
20
20
|
"LICENSE"
|
|
21
21
|
],
|
|
22
22
|
"bin": {
|
|
23
|
-
"neurastream": "
|
|
24
|
-
"agent-score": "
|
|
25
|
-
"agent-ledger": "
|
|
26
|
-
"brief-lint": "
|
|
27
|
-
"oc-run": "
|
|
28
|
-
"wip-check": "
|
|
29
|
-
"wip-backup": "
|
|
30
|
-
"wip-snapshot": "
|
|
31
|
-
"pm-fail-facts": "
|
|
32
|
-
"main-health": "
|
|
33
|
-
"queue-check": "
|
|
34
|
-
"queue-close": "
|
|
35
|
-
"queue-cancel": "
|
|
36
|
-
"main-start": "
|
|
37
|
-
"machine-guard": "
|
|
38
|
-
"glm-reset": "
|
|
39
|
-
"priority-watch": "
|
|
40
|
-
"resource-alerts": "
|
|
41
|
-
"tmp-snapshot": "
|
|
42
|
-
"heavy-run": "
|
|
23
|
+
"neurastream": "src/cli/main.ts",
|
|
24
|
+
"agent-score": "bin/agent-score",
|
|
25
|
+
"agent-ledger": "bin/agent-ledger",
|
|
26
|
+
"brief-lint": "bin/brief-lint",
|
|
27
|
+
"oc-run": "bin/oc-run",
|
|
28
|
+
"wip-check": "bin/wip-check",
|
|
29
|
+
"wip-backup": "bin/wip-backup",
|
|
30
|
+
"wip-snapshot": "bin/wip-snapshot",
|
|
31
|
+
"pm-fail-facts": "bin/pm-fail-facts",
|
|
32
|
+
"main-health": "bin/main-health",
|
|
33
|
+
"queue-check": "bin/queue-check",
|
|
34
|
+
"queue-close": "bin/queue-close",
|
|
35
|
+
"queue-cancel": "bin/queue-cancel",
|
|
36
|
+
"main-start": "bin/main-start",
|
|
37
|
+
"machine-guard": "bin/machine-guard",
|
|
38
|
+
"glm-reset": "bin/glm-reset",
|
|
39
|
+
"priority-watch": "bin/priority-watch",
|
|
40
|
+
"resource-alerts": "bin/resource-alerts",
|
|
41
|
+
"tmp-snapshot": "bin/tmp-snapshot",
|
|
42
|
+
"heavy-run": "bin/heavy-run"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"typecheck": "tsc --noEmit",
|
|
@@ -99,8 +99,12 @@ function linkPlugin(home: string): string {
|
|
|
99
99
|
env: { ...process.env, HOME: home },
|
|
100
100
|
encoding: "utf8",
|
|
101
101
|
});
|
|
102
|
+
if (r.error) {
|
|
103
|
+
throw new Error("omp not found on PATH: install oh-my-pi (npm i -g @oh-my-pi/pi-coding-agent) and retry", { cause: r.error });
|
|
104
|
+
}
|
|
102
105
|
if (r.status !== 0) {
|
|
103
|
-
|
|
106
|
+
const out = [r.stderr, r.stdout].find(s => typeof s === "string" && s.length > 0) ?? "";
|
|
107
|
+
throw new Error(`omp plugin link failed (exit ${r.status ?? "signal"}): ${out.trim()}`);
|
|
104
108
|
}
|
|
105
109
|
return join(home, ".omp/plugins/node_modules/neurastream");
|
|
106
110
|
}
|
package/src/core/agent-score.ts
CHANGED
|
@@ -88,6 +88,55 @@ function phaseDifficulty(plan: string, phase: string): number | null {
|
|
|
88
88
|
throw new Error("stop");
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/** True when the plan's phase line exists and is ticked `- [x]`/`- [X]` (ticking = reviewed by Main). */
|
|
92
|
+
function phaseTicked(plan: string, phase: string): boolean {
|
|
93
|
+
if (!existsSync(plan)) {
|
|
94
|
+
exitText(`plan not found: ${plan}`);
|
|
95
|
+
throw new Error("stop");
|
|
96
|
+
}
|
|
97
|
+
const re = new RegExp(`^- \\[[ xX]\\] ${phase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
|
|
98
|
+
for (const line of readFileSync(plan, "utf8").split("\n")) {
|
|
99
|
+
if (re.test(line)) return /^- \[[xX]\]/.test(line);
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Body of the first heading whose text starts with `section` and ends at end-of-line, space or `(`,
|
|
106
|
+
* so `## CP` matches `## CP (P5, ...)` but not `## CPX`. The body runs to the next heading of the
|
|
107
|
+
* same or higher level. Null when no heading matches.
|
|
108
|
+
*/
|
|
109
|
+
function sectionBody(text: string, section: string): string | null {
|
|
110
|
+
const lines = text.split("\n");
|
|
111
|
+
for (let i = 0; i < lines.length; i++) {
|
|
112
|
+
const line = lines[i]!;
|
|
113
|
+
if (!/^#{1,6} /.test(line)) continue;
|
|
114
|
+
if (!(line === section || line.startsWith(`${section} `) || line.startsWith(`${section}(`))) continue;
|
|
115
|
+
const level = (line.match(/^#+/) ?? ["#"])[0]!.length;
|
|
116
|
+
const body: string[] = [];
|
|
117
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
118
|
+
const next = lines[j]!;
|
|
119
|
+
const m = next.match(/^#+(?= )/);
|
|
120
|
+
if (m && m[0].length <= level) break;
|
|
121
|
+
body.push(next);
|
|
122
|
+
}
|
|
123
|
+
return body.join("\n");
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Labels from the heavy-run log's `END <agent> | <label> | exit <n>` lines (START lines never match). */
|
|
129
|
+
function heavyEndLabels(log: string, agent: string): string[] {
|
|
130
|
+
if (!existsSync(log)) return [];
|
|
131
|
+
const re = new RegExp(`END\\s+${agent.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} \\| (.*) \\| exit \\d+$`);
|
|
132
|
+
const out: string[] = [];
|
|
133
|
+
for (const line of readFileSync(log, "utf8").split("\n")) {
|
|
134
|
+
const m = line.match(re);
|
|
135
|
+
if (m) out.push(m[1]!);
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
|
|
91
140
|
function weightFor(args: string[], plan: string, phase: string, cfg: Config, fallback: number | null): { w: number; src: string } {
|
|
92
141
|
if (args.includes("--weight")) return { w: Number(needOpt(args, "--weight")), src: "flag" };
|
|
93
142
|
const d = phaseDifficulty(plan, phase);
|
|
@@ -110,6 +159,7 @@ interface Row {
|
|
|
110
159
|
first: number;
|
|
111
160
|
pen: number;
|
|
112
161
|
diag: number;
|
|
162
|
+
val: number;
|
|
113
163
|
net: number;
|
|
114
164
|
total: number;
|
|
115
165
|
ppd: number | null;
|
|
@@ -183,7 +233,7 @@ function table(days: number, cfg: Config, source: RunSource): { rows: Row[]; byA
|
|
|
183
233
|
for (const [n, r] of byAgent) agentModel.set(n, r.model);
|
|
184
234
|
const rows = new Map<string, Row>();
|
|
185
235
|
const blank = (model: string): Row => ({
|
|
186
|
-
model, runs: 0, done: 0, running: 0, cost: 0, steer: 0, hours: 0, pts: 0, acc: 0, first: 0, pen: 0, diag: 0,
|
|
236
|
+
model, runs: 0, done: 0, running: 0, cost: 0, steer: 0, hours: 0, pts: 0, acc: 0, first: 0, pen: 0, diag: 0, val: 0,
|
|
187
237
|
net: 0, total: 0, ppd: null, pph: null, surv: null, fp: null,
|
|
188
238
|
});
|
|
189
239
|
for (const r of byAgent.values()) {
|
|
@@ -203,11 +253,12 @@ function table(days: number, cfg: Config, source: RunSource): { rows: Row[]; byA
|
|
|
203
253
|
const agent = String(e.agent ?? "");
|
|
204
254
|
const model = (typeof e.model === "string" && e.model) || agentModel.get(agent) || "?";
|
|
205
255
|
const m = rows.get(model) ?? blank(model);
|
|
206
|
-
if (e.type === "accept" || e.type === "diagnose") {
|
|
256
|
+
if (e.type === "accept" || e.type === "diagnose" || e.type === "validate") {
|
|
207
257
|
m.pts += Number(e.weight);
|
|
208
258
|
m.acc += 1;
|
|
209
259
|
m.first += e.rework ? 0 : 1;
|
|
210
260
|
m.diag += e.type === "diagnose" ? 1 : 0;
|
|
261
|
+
m.val += e.type === "validate" ? 1 : 0;
|
|
211
262
|
} else if (e.type === "penalty") {
|
|
212
263
|
m.pen += cfg.score.penalties[String(e.kind)] ?? 0;
|
|
213
264
|
}
|
|
@@ -247,12 +298,12 @@ function board(days: number, cfg: Config, source: RunSource): void {
|
|
|
247
298
|
+ "(false-claim 5, rule-break 2, escaped-defect 8). Survival = finished runs that ended with a final result. Steering is approximate "
|
|
248
299
|
+ "(Main turns naming the agent).",
|
|
249
300
|
"",
|
|
250
|
-
"| # | Worker model | Runs (live) | Survival | Accepted (diag) | First-pass | Net pts | $ worker | $ steering | pts/$ | pts/h |",
|
|
301
|
+
"| # | Worker model | Runs (live) | Survival | Accepted (diag/val) | First-pass | Net pts | $ worker | $ steering | pts/$ | pts/h |",
|
|
251
302
|
"|---|---|---|---|---|---|---|---|---|---|---|",
|
|
252
303
|
];
|
|
253
304
|
rows.forEach((r, i) => {
|
|
254
305
|
lines.push(
|
|
255
|
-
`| ${i + 1} | ${r.model} | ${r.runs} (${r.running}) | ${fmt(r.surv, "pct")} | ${r.acc} (${r.diag}) | ${fmt(r.fp, "pct")} | ${r.net} | ${pyFixed(r.cost, 2)} | ${pyFixed(r.steer, 2)} | ${fmt(r.ppd, "1f")} | ${fmt(r.pph, "1f")} |`,
|
|
306
|
+
`| ${i + 1} | ${r.model} | ${r.runs} (${r.running}) | ${fmt(r.surv, "pct")} | ${r.acc} (${r.diag}/${r.val}) | ${fmt(r.fp, "pct")} | ${r.net} | ${pyFixed(r.cost, 2)} | ${pyFixed(r.steer, 2)} | ${fmt(r.ppd, "1f")} | ${fmt(r.pph, "1f")} |`,
|
|
256
307
|
);
|
|
257
308
|
});
|
|
258
309
|
const text = `${lines.join("\n")}\n`;
|
|
@@ -301,6 +352,29 @@ export function agentScore(args: string[], cfg: Config, source: RunSource): numb
|
|
|
301
352
|
}, cfg);
|
|
302
353
|
return 0;
|
|
303
354
|
}
|
|
355
|
+
if (cmd === "validate") {
|
|
356
|
+
const agent = rest[0];
|
|
357
|
+
const plan = opt(rest, "--plan");
|
|
358
|
+
const phase = opt(rest, "--phase");
|
|
359
|
+
const ev = opt(rest, "--evidence");
|
|
360
|
+
const section = opt(rest, "--section");
|
|
361
|
+
if (!agent || !plan || !phase || !ev || !section) return exitText("validate needs --plan, --phase, --evidence and --section");
|
|
362
|
+
if (!phaseTicked(plan, phase)) return exitText(`refused: ${phase} not ticked in ${plan}`);
|
|
363
|
+
const { w, src } = weightFor(rest, plan, phase, cfg, null);
|
|
364
|
+
if (!existsSync(ev)) return exitText(`refused: evidence file not found: ${ev}`);
|
|
365
|
+
const body = sectionBody(readFileSync(ev, "utf8"), section);
|
|
366
|
+
if (body === null) return exitText(`refused: no heading starting with ${section} in ${ev}`);
|
|
367
|
+
if (!body.split("\n").some(l => l.trim())) return exitText(`refused: section ${section} has no body in ${ev}`);
|
|
368
|
+
const labels = heavyEndLabels(cfg.paths.heavy_log, agent).filter(l => body.includes(l));
|
|
369
|
+
if (labels.length === 0) return exitText(`refused: section cites no heavy-run label run by ${agent}`);
|
|
370
|
+
const scored = readEvents(cfg).some(e => (e.type === "accept" || e.type === "validate") && e.agent === agent && e.plan === plan && e.phase === phase);
|
|
371
|
+
if (scored) return exitText("refused: already scored");
|
|
372
|
+
record({
|
|
373
|
+
type: "validate", agent, plan, phase, weight: w, weight_src: src, evidence: ev, section,
|
|
374
|
+
labels, rework: rest.includes("--rework"), note: opt(rest, "--note") ?? "",
|
|
375
|
+
}, cfg);
|
|
376
|
+
return 0;
|
|
377
|
+
}
|
|
304
378
|
if (cmd === "penalty") {
|
|
305
379
|
const kind = opt(rest, "--kind");
|
|
306
380
|
const note = opt(rest, "--note");
|
|
@@ -7,6 +7,8 @@ Cost = the worker's own list cost + Main's steering cost (Main tokens spent on t
|
|
|
7
7
|
commits must be ancestors of --branch (default pm/pr01-counts in ~/Work/pm-counts); --rework = needed fixes by Main/another agent
|
|
8
8
|
agent-score diagnose <Agent> --plan <plan.md> --phase P --evidence <file> [--weight N]
|
|
9
9
|
a real product bug found and recorded (no commit needed); default weight 4
|
|
10
|
+
agent-score validate <Agent> --plan <plan.md> --phase P --evidence <report.md> --section "## Heading" [--weight N] [--rework] [--note T]
|
|
11
|
+
validation phase with proof, no commits: phase ticked in the plan, the section body cites a heavy-run label run by <Agent>
|
|
10
12
|
agent-score penalty <Agent> --kind false-claim|rule-break|escaped-defect --note T [--plan <plan.md>]
|
|
11
13
|
agent-score exempt <sha,...> --note T [--repo R] commits that are Main's own (merges, rebuilds), not a worker's
|
|
12
14
|
agent-score check [--repo R] [--branch B] exit 1 = commits on the branch since the base with no accept/exempt record
|