fix(yardstick): use counts an order only when a Voyage record names its id (green)
The slug join (voyageSlugs: every slug in every stats file against the order's `slug:`, which 0 of 690 orders carried) is replaced by an order-id join: orderRuns() indexes trekbrief-stats.jsonl's order_id <-> slug, readOrders() keeps each order's file stem and its own-line arbeidsform marker. Per new-work order: arbeidsform: ordre = declared NO; one record under one slug that names no other order = YES; no record or disputed = not measured, never a no (PM 04.10, point 1). value = YES, denominator = YES + declared NO. USE_COUNTING_SINCE = null (operator 04.10: the tool now, the counting after the work is declared done): until it is set the row is NOT COUNTABLE and every order not measured; once set, the window starts at the latest of cutoff, MARKER_SINCE and that date. facts.ordersNamedByARecord is the known-positive diagnostic. The header names the measured-share bias and the false-positive class (Goal iii) next to the number. The CLI guard compares real paths. Real queue 2026-10-04: use NOT COUNTABLE, 695 of 695 orders not measured, 0 named by a record. yardstick.test.mjs 43/43. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ee4d748639
commit
5dac5b59be
1 changed files with 109 additions and 42 deletions
|
|
@ -2,13 +2,18 @@
|
|||
// scripts/yardstick.mjs
|
||||
// The yardstick - Voyage's three numbers, each with a denominator read from its SOURCE:
|
||||
//
|
||||
// use new work that went through Voyage (the order queue: orders dated on or after
|
||||
// the later of the cutoff and MARKER_SINCE
|
||||
// whose `<!-- loesningsvalg: plan|prove -->`
|
||||
// marker says new work; counted = those with
|
||||
// a Voyage run whose slug equals the order's
|
||||
// `slug:`. Unmarked or earlier orders are
|
||||
// not measured, never a no)
|
||||
// use new work that went through Voyage (the order queue joined to trekbrief-stats.jsonl
|
||||
// by order id. New work = an order whose
|
||||
// `<!-- loesningsvalg: plan|prove -->` marker
|
||||
// says so, dated on or after the latest of the
|
||||
// cutoff, MARKER_SINCE and USE_COUNTING_SINCE.
|
||||
// YES = a /trekbrief record names the order's
|
||||
// id (its file stem) under one slug that names
|
||||
// no other order. NO = the order's own
|
||||
// `<!-- arbeidsform: ordre -->` line. No record,
|
||||
// or a disputed one, is not measured, never a
|
||||
// no. USE_COUNTING_SINCE unset = counting has
|
||||
// not started: not countable)
|
||||
// trust reviews in a row with nothing that (trekreview-stats.jsonl, read as JSON and
|
||||
// stops a release ordered by `ts` - file order is not
|
||||
// chronological; value = the current ALLOW streak)
|
||||
|
|
@ -23,6 +28,13 @@
|
|||
// checks `typeof`. The gate fells it when it
|
||||
// refuses to close the probe on that tree.)
|
||||
//
|
||||
// What `use` can and cannot see (PM 04.10): the "no" side is only what orders
|
||||
// declare, so undeclared non-Voyage work is not measured and the value covers the
|
||||
// measured share only. Known false-positive class: an `arbeidsform: voyage`
|
||||
// order claimed while an unrelated /trekbrief runs would be named by that
|
||||
// record; PM chose no repo-mailbox change (the record must itself name the id) -
|
||||
// a strong link would need the claim to name the claiming session.
|
||||
//
|
||||
// A number the source cannot give is NOT COUNTABLE - never 0. The verdict over the first five
|
||||
// deliveries is not judged by anything yet, and says so. Exit 0 = every number countable, every
|
||||
// mutant felled and the verdict judged and met; 1 = anything short of that; 2 = usage error.
|
||||
|
|
@ -36,7 +48,7 @@ import {
|
|||
realpathSync, rmSync,
|
||||
} from 'node:fs';
|
||||
import { homedir, tmpdir } from 'node:os';
|
||||
import { join, resolve, dirname, relative, sep } from 'node:path';
|
||||
import { join, resolve, dirname, relative, sep, basename } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { evaluateCheck, loadRegistry, TEST_TIMEOUT_MS } from './end-state-gate.mjs';
|
||||
|
|
@ -46,11 +58,15 @@ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|||
export const DEFAULT_CUTOFF = '2026-09-21';
|
||||
// The day pre-order-loesningsvalg.sh began refusing an order without the marker (PM 02.10).
|
||||
export const MARKER_SINCE = '2026-09-28';
|
||||
// The day `use` starts counting. Unset until the operator declares the work done
|
||||
// (operator 04.10): until then `use` is NOT COUNTABLE and every order not measured.
|
||||
export const USE_COUNTING_SINCE = null;
|
||||
export const DEFAULT_COORD = join(homedir(), '.claude', 'coord');
|
||||
export const DEFAULT_DATA = process.env.CLAUDE_PLUGIN_DATA
|
||||
|| join(homedir(), '.claude', 'plugins', 'data', 'voyage-ktg-plugin-marketplace');
|
||||
|
||||
const REVIEW_FILE = 'trekreview-stats.jsonl';
|
||||
const BRIEF_FILE = 'trekbrief-stats.jsonl';
|
||||
const toPosix = (p) => p.split(sep).join('/');
|
||||
|
||||
function walk(dir) {
|
||||
|
|
@ -95,15 +111,23 @@ export function loadStats(dataDir) {
|
|||
return { dir: dataDir, missing: false, files };
|
||||
}
|
||||
|
||||
// The slugs of every Voyage run the stats know about.
|
||||
function voyageSlugs(stats) {
|
||||
const slugs = new Set();
|
||||
for (const f of stats.files) {
|
||||
for (const r of f.records) {
|
||||
if (r && typeof r.slug === 'string' && r.slug !== '') slugs.add(r.slug);
|
||||
}
|
||||
/**
|
||||
* The orders /trekbrief runs named: order id -> its slugs, and slug -> its order
|
||||
* ids. Only trekbrief-stats.jsonl - the one record that carries order_id.
|
||||
*/
|
||||
export function orderRuns(stats) {
|
||||
const byOrder = new Map();
|
||||
const bySlug = new Map();
|
||||
const file = stats.files.find((f) => f.name === BRIEF_FILE);
|
||||
for (const r of file ? file.records : []) {
|
||||
if (!r || typeof r.order_id !== 'string' || r.order_id === '') continue;
|
||||
const slug = typeof r.slug === 'string' ? r.slug : '';
|
||||
if (!byOrder.has(r.order_id)) byOrder.set(r.order_id, new Set());
|
||||
byOrder.get(r.order_id).add(slug);
|
||||
if (!bySlug.has(slug)) bySlug.set(slug, new Set());
|
||||
bySlug.get(slug).add(r.order_id);
|
||||
}
|
||||
return slugs;
|
||||
return { byOrder, bySlug };
|
||||
}
|
||||
|
||||
const notCountable = (detail, extra = {}) => ({ countable: false, value: null, detail, ...extra });
|
||||
|
|
@ -172,6 +196,8 @@ function frontmatter(text) {
|
|||
const MARKER = /^<!--\s*loesningsvalg:\s*([^;\s>]+)/m;
|
||||
const NEW_WORK = new Set(['plan', 'prove']);
|
||||
const NOT_NEW = new Set(['ikke-nytt']);
|
||||
// The work-form marker dispatch writes on its own line; `ordre` declares the work is not done through Voyage.
|
||||
const ARBEIDSFORM = /^<!--\s*arbeidsform:\s*([^;\s>]+)/m;
|
||||
|
||||
/** Every order file under `<coord>/<repo>/orders/` - pending, `archive/` and `claimed/`. */
|
||||
export function readOrders(coordDir) {
|
||||
|
|
@ -186,7 +212,12 @@ export function readOrders(coordDir) {
|
|||
files++;
|
||||
const text = readFileSync(path, 'utf8');
|
||||
const fm = frontmatter(text);
|
||||
if (fm && fm['order-id']) orders.push({ path, repo, fm, marker: text.match(MARKER)?.[1] });
|
||||
if (fm && fm['order-id']) {
|
||||
orders.push({
|
||||
path, repo, fm, id: basename(path, '.md'),
|
||||
marker: text.match(MARKER)?.[1], arbeidsform: text.match(ARBEIDSFORM)?.[1],
|
||||
});
|
||||
}
|
||||
else unreadable.push(path);
|
||||
}
|
||||
}
|
||||
|
|
@ -194,48 +225,75 @@ export function readOrders(coordDir) {
|
|||
return { dir: coordDir, files, orders, unreadable };
|
||||
}
|
||||
|
||||
export function measureUse({ queue, slugs, cutoff }) {
|
||||
const from = cutoff > MARKER_SINCE ? cutoff : MARKER_SINCE;
|
||||
export function measureUse({ queue, runs, cutoff, countingSince }) {
|
||||
const started = typeof countingSince === 'string' && countingSince !== '';
|
||||
const from = [cutoff, MARKER_SINCE, ...(started ? [countingSince] : [])].reduce((a, b) => (b > a ? b : a));
|
||||
const glob = `${join(queue.dir, '*', 'orders')}/**/*.md`;
|
||||
const base = {
|
||||
id: 'use', label: 'new work that went through Voyage',
|
||||
source: `${join(queue.dir, '*', 'orders')}/**/*.md (loesningsvalg marker, from ${from})`,
|
||||
source: started
|
||||
? `${glob} joined to ${BRIEF_FILE} by order id (loesningsvalg marker, from ${from})`
|
||||
: `${glob} joined to ${BRIEF_FILE} by order id (counting not started - USE_COUNTING_SINCE is not set)`,
|
||||
};
|
||||
const named = queue.orders.filter((o) => runs.byOrder.has(o.id)).length;
|
||||
const withMarker = queue.orders.filter((o) => o.marker !== undefined);
|
||||
const perMailbox = {};
|
||||
for (const o of queue.orders) perMailbox[o.repo] = (perMailbox[o.repo] ?? 0) + 1;
|
||||
const dot = Object.entries(perMailbox).filter(([k]) => k.startsWith('.'));
|
||||
const files = `${queue.files} order files in ${Object.keys(perMailbox).length} mailboxes`
|
||||
+ (dot.length ? ` (dot-mailboxes ${dot.map(([k, v]) => `${k} ${v}`).join(' · ')} - a shell \`*\` skips them)` : '');
|
||||
const unreadableNote = queue.unreadable.length ? `; ${queue.unreadable.length} order file(s) without frontmatter` : '';
|
||||
if (!started) {
|
||||
const facts = { scanned: queue.orders.length, files: queue.files, unreadable: queue.unreadable.length,
|
||||
withMarker: withMarker.length, notMeasured: queue.orders.length, withVoyageRun: 0,
|
||||
ordersNamedByARecord: named, countingSince: null };
|
||||
return { ...base, denominator: 0, facts, ...notCountable(
|
||||
`${files}${unreadableNote}; counting has not started (USE_COUNTING_SINCE is not set) - all ${queue.orders.length} orders not measured`
|
||||
+ `; ${named} order(s) already named by a trekbrief record`) };
|
||||
}
|
||||
const inWindow = queue.orders.filter((o) => String(o.fm.date ?? '').slice(0, 10) >= from);
|
||||
const newWork = inWindow.filter((o) => NEW_WORK.has(o.marker));
|
||||
const notNew = inWindow.filter((o) => NOT_NEW.has(o.marker));
|
||||
const unmarked = inWindow.length - newWork.length - notNew.length;
|
||||
const beforeMarker = queue.orders.length - inWindow.length;
|
||||
const through = newWork.filter((o) => o.fm.slug && slugs.has(o.fm.slug));
|
||||
const withoutSlug = newWork.filter((o) => !o.fm.slug).length;
|
||||
const yes = [];
|
||||
let declaredNo = 0;
|
||||
let noRecord = 0;
|
||||
let disputed = 0;
|
||||
for (const o of newWork) {
|
||||
if (o.arbeidsform === 'ordre') { declaredNo++; continue; }
|
||||
const slugs = runs.byOrder.get(o.id);
|
||||
if (!slugs) { noRecord++; continue; }
|
||||
const [slug] = slugs;
|
||||
if (slugs.size !== 1 || runs.bySlug.get(slug).size !== 1) { disputed++; continue; }
|
||||
yes.push(o.id);
|
||||
}
|
||||
const measured = yes.length + declaredNo;
|
||||
const facts = {
|
||||
scanned: queue.orders.length, files: queue.files, unreadable: queue.unreadable.length,
|
||||
withMarker: withMarker.length, newWork: newWork.length, notNew: notNew.length,
|
||||
unmarked, beforeMarker, notMeasured: unmarked + beforeMarker, withVoyageRun: through.length, withoutSlug,
|
||||
unmarked, beforeMarker, notMeasured: unmarked + beforeMarker + noRecord + disputed,
|
||||
withVoyageRun: yes.length, declaredNo, noRecord, disputed, matched: yes,
|
||||
ordersNamedByARecord: named, countingSince,
|
||||
};
|
||||
const perMailbox = {};
|
||||
for (const o of queue.orders) perMailbox[o.repo] = (perMailbox[o.repo] ?? 0) + 1;
|
||||
const dot = Object.entries(perMailbox).filter(([k]) => k.startsWith('.'));
|
||||
const census = `${queue.files} order files in ${Object.keys(perMailbox).length} mailboxes`
|
||||
+ (dot.length ? ` (dot-mailboxes ${dot.map(([k, v]) => `${k} ${v}`).join(' · ')} - a shell \`*\` skips them)` : '')
|
||||
+ `; ${withMarker.length} of ${queue.orders.length} orders carry a loesningsvalg marker`
|
||||
const census = `${files}; ${withMarker.length} of ${queue.orders.length} orders carry a loesningsvalg marker`
|
||||
+ `; from ${from}: ${newWork.length} new (plan|prove) · ${notNew.length} not new (ikke-nytt)`
|
||||
+ `; ${facts.notMeasured} not measured (${beforeMarker} before ${from} · ${unmarked} unmarked or unknown value)`
|
||||
+ (queue.unreadable.length ? `; ${queue.unreadable.length} order file(s) without frontmatter` : '');
|
||||
+ `; ${facts.notMeasured} not measured (${beforeMarker} before ${from} · ${unmarked} unmarked or unknown value`
|
||||
+ ` · ${noRecord} new without a trekbrief record · ${disputed} disputed)${unreadableNote}`;
|
||||
if (newWork.length === 0) {
|
||||
return { ...base, denominator: 0, facts, ...notCountable(
|
||||
`${census}; 0 new-work orders dated >= ${from} - the denominator is empty`) };
|
||||
}
|
||||
// The numerator joins an order to a Voyage run by its `slug:`. An order without one cannot
|
||||
// match anything, so its "no" would be the missing key talking - the numerator is not measured.
|
||||
if (withoutSlug > 0) {
|
||||
return { ...base, denominator: newWork.length, facts, ...notCountable(
|
||||
`${census}; ${withoutSlug} of ${newWork.length} new-work orders carry no slug - the numerator is not measured`
|
||||
+ ` (${through.length} matched a Voyage run)`) };
|
||||
if (measured === 0) {
|
||||
return { ...base, denominator: 0, facts, ...notCountable(
|
||||
`${census}; none of ${newWork.length} new-work orders is measured - a yes needs a trekbrief record naming the order,`
|
||||
+ ' a no the order\'s own arbeidsform: ordre') };
|
||||
}
|
||||
return {
|
||||
...base, countable: true, value: through.length, denominator: newWork.length, facts,
|
||||
detail: `${census}; ${through.length} of ${newWork.length} new-work orders have a Voyage run in the stats with the order's slug`,
|
||||
...base, countable: true, value: yes.length, denominator: measured, facts,
|
||||
detail: `${census}; of ${newWork.length} new-work orders: ${yes.length} through Voyage (a trekbrief record names the order)`
|
||||
+ ` · ${declaredNo} declared outside Voyage (arbeidsform: ordre); value = ${yes.length} of ${measured} measured`
|
||||
+ ' - the measured share only: undeclared non-Voyage work is not measured, never a no',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -425,10 +483,10 @@ const VERDICT = {
|
|||
detail: 'the verdict needs the first five deliveries through the new Voyage; nothing counts a delivery yet',
|
||||
};
|
||||
|
||||
export function measureAll({ root = REPO_ROOT, coordDir = DEFAULT_COORD, dataDir = DEFAULT_DATA, cutoff = DEFAULT_CUTOFF, mutantOptions = {} } = {}) {
|
||||
export function measureAll({ root = REPO_ROOT, coordDir = DEFAULT_COORD, dataDir = DEFAULT_DATA, cutoff = DEFAULT_CUTOFF, countingSince = USE_COUNTING_SINCE, mutantOptions = {} } = {}) {
|
||||
const stats = loadStats(dataDir);
|
||||
const numbers = [
|
||||
measureUse({ queue: readOrders(coordDir), slugs: voyageSlugs(stats), cutoff }),
|
||||
measureUse({ queue: readOrders(coordDir), runs: orderRuns(stats), cutoff, countingSince }),
|
||||
measureTrust(stats),
|
||||
measureInterrupts({ stats, callSites: emitterCallSites(root) }),
|
||||
];
|
||||
|
|
@ -493,6 +551,15 @@ export function main(argv) {
|
|||
return result.green ? 0 : 1;
|
||||
}
|
||||
|
||||
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
// Run as a CLI? Compared as real paths, so a symlink or a space in the path still runs it.
|
||||
function isMain() {
|
||||
try {
|
||||
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMain()) {
|
||||
process.exitCode = main(process.argv.slice(2));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue