akm-cli 0.9.25-alpha.2 → 0.9.25-alpha.3
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/CHANGELOG.md +84 -0
- package/dist/assets/prompts/reflect-feedback-framing.md +1 -1
- package/dist/assets/prompts/reflect-llm-framed-contract.md +2 -9
- package/dist/assets/prompts/reflect-llm-schema-contract.md +1 -3
- package/dist/assets/prompts/reflect-output-repair.md +1 -1
- package/dist/commands/improve/extract-cli.js +3 -2
- package/dist/commands/improve/extract.js +1 -1
- package/dist/commands/improve/reflect.js +140 -320
- package/dist/commands/improve/session-asset.js +6 -0
- package/dist/commands/proposal/validators/proposal-quality-validators.js +7 -3
- package/dist/commands/proposal/validators/proposal-validators.js +4 -5
- package/dist/core/asset/asset-serialize.js +1 -1
- package/dist/core/content-safety.js +0 -24
- package/dist/integrations/agent/prompts.js +51 -91
- package/dist/integrations/harnesses/codex/index.js +6 -11
- package/dist/integrations/harnesses/codex/session-log.js +211 -0
- package/dist/integrations/harnesses/types.js +3 -3
- package/dist/scripts/akm-migrate-node.js +258 -92
- package/dist/scripts/akm-migrate.js +258 -92
- package/docs/reference/cli.md +17 -9
- package/docs/reference/configuration.md +1 -3
- package/package.json +1 -1
|
@@ -2,23 +2,24 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
* `akm reflect [ref]` — ask an engine
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* `akm reflect [ref]` — ask an engine whether an asset's `description`,
|
|
6
|
+
* `when_to_use` and title need a fix, and queue the asset with that fix as a
|
|
7
|
+
* proposal (`source: "reflect"`). The engine never writes the body: akm keeps
|
|
8
|
+
* it byte for byte. Reflect never writes an asset: the proposal queue is the
|
|
9
|
+
* only path, `akm proposal accept` the bridge.
|
|
8
10
|
*
|
|
9
11
|
* Every invocation closes with one `reflect_completed` event; `reflect_invoked`
|
|
10
12
|
* is emitted once the dispatch has validated its credentials (deterministic
|
|
11
13
|
* pre-dispatch refusals still emit both).
|
|
12
14
|
*/
|
|
13
15
|
import fs from "node:fs";
|
|
14
|
-
import
|
|
15
|
-
import { assembleAssetFromString, serializeFrontmatter } from "../../core/asset/asset-serialize.js";
|
|
16
|
+
import { serializeFrontmatter } from "../../core/asset/asset-serialize.js";
|
|
16
17
|
import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
17
|
-
import {
|
|
18
|
+
import { parseRefInput } from "../../core/asset/resolve-ref.js";
|
|
18
19
|
import { DESCRIPTION_MAX_CHARS, requiresDescription } from "../../core/authoring-rules.js";
|
|
19
20
|
import { resolveStashDir } from "../../core/common.js";
|
|
20
21
|
import { loadConfig } from "../../core/config/config.js";
|
|
21
|
-
import { generatedContentRejection
|
|
22
|
+
import { generatedContentRejection } from "../../core/content-safety.js";
|
|
22
23
|
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
23
24
|
import { appendEvent, readEvents } from "../../core/events.js";
|
|
24
25
|
import { lintLessonContent } from "../../core/lesson-lint.js";
|
|
@@ -30,14 +31,13 @@ import { lookup } from "../../indexer/indexer.js";
|
|
|
30
31
|
import { DEFAULT_LLM_TIMEOUT_MS } from "../../integrations/agent/config.js";
|
|
31
32
|
import { fallbackAnnouncement, NO_ENGINE_MESSAGE_SUFFIX, NO_ENGINE_REMEDY, withEngineFallback, } from "../../integrations/agent/engine-fallback.js";
|
|
32
33
|
import { buildExecution, resolveExecution } from "../../integrations/agent/execution.js";
|
|
33
|
-
import { buildReflectOutputRepairPrompt, buildReflectPrompt,
|
|
34
|
+
import { buildReflectOutputRepairPrompt, buildReflectPrompt, REFLECT_CONTENT_CAP, } from "../../integrations/agent/prompts.js";
|
|
34
35
|
import { runnerIsLlm } from "../../integrations/agent/runner.js";
|
|
35
36
|
import { assertRunnerCredentials, collectDispatchSensitiveValues, } from "../../integrations/agent/runner-dispatch.js";
|
|
36
37
|
import { isJsonSchemaKnownUnsupported, LlmCallError } from "../../llm/client.js";
|
|
37
38
|
import { baseFailureFields, enoentHintMessage, isEnoentFailure } from "../agent/agent-support.js";
|
|
38
|
-
import {
|
|
39
|
+
import { isValidDescription } from "../proposal/validators/proposal-quality-validators.js";
|
|
39
40
|
import { CHARS_PER_TOKEN, DEFAULT_CONTEXT_LENGTH_TOKENS } from "./consolidate/chunking.js";
|
|
40
|
-
import { deriveLessonRef } from "./distill.js";
|
|
41
41
|
import { findAssetFilePath } from "./eligibility.js";
|
|
42
42
|
import { resolveImproveExecution } from "./execution.js";
|
|
43
43
|
import { recordLedgerAttempt } from "./ledger.js";
|
|
@@ -66,7 +66,7 @@ function readRecentFeedback(ref, eventsCtx) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
* Types reflect may
|
|
69
|
+
* Types reflect may patch: its output is frontmatter + markdown, which would
|
|
70
70
|
* break a script or env file. Another type is allowed only when its current
|
|
71
71
|
* content already has that shape; secrets are never read.
|
|
72
72
|
*/
|
|
@@ -80,99 +80,12 @@ export const REFLECT_ALLOWED_TYPES = new Set([
|
|
|
80
80
|
"workflow",
|
|
81
81
|
]);
|
|
82
82
|
const REFLECT_REFUSED_TYPES = new Set(["secret"]);
|
|
83
|
-
/** Identity fields the model may never change (a renamed `name` breaks ref resolution). */
|
|
84
|
-
const PROTECTED_FRONTMATTER_FIELDS = new Set(["name", "ref", "id", "slug", "type"]);
|
|
85
83
|
/** Lesson lint findings for the prompt: a concrete starting point for the revision. */
|
|
86
84
|
function buildSchemaHints(type, content) {
|
|
87
85
|
if (!content || type !== "lesson")
|
|
88
86
|
return [];
|
|
89
87
|
return lintLessonContent(content, "reflect").findings.map((f) => `[${f.kind}] ${f.message}`);
|
|
90
88
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Lessons related to a skill: its derived lesson, lessons distilled from it,
|
|
93
|
-
* and lessons citing it in `sources`. Without independent feedback on the skill,
|
|
94
|
-
* lessons reflect itself produced are dropped so its own output is not fed
|
|
95
|
-
* back as evidence.
|
|
96
|
-
*/
|
|
97
|
-
async function readRelatedLessons(stash, ref, parsedRef, itemRef, eventsCtx) {
|
|
98
|
-
if (parsedRef.type !== "skill")
|
|
99
|
-
return [];
|
|
100
|
-
const cache = new Map();
|
|
101
|
-
const read = (filePath) => {
|
|
102
|
-
const key = path.resolve(filePath);
|
|
103
|
-
const cached = cache.get(key) ?? fs.readFileSync(filePath, "utf8");
|
|
104
|
-
cache.set(key, cached);
|
|
105
|
-
return cached;
|
|
106
|
-
};
|
|
107
|
-
const related = new Map();
|
|
108
|
-
const derivedLessonRef = deriveLessonRef(ref);
|
|
109
|
-
const candidateRefs = new Set([derivedLessonRef]);
|
|
110
|
-
const derivedLessonPath = path.join(stash, "lessons", `${parseRefInput(derivedLessonRef).name}.md`);
|
|
111
|
-
if (fs.existsSync(derivedLessonPath)) {
|
|
112
|
-
related.set(derivedLessonRef, { ref: derivedLessonRef, content: read(derivedLessonPath) });
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
const keys = new Set([itemRef ?? ref]);
|
|
116
|
-
for (const event of readEvents({ type: "distill_invoked" }, readOnlyEventsContext(eventsCtx)).events) {
|
|
117
|
-
if (event.ref === undefined || !keys.has(event.ref))
|
|
118
|
-
continue;
|
|
119
|
-
const proposalRef = typeof event.metadata?.proposalRef === "string" ? event.metadata.proposalRef : undefined;
|
|
120
|
-
if (proposalRef && lenientRefType(proposalRef) === "lesson")
|
|
121
|
-
candidateRefs.add(proposalRef);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
catch {
|
|
125
|
-
// best-effort
|
|
126
|
-
}
|
|
127
|
-
for (const candidateRef of candidateRefs) {
|
|
128
|
-
try {
|
|
129
|
-
const filePath = await findAssetFilePath(candidateRef, stash);
|
|
130
|
-
if (filePath && fs.existsSync(filePath))
|
|
131
|
-
related.set(candidateRef, { ref: candidateRef, content: read(filePath) });
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
// An index miss is not fatal.
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
try {
|
|
138
|
-
const lessonsDir = path.join(stash, "lessons");
|
|
139
|
-
if (fs.existsSync(lessonsDir)) {
|
|
140
|
-
for (const fileName of fs.readdirSync(lessonsDir)) {
|
|
141
|
-
if (!fileName.endsWith(".md"))
|
|
142
|
-
continue;
|
|
143
|
-
const content = read(path.join(lessonsDir, fileName));
|
|
144
|
-
const sources = parseFrontmatter(content).data.sources;
|
|
145
|
-
if (!Array.isArray(sources) || !sources.some((s) => typeof s === "string" && s.trim() === ref))
|
|
146
|
-
continue;
|
|
147
|
-
const lessonRef = conceptIdFromTypeName("lesson", fileName.slice(0, -3));
|
|
148
|
-
if (!related.has(lessonRef))
|
|
149
|
-
related.set(lessonRef, { ref: lessonRef, content });
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
// best-effort
|
|
155
|
-
}
|
|
156
|
-
let hasIndependentFeedback = true;
|
|
157
|
-
try {
|
|
158
|
-
hasIndependentFeedback = readEvents({ type: "feedback", ref }, readOnlyEventsContext(eventsCtx)).events.length > 0;
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
// Unknown: keep every lesson.
|
|
162
|
-
}
|
|
163
|
-
if (!hasIndependentFeedback) {
|
|
164
|
-
for (const [lessonRef, lesson] of related) {
|
|
165
|
-
try {
|
|
166
|
-
if (parseFrontmatter(lesson.content).data.derived_from_reflect === true)
|
|
167
|
-
related.delete(lessonRef);
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
170
|
-
// Unparseable frontmatter: keep it.
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return [...related.values()];
|
|
175
|
-
}
|
|
176
89
|
/** The asset type of a maybe-ref, or `""` when it does not parse. */
|
|
177
90
|
function lenientRefType(ref) {
|
|
178
91
|
if (!ref)
|
|
@@ -184,35 +97,21 @@ function lenientRefType(ref) {
|
|
|
184
97
|
return "";
|
|
185
98
|
}
|
|
186
99
|
}
|
|
187
|
-
/**
|
|
188
|
-
* Cut a duplicate frontmatter block the model appended after its rewrite.
|
|
189
|
-
* Requires a balanced fence AND `key:` lines so thematic breaks survive.
|
|
190
|
-
*/
|
|
191
|
-
function stripAppendedFrontmatter(body) {
|
|
192
|
-
const match = body.match(/\n---\r?\n([\s\S]*?)\n---\r?\n/);
|
|
193
|
-
if (!match || !/^\w[\w-]*:/m.test(match[1]))
|
|
194
|
-
return body;
|
|
195
|
-
return body.slice(0, body.indexOf(match[0])).replace(/\s+$/, "");
|
|
196
|
-
}
|
|
197
100
|
/**
|
|
198
101
|
* A description derived from existing metadata (title, first heading, first
|
|
199
102
|
* prose sentence) that passes `isValidDescription`, or `undefined`. Never
|
|
200
103
|
* free-form invention.
|
|
201
104
|
*/
|
|
202
|
-
function deriveDescriptionFromAsset(title,
|
|
105
|
+
function deriveDescriptionFromAsset(title, body, targetRef) {
|
|
203
106
|
const candidates = [];
|
|
204
107
|
if (typeof title === "string" && title.trim())
|
|
205
108
|
candidates.push({ text: title.trim(), kind: "fragment" });
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const sentence = firstProseSentence(body);
|
|
213
|
-
if (sentence)
|
|
214
|
-
candidates.push({ text: sentence, kind: "prose" });
|
|
215
|
-
}
|
|
109
|
+
const heading = body.match(/^#{1,6}\s+(.+?)\s*$/m)?.[1];
|
|
110
|
+
if (heading)
|
|
111
|
+
candidates.push({ text: heading.trim(), kind: "fragment" });
|
|
112
|
+
const sentence = firstProseSentence(body);
|
|
113
|
+
if (sentence)
|
|
114
|
+
candidates.push({ text: sentence, kind: "prose" });
|
|
216
115
|
for (const { text, kind } of candidates) {
|
|
217
116
|
const normalized = text
|
|
218
117
|
.replace(/`/g, "")
|
|
@@ -241,81 +140,71 @@ function firstProseSentence(body) {
|
|
|
241
140
|
return "";
|
|
242
141
|
}
|
|
243
142
|
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* required description
|
|
248
|
-
*
|
|
143
|
+
* The asset with the patch applied, or `undefined` when the patch changes
|
|
144
|
+
* nothing (or there is no asset to patch). The body is the source's own, byte
|
|
145
|
+
* for byte; the one thing akm adds to it is a `# title` heading it lacks. A
|
|
146
|
+
* required description that neither the source nor the patch has is derived
|
|
147
|
+
* from the asset's own text (#636). Only the changed keys' frontmatter lines are
|
|
148
|
+
* rewritten: every other line is kept as it is, so a source whose YAML the
|
|
149
|
+
* parser reads only in part (a broken description beside a list) loses nothing.
|
|
249
150
|
*/
|
|
250
|
-
export function
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
catch {
|
|
264
|
-
llmFm = {};
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
if (payload.frontmatter && typeof payload.frontmatter === "object")
|
|
268
|
-
llmFm = { ...llmFm, ...payload.frontmatter };
|
|
269
|
-
for (const field of PROTECTED_FRONTMATTER_FIELDS) {
|
|
270
|
-
if (field in llmFm && llmFm[field] !== sourceFm[field]) {
|
|
271
|
-
warnings.push(`LLM attempted to change protected frontmatter field "${field}"; restored from source.`);
|
|
272
|
-
delete llmFm[field];
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
const mergedFm = { ...sourceFm, ...llmFm };
|
|
276
|
-
for (const field of PROTECTED_FRONTMATTER_FIELDS)
|
|
277
|
-
if (field in sourceFm)
|
|
278
|
-
mergedFm[field] = sourceFm[field];
|
|
279
|
-
const scaffolding = stripReflectPromptScaffolding(stripAppendedFrontmatter(rawLlmBody.replace(/^\s+/, "")));
|
|
280
|
-
const cleanedBody = scaffolding.content;
|
|
281
|
-
if (scaffolding.stripped) {
|
|
282
|
-
warnings.push('Removed echoed run-only "Avoid These Patterns" guidance from the proposed asset body (#963).');
|
|
283
|
-
}
|
|
151
|
+
export function applyReflectPatch(patch, sourceContent, targetRef) {
|
|
152
|
+
if (!sourceContent.trim())
|
|
153
|
+
return undefined;
|
|
154
|
+
const { fmText, body: sourceBody } = splitFrontmatter(sourceContent);
|
|
155
|
+
// A fence that is opened and never closed (`---` fused onto the last value)
|
|
156
|
+
// would leave the old block in the body under a new one: a person fixes it.
|
|
157
|
+
if (fmText === null && /^---\r?\n/.test(sourceContent))
|
|
158
|
+
return undefined;
|
|
159
|
+
const sourceFm = fmText !== null ? parseFrontmatter(sourceContent).data : {};
|
|
160
|
+
const { title, ...changes } = patch;
|
|
161
|
+
const addTitle = title !== undefined && !/^#[ \t]+\S/m.test(sourceBody);
|
|
162
|
+
const body = addTitle ? `# ${title}\n\n${sourceBody.replace(/^(\r?\n)+/, "")}` : sourceBody;
|
|
284
163
|
// Only a source that already has frontmatter but no description gets one:
|
|
285
164
|
// injecting a whole block, or overwriting an authored one, is out of scope.
|
|
286
165
|
const refType = lenientRefType(targetRef);
|
|
287
|
-
const desc =
|
|
288
|
-
const sourceHadFrontmatter = sourceFmText !== null && Object.keys(sourceFm).length > 0;
|
|
166
|
+
const desc = changes.description ?? sourceFm.description;
|
|
289
167
|
if (refType &&
|
|
290
168
|
requiresDescription(refType) &&
|
|
291
169
|
(typeof desc !== "string" || desc.trim().length === 0) &&
|
|
292
|
-
|
|
293
|
-
const derived = deriveDescriptionFromAsset(
|
|
294
|
-
if (derived)
|
|
295
|
-
|
|
296
|
-
|
|
170
|
+
Object.keys(sourceFm).length > 0) {
|
|
171
|
+
const derived = deriveDescriptionFromAsset(sourceFm.title, body, targetRef);
|
|
172
|
+
if (derived)
|
|
173
|
+
changes.description = derived;
|
|
174
|
+
}
|
|
175
|
+
for (const key of Object.keys(changes))
|
|
176
|
+
if (changes[key] === sourceFm[key])
|
|
177
|
+
delete changes[key];
|
|
178
|
+
if (!addTitle && Object.keys(changes).length === 0)
|
|
179
|
+
return undefined;
|
|
180
|
+
// No frontmatter at all stays body-only unless the patch adds a field. The
|
|
181
|
+
// blank line after a closing fence is part of the source body, so only a
|
|
182
|
+
// block or heading akm adds brings its own.
|
|
183
|
+
if (fmText === null) {
|
|
184
|
+
if (Object.keys(changes).length === 0)
|
|
185
|
+
return { content: body };
|
|
186
|
+
const content = `---\n${serializeFrontmatter(changes)}\n---\n\n${body}`;
|
|
187
|
+
return { content, frontmatter: parseFrontmatter(content).data };
|
|
188
|
+
}
|
|
189
|
+
const content = `---\n${patchFrontmatterLines(fmText, changes)}\n---\n${addTitle ? "\n" : ""}${body}`;
|
|
190
|
+
return { content, frontmatter: parseFrontmatter(content).data };
|
|
191
|
+
}
|
|
192
|
+
/** The frontmatter text with each changed key's lines (the key line and its indented continuation) replaced, or appended. */
|
|
193
|
+
function patchFrontmatterLines(fmText, changes) {
|
|
194
|
+
const lines = fmText.split(/\r?\n/);
|
|
195
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
196
|
+
const replacement = serializeFrontmatter({ [key]: value }).split("\n");
|
|
197
|
+
const start = lines.findIndex((line) => line.startsWith(`${key}:`));
|
|
198
|
+
if (start === -1) {
|
|
199
|
+
lines.push(...replacement);
|
|
200
|
+
continue;
|
|
297
201
|
}
|
|
202
|
+
let end = start + 1;
|
|
203
|
+
while (end < lines.length && /^[ \t]/.test(lines[end] ?? ""))
|
|
204
|
+
end++;
|
|
205
|
+
lines.splice(start, end - start, ...replacement);
|
|
298
206
|
}
|
|
299
|
-
|
|
300
|
-
let sizeGuardRatio;
|
|
301
|
-
if (!size.ok) {
|
|
302
|
-
const shrink = size.code === "EXCESSIVE_SHRINKAGE";
|
|
303
|
-
warnings.push(`${size.code} — proposed body is ${(size.ratio * 100).toFixed(0)}% of source (${shrink ? "minimum 50%" : "maximum 250%"}) for ref ${targetRef}. ${shrink ? "Concrete content was likely deleted." : "Speculative material was likely added."} Flagged for review.`);
|
|
304
|
-
sizeGuardRatio = { code: size.code, ratio: size.ratio };
|
|
305
|
-
}
|
|
306
|
-
const truncationMarkerLeaked = cleanedBody.includes(REFLECT_TRUNCATION_MARKER);
|
|
307
|
-
if (truncationMarkerLeaked) {
|
|
308
|
-
warnings.push(`Proposed body for ref ${targetRef} contains the truncation-notice text the model was shown for a capped source asset ("${REFLECT_TRUNCATION_MARKER}"). The model likely echoed the notice instead of writing real content. Flagged for review.`);
|
|
309
|
-
}
|
|
310
|
-
// No frontmatter at all stays body-only, never gaining a stray `---`.
|
|
311
|
-
const hasFrontmatter = Object.keys(mergedFm).length > 0;
|
|
312
|
-
return {
|
|
313
|
-
content: hasFrontmatter ? assembleAssetFromString(serializeFrontmatter(mergedFm), cleanedBody) : cleanedBody,
|
|
314
|
-
...(hasFrontmatter ? { frontmatter: mergedFm } : {}),
|
|
315
|
-
warnings,
|
|
316
|
-
...(sizeGuardRatio ? { sizeGuardRatio } : {}),
|
|
317
|
-
...(truncationMarkerLeaked ? { truncationMarkerLeaked } : {}),
|
|
318
|
-
};
|
|
207
|
+
return lines.join("\n");
|
|
319
208
|
}
|
|
320
209
|
// ── Output contract ──────────────────────────────────────────────────────────
|
|
321
210
|
//
|
|
@@ -325,11 +214,12 @@ export function sanitizeReflectPayload(payload, sourceContent, targetRef) {
|
|
|
325
214
|
// repaired once, whatever engine produced it.
|
|
326
215
|
const REFLECT_FRONTMATTER_PATCH_JSON_SCHEMA = {
|
|
327
216
|
type: "object",
|
|
328
|
-
required: ["description", "when_to_use"],
|
|
217
|
+
required: ["description", "when_to_use", "title"],
|
|
329
218
|
additionalProperties: false,
|
|
330
219
|
properties: {
|
|
331
220
|
description: { type: ["string", "null"] },
|
|
332
221
|
when_to_use: { type: ["string", "null"] },
|
|
222
|
+
title: { type: ["string", "null"] },
|
|
333
223
|
},
|
|
334
224
|
};
|
|
335
225
|
const REFLECT_CONFIDENCE_SCHEMA = {
|
|
@@ -340,21 +230,19 @@ const REFLECT_CONFIDENCE_SCHEMA = {
|
|
|
340
230
|
};
|
|
341
231
|
export const REFLECT_JSON_SCHEMA = {
|
|
342
232
|
type: "object",
|
|
343
|
-
required: ["
|
|
233
|
+
required: ["confidence", "frontmatterPatch"],
|
|
344
234
|
additionalProperties: false,
|
|
345
235
|
properties: {
|
|
346
|
-
content: { type: "string", description: "Complete improved markdown body without YAML frontmatter." },
|
|
347
236
|
confidence: REFLECT_CONFIDENCE_SCHEMA,
|
|
348
237
|
frontmatterPatch: REFLECT_FRONTMATTER_PATCH_JSON_SCHEMA,
|
|
349
238
|
},
|
|
350
239
|
};
|
|
351
240
|
const REFLECT_UNSCOPED_JSON_SCHEMA = {
|
|
352
241
|
type: "object",
|
|
353
|
-
required: ["ref", "
|
|
242
|
+
required: ["ref", "confidence", "frontmatterPatch"],
|
|
354
243
|
additionalProperties: false,
|
|
355
244
|
properties: {
|
|
356
245
|
ref: { type: "string", description: "Selected asset ref as a subdir-qualified conceptId." },
|
|
357
|
-
content: { type: "string", description: "Complete improved markdown body without YAML frontmatter." },
|
|
358
246
|
confidence: { type: "number", minimum: 0, maximum: 1, description: "Self-reported quality confidence in [0, 1]." },
|
|
359
247
|
frontmatterPatch: REFLECT_FRONTMATTER_PATCH_JSON_SCHEMA,
|
|
360
248
|
},
|
|
@@ -387,67 +275,48 @@ function parseReflectConfidence(value) {
|
|
|
387
275
|
}
|
|
388
276
|
return value;
|
|
389
277
|
}
|
|
278
|
+
const REFLECT_PATCH_FIELDS = ["description", "when_to_use", "title"];
|
|
390
279
|
function parseReflectFrontmatterPatch(value) {
|
|
391
280
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
392
281
|
throw new Error('direct reflect response missing required object field "frontmatterPatch"');
|
|
393
282
|
}
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
throw new Error("direct reflect frontmatterPatch fields must be exactly: description, when_to_use");
|
|
283
|
+
const fields = value;
|
|
284
|
+
if (Object.keys(fields).length !== REFLECT_PATCH_FIELDS.length || REFLECT_PATCH_FIELDS.some((f) => !(f in fields))) {
|
|
285
|
+
throw new Error(`direct reflect frontmatterPatch fields must be exactly: ${REFLECT_PATCH_FIELDS.join(", ")}`);
|
|
398
286
|
}
|
|
399
|
-
const
|
|
400
|
-
for (const field of
|
|
401
|
-
const fieldValue =
|
|
287
|
+
const patch = {};
|
|
288
|
+
for (const field of REFLECT_PATCH_FIELDS) {
|
|
289
|
+
const fieldValue = fields[field];
|
|
402
290
|
if (fieldValue === null)
|
|
403
291
|
continue;
|
|
404
292
|
if (typeof fieldValue !== "string" || !fieldValue.trim() || /[\r\n]/.test(fieldValue)) {
|
|
405
293
|
throw new Error(`direct reflect frontmatterPatch.${field} must be a non-empty single-line string or null`);
|
|
406
294
|
}
|
|
407
|
-
|
|
295
|
+
patch[field] = fieldValue.trim();
|
|
408
296
|
}
|
|
409
|
-
return
|
|
297
|
+
return patch;
|
|
410
298
|
}
|
|
411
299
|
function parseSchemaReflectOutput(raw, targetRef) {
|
|
412
300
|
const parsed = parseEmbeddedJsonResponse(raw);
|
|
413
301
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
414
302
|
throw new Error("direct reflect response was not valid JSON");
|
|
415
303
|
}
|
|
416
|
-
const expectedKeys = targetRef
|
|
417
|
-
? ["confidence", "content", "frontmatterPatch"]
|
|
418
|
-
: ["confidence", "content", "frontmatterPatch", "ref"];
|
|
304
|
+
const expectedKeys = targetRef ? ["confidence", "frontmatterPatch"] : ["confidence", "frontmatterPatch", "ref"];
|
|
419
305
|
const actualKeys = Object.keys(parsed).sort();
|
|
420
306
|
if (actualKeys.length !== expectedKeys.length || actualKeys.some((key, index) => key !== expectedKeys[index])) {
|
|
421
307
|
throw new Error(`direct reflect response fields must be exactly: ${expectedKeys.join(", ")}`);
|
|
422
308
|
}
|
|
423
|
-
if (typeof parsed.content !== "string" || !parsed.content.trim()) {
|
|
424
|
-
throw new Error('direct reflect response missing required string field "content"');
|
|
425
|
-
}
|
|
426
309
|
const ref = targetRef ?? (typeof parsed.ref === "string" ? parsed.ref.trim() : "");
|
|
427
310
|
if (!ref)
|
|
428
311
|
throw new Error('direct reflect response missing required string field "ref"');
|
|
429
|
-
const frontmatter = parseReflectFrontmatterPatch(parsed.frontmatterPatch);
|
|
430
312
|
return {
|
|
431
313
|
ref,
|
|
432
|
-
content: parsed.content,
|
|
433
314
|
confidence: parseReflectConfidence(parsed.confidence),
|
|
434
|
-
|
|
315
|
+
patch: parseReflectFrontmatterPatch(parsed.frontmatterPatch),
|
|
435
316
|
};
|
|
436
317
|
}
|
|
437
318
|
function parseFramedReflectOutput(raw, targetRef) {
|
|
438
|
-
const
|
|
439
|
-
const beginMarker = "AKM_REFLECT_CONTENT_BEGIN\n";
|
|
440
|
-
const endMarker = "\nAKM_REFLECT_CONTENT_END";
|
|
441
|
-
const beginIndex = normalized.indexOf(beginMarker);
|
|
442
|
-
if (beginIndex < 0 || (beginIndex > 0 && normalized[beginIndex - 1] !== "\n")) {
|
|
443
|
-
throw new Error("direct reflect response missing AKM_REFLECT_CONTENT_BEGIN marker");
|
|
444
|
-
}
|
|
445
|
-
const contentStart = beginIndex + beginMarker.length;
|
|
446
|
-
const endIndex = normalized.lastIndexOf(endMarker);
|
|
447
|
-
if (endIndex < contentStart || normalized.slice(endIndex + endMarker.length).trim()) {
|
|
448
|
-
throw new Error("direct reflect response missing terminal AKM_REFLECT_CONTENT_END marker");
|
|
449
|
-
}
|
|
450
|
-
const headerLines = normalized.slice(0, beginIndex).trim().split("\n").filter(Boolean);
|
|
319
|
+
const headerLines = raw.replaceAll("\r\n", "\n").trim().split("\n").filter(Boolean);
|
|
451
320
|
const header = (prefix) => headerLines.find((line) => line.startsWith(prefix));
|
|
452
321
|
const confidenceLine = header("AKM_REFLECT_CONFIDENCE:");
|
|
453
322
|
const refLine = header("AKM_REFLECT_REF:");
|
|
@@ -463,9 +332,6 @@ function parseFramedReflectOutput(raw, targetRef) {
|
|
|
463
332
|
const ref = targetRef ?? refLine?.slice("AKM_REFLECT_REF:".length).trim() ?? "";
|
|
464
333
|
if (!ref)
|
|
465
334
|
throw new Error("direct reflect response contained an empty AKM_REFLECT_REF value");
|
|
466
|
-
const content = normalized.slice(contentStart, endIndex);
|
|
467
|
-
if (!content.trim())
|
|
468
|
-
throw new Error("direct reflect response contained empty framed content");
|
|
469
335
|
let parsedPatch;
|
|
470
336
|
try {
|
|
471
337
|
parsedPatch = JSON.parse(patchLine.slice("AKM_REFLECT_FRONTMATTER_PATCH:".length).trim());
|
|
@@ -473,9 +339,11 @@ function parseFramedReflectOutput(raw, targetRef) {
|
|
|
473
339
|
catch {
|
|
474
340
|
throw new Error("direct reflect response contained invalid frontmatter patch JSON");
|
|
475
341
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
342
|
+
return {
|
|
343
|
+
ref,
|
|
344
|
+
confidence: parseReflectConfidence(Number(confidenceText)),
|
|
345
|
+
patch: parseReflectFrontmatterPatch(parsedPatch),
|
|
346
|
+
};
|
|
479
347
|
}
|
|
480
348
|
/**
|
|
481
349
|
* One reflect iteration on any engine, as an agent-shaped result (errors
|
|
@@ -660,7 +528,7 @@ function unsupportedTypeFailure(ref, type, detail, emitFailed) {
|
|
|
660
528
|
},
|
|
661
529
|
};
|
|
662
530
|
}
|
|
663
|
-
/** The target's parsed ref and current content, or a refusal for a type reflect cannot
|
|
531
|
+
/** The target's parsed ref and current content, or a refusal for a type reflect cannot patch. */
|
|
664
532
|
async function resolveReflectSource(options, stash, emitFailed) {
|
|
665
533
|
if (!options.ref)
|
|
666
534
|
return { assetContent: undefined, parsedRef: undefined };
|
|
@@ -684,7 +552,7 @@ async function resolveReflectSource(options, stash, emitFailed) {
|
|
|
684
552
|
}
|
|
685
553
|
}
|
|
686
554
|
catch {
|
|
687
|
-
// An index miss is not fatal:
|
|
555
|
+
// An index miss is not fatal: reflect then has no content to patch.
|
|
688
556
|
}
|
|
689
557
|
}
|
|
690
558
|
if (!REFLECT_ALLOWED_TYPES.has(parsedRef.type) &&
|
|
@@ -756,7 +624,7 @@ function preflightReflectDispatch(runnerSpec, onNotices) {
|
|
|
756
624
|
/**
|
|
757
625
|
* The flat 12k content cap exists for CLI argv; the HTTP runner can spend half
|
|
758
626
|
* its context window (after the rest of the prompt) on the asset, reserving
|
|
759
|
-
* the other half for the
|
|
627
|
+
* the other half for the reply. Never below the flat floor.
|
|
760
628
|
*/
|
|
761
629
|
function computeReflectContentBudgetChars(promptInput, runnerSpec) {
|
|
762
630
|
if (!runnerIsLlm(runnerSpec) || !promptInput.assetContent?.trim())
|
|
@@ -766,13 +634,10 @@ function computeReflectContentBudgetChars(promptInput, runnerSpec) {
|
|
|
766
634
|
return Math.max(REFLECT_CONTENT_CAP, Math.floor((window - overhead) / 2));
|
|
767
635
|
}
|
|
768
636
|
/** Every read-only prompt input, shared by dispatch and `--show-prompt`. */
|
|
769
|
-
|
|
637
|
+
function gatherReflectPromptSources(options, stash, parsedRef, assetContent) {
|
|
770
638
|
return {
|
|
771
639
|
feedback: readRecentFeedback(options.ref ? (options.itemRef ?? options.ref) : undefined, options.eventsCtx),
|
|
772
640
|
schemaHints: buildSchemaHints(parsedRef?.type ?? "", assetContent),
|
|
773
|
-
relatedLessons: options.ref && parsedRef
|
|
774
|
-
? await readRelatedLessons(stash, options.ref, parsedRef, options.itemRef, options.eventsCtx)
|
|
775
|
-
: [],
|
|
776
641
|
rejectedProposals: rejectedProposalContext(stash, options.ref, options.ctx, options.eventsCtx),
|
|
777
642
|
standardsContext: resolveStandardsContext(options.ref, stash),
|
|
778
643
|
};
|
|
@@ -780,7 +645,7 @@ async function gatherReflectPromptSources(options, stash, parsedRef, assetConten
|
|
|
780
645
|
/** The exact prompt reflect sends, shared by dispatch and `--show-prompt`. */
|
|
781
646
|
function buildReflectPromptText(args) {
|
|
782
647
|
const { options, parsedRef, assetContent, sources, runnerSpec, priorDraft } = args;
|
|
783
|
-
const { feedback, schemaHints,
|
|
648
|
+
const { feedback, schemaHints, rejectedProposals, standardsContext } = sources;
|
|
784
649
|
// An LLM engine that rejects JSON Schema gets the framed contract; every other engine gets the JSON object.
|
|
785
650
|
const outputMode = runnerIsLlm(runnerSpec) && !wantsJsonSchemaOutput(runnerSpec.connection) ? "framed_markdown" : "json_schema";
|
|
786
651
|
const input = {
|
|
@@ -790,7 +655,6 @@ function buildReflectPromptText(args) {
|
|
|
790
655
|
...(assetContent !== undefined ? { assetContent } : {}),
|
|
791
656
|
...(feedback.length > 0 ? { feedback } : {}),
|
|
792
657
|
...(schemaHints.length > 0 ? { schemaHints } : {}),
|
|
793
|
-
...(relatedLessons.length > 0 ? { relatedLessons } : {}),
|
|
794
658
|
...(options.task ? { task: options.task } : {}),
|
|
795
659
|
...(standardsContext.trim() ? { standardsContext } : {}),
|
|
796
660
|
...(options.avoidPatterns && options.avoidPatterns.length > 0 ? { avoidPatterns: options.avoidPatterns } : {}),
|
|
@@ -872,61 +736,36 @@ async function runReflectRefineIterations(args) {
|
|
|
872
736
|
}
|
|
873
737
|
return result;
|
|
874
738
|
}
|
|
875
|
-
/** The proposal payload from a successful run: the JSON payload on stdout. */
|
|
876
|
-
function resolveReflectPayload(run, result) {
|
|
877
|
-
const { options } = run;
|
|
878
|
-
try {
|
|
879
|
-
return { payload: parseAgentProposalPayload(result.stdout ?? "") };
|
|
880
|
-
}
|
|
881
|
-
catch (err) {
|
|
882
|
-
run.emitFailed("parse_error", "parse_error", options.ref, {
|
|
883
|
-
...exitCodeMeta(result),
|
|
884
|
-
...(reflectTelemetry(result) ?? {}),
|
|
885
|
-
});
|
|
886
|
-
return {
|
|
887
|
-
failure: reflectFailure(run, result, "parse_error", err instanceof Error ? err.message : String(err), true),
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
739
|
const NOISE_SUBREASONS = {
|
|
892
740
|
noop: "reflect_skipped_noop",
|
|
893
741
|
cosmetic: "reflect_skipped_cosmetic",
|
|
894
742
|
"low-value": "reflect_skipped_low_value",
|
|
895
743
|
};
|
|
896
744
|
/**
|
|
897
|
-
*
|
|
898
|
-
* exact content that would be persisted, then mint. A
|
|
899
|
-
*
|
|
900
|
-
* gate
|
|
901
|
-
* the judge and waits for review. A revision with a deterministic defect is
|
|
902
|
-
* refused before the judge runs, whether or not the gate is on.
|
|
745
|
+
* Apply the patch, drop a no-op/cosmetic (and optionally low-value) change,
|
|
746
|
+
* judge the exact content that would be persisted, then mint. A revision with a
|
|
747
|
+
* deterministic defect is refused before the judge runs, whether or not the
|
|
748
|
+
* gate is on.
|
|
903
749
|
*/
|
|
904
750
|
async function finalizeReflectProposal(args) {
|
|
905
|
-
const { run, assetContent, result, judge, feedback } = args;
|
|
751
|
+
const { run, payload, assetContent, result, judge, feedback } = args;
|
|
906
752
|
const { options } = run;
|
|
907
753
|
const telemetry = reflectTelemetry(result) ?? {};
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
: changeKind === "low-value"
|
|
923
|
-
? "a low-value prose micro-rewrite (few changed tokens, no structural changes)"
|
|
924
|
-
: "a cosmetic-only reformat of the current asset (whitespace/fence/YAML-folding changes)";
|
|
925
|
-
return reflectFailure(run, result, "no_change", `Reflect skipped: proposed content for ${payload.ref} is ${what}; no proposal created.`, false);
|
|
926
|
-
}
|
|
754
|
+
const patched = applyReflectPatch(payload.patch, assetContent, payload.ref);
|
|
755
|
+
// A patch that changes nothing leaves the asset as it is: an empty diff.
|
|
756
|
+
const content = patched?.content ?? assetContent;
|
|
757
|
+
const changeKind = classifyReflectChange(assetContent, content);
|
|
758
|
+
if (changeKind === "noop" ||
|
|
759
|
+
changeKind === "cosmetic" ||
|
|
760
|
+
(changeKind === "low-value" && options.lowValueFilter === true)) {
|
|
761
|
+
run.emitFailed("no_change", NOISE_SUBREASONS[changeKind], options.ref, { changeKind, ...telemetry });
|
|
762
|
+
const what = changeKind === "noop"
|
|
763
|
+
? "identical to the current asset (empty diff)"
|
|
764
|
+
: changeKind === "low-value"
|
|
765
|
+
? "a low-value prose micro-rewrite (few changed tokens, no structural changes)"
|
|
766
|
+
: "a cosmetic-only reformat of the current asset (whitespace/fence/YAML-folding changes)";
|
|
767
|
+
return reflectFailure(run, result, "no_change", `Reflect skipped: proposed content for ${payload.ref} is ${what}; no proposal created.`, false);
|
|
927
768
|
}
|
|
928
|
-
const flagged = Boolean(sanitized.sizeGuardRatio || sanitized.truncationMarkerLeaked);
|
|
929
|
-
const judged = judge.enabled && !flagged;
|
|
930
769
|
/** A judge refused the revision: record it for the ledger's rejection window and stop. */
|
|
931
770
|
const refuse = (detail, metadata, message) => {
|
|
932
771
|
if (options.ref) {
|
|
@@ -946,13 +785,13 @@ async function finalizeReflectProposal(args) {
|
|
|
946
785
|
return reflectFailure(run, result, "quality_rejected", message, false);
|
|
947
786
|
};
|
|
948
787
|
// A defect no judge needs to weigh is refused before any judge call, whether or not the gate is on.
|
|
949
|
-
const defect =
|
|
788
|
+
const defect = findReflectDefect(assetContent, content, options.defectFilter);
|
|
950
789
|
if (defect)
|
|
951
790
|
return refuse(defect, { reflectDefect: defect }, `Reflect proposal refused before the judge: ${defect}`);
|
|
952
791
|
let verdict;
|
|
953
792
|
let judgeFailed = false;
|
|
954
|
-
if (
|
|
955
|
-
verdict = await runReflectQualityJudge(run.config,
|
|
793
|
+
if (judge.enabled) {
|
|
794
|
+
verdict = await runReflectQualityJudge(run.config, content, assetContent, feedback, options.chat, {
|
|
956
795
|
runnerSelectionFrozen: true,
|
|
957
796
|
ref: payload.ref,
|
|
958
797
|
...(judge.runner ? { llmRunner: judge.runner } : {}),
|
|
@@ -970,12 +809,12 @@ async function finalizeReflectProposal(args) {
|
|
|
970
809
|
}, `Reflect proposal quality gate rejected: score=${verdict.score}, reason="${verdict.reason}"`);
|
|
971
810
|
}
|
|
972
811
|
}
|
|
973
|
-
// #722: a
|
|
974
|
-
if (verdict?.pass && judge.runner
|
|
812
|
+
// #722: a revision of an existing asset must not grade lower on its own retrieval queries.
|
|
813
|
+
if (verdict?.pass && judge.runner) {
|
|
975
814
|
const retrieval = await runRetrievalRegressionGate({
|
|
976
815
|
ref: payload.ref,
|
|
977
816
|
before: assetContent,
|
|
978
|
-
after:
|
|
817
|
+
after: content,
|
|
979
818
|
queries: loadRetrievalQueries({ proposalsCtx: options.ctx, eventsCtx: options.eventsCtx }, payload.ref),
|
|
980
819
|
runner: judge.runner,
|
|
981
820
|
...(options.chat ? { chat: options.chat } : {}),
|
|
@@ -992,33 +831,17 @@ async function finalizeReflectProposal(args) {
|
|
|
992
831
|
}, `Reflect proposal refused: ${retrieval.reason}`);
|
|
993
832
|
}
|
|
994
833
|
}
|
|
995
|
-
// A lesson reflect wrote is marked so a later reflect on the same skill does
|
|
996
|
-
// not read it back as independent evidence.
|
|
997
|
-
const frontmatter = {
|
|
998
|
-
...(payload.frontmatter ?? {}),
|
|
999
|
-
...(lenientRefType(payload.ref) === "lesson" ? { derived_from_reflect: true } : {}),
|
|
1000
|
-
};
|
|
1001
834
|
const reviewReasons = [
|
|
1002
835
|
...(judge.skippedNoJudge ? ["no-judge-configured"] : []),
|
|
1003
836
|
...(judgeFailed ? ["judge-error"] : []),
|
|
1004
|
-
...(sanitized.sizeGuardRatio ? ["reflect-size-ratio"] : []),
|
|
1005
|
-
...(sanitized.truncationMarkerLeaked ? ["reflect-truncation-leak"] : []),
|
|
1006
837
|
];
|
|
1007
|
-
// A revision that changes the body is never auto-accepted: on labelled edits, the judge's
|
|
1008
|
-
// passes on body edits were good 12 times in 37, and on frontmatter-only edits 13 in 13.
|
|
1009
|
-
// One that nothing above holds for review (the judge passed it, or the gate is off) waits
|
|
1010
|
-
// for a person, as does a revision with no source to compare.
|
|
1011
|
-
const bodyOf = (content) => splitFrontmatter(content).body.replace(/\s+/g, " ").trim();
|
|
1012
|
-
const bodyEdit = reviewReasons.length === 0 && (assetContent === undefined || bodyOf(assetContent) !== bodyOf(payload.content));
|
|
1013
|
-
if (bodyEdit)
|
|
1014
|
-
reviewReasons.push("body-edit");
|
|
1015
838
|
const proposal = mintProposal(run.stash, options.ctx, {
|
|
1016
839
|
ref: payload.ref,
|
|
1017
840
|
...(options.target ? { target: options.target } : {}),
|
|
1018
841
|
source: "reflect",
|
|
1019
842
|
sourceRun: `reflect-${Date.now()}`,
|
|
1020
|
-
payload: { content
|
|
1021
|
-
|
|
843
|
+
payload: { content, ...(patched?.frontmatter ? { frontmatter: patched.frontmatter } : {}) },
|
|
844
|
+
confidence: payload.confidence,
|
|
1022
845
|
...(options.eligibilitySource ? { eligibilitySource: options.eligibilitySource } : {}),
|
|
1023
846
|
...(options.itemRef ? { itemRef: options.itemRef, attemptedRefs: [options.itemRef] } : {}),
|
|
1024
847
|
}, reviewReasons.length > 0
|
|
@@ -1027,10 +850,6 @@ async function finalizeReflectProposal(args) {
|
|
|
1027
850
|
reason: reviewReasons.join("+"),
|
|
1028
851
|
// The quality gate's hand-off to a person, as distill's: the triage drain leaves it alone.
|
|
1029
852
|
gate: judgeFailed ? "quality-gate" : "reflect",
|
|
1030
|
-
...(sanitized.sizeGuardRatio ? { measured: Math.round(sanitized.sizeGuardRatio.ratio * 100) } : {}),
|
|
1031
|
-
// The reviewer sees why the judge passed it (with the gate off, nothing judged it).
|
|
1032
|
-
...(bodyEdit && verdict?.criteria ? { scores: verdict.criteria } : {}),
|
|
1033
|
-
...(bodyEdit && verdict ? { judgeReason: verdict.reason } : {}),
|
|
1034
853
|
},
|
|
1035
854
|
}
|
|
1036
855
|
: { judged: verdict });
|
|
@@ -1043,10 +862,6 @@ async function finalizeReflectProposal(args) {
|
|
|
1043
862
|
engine: run.engineName,
|
|
1044
863
|
...(judge.skippedNoJudge ? { qualityGateSkippedNoJudge: true } : {}),
|
|
1045
864
|
...(judgeFailed ? { qualityReason: verdict?.reason } : {}),
|
|
1046
|
-
...(sanitized.sizeGuardRatio
|
|
1047
|
-
? { sizeGuardRatio: sanitized.sizeGuardRatio.code, sizeGuardRatioValue: sanitized.sizeGuardRatio.ratio }
|
|
1048
|
-
: {}),
|
|
1049
|
-
...(sanitized.truncationMarkerLeaked ? { truncationMarkerLeaked: true } : {}),
|
|
1050
865
|
...telemetry,
|
|
1051
866
|
},
|
|
1052
867
|
}, options.eventsCtx);
|
|
@@ -1076,7 +891,7 @@ export async function renderReflectPromptPreview(options) {
|
|
|
1076
891
|
throw new UsageError((!failure.ok && failure.error) || `Reflect cannot preview ref "${ref}".`, "INVALID_FLAG_VALUE");
|
|
1077
892
|
}
|
|
1078
893
|
const { runnerSpec, engineName } = resolveReflectRunner(options);
|
|
1079
|
-
const sources =
|
|
894
|
+
const sources = gatherReflectPromptSources(options, stash, source.parsedRef, source.assetContent);
|
|
1080
895
|
const { prompt } = buildReflectPromptText({
|
|
1081
896
|
options,
|
|
1082
897
|
parsedRef: source.parsedRef,
|
|
@@ -1123,7 +938,7 @@ export async function akmReflect(options = {}) {
|
|
|
1123
938
|
preflightReflectDispatch(runnerSpec, notices.add);
|
|
1124
939
|
if (judgeRunner && judgeRunner !== runnerSpec)
|
|
1125
940
|
preflightReflectDispatch(judgeRunner, notices.add);
|
|
1126
|
-
const sources =
|
|
941
|
+
const sources = gatherReflectPromptSources(options, stash, parsedRef, assetContent);
|
|
1127
942
|
const agentEnv = options.eventSource === "improve" ? { AKM_EVENT_SOURCE: "improve" } : {};
|
|
1128
943
|
const sensitiveValues = collectDispatchSensitiveValues(runnerSpec, {
|
|
1129
944
|
...(Object.keys(agentEnv).length > 0 ? { env: agentEnv } : {}),
|
|
@@ -1160,25 +975,30 @@ export async function akmReflect(options = {}) {
|
|
|
1160
975
|
});
|
|
1161
976
|
return { ...envelope, ...notices.fields() };
|
|
1162
977
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
return resolved.failure;
|
|
1166
|
-
payload = resolved.payload;
|
|
978
|
+
// The iteration parsed the reply and put its payload on stdout.
|
|
979
|
+
payload = JSON.parse(result.stdout);
|
|
1167
980
|
}
|
|
1168
981
|
catch (error) {
|
|
1169
982
|
if (!(error instanceof ConfigError))
|
|
1170
983
|
emitInvoked();
|
|
1171
984
|
throw error;
|
|
1172
985
|
}
|
|
1173
|
-
const
|
|
986
|
+
const generated = Object.values(payload.patch).join("\n");
|
|
987
|
+
const unsafeContent = generatedContentRejection(generated, redactSensitiveText(generated, sensitiveValues));
|
|
1174
988
|
if (unsafeContent) {
|
|
1175
989
|
emitFailed("parse_error", "parse_error", options.ref, exitCodeMeta(result));
|
|
1176
990
|
return reflectFailure(run, result, "parse_error", unsafeContent, false);
|
|
1177
991
|
}
|
|
992
|
+
// An unscoped reply names its asset only now: read it as a targeted run read its own before dispatch.
|
|
993
|
+
const target = options.ref
|
|
994
|
+
? { assetContent }
|
|
995
|
+
: await resolveReflectSource({ ...options, ref: payload.ref }, stash, emitFailed);
|
|
996
|
+
if ("failure" in target)
|
|
997
|
+
return target.failure;
|
|
1178
998
|
return finalizeReflectProposal({
|
|
1179
999
|
run,
|
|
1180
1000
|
payload,
|
|
1181
|
-
assetContent,
|
|
1001
|
+
assetContent: target.assetContent ?? "",
|
|
1182
1002
|
result,
|
|
1183
1003
|
judge: { enabled: judgeWanted && !skippedNoJudge, skippedNoJudge, runner: judgeRunner },
|
|
1184
1004
|
feedback: sources.feedback,
|