@claude-flow/cli 3.63.9 → 3.65.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/.claude/helpers/helpers.manifest.json +2 -2
- package/catalog-manifest.json +3 -3
- package/dist/src/commands/cost.d.ts +18 -0
- package/dist/src/commands/cost.js +195 -0
- package/dist/src/commands/doctor-auto-approve.d.ts +9 -0
- package/dist/src/commands/doctor-auto-approve.js +32 -0
- package/dist/src/commands/doctor.js +28 -2
- package/dist/src/commands/hive-mind-worker.d.ts +2 -0
- package/dist/src/commands/hive-mind-worker.js +9 -3
- package/dist/src/commands/hive-mind.js +15 -1
- package/dist/src/commands/hooks.js +45 -0
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/init.js +76 -2
- package/dist/src/commands/mods.js +13 -0
- package/dist/src/commands/plugins-paths.d.ts +13 -0
- package/dist/src/commands/plugins-paths.js +55 -0
- package/dist/src/commands/plugins-verify.d.ts +9 -0
- package/dist/src/commands/plugins-verify.js +92 -0
- package/dist/src/commands/plugins.js +13 -1
- package/dist/src/cost/attribution.d.ts +35 -0
- package/dist/src/cost/attribution.js +101 -0
- package/dist/src/cost/budget-file.d.ts +57 -0
- package/dist/src/cost/budget-file.js +113 -0
- package/dist/src/cost/litellm.d.ts +69 -0
- package/dist/src/cost/litellm.js +135 -0
- package/dist/src/init/executor.d.ts +7 -2
- package/dist/src/init/executor.js +64 -10
- package/dist/src/init/helper-commands.d.ts +16 -0
- package/dist/src/init/helper-commands.js +73 -0
- package/dist/src/init/risky-settings-gate.d.ts +39 -0
- package/dist/src/init/risky-settings-gate.js +130 -0
- package/dist/src/init/settings-risk-scanner.d.ts +35 -0
- package/dist/src/init/settings-risk-scanner.js +85 -0
- package/dist/src/init/types.d.ts +12 -0
- package/dist/src/mcp-tools/capability-brain.d.ts +1 -1
- package/dist/src/mcp-tools/capability-brain.js +1 -1
- package/dist/src/mcp-tools/hooks-tools.d.ts +1 -0
- package/dist/src/mcp-tools/hooks-tools.js +38 -0
- package/dist/src/memory/verified-guards.d.ts +70 -0
- package/dist/src/memory/verified-guards.js +101 -0
- package/dist/src/memory/verified-outcomes.d.ts +228 -0
- package/dist/src/memory/verified-outcomes.js +372 -0
- package/dist/src/memory/verified-reverify.d.ts +63 -0
- package/dist/src/memory/verified-reverify.js +174 -0
- package/dist/src/missions/schemas.d.ts +36 -36
- package/dist/src/mods/apply.d.ts +2 -0
- package/dist/src/mods/apply.js +68 -8
- package/dist/src/mods/command-registry/schema.d.ts +222 -222
- package/dist/src/mods/compact-snapshots.d.ts +21 -0
- package/dist/src/mods/compact-snapshots.js +45 -0
- package/dist/src/mods/headless-check.d.ts +58 -0
- package/dist/src/mods/headless-check.js +137 -0
- package/dist/src/mods/headless-plugins.d.ts +44 -0
- package/dist/src/mods/headless-plugins.js +92 -0
- package/dist/src/mods/install-trust.d.ts +28 -0
- package/dist/src/mods/install-trust.js +127 -0
- package/dist/src/mods/install.d.ts +19 -0
- package/dist/src/mods/install.js +38 -1
- package/dist/src/mods/plugin-paths.d.ts +60 -0
- package/dist/src/mods/plugin-paths.js +131 -0
- package/dist/src/mods/plugin-repair.d.ts +8 -0
- package/dist/src/mods/plugin-repair.js +7 -0
- package/dist/src/plugins/marketplace-trust/scan.d.ts +23 -0
- package/dist/src/plugins/marketplace-trust/scan.js +255 -0
- package/dist/src/plugins/marketplace-trust/signature.d.ts +53 -0
- package/dist/src/plugins/marketplace-trust/signature.js +140 -0
- package/dist/src/plugins/marketplace-trust/verify-status.d.ts +42 -0
- package/dist/src/plugins/marketplace-trust/verify-status.js +110 -0
- package/dist/src/services/headless-worker-executor.d.ts +2 -0
- package/dist/src/services/headless-worker-executor.js +13 -3
- package/package.json +2 -1
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +8 -1
- package/plugins/ruflo-metaharness/CHANGELOG.md +6 -0
- package/plugins/ruflo-metaharness/README.md +14 -1
- package/plugins/ruflo-metaharness/agents/metaharness-architect.md +2 -0
- package/plugins/ruflo-metaharness/hooks/hooks.json +16 -2
- package/plugins/ruflo-metaharness/scripts/_redblue.mjs +12 -2
- package/plugins/ruflo-metaharness/scripts/_secret-env.cjs +50 -0
- package/plugins/ruflo-metaharness/scripts/_secret.mjs +35 -0
- package/plugins/ruflo-metaharness/scripts/smoke.sh +7 -7
- package/plugins/ruflo-metaharness/skills/harness-bench/SKILL.md +2 -1
- package/plugins/ruflo-metaharness/skills/harness-drift-from-history/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-evolve/SKILL.md +2 -1
- package/plugins/ruflo-metaharness/skills/harness-gepa/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-learn/SKILL.md +2 -1
- package/plugins/ruflo-metaharness/skills/harness-mint/SKILL.md +2 -1
- package/plugins/ruflo-metaharness/skills/harness-oia-audit/SKILL.md +1 -0
- package/plugins/ruflo-metaharness/skills/harness-score/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-security-bench/SKILL.md +2 -1
- package/plugins/ruflo-metaharness/skills/harness-similarity/SKILL.md +1 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verified-outcome learning loop, CLI half (#4581, ADR-502).
|
|
3
|
+
*
|
|
4
|
+
* The ruflo-mods `learningLoop` option records one trajectory per main-loop
|
|
5
|
+
* turn in `.claude-flow/data/verified-trajectories.jsonl`. A mod may not run
|
|
6
|
+
* processes or call MCP tools, so it can only observe: a turn is
|
|
7
|
+
* `verified-success` or `verified-failure` when a recognised test, build or
|
|
8
|
+
* typecheck command ran after the turn's last edit and its process exit
|
|
9
|
+
* status said so; anything else is `unknown`.
|
|
10
|
+
*
|
|
11
|
+
* This module is the only place those records reach SONA:
|
|
12
|
+
* - `verified-*` records train SONA (`processTrajectoryOutcome`);
|
|
13
|
+
* - `unknown` records are counted and never train;
|
|
14
|
+
* - each record is ingested once (an id ledger, never a rewrite of the
|
|
15
|
+
* mod's file, which the mod appends to concurrently);
|
|
16
|
+
* - task text is PII-redacted before it reaches SONA's keyword store.
|
|
17
|
+
*
|
|
18
|
+
* It then writes `.claude-flow/data/learned-lessons.json`: patterns that
|
|
19
|
+
* verified outcomes produced, those at or above the SONA routing gate as
|
|
20
|
+
* lessons, and repeated verified failures as cautions. The mod reads that
|
|
21
|
+
* file and adds at most three cited lessons to a prompt.
|
|
22
|
+
*
|
|
23
|
+
* ADR-503 hardens it against gamed signals (verified-guards.ts,
|
|
24
|
+
* verified-reverify.ts): a tainted success does not train unless the project
|
|
25
|
+
* allows its taint class, an opt-in re-verify can downgrade a success before
|
|
26
|
+
* it trains, and an anomaly gate holds suspicious patterns back from the
|
|
27
|
+
* prompt. Every one of these can only downgrade.
|
|
28
|
+
*/
|
|
29
|
+
import { type HoldReason, type LoopConfig, type PatternStats, type Taint } from './verified-guards.js';
|
|
30
|
+
import { type ReverifyRunner } from './verified-reverify.js';
|
|
31
|
+
export declare const VERIFIED_PATH: string;
|
|
32
|
+
export declare const LEDGER_PATH: string;
|
|
33
|
+
export declare const LESSONS_PATH: string;
|
|
34
|
+
/** The SONA routing gate (sona-optimizer.ts `SONA_ROUTING_GATE`); a lesson below it never influences routing. */
|
|
35
|
+
export declare const LESSON_GATE = 0.6;
|
|
36
|
+
/** Ingested ids remembered; the mod keeps at most 500 lines, so this always covers the whole file. */
|
|
37
|
+
export declare const MAX_LEDGER_IDS = 2000;
|
|
38
|
+
/** A file past this many bytes is not read (the mod caps its own at far less). */
|
|
39
|
+
export declare const MAX_FILE_BYTES: number;
|
|
40
|
+
export declare const MAX_LESSONS = 20;
|
|
41
|
+
/** Verified failures (and more failures than successes) before a pattern becomes a caution. */
|
|
42
|
+
export declare const CAUTION_MIN_FAILURES = 2;
|
|
43
|
+
export type Outcome = 'verified-success' | 'verified-failure' | 'unknown';
|
|
44
|
+
export type VerifierKind = 'test' | 'build' | 'typecheck' | 'lint';
|
|
45
|
+
export interface VerifiedRecord {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
readonly task: string;
|
|
48
|
+
readonly agent: string;
|
|
49
|
+
readonly outcome: Outcome;
|
|
50
|
+
readonly verifier: {
|
|
51
|
+
readonly kind: VerifierKind;
|
|
52
|
+
readonly command: string;
|
|
53
|
+
readonly passed: boolean;
|
|
54
|
+
} | null;
|
|
55
|
+
readonly steps: ReadonlyArray<{
|
|
56
|
+
readonly tool: string;
|
|
57
|
+
readonly ok: boolean;
|
|
58
|
+
}>;
|
|
59
|
+
readonly edits: number;
|
|
60
|
+
/** ADR-503 §2.1: what the turn did to its own check; only a `verified-success` carries any. */
|
|
61
|
+
readonly taint: readonly Taint[];
|
|
62
|
+
/** The mod process that recorded it, when the mod wrote one (ADR-503 §2.4). */
|
|
63
|
+
readonly session: string | null;
|
|
64
|
+
readonly startedAt: number;
|
|
65
|
+
readonly endedAt: number;
|
|
66
|
+
}
|
|
67
|
+
export interface IngestLedger {
|
|
68
|
+
version: 1;
|
|
69
|
+
ids: string[];
|
|
70
|
+
/** mtime of the jsonl at the last ingest, so a caller can skip an unchanged file cheaply. */
|
|
71
|
+
lastMtimeMs: number;
|
|
72
|
+
totals: {
|
|
73
|
+
verifiedSuccess: number;
|
|
74
|
+
verifiedFailure: number;
|
|
75
|
+
unknown: number;
|
|
76
|
+
rejected: number;
|
|
77
|
+
tainted: number;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Pattern keys verified outcomes trained, with the last verifier command that decided them (the lesson's evidence)
|
|
81
|
+
* and what the anomaly gate needs (ADR-503 §2.4).
|
|
82
|
+
*/
|
|
83
|
+
patterns: Record<string, {
|
|
84
|
+
evidence: string;
|
|
85
|
+
at: number;
|
|
86
|
+
stats: PatternStats;
|
|
87
|
+
}>;
|
|
88
|
+
/** ADR-503 §2.3: the rotation cursor and the latest re-verifications (which commit, which check, what happened). */
|
|
89
|
+
reverify: {
|
|
90
|
+
cursor: number;
|
|
91
|
+
log: ReverifyLogEntry[];
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export interface ReverifyLogEntry {
|
|
95
|
+
readonly id: string;
|
|
96
|
+
readonly check: string | null;
|
|
97
|
+
readonly commit: string | null;
|
|
98
|
+
readonly status: 'pass' | 'fail' | 'timeout' | 'error';
|
|
99
|
+
readonly at: number;
|
|
100
|
+
}
|
|
101
|
+
export declare const MAX_REVERIFY_LOG = 50;
|
|
102
|
+
export interface Lesson {
|
|
103
|
+
readonly id: string;
|
|
104
|
+
readonly kind: 'lesson' | 'caution';
|
|
105
|
+
readonly agent: string;
|
|
106
|
+
readonly keywords: string[];
|
|
107
|
+
readonly confidence: number;
|
|
108
|
+
readonly successes: number;
|
|
109
|
+
readonly failures: number;
|
|
110
|
+
readonly evidence: string;
|
|
111
|
+
}
|
|
112
|
+
export interface LessonsFile {
|
|
113
|
+
version: 1;
|
|
114
|
+
gate: number;
|
|
115
|
+
generatedAt: number;
|
|
116
|
+
lessons: Lesson[];
|
|
117
|
+
/** Verified patterns still below the gate, for the cold-start progress line. */
|
|
118
|
+
belowGate: number;
|
|
119
|
+
/** Patterns at or above the gate that the anomaly gate kept out of the prompt (ADR-503 §2.4). */
|
|
120
|
+
held: Array<{
|
|
121
|
+
id: string;
|
|
122
|
+
reason: HoldReason;
|
|
123
|
+
}>;
|
|
124
|
+
}
|
|
125
|
+
export interface IngestResult {
|
|
126
|
+
/** `tainted`: successes whose taint the project did not allow, counted and never trained (ADR-503 §2.2). */
|
|
127
|
+
ingested: {
|
|
128
|
+
verifiedSuccess: number;
|
|
129
|
+
verifiedFailure: number;
|
|
130
|
+
unknown: number;
|
|
131
|
+
rejected: number;
|
|
132
|
+
tainted: number;
|
|
133
|
+
};
|
|
134
|
+
trained: number;
|
|
135
|
+
skipped: 'no-file' | 'unchanged' | 'too-large' | null;
|
|
136
|
+
lessons: number;
|
|
137
|
+
cautions: number;
|
|
138
|
+
belowGate: number;
|
|
139
|
+
held: LessonsFile['held'];
|
|
140
|
+
/**
|
|
141
|
+
* ADR-503 §2.3: checks run, successes that passed or were downgraded to unknown, and successes left for an explicit
|
|
142
|
+
* ingest because this caller may not run project code.
|
|
143
|
+
*/
|
|
144
|
+
reverify: {
|
|
145
|
+
enabled: boolean;
|
|
146
|
+
ran: number;
|
|
147
|
+
passed: number;
|
|
148
|
+
downgraded: number;
|
|
149
|
+
deferred: number;
|
|
150
|
+
};
|
|
151
|
+
totals: IngestLedger['totals'];
|
|
152
|
+
sonaAvailable: boolean;
|
|
153
|
+
}
|
|
154
|
+
/** The SONA surface ingest needs; injectable so tests never touch `.swarm/`. */
|
|
155
|
+
export interface SonaLike {
|
|
156
|
+
processTrajectoryOutcome(outcome: {
|
|
157
|
+
trajectoryId: string;
|
|
158
|
+
task: string;
|
|
159
|
+
agent: string;
|
|
160
|
+
success: boolean;
|
|
161
|
+
steps?: Array<{
|
|
162
|
+
action: string;
|
|
163
|
+
result: string;
|
|
164
|
+
quality: number;
|
|
165
|
+
timestamp: string;
|
|
166
|
+
}>;
|
|
167
|
+
duration?: number;
|
|
168
|
+
feedback?: string;
|
|
169
|
+
}): {
|
|
170
|
+
learned: boolean;
|
|
171
|
+
patternKey: string;
|
|
172
|
+
confidence: number;
|
|
173
|
+
};
|
|
174
|
+
exportPatterns(): Record<string, {
|
|
175
|
+
keywords: string[];
|
|
176
|
+
agent: string;
|
|
177
|
+
confidence: number;
|
|
178
|
+
successCount: number;
|
|
179
|
+
failureCount: number;
|
|
180
|
+
}>;
|
|
181
|
+
}
|
|
182
|
+
/** One line of the mod's file, validated; anything malformed is null. Unknown fields never survive. */
|
|
183
|
+
export declare function parseRecord(line: string): VerifiedRecord | null;
|
|
184
|
+
export declare function emptyLedger(): IngestLedger;
|
|
185
|
+
/** A ledger file read back; a corrupt or hostile one starts over (re-ingest is bounded by the mod's 500-line cap). */
|
|
186
|
+
export declare function readLedger(raw: string | null): IngestLedger;
|
|
187
|
+
/** A lesson's citation: a short hash of the SONA pattern key, so the key's own text never reaches a prompt. */
|
|
188
|
+
export declare const lessonId: (patternKey: string) => string;
|
|
189
|
+
/**
|
|
190
|
+
* Builds the lessons file from SONA's patterns, keeping only the ones verified outcomes trained. A would-be lesson the
|
|
191
|
+
* anomaly gate holds (ADR-503 §2.4) is listed under `held` instead: it stays in SONA, never in a prompt.
|
|
192
|
+
*/
|
|
193
|
+
export declare function buildLessons(patterns: ReturnType<SonaLike['exportPatterns']>, verified: Record<string, {
|
|
194
|
+
evidence: string;
|
|
195
|
+
at: number;
|
|
196
|
+
stats?: PatternStats;
|
|
197
|
+
}>, now: number): LessonsFile;
|
|
198
|
+
export interface IngestOptions {
|
|
199
|
+
/** Project root; defaults to process.cwd(), where SONA keeps `.swarm/`. */
|
|
200
|
+
root?: string;
|
|
201
|
+
/** Ingest even when the file's mtime matches the last ingest. */
|
|
202
|
+
force?: boolean;
|
|
203
|
+
/** Count only: train nothing and write nothing. */
|
|
204
|
+
dryRun?: boolean;
|
|
205
|
+
/** SONA to train; defaults to the process's optimizer. Null means unavailable. */
|
|
206
|
+
sona?: SonaLike | null;
|
|
207
|
+
now?: number;
|
|
208
|
+
/** `learningLoop` settings; defaults to `.claude-flow/config.json` under `root` (ADR-503). */
|
|
209
|
+
config?: LoopConfig;
|
|
210
|
+
/**
|
|
211
|
+
* Whether this caller may run project code to re-verify (ADR-503 §2.3). Only an explicit ingest passes true; a hot
|
|
212
|
+
* caller (hooks_route) leaves successes un-ingested when re-verify is on, instead of blocking for the timeout.
|
|
213
|
+
*/
|
|
214
|
+
allowReverify?: boolean;
|
|
215
|
+
/** Re-verify runner; defaults to the detached-git-worktree runner. Tests inject one. */
|
|
216
|
+
reverifier?: ReverifyRunner;
|
|
217
|
+
}
|
|
218
|
+
export declare function ingestVerifiedOutcomes(options?: IngestOptions): Promise<IngestResult>;
|
|
219
|
+
/**
|
|
220
|
+
* The doctor's view: whether the loop is recording and whether its records
|
|
221
|
+
* reach SONA. Read-only.
|
|
222
|
+
*/
|
|
223
|
+
export declare function learningLoopHealth(root?: string): {
|
|
224
|
+
status: 'pass' | 'warn';
|
|
225
|
+
message: string;
|
|
226
|
+
fix?: string;
|
|
227
|
+
};
|
|
228
|
+
//# sourceMappingURL=verified-outcomes.d.ts.map
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verified-outcome learning loop, CLI half (#4581, ADR-502).
|
|
3
|
+
*
|
|
4
|
+
* The ruflo-mods `learningLoop` option records one trajectory per main-loop
|
|
5
|
+
* turn in `.claude-flow/data/verified-trajectories.jsonl`. A mod may not run
|
|
6
|
+
* processes or call MCP tools, so it can only observe: a turn is
|
|
7
|
+
* `verified-success` or `verified-failure` when a recognised test, build or
|
|
8
|
+
* typecheck command ran after the turn's last edit and its process exit
|
|
9
|
+
* status said so; anything else is `unknown`.
|
|
10
|
+
*
|
|
11
|
+
* This module is the only place those records reach SONA:
|
|
12
|
+
* - `verified-*` records train SONA (`processTrajectoryOutcome`);
|
|
13
|
+
* - `unknown` records are counted and never train;
|
|
14
|
+
* - each record is ingested once (an id ledger, never a rewrite of the
|
|
15
|
+
* mod's file, which the mod appends to concurrently);
|
|
16
|
+
* - task text is PII-redacted before it reaches SONA's keyword store.
|
|
17
|
+
*
|
|
18
|
+
* It then writes `.claude-flow/data/learned-lessons.json`: patterns that
|
|
19
|
+
* verified outcomes produced, those at or above the SONA routing gate as
|
|
20
|
+
* lessons, and repeated verified failures as cautions. The mod reads that
|
|
21
|
+
* file and adds at most three cited lessons to a prompt.
|
|
22
|
+
*
|
|
23
|
+
* ADR-503 hardens it against gamed signals (verified-guards.ts,
|
|
24
|
+
* verified-reverify.ts): a tainted success does not train unless the project
|
|
25
|
+
* allows its taint class, an opt-in re-verify can downgrade a success before
|
|
26
|
+
* it trains, and an anomaly gate holds suspicious patterns back from the
|
|
27
|
+
* prompt. Every one of these can only downgrade.
|
|
28
|
+
*/
|
|
29
|
+
import { createHash } from 'node:crypto';
|
|
30
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
|
|
31
|
+
import { dirname, join } from 'node:path';
|
|
32
|
+
import { addOutcome, blockedTaints, emptyStats, holdReason, parseStats, parseTaint, readLoopConfig } from './verified-guards.js';
|
|
33
|
+
import { gitWorktreeRunner, ReverifySession } from './verified-reverify.js';
|
|
34
|
+
export const VERIFIED_PATH = join('.claude-flow', 'data', 'verified-trajectories.jsonl');
|
|
35
|
+
export const LEDGER_PATH = join('.claude-flow', 'data', 'verified-ingest.json');
|
|
36
|
+
export const LESSONS_PATH = join('.claude-flow', 'data', 'learned-lessons.json');
|
|
37
|
+
/** The SONA routing gate (sona-optimizer.ts `SONA_ROUTING_GATE`); a lesson below it never influences routing. */
|
|
38
|
+
export const LESSON_GATE = 0.6;
|
|
39
|
+
/** Ingested ids remembered; the mod keeps at most 500 lines, so this always covers the whole file. */
|
|
40
|
+
export const MAX_LEDGER_IDS = 2000;
|
|
41
|
+
/** A file past this many bytes is not read (the mod caps its own at far less). */
|
|
42
|
+
export const MAX_FILE_BYTES = 2 * 1024 * 1024;
|
|
43
|
+
export const MAX_LESSONS = 20;
|
|
44
|
+
/** Verified failures (and more failures than successes) before a pattern becomes a caution. */
|
|
45
|
+
export const CAUTION_MIN_FAILURES = 2;
|
|
46
|
+
export const MAX_REVERIFY_LOG = 50;
|
|
47
|
+
const OUTCOMES = ['verified-success', 'verified-failure', 'unknown'];
|
|
48
|
+
const KINDS = ['test', 'build', 'typecheck', 'lint'];
|
|
49
|
+
const ID = /^vt-[A-Za-z0-9_-]{1,80}$/;
|
|
50
|
+
const AGENT = /^[a-z][a-z0-9-]{0,40}$/;
|
|
51
|
+
const TOOL = /^[A-Za-z0-9_.:-]{1,80}$/;
|
|
52
|
+
/** SONA's agent roles (sona-optimizer.ts AGENT_TYPES); a route agent outside them trains as `coder`. */
|
|
53
|
+
const SONA_AGENTS = new Set(['coder', 'tester', 'reviewer', 'architect', 'researcher', 'optimizer', 'debugger', 'documenter', 'security-architect', 'performance-engineer']);
|
|
54
|
+
const num = (v, max = Number.MAX_SAFE_INTEGER) => (typeof v === 'number' && Number.isFinite(v) && v >= 0 ? Math.min(Math.floor(v), max) : 0);
|
|
55
|
+
const text = (v, max) => (typeof v === 'string' ? v.replace(/[\u0000-\u001f\u007f]/g, ' ').trim().slice(0, max) : '');
|
|
56
|
+
/** One line of the mod's file, validated; anything malformed is null. Unknown fields never survive. */
|
|
57
|
+
export function parseRecord(line) {
|
|
58
|
+
let raw;
|
|
59
|
+
try {
|
|
60
|
+
raw = JSON.parse(line);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
66
|
+
return null;
|
|
67
|
+
const r = raw;
|
|
68
|
+
if (r.v !== 1 || typeof r.id !== 'string' || !ID.test(r.id))
|
|
69
|
+
return null;
|
|
70
|
+
if (!OUTCOMES.includes(r.outcome))
|
|
71
|
+
return null;
|
|
72
|
+
const agent = typeof r.agent === 'string' && AGENT.test(r.agent) ? r.agent : 'coder';
|
|
73
|
+
const task = text(r.task, 300);
|
|
74
|
+
if (!task)
|
|
75
|
+
return null;
|
|
76
|
+
let verifier = null;
|
|
77
|
+
if (r.verifier && typeof r.verifier === 'object') {
|
|
78
|
+
const v = r.verifier;
|
|
79
|
+
if (KINDS.includes(v.kind) && typeof v.passed === 'boolean') {
|
|
80
|
+
verifier = { kind: v.kind, command: text(v.command, 80), passed: v.passed };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// A verified outcome must carry the verifier that decided it, and agree with it.
|
|
84
|
+
const outcome = r.outcome;
|
|
85
|
+
if (outcome !== 'unknown' && (!verifier || verifier.passed !== (outcome === 'verified-success')))
|
|
86
|
+
return null;
|
|
87
|
+
const steps = Array.isArray(r.steps)
|
|
88
|
+
? r.steps
|
|
89
|
+
.slice(0, 64)
|
|
90
|
+
.filter((s) => !!s && typeof s === 'object' && typeof s.tool === 'string' && TOOL.test(s.tool))
|
|
91
|
+
.map(s => ({ tool: s.tool, ok: s.ok === true }))
|
|
92
|
+
: [];
|
|
93
|
+
const taint = outcome === 'verified-success' ? parseTaint(r.taint) : [];
|
|
94
|
+
const session = typeof r.session === 'string' && /^[A-Za-z0-9_-]{1,40}$/.test(r.session) ? r.session : null;
|
|
95
|
+
return { id: r.id, task, agent, outcome, verifier, steps, edits: num(r.edits, 10_000), taint, session, startedAt: num(r.startedAt), endedAt: num(r.endedAt) };
|
|
96
|
+
}
|
|
97
|
+
export function emptyLedger() {
|
|
98
|
+
return { version: 1, ids: [], lastMtimeMs: 0, totals: { verifiedSuccess: 0, verifiedFailure: 0, unknown: 0, rejected: 0, tainted: 0 }, patterns: {}, reverify: { cursor: 0, log: [] } };
|
|
99
|
+
}
|
|
100
|
+
/** A ledger file read back; a corrupt or hostile one starts over (re-ingest is bounded by the mod's 500-line cap). */
|
|
101
|
+
export function readLedger(raw) {
|
|
102
|
+
if (!raw)
|
|
103
|
+
return emptyLedger();
|
|
104
|
+
try {
|
|
105
|
+
const j = JSON.parse(raw);
|
|
106
|
+
if (!j || j.version !== 1 || !Array.isArray(j.ids))
|
|
107
|
+
return emptyLedger();
|
|
108
|
+
const t = (j.totals ?? {});
|
|
109
|
+
const patterns = {};
|
|
110
|
+
for (const [k, v] of Object.entries(j.patterns && typeof j.patterns === 'object' ? j.patterns : {})) {
|
|
111
|
+
if (k === '__proto__' || typeof k !== 'string' || k.length > 400 || !v || typeof v !== 'object')
|
|
112
|
+
continue;
|
|
113
|
+
patterns[k] = { evidence: text(v.evidence, 80), at: num(v.at), stats: parseStats(v.stats) };
|
|
114
|
+
}
|
|
115
|
+
const rv = (j.reverify && typeof j.reverify === 'object' ? j.reverify : {});
|
|
116
|
+
const STATUS = ['pass', 'fail', 'timeout', 'error'];
|
|
117
|
+
const log = (Array.isArray(rv.log) ? rv.log : [])
|
|
118
|
+
.filter((e) => !!e && typeof e === 'object' && typeof e.id === 'string' && ID.test(e.id) && STATUS.includes(e.status))
|
|
119
|
+
.slice(-MAX_REVERIFY_LOG)
|
|
120
|
+
.map(e => ({ id: e.id, check: typeof e.check === 'string' ? text(e.check, 40) : null, commit: typeof e.commit === 'string' && /^[0-9a-f]{40,64}$/.test(e.commit) ? e.commit : null, status: e.status, at: num(e.at) }));
|
|
121
|
+
return {
|
|
122
|
+
version: 1,
|
|
123
|
+
ids: j.ids.filter((id) => typeof id === 'string' && ID.test(id)).slice(-MAX_LEDGER_IDS),
|
|
124
|
+
lastMtimeMs: num(j.lastMtimeMs),
|
|
125
|
+
totals: { verifiedSuccess: num(t.verifiedSuccess), verifiedFailure: num(t.verifiedFailure), unknown: num(t.unknown), rejected: num(t.rejected), tainted: num(t.tainted) },
|
|
126
|
+
patterns,
|
|
127
|
+
reverify: { cursor: num(rv.cursor, 1_000_000_000), log },
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return emptyLedger();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** Redacts PII from task text before it reaches SONA's keyword store; never throws. */
|
|
135
|
+
async function redact(task) {
|
|
136
|
+
try {
|
|
137
|
+
const { detectPII, redactPII } = await import('../transfer/anonymization/index.js');
|
|
138
|
+
return detectPII(task).found ? redactPII(task) : task;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return task;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** Keywords a lesson may show: SONA keeps any non-separator text, the prompt gets only plain words. */
|
|
145
|
+
const SAFE_KEYWORD = /^[a-z0-9][a-z0-9 _./-]{0,40}$/;
|
|
146
|
+
/** A lesson's citation: a short hash of the SONA pattern key, so the key's own text never reaches a prompt. */
|
|
147
|
+
export const lessonId = (patternKey) => `L-${createHash('sha256').update(patternKey).digest('hex').slice(0, 10)}`;
|
|
148
|
+
/**
|
|
149
|
+
* Builds the lessons file from SONA's patterns, keeping only the ones verified outcomes trained. A would-be lesson the
|
|
150
|
+
* anomaly gate holds (ADR-503 §2.4) is listed under `held` instead: it stays in SONA, never in a prompt.
|
|
151
|
+
*/
|
|
152
|
+
export function buildLessons(patterns, verified, now) {
|
|
153
|
+
const lessons = [];
|
|
154
|
+
const held = [];
|
|
155
|
+
let belowGate = 0;
|
|
156
|
+
for (const [id, p] of Object.entries(patterns)) {
|
|
157
|
+
const v = verified[id];
|
|
158
|
+
if (!v || !p || !Array.isArray(p.keywords))
|
|
159
|
+
continue;
|
|
160
|
+
const keywords = p.keywords.filter(k => typeof k === 'string' && SAFE_KEYWORD.test(k)).slice(0, 10);
|
|
161
|
+
if (keywords.length === 0 || !AGENT.test(p.agent))
|
|
162
|
+
continue;
|
|
163
|
+
const base = { id: lessonId(id), agent: p.agent, keywords, confidence: Math.round(p.confidence * 1000) / 1000, successes: p.successCount, failures: p.failureCount, evidence: v.evidence };
|
|
164
|
+
if (p.failureCount >= CAUTION_MIN_FAILURES && p.failureCount > p.successCount)
|
|
165
|
+
lessons.push({ ...base, kind: 'caution' });
|
|
166
|
+
else if (p.confidence >= LESSON_GATE) {
|
|
167
|
+
const reason = holdReason(v.stats ?? emptyStats(), p.successCount);
|
|
168
|
+
if (reason)
|
|
169
|
+
held.push({ id: base.id, reason });
|
|
170
|
+
else
|
|
171
|
+
lessons.push({ ...base, kind: 'lesson' });
|
|
172
|
+
}
|
|
173
|
+
else
|
|
174
|
+
belowGate++;
|
|
175
|
+
}
|
|
176
|
+
lessons.sort((a, b) => b.confidence - a.confidence || b.successes + b.failures - (a.successes + a.failures));
|
|
177
|
+
held.sort((a, b) => a.id.localeCompare(b.id));
|
|
178
|
+
return { version: 1, gate: LESSON_GATE, generatedAt: now, lessons: lessons.slice(0, MAX_LESSONS), belowGate, held: held.slice(0, MAX_LESSONS) };
|
|
179
|
+
}
|
|
180
|
+
function writeAtomic(path, body) {
|
|
181
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
182
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
183
|
+
writeFileSync(tmp, body);
|
|
184
|
+
renameSync(tmp, path);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Ingests every not-yet-ingested record. Verified records train SONA; unknown
|
|
188
|
+
* ones are counted. Safe to call often: an unchanged file is one stat.
|
|
189
|
+
*/
|
|
190
|
+
/** Per-root mtime of the last completed ingest in this process, so a hot caller (hooks_route) skips the ledger read too. */
|
|
191
|
+
const lastIngested = new Map();
|
|
192
|
+
export async function ingestVerifiedOutcomes(options = {}) {
|
|
193
|
+
const root = options.root ?? process.cwd();
|
|
194
|
+
const now = options.now ?? Date.now();
|
|
195
|
+
const file = join(root, VERIFIED_PATH);
|
|
196
|
+
const config = options.config ?? readLoopConfig(root);
|
|
197
|
+
const reverify = { enabled: config.reverify, ran: 0, passed: 0, downgraded: 0, deferred: 0 };
|
|
198
|
+
if (!options.force && !options.dryRun && options.sona !== null) {
|
|
199
|
+
try {
|
|
200
|
+
const memo = lastIngested.get(root);
|
|
201
|
+
if (memo && memo.mtimeMs === statSync(file).mtimeMs) {
|
|
202
|
+
return { ingested: { verifiedSuccess: 0, verifiedFailure: 0, unknown: 0, rejected: 0, tainted: 0 }, trained: 0, skipped: 'unchanged', lessons: 0, cautions: 0, belowGate: 0, held: [], reverify, totals: { ...memo.totals }, sonaAvailable: true };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// no file: the full path below reports it
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const ledgerPath = join(root, LEDGER_PATH);
|
|
210
|
+
const ledger = readLedger(existsSync(ledgerPath) ? readFileSync(ledgerPath, 'utf8') : null);
|
|
211
|
+
const ingested = { verifiedSuccess: 0, verifiedFailure: 0, unknown: 0, rejected: 0, tainted: 0 };
|
|
212
|
+
const done = (skipped, sonaAvailable, lessons) => ({
|
|
213
|
+
ingested,
|
|
214
|
+
trained: ingested.verifiedSuccess + ingested.verifiedFailure,
|
|
215
|
+
skipped,
|
|
216
|
+
lessons: lessons?.lessons.filter(l => l.kind === 'lesson').length ?? 0,
|
|
217
|
+
cautions: lessons?.lessons.filter(l => l.kind === 'caution').length ?? 0,
|
|
218
|
+
belowGate: lessons?.belowGate ?? 0,
|
|
219
|
+
held: lessons?.held ?? [],
|
|
220
|
+
reverify,
|
|
221
|
+
totals: ledger.totals,
|
|
222
|
+
sonaAvailable,
|
|
223
|
+
});
|
|
224
|
+
if (!existsSync(file))
|
|
225
|
+
return done('no-file', options.sona !== null);
|
|
226
|
+
const st = statSync(file);
|
|
227
|
+
if (st.size > MAX_FILE_BYTES)
|
|
228
|
+
return done('too-large', options.sona !== null);
|
|
229
|
+
if (!options.force && st.mtimeMs === ledger.lastMtimeMs)
|
|
230
|
+
return done('unchanged', options.sona !== null);
|
|
231
|
+
let sona = options.sona;
|
|
232
|
+
if (sona === undefined) {
|
|
233
|
+
try {
|
|
234
|
+
const { getSONAOptimizer } = await import('./sona-optimizer.js');
|
|
235
|
+
sona = (await getSONAOptimizer());
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
sona = null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// ADR-503 §2.3: one re-verify session per ingest; the cursor advances once, so checks rotate across ingests.
|
|
242
|
+
const runReverify = config.reverify && !options.dryRun && options.allowReverify === true;
|
|
243
|
+
const session = runReverify ? new ReverifySession(root, ledger.reverify.cursor, config.reverifyTimeoutMs, options.reverifier ?? gitWorktreeRunner) : null;
|
|
244
|
+
const seen = new Set(ledger.ids);
|
|
245
|
+
for (const line of readFileSync(file, 'utf8').split('\n')) {
|
|
246
|
+
if (!line.trim())
|
|
247
|
+
continue;
|
|
248
|
+
const record = parseRecord(line);
|
|
249
|
+
if (!record) {
|
|
250
|
+
ingested.rejected++;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (seen.has(record.id))
|
|
254
|
+
continue;
|
|
255
|
+
// Without SONA nothing verified can train yet: leave it un-ingested for the next call.
|
|
256
|
+
if (record.outcome !== 'unknown' && !sona)
|
|
257
|
+
continue;
|
|
258
|
+
const success = record.outcome === 'verified-success';
|
|
259
|
+
// ADR-503 §2.2: a success with a taint its project did not allow is counted and never trains.
|
|
260
|
+
const tainted = success && blockedTaints(record.taint, config).length > 0;
|
|
261
|
+
// ADR-503 §2.3: with re-verify on, a caller that may not run project code leaves a trainable success for later.
|
|
262
|
+
if (success && !tainted && config.reverify && !options.dryRun && !runReverify) {
|
|
263
|
+
reverify.deferred++;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
seen.add(record.id);
|
|
267
|
+
ledger.ids.push(record.id);
|
|
268
|
+
if (record.outcome === 'unknown') {
|
|
269
|
+
ingested.unknown++;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (tainted) {
|
|
273
|
+
ingested.tainted++;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (options.dryRun) {
|
|
277
|
+
if (success)
|
|
278
|
+
ingested.verifiedSuccess++;
|
|
279
|
+
else
|
|
280
|
+
ingested.verifiedFailure++;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (success && session) {
|
|
284
|
+
// Only the record's verifier KIND is used, to pick a different allowlisted check; its text never runs.
|
|
285
|
+
const { check, outcome } = await session.verify(record.verifier.kind);
|
|
286
|
+
ledger.reverify.log.push({ id: record.id, check, commit: outcome.commit, status: outcome.status, at: now });
|
|
287
|
+
if (outcome.status !== 'pass') {
|
|
288
|
+
reverify.downgraded++;
|
|
289
|
+
ingested.unknown++;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
reverify.passed++;
|
|
293
|
+
}
|
|
294
|
+
const result = sona.processTrajectoryOutcome({
|
|
295
|
+
trajectoryId: record.id,
|
|
296
|
+
task: await redact(record.task),
|
|
297
|
+
agent: SONA_AGENTS.has(record.agent) ? record.agent : 'coder',
|
|
298
|
+
success,
|
|
299
|
+
steps: record.steps.map(s => ({ action: s.tool, result: s.ok ? 'ok' : 'error', quality: s.ok ? 1 : 0, timestamp: new Date(record.endedAt || now).toISOString() })),
|
|
300
|
+
duration: record.endedAt > record.startedAt ? record.endedAt - record.startedAt : 0,
|
|
301
|
+
feedback: `verified by ${record.verifier.kind}: ${record.verifier.command}`,
|
|
302
|
+
});
|
|
303
|
+
if (result.learned && result.patternKey) {
|
|
304
|
+
const entry = ledger.patterns[result.patternKey] ?? { evidence: '', at: now, stats: emptyStats() };
|
|
305
|
+
entry.evidence = record.verifier.command;
|
|
306
|
+
entry.at = now;
|
|
307
|
+
addOutcome(entry.stats, success, record.taint, record.session);
|
|
308
|
+
ledger.patterns[result.patternKey] = entry;
|
|
309
|
+
}
|
|
310
|
+
if (success)
|
|
311
|
+
ingested.verifiedSuccess++;
|
|
312
|
+
else
|
|
313
|
+
ingested.verifiedFailure++;
|
|
314
|
+
}
|
|
315
|
+
if (options.dryRun)
|
|
316
|
+
return done(null, !!sona);
|
|
317
|
+
if (session) {
|
|
318
|
+
reverify.ran = session.ran;
|
|
319
|
+
if (session.ran > 0)
|
|
320
|
+
ledger.reverify.cursor = (ledger.reverify.cursor + 1) % 1_000_000_000;
|
|
321
|
+
ledger.reverify.log = ledger.reverify.log.slice(-MAX_REVERIFY_LOG);
|
|
322
|
+
}
|
|
323
|
+
ledger.ids = ledger.ids.slice(-MAX_LEDGER_IDS);
|
|
324
|
+
for (const k of Object.keys(ingested))
|
|
325
|
+
ledger.totals[k] += ingested[k];
|
|
326
|
+
// Without SONA, or with successes deferred for re-verify, records stayed un-ingested: keep the mtime stale so the
|
|
327
|
+
// next call retries them.
|
|
328
|
+
if (sona && reverify.deferred === 0) {
|
|
329
|
+
ledger.lastMtimeMs = st.mtimeMs;
|
|
330
|
+
lastIngested.set(root, { mtimeMs: st.mtimeMs, totals: { ...ledger.totals } });
|
|
331
|
+
}
|
|
332
|
+
// Keep the verified-pattern map bounded to what SONA still holds.
|
|
333
|
+
const patterns = sona ? sona.exportPatterns() : {};
|
|
334
|
+
if (sona)
|
|
335
|
+
for (const k of Object.keys(ledger.patterns))
|
|
336
|
+
if (!patterns[k])
|
|
337
|
+
delete ledger.patterns[k];
|
|
338
|
+
writeAtomic(ledgerPath, JSON.stringify(ledger));
|
|
339
|
+
const lessons = sona ? buildLessons(patterns, ledger.patterns, now) : undefined;
|
|
340
|
+
if (lessons)
|
|
341
|
+
writeAtomic(join(root, LESSONS_PATH), JSON.stringify(lessons, null, 2));
|
|
342
|
+
return done(null, !!sona, lessons);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* The doctor's view: whether the loop is recording and whether its records
|
|
346
|
+
* reach SONA. Read-only.
|
|
347
|
+
*/
|
|
348
|
+
export function learningLoopHealth(root = process.cwd()) {
|
|
349
|
+
const file = join(root, VERIFIED_PATH);
|
|
350
|
+
if (!existsSync(file))
|
|
351
|
+
return { status: 'pass', message: 'not recording (ruflo-mods learningLoop option off or no turn yet)' };
|
|
352
|
+
let lines = [];
|
|
353
|
+
try {
|
|
354
|
+
if (statSync(file).size <= MAX_FILE_BYTES)
|
|
355
|
+
lines = readFileSync(file, 'utf8').split('\n').filter(l => l.trim());
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return { status: 'warn', message: 'verified-trajectories.jsonl unreadable' };
|
|
359
|
+
}
|
|
360
|
+
const ledgerPath = join(root, LEDGER_PATH);
|
|
361
|
+
const ledger = readLedger(existsSync(ledgerPath) ? readFileSync(ledgerPath, 'utf8') : null);
|
|
362
|
+
const seen = new Set(ledger.ids);
|
|
363
|
+
const records = lines.map(parseRecord).filter((r) => r !== null);
|
|
364
|
+
const waiting = records.filter(r => !seen.has(r.id) && r.outcome !== 'unknown').length;
|
|
365
|
+
const verified = records.filter(r => r.outcome !== 'unknown').length;
|
|
366
|
+
const summary = `${records.length} turn(s) recorded, ${verified} verified; trained so far ${ledger.totals.verifiedSuccess} success / ${ledger.totals.verifiedFailure} failure, ${ledger.totals.unknown} unknown, ${ledger.totals.tainted} tainted (never trained)`;
|
|
367
|
+
if (waiting > 0) {
|
|
368
|
+
return { status: 'warn', message: `${summary}; ${waiting} verified outcome(s) not yet ingested`, fix: 'ruflo hooks verified-ingest (or any hooks_route / hooks_session-end call ingests them)' };
|
|
369
|
+
}
|
|
370
|
+
return { status: 'pass', message: summary };
|
|
371
|
+
}
|
|
372
|
+
//# sourceMappingURL=verified-outcomes.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-verify before training (ADR-503 §2.3). Opt-in: `.claude-flow/config.json`
|
|
3
|
+
* `learningLoop.reverify: true`. Off by default because it runs project code.
|
|
4
|
+
*
|
|
5
|
+
* - The command comes only from a fixed allowlist built from the project's own
|
|
6
|
+
* manifest (`package.json` scripts named test/typecheck/build/lint, or
|
|
7
|
+
* `cargo test`/`cargo check` when `Cargo.toml` exists). A record's text is
|
|
8
|
+
* never executed: it only selects, by its verifier *kind*, which allowlisted
|
|
9
|
+
* check NOT to run.
|
|
10
|
+
* - Checks rotate round-robin across ingests, preferring one the record did
|
|
11
|
+
* not use, so no single check is the one that counts.
|
|
12
|
+
* - The real runner works in a temporary detached `git worktree` at HEAD,
|
|
13
|
+
* with a hard timeout; timeout, error or a non-zero exit downgrades the
|
|
14
|
+
* record to `unknown`.
|
|
15
|
+
*/
|
|
16
|
+
import type { VerifierKind } from './verified-outcomes.js';
|
|
17
|
+
export interface ReverifyCheck {
|
|
18
|
+
/** `npm:test`, `cargo:check`, ...: a stable name for the ledger. */
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly kind: VerifierKind;
|
|
21
|
+
readonly argv: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
export interface ReverifyOutcome {
|
|
24
|
+
readonly status: 'pass' | 'fail' | 'timeout' | 'error';
|
|
25
|
+
/** The commit the check ran at, when known. */
|
|
26
|
+
readonly commit: string | null;
|
|
27
|
+
}
|
|
28
|
+
/** Runs one allowlisted check for `root` at HEAD. Injectable so tests never start a process. */
|
|
29
|
+
export type ReverifyRunner = (root: string, check: ReverifyCheck, timeoutMs: number) => Promise<ReverifyOutcome>;
|
|
30
|
+
/**
|
|
31
|
+
* The allowlist for a project. Only the script NAMES are read from the
|
|
32
|
+
* manifest; the argv is fixed here (`npm run <name>`, `cargo test`, ...).
|
|
33
|
+
*/
|
|
34
|
+
export declare function reverifyChecks(root: string): ReverifyCheck[];
|
|
35
|
+
/**
|
|
36
|
+
* The check to run for a record decided by a `used` verifier: round-robin by
|
|
37
|
+
* `cursor` over the checks of another kind, or over all of them when every
|
|
38
|
+
* check is that kind. Null when the project has none.
|
|
39
|
+
*/
|
|
40
|
+
export declare function pickCheck(checks: readonly ReverifyCheck[], used: VerifierKind, cursor: number): ReverifyCheck | null;
|
|
41
|
+
/**
|
|
42
|
+
* The real runner: a detached `git worktree` at HEAD in a temp directory, the
|
|
43
|
+
* root's `node_modules` linked in (a fresh worktree has none), the check run,
|
|
44
|
+
* the worktree removed. Any failure along the way is `error`.
|
|
45
|
+
*/
|
|
46
|
+
export declare const gitWorktreeRunner: ReverifyRunner;
|
|
47
|
+
/** One ingest's re-verification: picks per record, runs each distinct check at most once, never throws. */
|
|
48
|
+
export declare class ReverifySession {
|
|
49
|
+
private readonly root;
|
|
50
|
+
private readonly cursor;
|
|
51
|
+
private readonly timeoutMs;
|
|
52
|
+
private readonly runner;
|
|
53
|
+
private readonly results;
|
|
54
|
+
private readonly checks;
|
|
55
|
+
ran: number;
|
|
56
|
+
constructor(root: string, cursor: number, timeoutMs: number, runner: ReverifyRunner, checks?: ReverifyCheck[]);
|
|
57
|
+
/** Re-verifies a success decided by a `used` verifier. The record's own command and task are never passed. */
|
|
58
|
+
verify(used: VerifierKind): Promise<{
|
|
59
|
+
check: string | null;
|
|
60
|
+
outcome: ReverifyOutcome;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=verified-reverify.d.ts.map
|