neurastream 0.1.1 → 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 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.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",
@@ -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