@ai-dossier/cli 0.24.1 → 0.26.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/README.md +132 -5
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/classify.d.ts +11 -0
- package/dist/commands/classify.d.ts.map +1 -0
- package/dist/commands/classify.js +136 -0
- package/dist/commands/classify.js.map +1 -0
- package/dist/commands/runstate.d.ts.map +1 -1
- package/dist/commands/runstate.js +67 -10
- package/dist/commands/runstate.js.map +1 -1
- package/dist/commands/sched.d.ts +0 -24
- package/dist/commands/sched.d.ts.map +1 -1
- package/dist/commands/sched.js +12 -35
- package/dist/commands/sched.js.map +1 -1
- package/dist/gh.d.ts +38 -4
- package/dist/gh.d.ts.map +1 -1
- package/dist/gh.js +88 -9
- package/dist/gh.js.map +1 -1
- package/dist/hard-block-labels.d.ts +24 -0
- package/dist/hard-block-labels.d.ts.map +1 -0
- package/dist/hard-block-labels.js +36 -0
- package/dist/hard-block-labels.js.map +1 -0
- package/dist/plan-artifact.d.ts +1 -1
- package/dist/plan-artifact.d.ts.map +1 -1
- package/dist/plan-artifact.js.map +1 -1
- package/dist/prescreen.d.ts +66 -0
- package/dist/prescreen.d.ts.map +1 -0
- package/dist/prescreen.js +187 -0
- package/dist/prescreen.js.map +1 -0
- package/dist/runstate-stats.d.ts +84 -3
- package/dist/runstate-stats.d.ts.map +1 -1
- package/dist/runstate-stats.js +197 -6
- package/dist/runstate-stats.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic classify pre-screen (#538) — the "no tokens spent" gate `issue-cycle-classifier`
|
|
3
|
+
* runs before any model call. `docs/reports/batch-pilot-2-execution.md` §4.1 measured the
|
|
4
|
+
* classifier costing ~64k tokens/dispatch, at mid tier, with full repo exploration, even for
|
|
5
|
+
* issues that hit an obvious RFC-0001 E.2 floor rule a deterministic check can catch for free.
|
|
6
|
+
*
|
|
7
|
+
* Scope is deliberately partial: this catches the OBVIOUS floor hits (hard-block labels, a
|
|
8
|
+
* text-keyword approximation of the risk-floor/new-package/deploy-pipeline rules, rule-9 open
|
|
9
|
+
* dependencies, and — when a plan:v1 artifact is already on the issue — the path-based risk
|
|
10
|
+
* floor and the >8-files rule). Everything it does not catch (rule 2 beyond the `migration`
|
|
11
|
+
* keyword, rule 7 hard rollback, rule 8 visual/browser review, rules 5/6 diff/file size without
|
|
12
|
+
* a plan artifact, rule 10 confidence) falls through to the classifier's own bounded
|
|
13
|
+
* mechanical-tier pass, which is the intended safety net — not a gap this module needs to close.
|
|
14
|
+
*
|
|
15
|
+
* Pure and dependency-free (no `gh`, network, or fs), same discipline as `plan-artifact.ts` and
|
|
16
|
+
* `runstate.ts` — unit-testable directly. Subprocess access (fetching the issue, resolving
|
|
17
|
+
* dependency state, filtering by submitted set) lives in the command layer (`commands/classify.ts`).
|
|
18
|
+
*/
|
|
19
|
+
/** RFC-0001 E.2 floor rules a text-keyword scan can approximate without reading any file. */
|
|
20
|
+
export interface TextFloorPattern {
|
|
21
|
+
/** Reported in the reason; also the RFC-0001 E.2 rule it approximates. */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Matched case-insensitively against title + body + label names, joined. Returns the matched keyword, or null. */
|
|
24
|
+
match: (text: string) => string | null;
|
|
25
|
+
}
|
|
26
|
+
export declare const TEXT_FLOOR_PATTERNS: readonly TextFloorPattern[];
|
|
27
|
+
/** `Depends on #N` references resolved per issue; each costs a `gh` call downstream (command layer), same rationale as `MAX_ISSUE_SELECTION` (`issue-selection.ts`). */
|
|
28
|
+
export declare const MAX_DEPENDENCY_REFS = 32;
|
|
29
|
+
/**
|
|
30
|
+
* Every issue number referenced by a `Depends on #N` phrase in `text`, de-duplicated, in order,
|
|
31
|
+
* capped at {@link MAX_DEPENDENCY_REFS} — `text` is untrusted (issue body), so an adversarial
|
|
32
|
+
* body cannot force an unbounded `gh` fan-out downstream.
|
|
33
|
+
*/
|
|
34
|
+
export declare function extractDependencyRefs(text: string): number[];
|
|
35
|
+
/** One deterministic finding — always recorded, whether or not it decided the verdict. */
|
|
36
|
+
export interface PrescreenReason {
|
|
37
|
+
/** Which check produced the finding — mirrors `plan validate`'s `PlanValidationReason.check` shape. */
|
|
38
|
+
check: 'hard-block-label' | 'text-floor' | 'path-floor' | 'file-count' | 'open-dependency';
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
export interface PrescreenInput {
|
|
42
|
+
title: string;
|
|
43
|
+
body: string;
|
|
44
|
+
labels: readonly string[];
|
|
45
|
+
/** Predicted files from a plan:v1 artifact, when one exists on the issue. `undefined` when none. */
|
|
46
|
+
predictedFiles?: readonly string[];
|
|
47
|
+
/**
|
|
48
|
+
* Issue numbers that resolved OPEN and outside the submitted set — filtering by submitted set
|
|
49
|
+
* is the command layer's job (`classify.ts`, via `--submitted-set`); this module trusts the
|
|
50
|
+
* list it is given.
|
|
51
|
+
*/
|
|
52
|
+
openDependencies?: readonly number[];
|
|
53
|
+
}
|
|
54
|
+
export interface PrescreenVerdict {
|
|
55
|
+
/** `full` = an obvious floor hit found, reject before any model call. `candidate` = proceed to the bounded mechanical-tier classify pass. */
|
|
56
|
+
verdict: 'full' | 'candidate';
|
|
57
|
+
/** Every check's finding, in evaluation order — not just the one that decided `verdict`. */
|
|
58
|
+
reasons: PrescreenReason[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Run every deterministic check, in order, and record every hit — first hit decides `verdict`,
|
|
62
|
+
* but the caller (and the rationale a consumer posts) gets the full list, matching the existing
|
|
63
|
+
* "a verdict may hit several" precedent in `issue-cycle-classifier.ds.md`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function prescreenIssue(input: PrescreenInput): PrescreenVerdict;
|
|
66
|
+
//# sourceMappingURL=prescreen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prescreen.d.ts","sourceRoot":"","sources":["../src/prescreen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,6FAA6F;AAC7F,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,mHAAmH;IACnH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACxC;AAwBD,eAAO,MAAM,mBAAmB,EAAE,SAAS,gBAAgB,EAmD1D,CAAC;AAEF,wKAAwK;AACxK,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAKtC;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAY5D;AAED,0FAA0F;AAC1F,MAAM,WAAW,eAAe;IAC9B,uGAAuG;IACvG,KAAK,EAAE,kBAAkB,GAAG,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,iBAAiB,CAAC;IAC3F,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,oGAAoG;IACpG,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,6IAA6I;IAC7I,OAAO,EAAE,MAAM,GAAG,WAAW,CAAC;IAC9B,4FAA4F;IAC5F,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AA6BD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB,CA4CtE"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic classify pre-screen (#538) — the "no tokens spent" gate `issue-cycle-classifier`
|
|
4
|
+
* runs before any model call. `docs/reports/batch-pilot-2-execution.md` §4.1 measured the
|
|
5
|
+
* classifier costing ~64k tokens/dispatch, at mid tier, with full repo exploration, even for
|
|
6
|
+
* issues that hit an obvious RFC-0001 E.2 floor rule a deterministic check can catch for free.
|
|
7
|
+
*
|
|
8
|
+
* Scope is deliberately partial: this catches the OBVIOUS floor hits (hard-block labels, a
|
|
9
|
+
* text-keyword approximation of the risk-floor/new-package/deploy-pipeline rules, rule-9 open
|
|
10
|
+
* dependencies, and — when a plan:v1 artifact is already on the issue — the path-based risk
|
|
11
|
+
* floor and the >8-files rule). Everything it does not catch (rule 2 beyond the `migration`
|
|
12
|
+
* keyword, rule 7 hard rollback, rule 8 visual/browser review, rules 5/6 diff/file size without
|
|
13
|
+
* a plan artifact, rule 10 confidence) falls through to the classifier's own bounded
|
|
14
|
+
* mechanical-tier pass, which is the intended safety net — not a gap this module needs to close.
|
|
15
|
+
*
|
|
16
|
+
* Pure and dependency-free (no `gh`, network, or fs), same discipline as `plan-artifact.ts` and
|
|
17
|
+
* `runstate.ts` — unit-testable directly. Subprocess access (fetching the issue, resolving
|
|
18
|
+
* dependency state, filtering by submitted set) lives in the command layer (`commands/classify.ts`).
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.MAX_DEPENDENCY_REFS = exports.TEXT_FLOOR_PATTERNS = void 0;
|
|
22
|
+
exports.extractDependencyRefs = extractDependencyRefs;
|
|
23
|
+
exports.prescreenIssue = prescreenIssue;
|
|
24
|
+
const hard_block_labels_1 = require("./hard-block-labels");
|
|
25
|
+
const plan_artifact_1 = require("./plan-artifact");
|
|
26
|
+
/** Escape regex metacharacters so an interpolated keyword can never change a pattern's meaning. */
|
|
27
|
+
function escapeRegExp(s) {
|
|
28
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
29
|
+
}
|
|
30
|
+
/** A multi-word phrase, safe to interpolate into `\b...\b`: metacharacters escaped, any run of whitespace matches `\s+`. */
|
|
31
|
+
function phrase(s) {
|
|
32
|
+
return escapeRegExp(s).replace(/\s+/g, '\\s+');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* First keyword (in list order) that matches `text` as a whole word/phrase, or null. `\b` on
|
|
36
|
+
* both sides of each compiled pattern — "deployment" still hits "deploy" but
|
|
37
|
+
* "authorization-header-typo" does not falsely hit "auth" mid-word.
|
|
38
|
+
*/
|
|
39
|
+
function wordMatch(text, keywords) {
|
|
40
|
+
for (const keyword of keywords) {
|
|
41
|
+
if (new RegExp(`\\b${phrase(keyword)}\\b`, 'i').test(text))
|
|
42
|
+
return keyword;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
exports.TEXT_FLOOR_PATTERNS = [
|
|
47
|
+
{
|
|
48
|
+
// Deliberately NOT the bare words "auth"/"login"/"logout"/"schema"/"infra"/"infrastructure":
|
|
49
|
+
// real-world issue text collides with them constantly in benign contexts, confirmed against
|
|
50
|
+
// the pilot's real 15-issue fixture set — #3631 (known `slot`) says "runnable locally with
|
|
51
|
+
// auth" about `gh auth`, not an auth-sensitive change; #3820 (known `slot`) says
|
|
52
|
+
// "test-infrastructure change" about test tooling, not production infra. "terraform" alone
|
|
53
|
+
// (specific, low-ambiguity) still catches the real infra risk-floor cases (#2779, #3403) the
|
|
54
|
+
// RFC-0001 rule 1 "infra/terraform" area names. Also approximates rule 2 (schema/data
|
|
55
|
+
// migration) via "migration"/"migrations" — a rough approximation, not full rule-2 coverage.
|
|
56
|
+
name: 'rule1-risk-floor-area',
|
|
57
|
+
match: (t) => wordMatch(t, [
|
|
58
|
+
'authentication',
|
|
59
|
+
'authorization',
|
|
60
|
+
'oauth',
|
|
61
|
+
'sso',
|
|
62
|
+
'payment',
|
|
63
|
+
'payments',
|
|
64
|
+
'billing',
|
|
65
|
+
'invoice',
|
|
66
|
+
'invoices',
|
|
67
|
+
'checkout',
|
|
68
|
+
'stripe',
|
|
69
|
+
'migration',
|
|
70
|
+
'migrations',
|
|
71
|
+
'security',
|
|
72
|
+
'crypto',
|
|
73
|
+
'secret',
|
|
74
|
+
'secrets',
|
|
75
|
+
'credential',
|
|
76
|
+
'credentials',
|
|
77
|
+
'terraform',
|
|
78
|
+
]),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'rule3-new-package-workspace',
|
|
82
|
+
match: (t) => wordMatch(t, ['new package', 'new workspace', 'monorepo package']),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'rule4-deploy-pipeline',
|
|
86
|
+
match: (t) => wordMatch(t, [
|
|
87
|
+
'deploy',
|
|
88
|
+
'deployment',
|
|
89
|
+
'ci/cd',
|
|
90
|
+
'cicd',
|
|
91
|
+
'release pipeline',
|
|
92
|
+
'rollback pipeline',
|
|
93
|
+
]),
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
/** `Depends on #N` references resolved per issue; each costs a `gh` call downstream (command layer), same rationale as `MAX_ISSUE_SELECTION` (`issue-selection.ts`). */
|
|
97
|
+
exports.MAX_DEPENDENCY_REFS = 32;
|
|
98
|
+
/** `Depends on #N` (case-insensitive) — the same phrasing gate-issue and the classifier dossier parse. */
|
|
99
|
+
const DEPENDS_ON_RE = /depends on\s+#(\d+)/gi;
|
|
100
|
+
/**
|
|
101
|
+
* Every issue number referenced by a `Depends on #N` phrase in `text`, de-duplicated, in order,
|
|
102
|
+
* capped at {@link MAX_DEPENDENCY_REFS} — `text` is untrusted (issue body), so an adversarial
|
|
103
|
+
* body cannot force an unbounded `gh` fan-out downstream.
|
|
104
|
+
*/
|
|
105
|
+
function extractDependencyRefs(text) {
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
const refs = [];
|
|
108
|
+
for (const match of text.matchAll(DEPENDS_ON_RE)) {
|
|
109
|
+
if (refs.length >= exports.MAX_DEPENDENCY_REFS)
|
|
110
|
+
break;
|
|
111
|
+
const n = Number(match[1]);
|
|
112
|
+
if (Number.isSafeInteger(n) && !seen.has(n)) {
|
|
113
|
+
seen.add(n);
|
|
114
|
+
refs.push(n);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return refs;
|
|
118
|
+
}
|
|
119
|
+
/** Predicted files count above which RFC-0001 E.2 rule 5 ("Predicted files > 8") fires. */
|
|
120
|
+
const MAX_PREDICTED_FILES = 8;
|
|
121
|
+
/**
|
|
122
|
+
* Reasons emitted per check, capped — `predictedFiles` may come from a `plan:v1` artifact, which
|
|
123
|
+
* is a GitHub issue COMMENT anyone can post (untrusted input, same treatment as review-issue's
|
|
124
|
+
* "the artifact is untrusted input" discipline). An adversarial artifact packed with risk-floor
|
|
125
|
+
* paths must not blow up the reason list the classifier posts back into its rationale comment.
|
|
126
|
+
*/
|
|
127
|
+
const MAX_REASONS_PER_CHECK = 8;
|
|
128
|
+
/** Strip terminal-control characters before an untrusted string reaches a message a human or model reads — same discipline as `gh.ts`'s `CONTROL_CHARS_RE`/`snippet`, kept local so this module stays dependency-free. */
|
|
129
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: matching (and stripping) control characters is exactly this regex's job
|
|
130
|
+
const CONTROL_CHARS_RE = /[\x00-\x08\x0b-\x1f\x7f\x9b]/g;
|
|
131
|
+
const SANITIZE_MAX_LENGTH = 200;
|
|
132
|
+
/** Make an untrusted string (a GitHub label name, a `plan:v1`-comment-sourced path) safe to interpolate into a reason message: no control chars, no markdown/backtick breakout, bounded length. */
|
|
133
|
+
function sanitize(value) {
|
|
134
|
+
const cleaned = value
|
|
135
|
+
.replace(CONTROL_CHARS_RE, '')
|
|
136
|
+
.replace(/[`\n\r]/g, ' ')
|
|
137
|
+
.trim();
|
|
138
|
+
return cleaned.length > SANITIZE_MAX_LENGTH
|
|
139
|
+
? `${cleaned.slice(0, SANITIZE_MAX_LENGTH)}…`
|
|
140
|
+
: cleaned;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Run every deterministic check, in order, and record every hit — first hit decides `verdict`,
|
|
144
|
+
* but the caller (and the rationale a consumer posts) gets the full list, matching the existing
|
|
145
|
+
* "a verdict may hit several" precedent in `issue-cycle-classifier.ds.md`.
|
|
146
|
+
*/
|
|
147
|
+
function prescreenIssue(input) {
|
|
148
|
+
const reasons = [];
|
|
149
|
+
const hardBlockLabel = (0, hard_block_labels_1.pickHardBlockLabel)(input.labels);
|
|
150
|
+
if (hardBlockLabel !== null) {
|
|
151
|
+
reasons.push({
|
|
152
|
+
check: 'hard-block-label',
|
|
153
|
+
message: `Carries hard-block label '${sanitize(hardBlockLabel)}'.`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const text = `${input.title}\n${input.body}\n${input.labels.join(' ')}`;
|
|
157
|
+
for (const pattern of exports.TEXT_FLOOR_PATTERNS) {
|
|
158
|
+
const hit = pattern.match(text);
|
|
159
|
+
if (hit !== null) {
|
|
160
|
+
reasons.push({
|
|
161
|
+
check: 'text-floor',
|
|
162
|
+
message: `Title/body/labels match '${pattern.name}' (keyword: '${hit}').`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const predictedFiles = input.predictedFiles ?? [];
|
|
167
|
+
for (const hit of (0, plan_artifact_1.scanRiskFloor)(predictedFiles).slice(0, MAX_REASONS_PER_CHECK)) {
|
|
168
|
+
reasons.push({
|
|
169
|
+
check: 'path-floor',
|
|
170
|
+
message: `Predicted file '${sanitize(hit.path)}' touches '${hit.pattern}' (rule1-risk-floor-area).`,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (predictedFiles.length > MAX_PREDICTED_FILES) {
|
|
174
|
+
reasons.push({
|
|
175
|
+
check: 'file-count',
|
|
176
|
+
message: `Predicted files (${predictedFiles.length}) exceeds ${MAX_PREDICTED_FILES} (rule5-file-count).`,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
for (const dep of (input.openDependencies ?? []).slice(0, MAX_REASONS_PER_CHECK)) {
|
|
180
|
+
reasons.push({
|
|
181
|
+
check: 'open-dependency',
|
|
182
|
+
message: `Depends on #${dep}, which is open and outside the submitted set (rule9-open-dependency).`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return { verdict: reasons.length > 0 ? 'full' : 'candidate', reasons };
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=prescreen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prescreen.js","sourceRoot":"","sources":["../src/prescreen.ts"],"names":[],"mappings":"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"}
|
package/dist/runstate-stats.d.ts
CHANGED
|
@@ -33,8 +33,40 @@ export declare const BATCH_MERGE_WAIT_PHASE = "batch-merge-wait";
|
|
|
33
33
|
* here too, rather than silently sorting to the end alphabetically.
|
|
34
34
|
*/
|
|
35
35
|
export declare const STATS_PHASE_ORDER: readonly string[];
|
|
36
|
-
/**
|
|
37
|
-
|
|
36
|
+
/**
|
|
37
|
+
* The bucket a run lands in when no milestone in the run carries a `model=` key.
|
|
38
|
+
*
|
|
39
|
+
* Angle-bracketed so it cannot be forged: {@link canonicalModel} lowercases and peels routing
|
|
40
|
+
* prefixes, and no input it accepts produces a leading `<`. A bare `unknown` was reachable from
|
|
41
|
+
* remote input (`Unknown`, `llmgateway/unknown`, `~~~unknown` all canonicalise to it), which
|
|
42
|
+
* would let anyone who can comment on an issue steer the "we could not attribute this" row.
|
|
43
|
+
*/
|
|
44
|
+
export declare const UNKNOWN_MODEL = "<unknown>";
|
|
45
|
+
/**
|
|
46
|
+
* Gateway and provider tokens stripped from a recorded `model=` to find its bucket.
|
|
47
|
+
*
|
|
48
|
+
* Agents record whatever id their CLI was invoked with, and the same model reaches the trail
|
|
49
|
+
* under several spellings depending on how it was routed: `glm-5.3` direct vs
|
|
50
|
+
* `llmgateway/glm-5.3` through a gateway, `z-ai/glm-latest` vs opencode's `~z-ai/glm-latest`
|
|
51
|
+
* alias form, `openrouter-kimi-latest` vs `moonshotai/kimi-latest`. Unbucketed, a single
|
|
52
|
+
* model's runs split across rows and the per-model view — the whole reason the gate records
|
|
53
|
+
* `model=` — answers a question nobody asked (`imboard-ai/ai-dossier#528`).
|
|
54
|
+
*
|
|
55
|
+
* Deliberately an allowlist of *known routing* tokens, never a generic "drop the first
|
|
56
|
+
* segment" rule: merging two genuinely different models is the one error this table cannot
|
|
57
|
+
* survive, so an unrecognised leading segment is always kept.
|
|
58
|
+
*/
|
|
59
|
+
export declare const MODEL_ROUTING_PREFIXES: readonly string[];
|
|
60
|
+
/**
|
|
61
|
+
* The bucket key for a recorded `model=` — the model itself, with routing noise removed.
|
|
62
|
+
*
|
|
63
|
+
* Lowercased, any leading `~` (opencode's gateway-alias marker) dropped, then known routing
|
|
64
|
+
* prefixes peeled off. A value that is *only* routing tokens keeps its identity rather than
|
|
65
|
+
* collapsing into every other such id — that is the length guard in {@link stripRoutingPrefix},
|
|
66
|
+
* not the empty-string fallback below, which is reachable only for an id that was empty or
|
|
67
|
+
* whitespace to begin with.
|
|
68
|
+
*/
|
|
69
|
+
export declare function canonicalModel(raw: string): string;
|
|
38
70
|
/** The group a milestone lands in when it carries no `run=` id. */
|
|
39
71
|
export declare const UNKNOWN_RUN = "unknown-run";
|
|
40
72
|
/**
|
|
@@ -94,9 +126,23 @@ export interface PhaseAggregate {
|
|
|
94
126
|
/** Samples below zero, i.e. from clock skew — a median over these is not meaningful. */
|
|
95
127
|
negative_samples: number;
|
|
96
128
|
}
|
|
97
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* Per-`model=` view of whole-run totals and outcomes — the point of recording `model=`.
|
|
131
|
+
*
|
|
132
|
+
* Duration alone answers "how long", never "which models finish the work". Both are needed
|
|
133
|
+
* to route a tier at a model with any evidence behind it, so the bucket carries the run's
|
|
134
|
+
* terminal state alongside its span.
|
|
135
|
+
*/
|
|
98
136
|
export interface ModelAggregate {
|
|
137
|
+
/** Canonical bucket key — see {@link canonicalModel}. */
|
|
99
138
|
model: string;
|
|
139
|
+
/**
|
|
140
|
+
* The distinct raw `model=` spellings that folded into this bucket, sorted.
|
|
141
|
+
*
|
|
142
|
+
* Always populated (a bucket with one spelling lists that one), so a fold is visible in
|
|
143
|
+
* the output rather than silently reshaping someone's cohort.
|
|
144
|
+
*/
|
|
145
|
+
aliases: string[];
|
|
100
146
|
runs: number;
|
|
101
147
|
/** Runs of this model whose total was measurable; the rest are excluded below. */
|
|
102
148
|
samples: number;
|
|
@@ -104,6 +150,24 @@ export interface ModelAggregate {
|
|
|
104
150
|
min_total_seconds: number | null;
|
|
105
151
|
max_total_seconds: number | null;
|
|
106
152
|
negative_samples: number;
|
|
153
|
+
/**
|
|
154
|
+
* Runs whose last milestone was a delivery phase at `done` — see {@link DELIVERED_PHASES}.
|
|
155
|
+
*/
|
|
156
|
+
delivered: number;
|
|
157
|
+
/** Runs whose last milestone was `blocked`, whatever the phase. */
|
|
158
|
+
blocked: number;
|
|
159
|
+
/** Everything else: still in flight, parked awaiting merge, partial, or fenced. */
|
|
160
|
+
unfinished: number;
|
|
161
|
+
/**
|
|
162
|
+
* `delivered / runs`, in [0, 1] — a **floor**, not a rate.
|
|
163
|
+
*
|
|
164
|
+
* Every run is in the denominator, including ones still in flight, parked at
|
|
165
|
+
* `awaiting-merge`, `partial`, or superseded by a takeover fence. A model whose runs are
|
|
166
|
+
* mid-flight therefore reads low until they land, and a fenced generation counts against
|
|
167
|
+
* the same model whose successor delivered the work. Compare buckets over a closed window,
|
|
168
|
+
* or read `unfinished` alongside this.
|
|
169
|
+
*/
|
|
170
|
+
delivery_rate: number;
|
|
107
171
|
}
|
|
108
172
|
/** The two cross-run rollups: per-phase spread, and whole-run totals bucketed by model. */
|
|
109
173
|
export interface StatsAggregates {
|
|
@@ -149,6 +213,23 @@ export interface IssueTrail {
|
|
|
149
213
|
}
|
|
150
214
|
/** One aggregate row's skew note, or null when every sample was forwards. */
|
|
151
215
|
export declare function skewNote(negativeSamples: number): string | null;
|
|
216
|
+
/**
|
|
217
|
+
* Most folded spellings named inline on a model row before the rest become a count.
|
|
218
|
+
*
|
|
219
|
+
* An attacker who can comment on the issue can post arbitrarily many distinct `model=`
|
|
220
|
+
* spellings that all canonicalise to one bucket, so an uncapped list is an unbounded cell —
|
|
221
|
+
* the DoS {@link MAX_CELL_LENGTH} exists to stop, reached by another route.
|
|
222
|
+
*/
|
|
223
|
+
export declare const MAX_NAMED_ALIASES = 3;
|
|
224
|
+
/**
|
|
225
|
+
* The folded-alias note for a model row, or null when nothing folded.
|
|
226
|
+
*
|
|
227
|
+
* Pure and exported (like {@link skewNote}) so the disclosure guarantee — a merge is always
|
|
228
|
+
* visible, never silent — is testable, rather than living only in a private renderer.
|
|
229
|
+
* Values are milestone-derived and therefore remote: each is passed through
|
|
230
|
+
* {@link renderValue} before it can reach a terminal.
|
|
231
|
+
*/
|
|
232
|
+
export declare function modelNote(model: ModelAggregate): string | null;
|
|
152
233
|
export interface StatsInput {
|
|
153
234
|
trails: IssueTrail[];
|
|
154
235
|
/** Issues that could not be read at all. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runstate-stats.d.ts","sourceRoot":"","sources":["../src/runstate-stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAGL,KAAK,eAAe,EAGrB,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAE7C;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"runstate-stats.d.ts","sourceRoot":"","sources":["../src/runstate-stats.ts"],"names":[],"mappings":"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"}
|