@event4u/agent-config 15.0.0 → 16.0.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +142 -348
- package/dist/agent-src/commands/roadmap/create.md +3 -2
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +50 -0
- package/dist/agent-src/rules/user-interaction.md +31 -30
- package/dist/agent-src/skills/error-handling-patterns/SKILL.md +9 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/{chunk-HZ52BRGG.js → chunk-H32DGUJH.js} +14 -89
- package/dist/cli-delegate/chunk-NUHLI2RX.js +80 -0
- package/dist/cli-delegate/{chunk-TYSY33MD.js → chunk-QJWAUMJW.js} +12 -2
- package/dist/cli-delegate/{chunk-2KKXJW25.js → chunk-S3VFXMTM.js} +1 -1
- package/dist/cli-delegate/cmd_conformance.js +831 -272
- package/dist/cli-delegate/cmd_explain.js +4 -2
- package/dist/cli-delegate/cmd_handoff.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +13 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +5 -5
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +1274 -571
- package/dist/install/install.mjs +1 -0
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1218 -632
- package/dist/scripts/_lib/installed_lock.js +1 -1
- package/dist/server/io/yamlIO.js +50 -0
- package/package.json +1 -1
- package/src/config/continuity-surface.json +7 -0
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/host-injection-effect.json +1 -1
- package/src/config/preamble-payload-budget.json +12 -7
- package/src/config/preamble-payload-exceptions.json +28 -0
- package/src/scripts/_dispatch.bash +3 -1
- package/src/scripts/_lib/base_ref_payload.ts +315 -0
- package/src/scripts/_lib/dropped_decision.ts +142 -0
- package/src/scripts/_lib/installed_lock.ts +1 -1
- package/src/scripts/_lib/measured_payload_ceiling.ts +424 -0
- package/src/scripts/_lib/payload_catalogue_completeness.ts +211 -0
- package/src/scripts/_lib/release_findings_ingest.ts +324 -0
- package/src/scripts/_lib/release_tag_dispatch.ts +63 -0
- package/src/scripts/_lib/review_baseline.ts +254 -0
- package/src/scripts/_lib/review_skipped_record.ts +61 -1
- package/src/scripts/_lib/standing_bound_ratchet.ts +216 -103
- package/src/scripts/_lib/turn_end_refusals.ts +51 -4
- package/src/scripts/check_beta_review_markers.ts +35 -4
- package/src/scripts/check_finding_dispositions.ts +167 -16
- package/src/scripts/check_held_object_arrivals.ts +381 -0
- package/src/scripts/check_preamble_payload_budget.ts +399 -122
- package/src/scripts/check_release_published.ts +1 -1
- package/src/scripts/check_reply_consistency.ts +86 -17
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +39 -3
- package/src/scripts/hooks/block_kernel_rule_writes.ts +12 -2
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/end_review_nudge_hook.ts +101 -21
- package/src/scripts/hooks/review_baseline_hook.ts +130 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +232 -14
- package/src/scripts/hooks_doctor.ts +4 -1
- package/src/scripts/install.ts +1 -1
- package/src/scripts/interruption_report.ts +8 -2
- package/src/scripts/janitor.ts +8 -1
- package/src/scripts/measure_turn_end_gate.ts +302 -5
- package/src/scripts/release.ts +52 -64
- package/src/scripts/release_drill.ts +193 -14
- package/src/scripts/release_env.ts +1 -1
- package/src/scripts/release_publication.ts +1 -1
- package/src/scripts/report_held_object_arrivals.ts +456 -0
- package/src/scripts/sync_agent_settings.ts +140 -18
- package/src/server/io/yamlIO.ts +48 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session review baseline — the shape, the path, and the subtraction.
|
|
3
|
+
*
|
|
4
|
+
* WHY IT EXISTS. `end_review_nudge_hook` measures "did this session mutate
|
|
5
|
+
* enough to owe a review" as `git diff --numstat HEAD` plus untracked non-doc
|
|
6
|
+
* files, and its own header records the defect that reading produces: an
|
|
7
|
+
* uncommitted change that PREDATES the session is charged to the session. The
|
|
8
|
+
* header also records why it was not fixed — "no such baseline exists anywhere a
|
|
9
|
+
* `stop` concern can read it (recording one would be new session-wide state, out
|
|
10
|
+
* of this roadmap phase's one-concern scope)".
|
|
11
|
+
*
|
|
12
|
+
* That reason went stale. `session_start` is bound on seven hosts carrying
|
|
13
|
+
* fourteen concerns (`hook_manifest.yaml`), so a per-session baseline has a slot
|
|
14
|
+
* to be written from. The measured cost of leaving it: on an accumulator branch
|
|
15
|
+
* the nudge fired on 1,771 lines of another session's uncommitted work and
|
|
16
|
+
* became noise, which is worse than silence — an advisory that always fires is
|
|
17
|
+
* an advisory nobody reads.
|
|
18
|
+
*
|
|
19
|
+
* ONE MODULE FOR BOTH SIDES, deliberately. The writer is a `session_start`
|
|
20
|
+
* concern and the reader is a `stop` concern; if each owned its own idea of the
|
|
21
|
+
* path or the field names, the reader would silently find nothing and the whole
|
|
22
|
+
* mechanism would degrade to today's behaviour with no signal. That is the same
|
|
23
|
+
* argument `turn_end_refusals.ts` makes for its own shared path, and it is why
|
|
24
|
+
* `BASELINE_STATE_REL` is here rather than in either hook.
|
|
25
|
+
*
|
|
26
|
+
* ONE SELF-REFERENCE, named because it is invisible: the file this concern
|
|
27
|
+
* writes is itself an untracked non-doc file, so in a workspace where
|
|
28
|
+
* `agents/runtime/` is NOT gitignored it is counted by the very measurement it
|
|
29
|
+
* feeds — about six lines, once. This repository ignores that path, so the
|
|
30
|
+
* effect is zero here; a consumer whose ignore file does not cover it sees the
|
|
31
|
+
* nudge's count six lines high for the session.
|
|
32
|
+
*
|
|
33
|
+
* WHAT IT IS NOT. It is not a change set: no paths, no fingerprints, no patch.
|
|
34
|
+
* It answers exactly the question the nudge asks — how many non-doc lines were
|
|
35
|
+
* already dirty when this session began — and nothing else. A path-level ledger
|
|
36
|
+
* is a bigger object with a bigger privacy surface, and nothing in the nudge
|
|
37
|
+
* consumes one.
|
|
38
|
+
*/
|
|
39
|
+
import { spawnSync } from 'node:child_process';
|
|
40
|
+
import * as fs from 'node:fs';
|
|
41
|
+
import * as path from 'node:path';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* HEAD at this moment, or null.
|
|
45
|
+
*
|
|
46
|
+
* It lives HERE rather than in the writer because both sides need it and the
|
|
47
|
+
* writer already imports from the reader's module: putting it in the hook would
|
|
48
|
+
* make `end_review_nudge_hook → review_baseline_hook → end_review_nudge_hook` a
|
|
49
|
+
* cycle. The shared module is the only place with no edge back.
|
|
50
|
+
*
|
|
51
|
+
* Null is a real answer and not an error path — a workspace that is not a git
|
|
52
|
+
* repository, or one with no commit yet, has no HEAD, and `applyBaseline`
|
|
53
|
+
* compares null to null happily. What it must never do is throw: one caller runs
|
|
54
|
+
* on session start and the other on the stop path.
|
|
55
|
+
*/
|
|
56
|
+
export function currentHeadSha(cwd: string): string | null {
|
|
57
|
+
try {
|
|
58
|
+
const r = spawnSync('git', ['rev-parse', 'HEAD'], { cwd, encoding: 'utf8' });
|
|
59
|
+
if (r.status !== 0) return null;
|
|
60
|
+
const out = (r.stdout ?? '').trim();
|
|
61
|
+
return out === '' ? null : out;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** State directory, relative to a workspace root. */
|
|
68
|
+
export const BASELINE_STATE_REL = path.join('agents', 'runtime', 'state', 'review-baseline');
|
|
69
|
+
|
|
70
|
+
export function baselineStateDir(workspaceRoot: string): string {
|
|
71
|
+
return path.join(workspaceRoot, BASELINE_STATE_REL);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function baselineStateFile(workspaceRoot: string, sessionKey: string): string {
|
|
75
|
+
return path.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Whether a line count is an exact sum or the capped, guaranteed-over-threshold
|
|
80
|
+
* approximation `end_review_nudge_hook` returns past `UNTRACKED_FILE_CAP`.
|
|
81
|
+
*
|
|
82
|
+
* Structurally identical to that module's `MutationMeasure` and deliberately
|
|
83
|
+
* NOT imported from it: this module is the shared floor both hooks sit on, and
|
|
84
|
+
* importing the consumer would put the cycle back that `currentHeadSha` lives
|
|
85
|
+
* here to avoid.
|
|
86
|
+
*/
|
|
87
|
+
export type BaselineMeasure = 'exact' | 'capped_approximation';
|
|
88
|
+
|
|
89
|
+
/** What `session_start` records, and `stop` reads back. */
|
|
90
|
+
export interface ReviewBaseline {
|
|
91
|
+
/** HEAD at session start, or null where there is no repo / no commit yet. */
|
|
92
|
+
head_sha: string | null;
|
|
93
|
+
/** Non-doc mutated lines already present when the session began. */
|
|
94
|
+
baseline_lines: number;
|
|
95
|
+
/**
|
|
96
|
+
* Whether `baseline_lines` is exact or the capped approximation.
|
|
97
|
+
*
|
|
98
|
+
* It is recorded because subtracting one kind from the other is arithmetic
|
|
99
|
+
* over two different quantities — the same defect `head_sha` catches on the
|
|
100
|
+
* other axis. The capped path returns `THRESHOLD + 1 + tracked`, a synthetic
|
|
101
|
+
* number chosen to be over the bar rather than to be true, so a session that
|
|
102
|
+
* starts past the cap and then cleans up would have a real `exact` stop
|
|
103
|
+
* measurement reduced by a number that never counted anything.
|
|
104
|
+
*
|
|
105
|
+
* Optional: a record written before this field existed has no measure, and
|
|
106
|
+
* defaulting it to `exact` would assert the more permissive of the two on no
|
|
107
|
+
* evidence. Absent reads as unknown and refuses to subtract.
|
|
108
|
+
*/
|
|
109
|
+
measure?: BaselineMeasure;
|
|
110
|
+
written_at: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Why a `stop` read could not subtract. Each value is written into the
|
|
115
|
+
* `review_skipped` telemetry row, because a fallback nobody can see is
|
|
116
|
+
* indistinguishable from the mechanism never having shipped.
|
|
117
|
+
*/
|
|
118
|
+
export type BaselineFallback =
|
|
119
|
+
| 'absent' // no file — a host without session_start, or a session older than this
|
|
120
|
+
| 'unreadable' // present and malformed, or unreadable for any non-ENOENT reason
|
|
121
|
+
| 'head-moved' // the session committed, so the baseline's denominator is gone
|
|
122
|
+
| 'mixed-measure'; // one side is a capped approximation and the other is not
|
|
123
|
+
|
|
124
|
+
export type BaselineOutcome =
|
|
125
|
+
| { readonly applied: true; readonly baseline: ReviewBaseline; readonly lines: number }
|
|
126
|
+
| { readonly applied: false; readonly fallback: BaselineFallback; readonly lines: number };
|
|
127
|
+
|
|
128
|
+
export function parseBaseline(raw: string): ReviewBaseline | null {
|
|
129
|
+
let decoded: unknown;
|
|
130
|
+
try {
|
|
131
|
+
decoded = JSON.parse(raw);
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
if (typeof decoded !== 'object' || decoded === null || Array.isArray(decoded)) return null;
|
|
136
|
+
const o = decoded as Record<string, unknown>;
|
|
137
|
+
const lines = o['baseline_lines'];
|
|
138
|
+
if (typeof lines !== 'number' || !Number.isFinite(lines) || lines < 0) return null;
|
|
139
|
+
const written = o['written_at'];
|
|
140
|
+
if (typeof written !== 'string') return null;
|
|
141
|
+
const head = o['head_sha'];
|
|
142
|
+
const measure = o['measure'];
|
|
143
|
+
return {
|
|
144
|
+
head_sha: typeof head === 'string' && head !== '' ? head : null,
|
|
145
|
+
baseline_lines: lines,
|
|
146
|
+
...(measure === 'exact' || measure === 'capped_approximation' ? { measure } : {}),
|
|
147
|
+
written_at: written,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The baseline, or WHICH of the two not-found states applies.
|
|
153
|
+
*
|
|
154
|
+
* `absent` and `unreadable` are kept apart all the way to the telemetry row,
|
|
155
|
+
* and collapsing them would have been the easy shape: both mean "cannot
|
|
156
|
+
* subtract", both fall back identically. They are different findings. `absent`
|
|
157
|
+
* is the expected steady state on a host with no `session_start` slot and on
|
|
158
|
+
* every session that started before this shipped; `unreadable` is a corrupt
|
|
159
|
+
* write, and a rate of it above noise is a defect in this mechanism rather than
|
|
160
|
+
* a property of the host. A single bucket would hide the second inside the
|
|
161
|
+
* first, which is the population it is smallest against.
|
|
162
|
+
*/
|
|
163
|
+
export function readBaseline(
|
|
164
|
+
workspaceRoot: string,
|
|
165
|
+
sessionKey: string,
|
|
166
|
+
): ReviewBaseline | 'absent' | 'unreadable' {
|
|
167
|
+
const file = baselineStateFile(workspaceRoot, sessionKey);
|
|
168
|
+
let raw: string;
|
|
169
|
+
try {
|
|
170
|
+
raw = fs.readFileSync(file, 'utf-8');
|
|
171
|
+
} catch (exc) {
|
|
172
|
+
// ONLY a genuinely missing file is `absent`. Every other IO failure —
|
|
173
|
+
// EACCES, EISDIR, EMFILE — is a defect in this mechanism, which is the
|
|
174
|
+
// population `unreadable` exists to make visible; routing them all to
|
|
175
|
+
// `absent` would bury them inside the one bucket that is legitimately
|
|
176
|
+
// large on every host without a session_start slot.
|
|
177
|
+
const code = (exc as NodeJS.ErrnoException | null)?.code;
|
|
178
|
+
return code === 'ENOENT' ? 'absent' : 'unreadable';
|
|
179
|
+
}
|
|
180
|
+
return parseBaseline(raw) ?? 'unreadable';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Apply the baseline to a measured line count.
|
|
185
|
+
*
|
|
186
|
+
* FAIL OPEN, NEVER SILENT — the three not-applied branches all return the
|
|
187
|
+
* UNSUBTRACTED count, so every failure mode of this mechanism degrades to
|
|
188
|
+
* exactly the behaviour that shipped before it. The alternative, returning zero
|
|
189
|
+
* or refusing to fire, would let a missing state file silence a genuine review
|
|
190
|
+
* obligation, and a safety advisory must not be disarmed by its own
|
|
191
|
+
* instrumentation failing.
|
|
192
|
+
*
|
|
193
|
+
* `head-moved` is the branch worth naming, and worth naming honestly: it is
|
|
194
|
+
* TERMINAL for the session. A session that committed mid-run moved HEAD, so
|
|
195
|
+
* `git diff HEAD` now measures against a different base than the baseline did
|
|
196
|
+
* and the subtraction is arithmetic over two different quantities. It is caught
|
|
197
|
+
* rather than absorbed, because absorbing it would UNDER-report on exactly the
|
|
198
|
+
* sessions that did the most work — but the baseline is written once per
|
|
199
|
+
* session and never refreshed, so from the first commit onward that session is
|
|
200
|
+
* charged for the whole pre-existing dirty tree again. The mechanism's useful
|
|
201
|
+
* life is "until this session's first commit", which is stated here rather than
|
|
202
|
+
* discovered later. Re-baselining after a commit is the obvious repair and is
|
|
203
|
+
* NOT taken here: a second write has to distinguish "HEAD moved because we
|
|
204
|
+
* committed our own work" from "a peer session committed underneath us", and
|
|
205
|
+
* nothing in a stop payload answers that.
|
|
206
|
+
*
|
|
207
|
+
* `mixed-measure` is the same defect on the third axis, and it covers the
|
|
208
|
+
* SYMMETRIC case too. Past `UNTRACKED_FILE_CAP` the measurement is a synthetic
|
|
209
|
+
* `THRESHOLD + 1 + tracked` chosen to be over the bar rather than to be true, so
|
|
210
|
+
* subtracting it from — or out of — an exact count is meaningless in both
|
|
211
|
+
* directions; and subtracting one capped reading from another cancels the
|
|
212
|
+
* constant, which is worse than meaningless because it silences the nudge. The
|
|
213
|
+
* name is kept over a more literal `not-both-exact` because the fallback's
|
|
214
|
+
* subject is the measure, and a telemetry enum is read far more often than it is
|
|
215
|
+
* renamed.
|
|
216
|
+
*
|
|
217
|
+
* The clamp at zero is not cosmetic either: a session that reverts part of a
|
|
218
|
+
* pre-existing dirty tree measures below its own baseline, and a negative
|
|
219
|
+
* comparison against the threshold would read as "no mutation" for a session
|
|
220
|
+
* that deleted a thousand lines.
|
|
221
|
+
*/
|
|
222
|
+
export function applyBaseline(
|
|
223
|
+
measuredLines: number,
|
|
224
|
+
baseline: ReviewBaseline | 'absent' | 'unreadable',
|
|
225
|
+
headSha: () => string | null,
|
|
226
|
+
measuredMeasure: BaselineMeasure = 'exact',
|
|
227
|
+
): BaselineOutcome {
|
|
228
|
+
if (baseline === 'absent' || baseline === 'unreadable') {
|
|
229
|
+
return { applied: false, fallback: baseline, lines: measuredLines };
|
|
230
|
+
}
|
|
231
|
+
// The measure check runs BEFORE the HEAD check because it needs no subprocess.
|
|
232
|
+
// `headSha` is a thunk for the same reason: on the two fallback branches above
|
|
233
|
+
// — the steady state on every host without the nudge, and on every session
|
|
234
|
+
// before its first baseline write — the value is discarded, and evaluating it
|
|
235
|
+
// eagerly spent an 11-14 ms `git rev-parse` on each of those stops.
|
|
236
|
+
// Refused when the two measures DISAGREE, and equally when both are capped.
|
|
237
|
+
// The symmetric case is the dangerous one and the first version missed it:
|
|
238
|
+
// the capped path returns `THRESHOLD + 1 + tracked`, so two capped readings
|
|
239
|
+
// cancel their synthetic constant and an untracked-only session that started
|
|
240
|
+
// past the cap subtracts to ZERO — silencing the nudge, which is the one
|
|
241
|
+
// direction this module's own header forbids. Only `exact` minus `exact` is
|
|
242
|
+
// arithmetic over two counts of the same thing.
|
|
243
|
+
if (baseline.measure !== 'exact' || measuredMeasure !== 'exact') {
|
|
244
|
+
return { applied: false, fallback: 'mixed-measure', lines: measuredLines };
|
|
245
|
+
}
|
|
246
|
+
if (baseline.head_sha !== headSha()) {
|
|
247
|
+
return { applied: false, fallback: 'head-moved', lines: measuredLines };
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
applied: true,
|
|
251
|
+
baseline,
|
|
252
|
+
lines: Math.max(0, measuredLines - baseline.baseline_lines),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
@@ -34,6 +34,49 @@
|
|
|
34
34
|
*/
|
|
35
35
|
export type MutationMeasure = 'exact' | 'capped_approximation';
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Whether `diff_lines` had this session's start-of-session baseline subtracted,
|
|
39
|
+
* and when it did not, WHY.
|
|
40
|
+
*
|
|
41
|
+
* A closed enum, never free text, so the privacy floor below still holds by
|
|
42
|
+
* construction. It exists because the subtraction's failure modes all fall back
|
|
43
|
+
* to the unsubtracted count — which is the safe direction and also an invisible
|
|
44
|
+
* one: without this field, a line reading 1,771 cannot be told apart from a
|
|
45
|
+
* session that really did mutate 1,771 lines.
|
|
46
|
+
*
|
|
47
|
+
* WHAT IT MEASURES, and the half it cannot. This line is written only when the
|
|
48
|
+
* nudge FIRES, so the distribution here is the fallback rate among firing
|
|
49
|
+
* sessions. The sessions the subtraction silenced — the mechanism working —
|
|
50
|
+
* produce no line at all, so the silence rate is NOT observable from this
|
|
51
|
+
* stream and no clause here should be read as claiming it is. Recording a row
|
|
52
|
+
* for a nudge that did not fire would put a `review_skipped` event in the audit
|
|
53
|
+
* log for a session where no review was skipped, which is a worse defect than
|
|
54
|
+
* the blind spot; measuring it properly needs a counter of its own, and that is
|
|
55
|
+
* a decision rather than an oversight.
|
|
56
|
+
*
|
|
57
|
+
* `absent` is the expected steady state on a host with no `session_start` slot
|
|
58
|
+
* and on every session that predates the baseline concern; `unreadable` is a
|
|
59
|
+
* corrupt write and a rate above noise is a defect in the mechanism itself;
|
|
60
|
+
* `head_moved` is a session that committed mid-run, which moves `git diff
|
|
61
|
+
* HEAD`'s base and makes the subtraction arithmetic over two different
|
|
62
|
+
* quantities; `mixed_measure` is the same failure on the capped-approximation
|
|
63
|
+
* axis, where one side is a synthetic over-threshold number rather than a count.
|
|
64
|
+
*/
|
|
65
|
+
export type BaselineApplication =
|
|
66
|
+
| 'applied'
|
|
67
|
+
| 'absent'
|
|
68
|
+
| 'unreadable'
|
|
69
|
+
| 'head_moved'
|
|
70
|
+
| 'mixed_measure';
|
|
71
|
+
|
|
72
|
+
const BASELINE_APPLICATIONS: readonly BaselineApplication[] = [
|
|
73
|
+
'applied',
|
|
74
|
+
'absent',
|
|
75
|
+
'unreadable',
|
|
76
|
+
'head_moved',
|
|
77
|
+
'mixed_measure',
|
|
78
|
+
];
|
|
79
|
+
|
|
37
80
|
import { appliedIds } from './audit_field_provenance.js';
|
|
38
81
|
import type { NoFreeForm } from './runtime_journal.js';
|
|
39
82
|
import { type PrivacyClass } from './privacy_class.js';
|
|
@@ -51,6 +94,13 @@ export interface ReviewSkippedInput {
|
|
|
51
94
|
diff_lines: number;
|
|
52
95
|
/** Whether `diff_lines` is exact or a capped approximation — see above. */
|
|
53
96
|
mutation_measure: MutationMeasure;
|
|
97
|
+
/**
|
|
98
|
+
* Whether the session baseline was subtracted from `diff_lines`, and if not
|
|
99
|
+
* why. OPTIONAL, and absence is honest rather than a default: a line written
|
|
100
|
+
* by a producer that predates the baseline mechanism recorded nothing about
|
|
101
|
+
* it, and writing `absent` for those would assert a measurement nobody made.
|
|
102
|
+
*/
|
|
103
|
+
baseline?: BaselineApplication | undefined;
|
|
54
104
|
/** ISO-8601 UTC timestamp; caller supplies (keeps this fn pure/deterministic). */
|
|
55
105
|
ts: string;
|
|
56
106
|
/** Stable id (ULID, UUID, or content hash); caller supplies. */
|
|
@@ -103,6 +153,9 @@ export function buildReviewSkippedLine(input: ReviewSkippedInput): BuiltReviewSk
|
|
|
103
153
|
}
|
|
104
154
|
if (!input.ts) errors.push('ts (ISO-8601 UTC) is required');
|
|
105
155
|
if (!input.id) errors.push('id (ULID, UUID, or content hash) is required');
|
|
156
|
+
if (input.baseline !== undefined && !BASELINE_APPLICATIONS.includes(input.baseline)) {
|
|
157
|
+
errors.push(`baseline must be one of ${BASELINE_APPLICATIONS.join(', ')}`);
|
|
158
|
+
}
|
|
106
159
|
|
|
107
160
|
if (errors.length) return { line: null, errors };
|
|
108
161
|
|
|
@@ -131,7 +184,14 @@ export function buildReviewSkippedLine(input: ReviewSkippedInput): BuiltReviewSk
|
|
|
131
184
|
persona: null,
|
|
132
185
|
input_kind: 'prompt',
|
|
133
186
|
type: 'note',
|
|
134
|
-
review_skipped: {
|
|
187
|
+
review_skipped: {
|
|
188
|
+
diff_lines: input.diff_lines,
|
|
189
|
+
mutation_measure: input.mutation_measure,
|
|
190
|
+
// Omitted, not defaulted, when the producer recorded nothing —
|
|
191
|
+
// audit-log-v1's absent-versus-present split is the same one
|
|
192
|
+
// `skills_applied` uses above, for the same reason.
|
|
193
|
+
...(input.baseline === undefined ? {} : { baseline: input.baseline }),
|
|
194
|
+
},
|
|
135
195
|
};
|
|
136
196
|
|
|
137
197
|
return { line, errors: [] };
|