novel-writer-cli 0.0.3 → 0.1.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/dist/__tests__/advance-refine-invalidates-eval.test.js +37 -0
- package/dist/__tests__/character-voice.test.js +1 -1
- package/dist/__tests__/gate-decision.test.js +66 -0
- package/dist/__tests__/init.test.js +7 -2
- package/dist/__tests__/narrative-health-injection.test.js +8 -8
- package/dist/__tests__/next-step-gate-decision-routing.test.js +117 -0
- package/dist/__tests__/next-step-prejudge-guardrails.test.js +112 -16
- package/dist/__tests__/next-step-title-fix.test.js +64 -8
- package/dist/__tests__/orchestrator-state-routing.test.js +168 -0
- package/dist/__tests__/orchestrator-state-write-path.test.js +59 -0
- package/dist/__tests__/steps-id.test.js +23 -0
- package/dist/__tests__/volume-pipeline.test.js +227 -0
- package/dist/__tests__/volume-review-pipeline.test.js +112 -0
- package/dist/__tests__/volume-review-storyline-rhythm.test.js +19 -0
- package/dist/advance.js +145 -48
- package/dist/checkpoint.js +71 -12
- package/dist/cli.js +202 -8
- package/dist/commit.js +1 -0
- package/dist/fs-utils.js +18 -3
- package/dist/gate-decision.js +59 -0
- package/dist/init.js +2 -0
- package/dist/instructions.js +322 -24
- package/dist/next-step.js +198 -34
- package/dist/platform-profile.js +3 -0
- package/dist/steps.js +60 -17
- package/dist/validate.js +275 -2
- package/dist/volume-commit.js +101 -0
- package/dist/volume-planning.js +143 -0
- package/dist/volume-review.js +448 -0
- package/docs/user/novel-cli.md +29 -0
- package/package.json +3 -2
- package/schemas/platform-profile.schema.json +5 -0
package/dist/cli.js
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command, CommanderError } from "commander";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
|
-
import { resolve } from "node:path";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { buildCharacterVoiceProfiles, clearCharacterVoiceDriftFile, computeCharacterVoiceDrift, loadActiveCharacterVoiceDriftIds, loadCharacterVoiceProfiles, writeCharacterVoiceDriftFile, writeCharacterVoiceProfilesFile } from "./character-voice.js";
|
|
7
7
|
import { NovelCliError } from "./errors.js";
|
|
8
8
|
import { errJson, okJson, printJson } from "./output.js";
|
|
9
|
-
import { pathExists } from "./fs-utils.js";
|
|
9
|
+
import { pathExists, readJsonFile, writeJsonFile, writeTextFile } from "./fs-utils.js";
|
|
10
10
|
import { resolveProjectRoot } from "./project.js";
|
|
11
11
|
import { readCheckpoint } from "./checkpoint.js";
|
|
12
12
|
import { initProject, normalizePlatformId, resolveInitRootDir } from "./init.js";
|
|
13
13
|
import { advanceCheckpointForStep } from "./advance.js";
|
|
14
14
|
import { commitChapter } from "./commit.js";
|
|
15
|
+
import { commitVolume } from "./volume-commit.js";
|
|
15
16
|
import { buildInstructionPacket } from "./instructions.js";
|
|
16
17
|
import { getLockStatus, clearStaleLock, withWriteLock } from "./lock.js";
|
|
17
18
|
import { computeNextStep } from "./next-step.js";
|
|
18
19
|
import { computeEngagementReport, loadEngagementMetricsStream, writeEngagementLogs } from "./engagement.js";
|
|
19
20
|
import { computePromiseLedgerReport, ensurePromiseLedgerInitialized, loadPromiseLedger, writePromiseLedgerLogs } from "./promise-ledger.js";
|
|
20
|
-
import { parseStepId } from "./steps.js";
|
|
21
|
+
import { pad2, pad3, parseStepId } from "./steps.js";
|
|
22
|
+
import { isPlainObject } from "./type-guards.js";
|
|
21
23
|
import { validateStep } from "./validate.js";
|
|
24
|
+
import { VOL_REVIEW_RELS, collectVolumeData, computeBridgeCheck, computeForeshadowingAudit, computeStorylineRhythm } from "./volume-review.js";
|
|
25
|
+
import { tryResolveVolumeChapterRange } from "./consistency-auditor.js";
|
|
22
26
|
function detectCommandName(argv) {
|
|
23
27
|
for (const token of argv) {
|
|
24
28
|
if (token === "--")
|
|
@@ -95,7 +99,7 @@ function buildProgram(argv) {
|
|
|
95
99
|
return;
|
|
96
100
|
}
|
|
97
101
|
process.stdout.write(`Project: ${rootDir}\n`);
|
|
98
|
-
process.stdout.write(`Checkpoint: chapter=${checkpoint.last_completed_chapter} volume=${checkpoint.current_volume}\n`);
|
|
102
|
+
process.stdout.write(`Checkpoint: state=${checkpoint.orchestrator_state} chapter=${checkpoint.last_completed_chapter} volume=${checkpoint.current_volume}\n`);
|
|
99
103
|
process.stdout.write(`Pipeline: stage=${checkpoint.pipeline_stage ?? "null"} inflight=${checkpoint.inflight_chapter ?? "null"} revisions=${checkpoint.revision_count ?? 0} hook_fixes=${checkpoint.hook_fix_count ?? 0} title_fixes=${checkpoint.title_fix_count ?? 0}\n`);
|
|
100
104
|
if (lock.exists) {
|
|
101
105
|
process.stdout.write(`Lock: present${lock.stale ? " (stale)" : ""} started=${lock.info?.started ?? "unknown"} pid=${lock.info?.pid ?? "unknown"} chapter=${lock.info?.chapter ?? "unknown"}\n`);
|
|
@@ -187,13 +191,24 @@ function buildProgram(argv) {
|
|
|
187
191
|
program
|
|
188
192
|
.command("commit")
|
|
189
193
|
.description("Commit staging artifacts into final locations (transaction).")
|
|
190
|
-
.
|
|
194
|
+
.option("--chapter <n>", "Chapter number to commit.", (v) => Number.parseInt(String(v), 10))
|
|
195
|
+
.option("--volume <n>", "Volume number to commit (volume planning artifacts).", (v) => Number.parseInt(String(v), 10))
|
|
191
196
|
.option("--dry-run", "Show planned actions without applying them.")
|
|
192
197
|
.action(async (localOpts) => {
|
|
193
198
|
const opts = program.opts();
|
|
194
199
|
const json = Boolean(opts.json);
|
|
195
200
|
const rootDir = await resolveProjectRoot({ cwd: process.cwd(), projectOverride: opts.project });
|
|
196
|
-
const
|
|
201
|
+
const chapter = localOpts.chapter;
|
|
202
|
+
const volume = localOpts.volume;
|
|
203
|
+
if (chapter !== undefined && volume !== undefined) {
|
|
204
|
+
throw new NovelCliError("Invalid commit: provide exactly one of --chapter or --volume.", 2);
|
|
205
|
+
}
|
|
206
|
+
if (chapter === undefined && volume === undefined) {
|
|
207
|
+
throw new NovelCliError("Invalid commit: missing required option --chapter or --volume.", 2);
|
|
208
|
+
}
|
|
209
|
+
const result = chapter !== undefined
|
|
210
|
+
? await commitChapter({ rootDir, chapter, dryRun: Boolean(localOpts.dryRun) })
|
|
211
|
+
: await commitVolume({ rootDir, volume: volume, dryRun: Boolean(localOpts.dryRun) });
|
|
197
212
|
if (json) {
|
|
198
213
|
printJson(okJson("commit", { rootDir, ...result }));
|
|
199
214
|
return;
|
|
@@ -204,8 +219,187 @@ function buildProgram(argv) {
|
|
|
204
219
|
for (const w of result.warnings)
|
|
205
220
|
process.stdout.write(`WARN: ${w}\n`);
|
|
206
221
|
}
|
|
207
|
-
if (!localOpts.dryRun)
|
|
208
|
-
|
|
222
|
+
if (!localOpts.dryRun) {
|
|
223
|
+
if (chapter !== undefined)
|
|
224
|
+
process.stdout.write(`Committed chapter ${chapter}.\n`);
|
|
225
|
+
else
|
|
226
|
+
process.stdout.write(`Committed volume ${volume}.\n`);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
const volumeReview = program.command("volume-review").description("Volume-end review helper commands (issue #144).");
|
|
230
|
+
volumeReview
|
|
231
|
+
.command("collect")
|
|
232
|
+
.description(`Generate ${VOL_REVIEW_RELS.qualitySummary} from committed evals (best-effort).`)
|
|
233
|
+
.action(async () => {
|
|
234
|
+
const opts = program.opts();
|
|
235
|
+
const json = Boolean(opts.json);
|
|
236
|
+
const rootDir = await resolveProjectRoot({ cwd: process.cwd(), projectOverride: opts.project });
|
|
237
|
+
const result = await withWriteLock(rootDir, {}, async () => {
|
|
238
|
+
const checkpoint = await readCheckpoint(rootDir);
|
|
239
|
+
const summary = await collectVolumeData({ rootDir, checkpoint });
|
|
240
|
+
await writeJsonFile(join(rootDir, VOL_REVIEW_RELS.qualitySummary), summary);
|
|
241
|
+
return { checkpoint, summary };
|
|
242
|
+
});
|
|
243
|
+
if (json) {
|
|
244
|
+
printJson(okJson("volume-review collect", { rootDir, rel: VOL_REVIEW_RELS.qualitySummary, summary: result.summary }));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
process.stdout.write(`Wrote ${VOL_REVIEW_RELS.qualitySummary}.\n`);
|
|
248
|
+
});
|
|
249
|
+
volumeReview
|
|
250
|
+
.command("report")
|
|
251
|
+
.description(`Generate ${VOL_REVIEW_RELS.reviewReport} from quality summary + audit report (deterministic markdown).`)
|
|
252
|
+
.action(async () => {
|
|
253
|
+
const opts = program.opts();
|
|
254
|
+
const json = Boolean(opts.json);
|
|
255
|
+
const rootDir = await resolveProjectRoot({ cwd: process.cwd(), projectOverride: opts.project });
|
|
256
|
+
await withWriteLock(rootDir, {}, async () => {
|
|
257
|
+
const checkpoint = await readCheckpoint(rootDir);
|
|
258
|
+
const volume = checkpoint.current_volume;
|
|
259
|
+
const endChapter = checkpoint.last_completed_chapter;
|
|
260
|
+
const resolvedRange = (await tryResolveVolumeChapterRange({ rootDir, volume })) ??
|
|
261
|
+
(Number.isInteger(endChapter) && endChapter >= 1 ? { start: Math.max(1, endChapter - 9), end: endChapter } : null);
|
|
262
|
+
if (!resolvedRange) {
|
|
263
|
+
throw new NovelCliError(`Cannot resolve volume review chapter_range (last_completed_chapter=${String(endChapter)}).`, 2);
|
|
264
|
+
}
|
|
265
|
+
// Best-effort reads: validation guards presence.
|
|
266
|
+
let summary = null;
|
|
267
|
+
let audit = null;
|
|
268
|
+
try {
|
|
269
|
+
summary = await readJsonFile(join(rootDir, VOL_REVIEW_RELS.qualitySummary));
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
summary = null;
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
audit = await readJsonFile(join(rootDir, VOL_REVIEW_RELS.auditReport));
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
audit = null;
|
|
279
|
+
}
|
|
280
|
+
const lines = [];
|
|
281
|
+
lines.push(`# Volume Review Report`);
|
|
282
|
+
lines.push("");
|
|
283
|
+
lines.push(`- volume: ${volume}`);
|
|
284
|
+
lines.push(`- chapter_range: ${resolvedRange.start}-${resolvedRange.end}`);
|
|
285
|
+
if (isPlainObject(summary)) {
|
|
286
|
+
const stats = isPlainObject(summary.stats)
|
|
287
|
+
? summary.stats
|
|
288
|
+
: null;
|
|
289
|
+
if (stats) {
|
|
290
|
+
const avg = typeof stats.overall_avg === "number" ? stats.overall_avg : null;
|
|
291
|
+
const min = typeof stats.overall_min === "number" ? stats.overall_min : null;
|
|
292
|
+
const max = typeof stats.overall_max === "number" ? stats.overall_max : null;
|
|
293
|
+
lines.push(`- overall_avg: ${avg ?? "n/a"} (min=${min ?? "n/a"}, max=${max ?? "n/a"})`);
|
|
294
|
+
}
|
|
295
|
+
const lows = Array.isArray(summary.low_chapters)
|
|
296
|
+
? summary.low_chapters
|
|
297
|
+
: [];
|
|
298
|
+
if (lows.length > 0) {
|
|
299
|
+
lines.push("");
|
|
300
|
+
lines.push(`## Low Score Chapters (<3.5)`);
|
|
301
|
+
for (const it of lows.slice(0, 20)) {
|
|
302
|
+
if (!isPlainObject(it))
|
|
303
|
+
continue;
|
|
304
|
+
const ch = it.chapter;
|
|
305
|
+
const sc = it.overall_final;
|
|
306
|
+
if (typeof ch === "number" && typeof sc === "number")
|
|
307
|
+
lines.push(`- ch${pad3(ch)}: ${sc}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (isPlainObject(audit)) {
|
|
312
|
+
const stats = isPlainObject(audit.stats) ? audit.stats : null;
|
|
313
|
+
if (stats) {
|
|
314
|
+
const total = typeof stats.issues_total === "number" ? stats.issues_total : null;
|
|
315
|
+
lines.push("");
|
|
316
|
+
lines.push(`## Consistency Audit`);
|
|
317
|
+
lines.push(`- issues_total: ${total ?? "n/a"}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
await writeTextFile(join(rootDir, VOL_REVIEW_RELS.reviewReport), `${lines.join("\n")}\n`);
|
|
321
|
+
});
|
|
322
|
+
if (json) {
|
|
323
|
+
printJson(okJson("volume-review report", { rootDir, rel: VOL_REVIEW_RELS.reviewReport }));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
process.stdout.write(`Wrote ${VOL_REVIEW_RELS.reviewReport}.\n`);
|
|
327
|
+
});
|
|
328
|
+
volumeReview
|
|
329
|
+
.command("cleanup")
|
|
330
|
+
.description(`Generate ${VOL_REVIEW_RELS.foreshadowStatus} (foreshadowing audit + bridge check + storyline rhythm).`)
|
|
331
|
+
.action(async () => {
|
|
332
|
+
const opts = program.opts();
|
|
333
|
+
const json = Boolean(opts.json);
|
|
334
|
+
const rootDir = await resolveProjectRoot({ cwd: process.cwd(), projectOverride: opts.project });
|
|
335
|
+
const payload = await withWriteLock(rootDir, {}, async () => {
|
|
336
|
+
const checkpoint = await readCheckpoint(rootDir);
|
|
337
|
+
const volume = checkpoint.current_volume;
|
|
338
|
+
const endChapter = checkpoint.last_completed_chapter;
|
|
339
|
+
const resolvedRange = (await tryResolveVolumeChapterRange({ rootDir, volume })) ??
|
|
340
|
+
(Number.isInteger(endChapter) && endChapter >= 1 ? { start: Math.max(1, endChapter - 9), end: endChapter } : null);
|
|
341
|
+
if (!resolvedRange) {
|
|
342
|
+
throw new NovelCliError(`Cannot resolve volume review chapter_range (last_completed_chapter=${String(endChapter)}).`, 2);
|
|
343
|
+
}
|
|
344
|
+
const foreshadowingAudit = await computeForeshadowingAudit({ rootDir, checkpoint });
|
|
345
|
+
// Best-effort: compute bridge check using available foreshadow ids.
|
|
346
|
+
const globalIds = new Set();
|
|
347
|
+
const planIds = new Set();
|
|
348
|
+
try {
|
|
349
|
+
const globalRaw = await readJsonFile(join(rootDir, "foreshadowing/global.json"));
|
|
350
|
+
const list = Array.isArray(globalRaw)
|
|
351
|
+
? globalRaw
|
|
352
|
+
: isPlainObject(globalRaw) && Array.isArray(globalRaw.foreshadowing)
|
|
353
|
+
? globalRaw.foreshadowing
|
|
354
|
+
: [];
|
|
355
|
+
for (const it of list) {
|
|
356
|
+
if (!isPlainObject(it))
|
|
357
|
+
continue;
|
|
358
|
+
const id = typeof it.id === "string" ? it.id.trim() : "";
|
|
359
|
+
if (id)
|
|
360
|
+
globalIds.add(id);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// optional
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
const rel = `volumes/vol-${pad2(volume)}/foreshadowing.json`;
|
|
368
|
+
const planRaw = await readJsonFile(join(rootDir, rel));
|
|
369
|
+
const list = Array.isArray(planRaw)
|
|
370
|
+
? planRaw
|
|
371
|
+
: isPlainObject(planRaw) && Array.isArray(planRaw.foreshadowing)
|
|
372
|
+
? planRaw.foreshadowing
|
|
373
|
+
: [];
|
|
374
|
+
for (const it of list) {
|
|
375
|
+
if (!isPlainObject(it))
|
|
376
|
+
continue;
|
|
377
|
+
const id = typeof it.id === "string" ? it.id.trim() : "";
|
|
378
|
+
if (id)
|
|
379
|
+
planIds.add(id);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
// optional
|
|
384
|
+
}
|
|
385
|
+
const bridgeCheck = await computeBridgeCheck({ rootDir, volume, foreshadowIds: { global: globalIds, plan: planIds } });
|
|
386
|
+
const rhythm = await computeStorylineRhythm({ rootDir, volume, chapter_range: [resolvedRange.start, resolvedRange.end] });
|
|
387
|
+
const out = {
|
|
388
|
+
schema_version: 1,
|
|
389
|
+
generated_at: new Date().toISOString(),
|
|
390
|
+
as_of: { volume, chapter: endChapter },
|
|
391
|
+
foreshadowing_audit: foreshadowingAudit,
|
|
392
|
+
bridge_check: bridgeCheck,
|
|
393
|
+
storyline_rhythm: rhythm
|
|
394
|
+
};
|
|
395
|
+
await writeJsonFile(join(rootDir, VOL_REVIEW_RELS.foreshadowStatus), out);
|
|
396
|
+
return out;
|
|
397
|
+
});
|
|
398
|
+
if (json) {
|
|
399
|
+
printJson(okJson("volume-review cleanup", { rootDir, rel: VOL_REVIEW_RELS.foreshadowStatus, payload }));
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
process.stdout.write(`Wrote ${VOL_REVIEW_RELS.foreshadowStatus}.\n`);
|
|
209
403
|
});
|
|
210
404
|
const promises = program.command("promises").description("Promise ledger (long-horizon narrative promises).");
|
|
211
405
|
promises
|
package/dist/commit.js
CHANGED
|
@@ -1249,6 +1249,7 @@ export async function commitChapter(args) {
|
|
|
1249
1249
|
}
|
|
1250
1250
|
updatedCheckpoint.pipeline_stage = "committed";
|
|
1251
1251
|
updatedCheckpoint.inflight_chapter = null;
|
|
1252
|
+
updatedCheckpoint.orchestrator_state = "WRITING";
|
|
1252
1253
|
updatedCheckpoint.revision_count = 0;
|
|
1253
1254
|
updatedCheckpoint.hook_fix_count = 0;
|
|
1254
1255
|
updatedCheckpoint.title_fix_count = 0;
|
package/dist/fs-utils.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname } from "node:path";
|
|
1
|
+
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
3
|
import { NovelCliError } from "./errors.js";
|
|
4
4
|
export async function pathExists(path) {
|
|
5
5
|
try {
|
|
@@ -55,7 +55,22 @@ export async function writeTextFileIfMissing(path, contents) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
export async function writeJsonFile(path, payload) {
|
|
58
|
-
|
|
58
|
+
const content = `${JSON.stringify(payload, null, 2)}\n`;
|
|
59
|
+
const tmp = join(dirname(path), `.${process.pid}.tmp`);
|
|
60
|
+
try {
|
|
61
|
+
await ensureDir(dirname(path));
|
|
62
|
+
await writeFile(tmp, content, "utf8");
|
|
63
|
+
await rename(tmp, path);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
// Best-effort cleanup of temp file on failure.
|
|
67
|
+
try {
|
|
68
|
+
await rm(tmp, { force: true });
|
|
69
|
+
}
|
|
70
|
+
catch { /* ignore */ }
|
|
71
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
72
|
+
throw new NovelCliError(`Failed to write file: ${path}. ${message}`);
|
|
73
|
+
}
|
|
59
74
|
}
|
|
60
75
|
export async function removePath(path) {
|
|
61
76
|
try {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { isPlainObject } from "./type-guards.js";
|
|
2
|
+
export const GATE_DECISIONS = ["pass", "polish", "revise", "pause_for_user", "pause_for_user_force_rewrite", "force_passed"];
|
|
3
|
+
function isHighViolation(check) {
|
|
4
|
+
return check.status === "violation" && check.confidence === "high";
|
|
5
|
+
}
|
|
6
|
+
export function detectHighConfidenceViolation(evalRaw) {
|
|
7
|
+
if (!isPlainObject(evalRaw))
|
|
8
|
+
return { has_high_confidence_violation: false, high_confidence_violations: [] };
|
|
9
|
+
const evalObj = evalRaw;
|
|
10
|
+
const cvRaw = evalObj.contract_verification;
|
|
11
|
+
if (!isPlainObject(cvRaw))
|
|
12
|
+
return { has_high_confidence_violation: false, high_confidence_violations: [] };
|
|
13
|
+
const cv = cvRaw;
|
|
14
|
+
const pick = (key) => {
|
|
15
|
+
const raw = cv[key];
|
|
16
|
+
if (!Array.isArray(raw))
|
|
17
|
+
return [];
|
|
18
|
+
return raw.filter((it) => isPlainObject(it));
|
|
19
|
+
};
|
|
20
|
+
const hardChecks = [];
|
|
21
|
+
for (const key of ["l1_checks", "l2_checks", "l3_checks"]) {
|
|
22
|
+
for (const it of pick(key)) {
|
|
23
|
+
if (!isHighViolation(it))
|
|
24
|
+
continue;
|
|
25
|
+
hardChecks.push(it);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const it of pick("ls_checks")) {
|
|
29
|
+
if (!isHighViolation(it))
|
|
30
|
+
continue;
|
|
31
|
+
const constraintType = typeof it.constraint_type === "string" ? it.constraint_type : null;
|
|
32
|
+
// Default to hard when missing to preserve safety.
|
|
33
|
+
const isHard = constraintType === null || constraintType === "hard";
|
|
34
|
+
if (!isHard)
|
|
35
|
+
continue;
|
|
36
|
+
hardChecks.push(constraintType === null ? { ...it, constraint_type_inferred: true } : it);
|
|
37
|
+
}
|
|
38
|
+
return { has_high_confidence_violation: hardChecks.length > 0, high_confidence_violations: hardChecks };
|
|
39
|
+
}
|
|
40
|
+
export function computeGateDecision(args) {
|
|
41
|
+
const maxRevisions = typeof args.max_revisions === "number" && Number.isInteger(args.max_revisions) && args.max_revisions >= 0 ? args.max_revisions : 2;
|
|
42
|
+
if (args.force_pass)
|
|
43
|
+
return "force_passed";
|
|
44
|
+
if (args.has_high_confidence_violation) {
|
|
45
|
+
return args.revision_count >= maxRevisions ? "pause_for_user" : "revise";
|
|
46
|
+
}
|
|
47
|
+
const score = args.overall_final;
|
|
48
|
+
if (!Number.isFinite(score))
|
|
49
|
+
return "pause_for_user_force_rewrite";
|
|
50
|
+
if (score >= 4.0)
|
|
51
|
+
return "pass";
|
|
52
|
+
if (score >= 3.5)
|
|
53
|
+
return args.revision_count >= maxRevisions ? "force_passed" : "polish";
|
|
54
|
+
if (score >= 3.0)
|
|
55
|
+
return args.revision_count >= maxRevisions ? "force_passed" : "revise";
|
|
56
|
+
if (score >= 2.0)
|
|
57
|
+
return "pause_for_user";
|
|
58
|
+
return "pause_for_user_force_rewrite";
|
|
59
|
+
}
|
package/dist/init.js
CHANGED