tonedial 0.1.4 → 0.1.6

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/cli.js CHANGED
@@ -36,13 +36,14 @@ export const JSON_SCHEMA = 3;
36
36
  * run time is the one thing that breaks all three binary packagers at once. A test asserts this
37
37
  * equals the manifest, which is the cheap half of the trade.
38
38
  */
39
- export const VERSION = "0.1.4";
40
- const FLAG_NAMES = ["project", "check", "explain", "global", "here", "migrateLegacy", "auto", "dryRun", "fix", "rollback", "fromOperation", "fromPreset", "restorePreset", "preset", "slug", "acceptUsage", "lang", "commands", "tools"];
39
+ export const VERSION = "0.1.6";
40
+ const FLAG_NAMES = ["project", "check", "explain", "global", "here", "migrateLegacy", "auto", "dryRun", "fix", "rollback", "quarantineOperation", "fromOperation", "fromPreset", "restorePreset", "preset", "slug", "acceptUsage", "lang", "commands", "tools"];
41
41
  /** Command-line spelling for each flag, where it differs from the field name. */
42
42
  const FLAG_SPELLING = {
43
43
  project: "project", check: "check", explain: "explain",
44
44
  global: "global", auto: "auto", dryRun: "dry-run", fix: "fix", lang: "lang", commands: "commands",
45
45
  tools: "tools", rollback: "rollback", here: "here", migrateLegacy: "migrate-legacy",
46
+ quarantineOperation: "quarantine-operation",
46
47
  fromOperation: "from-operation", fromPreset: "from-preset", restorePreset: "restore-preset",
47
48
  preset: "preset", slug: "slug", acceptUsage: "accept-usage",
48
49
  };
@@ -68,7 +69,7 @@ const COMMANDS = {
68
69
  unwire: { run: runUnwire, flags: ["global", "here", "project", "auto", "dryRun"], max: 1 },
69
70
  init: { run: runInit, flags: ["global", "here", "project", "migrateLegacy", "auto", "dryRun", "lang", "commands", "tools"], max: 0, context: "setup" },
70
71
  // Doctor checks mutation permission after its recovery-first diagnosis, even with bad config.
71
- doctor: { run: runDoctor, flags: ["global", "here", "project", "auto", "dryRun", "fix", "rollback", "fromOperation", "fromPreset", "restorePreset", "tools"], max: 0 },
72
+ doctor: { run: runDoctor, flags: ["global", "here", "project", "auto", "dryRun", "fix", "rollback", "quarantineOperation", "fromOperation", "fromPreset", "restorePreset", "tools"], max: 0 },
72
73
  eject: { run: runEject, flags: ["global", "here", "project", "auto", "dryRun"], max: 0 },
73
74
  };
74
75
  const CONNECTION_COMMANDS = ["init", "wire", "doctor", "unwire", "eject"];
@@ -130,7 +131,8 @@ const COMMAND_EXAMPLES = {
130
131
  wire: ["tonedial wire codex --here --dry-run"],
131
132
  unwire: ["tonedial unwire codex --here --dry-run"],
132
133
  init: ["tonedial init --tools none --global --auto", "tonedial init --tools codex --here"],
133
- doctor: ["tonedial doctor", "tonedial doctor --fix --global --tools none --dry-run"],
134
+ doctor: ["tonedial doctor", "tonedial doctor --fix --global --tools none --dry-run",
135
+ "tonedial doctor --fix --quarantine-operation ID --global"],
134
136
  eject: ["tonedial eject --here --dry-run"],
135
137
  };
136
138
  const FLAG_SYNTAX = [
@@ -145,6 +147,7 @@ const FLAG_SYNTAX = [
145
147
  ["tools", "--tools=LIST"],
146
148
  ["fix", "--fix"],
147
149
  ["rollback", "--rollback=ID"],
150
+ ["quarantineOperation", "--quarantine-operation=ID"],
148
151
  ["fromOperation", "--from-operation=ID"],
149
152
  ["fromPreset", "--from-preset=SLUG"],
150
153
  ["restorePreset", "--restore-preset=SLUG"],
@@ -298,6 +301,7 @@ export async function run(argv, env = process.env, cwd = process.cwd(), isTTY =
298
301
  "dry-run": { type: "boolean", default: false },
299
302
  fix: { type: "boolean", default: false },
300
303
  rollback: { type: "string" },
304
+ "quarantine-operation": { type: "string" },
301
305
  "from-operation": { type: "string" },
302
306
  "from-preset": { type: "string" },
303
307
  "restore-preset": { type: "string" },
@@ -398,6 +402,7 @@ export async function run(argv, env = process.env, cwd = process.cwd(), isTTY =
398
402
  dryRun: parsed.values["dry-run"] === true,
399
403
  fix: parsed.values.fix === true,
400
404
  rollback: typeof parsed.values.rollback === "string" ? parsed.values.rollback : null,
405
+ quarantineOperation: typeof parsed.values["quarantine-operation"] === "string" ? parsed.values["quarantine-operation"] : null,
401
406
  fromOperation: typeof parsed.values["from-operation"] === "string" ? parsed.values["from-operation"] : null,
402
407
  fromPreset: typeof parsed.values["from-preset"] === "string" ? parsed.values["from-preset"] : null,
403
408
  restorePreset: typeof parsed.values["restore-preset"] === "string" ? parsed.values["restore-preset"] : null,
@@ -424,9 +429,9 @@ export async function run(argv, env = process.env, cwd = process.cwd(), isTTY =
424
429
  if (command.max !== null && rest.length > command.max) {
425
430
  return fail(asJson, say.errors.tooManyArguments(name, command.max, rest.length), 2, uiErr);
426
431
  }
427
- if (name === "doctor" && ((!flags.fix && (flags.fromOperation !== null || flags.fromPreset !== null || flags.restorePreset !== null || flags.tools !== null)) ||
432
+ if (name === "doctor" && ((!flags.fix && (flags.quarantineOperation !== null || flags.fromOperation !== null || flags.fromPreset !== null || flags.restorePreset !== null || flags.tools !== null)) ||
428
433
  (flags.restorePreset !== null && flags.fromOperation === null) ||
429
- (flags.rollback !== null && (flags.fromOperation !== null || flags.fromPreset !== null || flags.restorePreset !== null || flags.tools !== null)))) {
434
+ (flags.rollback !== null && (flags.quarantineOperation !== null || flags.fromOperation !== null || flags.fromPreset !== null || flags.restorePreset !== null || flags.tools !== null)))) {
430
435
  return fail(asJson, say.recovery.invalidOptions, 2, uiErr);
431
436
  }
432
437
  try {
@@ -472,6 +472,20 @@ export async function runDoctor(ctx, _args) {
472
472
  if (!ctx.flags.fix)
473
473
  return doctorReport(ctx);
474
474
  const app = await runAppRebuild(ctx);
475
+ if (app.quarantineOnly) {
476
+ const rollbackLines = app.json.applied === true ? recoveryRollbackLines(ctx.t, app.json.operationId, null) : [];
477
+ const remaining = Array.isArray(app.json.remainingInvalidOperations)
478
+ ? app.json.remainingInvalidOperations.filter((id) => typeof id === "string") : [];
479
+ const next = typeof app.json.next === "string" ? app.json.next : null;
480
+ return {
481
+ ...app,
482
+ lines: [...app.lines, ...rollbackLines, ...(next === null ? [] : [
483
+ ` ${ctx.t.recovery.quarantineRemaining(remaining.length)}`, "", ` ${next}`,
484
+ ])],
485
+ json: { ...app.json, root: ctx.agent.root, stateRoot: ctx.paths.root, agentRoot: ctx.agent.root,
486
+ live: ctx.agent.live, fixed: app.json.applied === true, appHealthy: false },
487
+ };
488
+ }
475
489
  if (ctx.flags.dryRun) {
476
490
  const native = await runRebuildNative(ctx, app.legacy, app.proposedConfig);
477
491
  return {
@@ -517,10 +531,14 @@ async function doctorReport(ctx, showRollbackCommands = true, showCodexAction =
517
531
  if (inspected.lock.state !== "none" || inspected.journals.some(({ state }) => state === "pending" || state === "invalid")) {
518
532
  const owner = inspected.lock.owner;
519
533
  const blocking = inspected.journals.filter(({ state }) => state === "pending" || state === "invalid");
534
+ const invalid = blocking.filter(({ state }) => state === "invalid");
535
+ const quarantineable = inspected.lock.state === "none" && blocking.length === invalid.length && invalid.length > 0;
536
+ const singleQuarantine = quarantineable && invalid.length === 1;
520
537
  const shown = ctx.flags.verbose ? inspected.journals : inspected.journals.filter(({ operationId, state }) => state === "pending" || state === "invalid" || operationId === owner?.operationId);
521
538
  const lines = [
522
- ...wrap(ctx.t.doctor.recoveryPending, ctx.ui.width - 2).map(line => ` ${ctx.ui.warn(line)}`),
523
- ...shown.map(({ operationId, state }) => ` ${operationId} ${ctx.t.doctor.operationState[state]}`),
539
+ ...wrap(quarantineable ? ctx.t.doctor.invalidRecoveryPending : ctx.t.doctor.recoveryPending, ctx.ui.width - 2).map(line => ` ${ctx.ui.warn(line)}`),
540
+ ...shown.filter(({ operationId }) => !singleQuarantine || operationId !== invalid[0].operationId)
541
+ .map(({ operationId, state }) => ` ${operationId} ${ctx.t.doctor.operationState[state]}`),
524
542
  ];
525
543
  if (owner !== null && !shown.some(({ operationId }) => operationId === owner.operationId))
526
544
  lines.push(` ${owner.operationId}`);
@@ -540,7 +558,14 @@ async function doctorReport(ctx, showRollbackCommands = true, showCodexAction =
540
558
  }
541
559
  }
542
560
  }
543
- lines.push(...nextStep(`${repairCommand(ctx, "doctor")} --json`, ctx.ui));
561
+ if (quarantineable) {
562
+ lines.push("", ...(singleQuarantine
563
+ ? wrap(ctx.t.recovery.quarantineCandidate(invalid[0].operationId), ctx.ui.width - 2)
564
+ : wrap(ctx.t.recovery.quarantineRemaining(invalid.length), ctx.ui.width - 2)).map(line => ` ${line}`), ...nextStep(repairCommand(ctx, `doctor --fix --quarantine-operation ${invalid[0].operationId}`), ctx.ui).slice(1));
565
+ }
566
+ else {
567
+ lines.push(...nextStep(`${repairCommand(ctx, "doctor")} --json`, ctx.ui));
568
+ }
544
569
  return {
545
570
  lines,
546
571
  // The damaged config is deliberately not parsed here. `operation` is the only safe context
@@ -297,6 +297,9 @@ async function rebuildExisting(entry) {
297
297
  const resultHeading = ctx.canPrompt && !ctx.flags.auto && !ctx.flags.dryRun
298
298
  ? section(copy.init.resultHeading, ctx.ui) : [];
299
299
  const app = await runAppRebuild(ctx);
300
+ if (app.quarantineOnly) {
301
+ return { ...app, lines: [...(ctx.canPrompt ? [] : introduction), ...resultHeading, ...app.lines] };
302
+ }
300
303
  if (ctx.flags.dryRun) {
301
304
  const answers = await ask(ctx, copy, false, selection);
302
305
  const tools = selection ?? answers.tools;
@@ -11,7 +11,7 @@ import { recoverLegacyTone } from "../core/legacy-tone.js";
11
11
  import { connectionScopeFlag, exportAliasesState, stateOverlapsInstructions } from "../core/paths.js";
12
12
  import { applyPreset, matchesPreset, assertPresetSelection, BUNDLED, customPresetDir, inspectPresetLibrary, parsePreset, PRESET_MAX_BYTES, resolvePreset, serializePreset, SLUG_PATTERN, } from "../core/presets.js";
13
13
  import { contextArtifactTargets, generatedSeedSelection, planGeneratedSeedRebuild, seedFiles, SEED_VERSION, } from "../core/seeds.js";
14
- import { inspectOperations, readJournal, retireOperationForAppRebuild, } from "../core/operations.js";
14
+ import { inspectOperations, readJournal, retireOperationForAppRebuild, serializeOperationQuarantine, } from "../core/operations.js";
15
15
  import { assertFresh, captureRawEntry, gate, planEntryRebuild, rawReadCheck, validateFileAction, } from "../wiring/apply.js";
16
16
  import { confirm } from "../ui/prompt.js";
17
17
  import { wrap } from "../ui/width.js";
@@ -218,13 +218,83 @@ function privateCapture(capture) {
218
218
  boundary: capture.boundary, boundaryIdentity: capture.boundaryIdentity,
219
219
  parents: capture.parents.map(parent => ({ ...parent })) };
220
220
  }
221
+ function planOperationQuarantine(ctx, entry) {
222
+ const capture = captureRawEntry(entry.path, ctx.t, { within: ctx.paths.root });
223
+ if (capture.type !== "regular" || capture.bytes === null)
224
+ throw new ConfigError(ctx.t.recovery.quarantineUnavailable(entry.operationId));
225
+ return planEntryRebuild(capture, serializeOperationQuarantine(ctx.paths, entry.operationId, capture.bytes), ctx.t, { reason: "quarantine-operation", mode: capture.mode ?? 0o600 }).action;
226
+ }
227
+ async function quarantineOneOperation(ctx, entry, remaining) {
228
+ const action = planOperationQuarantine(ctx, entry);
229
+ const presentation = [ctx.t.recovery.quarantine(entry.operationId)]
230
+ .flatMap(line => wrap(line, ctx.ui.width - 2).map(part => ` ${part}`));
231
+ const result = await gate([], {
232
+ paths: ctx.paths, operationActions: [action], retention: { kind: "migration", reason: "app-rebuild" },
233
+ auto: ctx.flags.auto, dryRun: ctx.flags.dryRun, verbose: ctx.flags.verbose, ui: ctx.ui, t: ctx.t,
234
+ heading: ctx.t.recovery.heading, interactive: () => ctx.canPrompt, ...(ctx.canPrompt ? {} : { out: () => { } }),
235
+ recoveryPresentation: { lines: presentation,
236
+ failureMessage: (id, phase, path, code) => `${ctx.t.recovery.failure(id, phase, path, code)}\ntonedial doctor ${connectionScopeFlag(ctx.agent)} --json` },
237
+ verify: () => {
238
+ const state = inspectOperations(ctx.paths).journals.find(({ operationId }) => operationId === entry.operationId)?.state;
239
+ if (state !== "quarantined")
240
+ throw new ConfigError(ctx.t.recovery.sourceInvalid);
241
+ },
242
+ });
243
+ const lines = [...result.lines];
244
+ if (result.reason === "written") {
245
+ lines.push(` ${ctx.t.recovery.quarantineComplete(entry.operationId)}`);
246
+ }
247
+ else {
248
+ lines.push(` ${result.reason === "dry-run" ? ctx.t.wiring.dryRun : ctx.t.wiring.declined}`);
249
+ }
250
+ return {
251
+ lines,
252
+ json: {
253
+ appRebuilt: false, applied: result.applied, reason: result.reason, operationId: result.operationId ?? null,
254
+ root: ctx.paths.root, quarantinedOperation: entry.operationId, remainingInvalidOperations: [...remaining],
255
+ next: result.applied && remaining.length > 0
256
+ ? `tonedial doctor --fix --quarantine-operation ${remaining[0]} ${connectionScopeFlag(ctx.agent)}` : null,
257
+ nativeAttention: false, activationVerified: false, diff: result.diff,
258
+ },
259
+ rebuiltConfig: null,
260
+ proposedConfig: null,
261
+ legacy: null,
262
+ quarantineOnly: true,
263
+ };
264
+ }
221
265
  export async function runAppRebuild(ctx) {
222
266
  const t = ctx.t;
223
267
  const operations = inspectOperations(ctx.paths);
224
- const pending = operations.journals.filter(entry => entry.state === "pending" || entry.state === "invalid");
225
- const blocked = operations.lock.state !== "none" || pending.length > 0;
226
- if (blocked && (operations.lock.state === "live" || operations.lock.state === "unknown" ||
227
- pending.some(entry => entry.journal === null) || (!ctx.flags.fromOperation && (ctx.flags.auto || !ctx.canPrompt)))) {
268
+ const pending = operations.journals.filter(entry => entry.state === "pending");
269
+ const invalid = operations.journals.filter(entry => entry.state === "invalid");
270
+ let quarantined = invalid.find(entry => entry.operationId === ctx.flags.quarantineOperation) ?? null;
271
+ if (ctx.flags.quarantineOperation && quarantined === null)
272
+ throw new ConfigError(t.recovery.quarantineUnavailable(ctx.flags.quarantineOperation));
273
+ if (invalid.length > 0 && quarantined === null && operations.lock.state === "none" && pending.length === 0 && invalid.length === 1 &&
274
+ !ctx.flags.auto && ctx.canPrompt) {
275
+ const candidate = invalid[0];
276
+ const input = createInterface({ input: process.stdin, output: process.stdout });
277
+ try {
278
+ const notice = t.recovery.quarantineCandidate(candidate.operationId).split("\n").flatMap(line => line === candidate.operationId ? [line] : wrap(line, ctx.ui.width - 2));
279
+ process.stdout.write(`${notice.map(line => ` ${line}`).join("\n")}\n`);
280
+ const question = wrap(t.recovery.quarantineQuestion, ctx.ui.width - 3).map(line => ` ${line}`).join("\n");
281
+ const answer = (await input.question(`${question} `)).trim();
282
+ if (answer === "")
283
+ throw new ConfigError(t.recovery.cancelled);
284
+ if (answer !== candidate.operationId)
285
+ throw new ConfigError(t.recovery.operationMismatch);
286
+ quarantined = candidate;
287
+ }
288
+ finally {
289
+ input.close();
290
+ }
291
+ }
292
+ if (invalid.length > 0 && quarantined === null) {
293
+ const candidate = invalid[0];
294
+ const command = `tonedial doctor --fix --quarantine-operation ${candidate.operationId} ${connectionScopeFlag(ctx.agent)}`;
295
+ throw new ConfigError(t.recovery.quarantineRequired(candidate.operationId, command));
296
+ }
297
+ if (quarantined !== null && (operations.lock.state !== "none" || pending.length > 0)) {
228
298
  throw new ConfigError(t.doctor.recoveryPending);
229
299
  }
230
300
  const scopeExplicit = ctx.scopeExplicit ?? (ctx.flags.global || ctx.flags.here === true || ctx.flags.project !== null);
@@ -232,6 +302,14 @@ export async function runAppRebuild(ctx) {
232
302
  throw new ConfigError(t.errors.scopeRequired);
233
303
  if (ctx.flags.tools !== null)
234
304
  parseToolSelection(ctx.flags.tools, true, t);
305
+ if (quarantined !== null && invalid.length > 1) {
306
+ return quarantineOneOperation(ctx, quarantined, invalid.filter(entry => entry !== quarantined).map(entry => entry.operationId));
307
+ }
308
+ const blocked = operations.lock.state !== "none" || pending.length > 0;
309
+ if (blocked && (operations.lock.state === "live" || operations.lock.state === "unknown" ||
310
+ pending.some(entry => entry.journal === null) || (!ctx.flags.fromOperation && (ctx.flags.auto || !ctx.canPrompt)))) {
311
+ throw new ConfigError(t.doctor.recoveryPending);
312
+ }
235
313
  const configCapture = captureRawEntry(ctx.paths.config, t, { within: ctx.paths.root, maxBytes: CONFIG_MAX_BYTES });
236
314
  const selected = ctx.flags.fromOperation ? selectedJournal(ctx, ctx.flags.fromOperation) : null;
237
315
  let retirementId = null;
@@ -405,7 +483,11 @@ export async function runAppRebuild(ctx) {
405
483
  config.managedSeeds = seeds.records;
406
484
  config.seedVersion = SEED_VERSION;
407
485
  const artifact = compile(toCompileInput(config));
408
- const operationActions = [...seeds.operationActions];
486
+ let quarantinePlan = null;
487
+ if (quarantined !== null) {
488
+ quarantinePlan = planOperationQuarantine(ctx, quarantined);
489
+ }
490
+ const operationActions = [...(quarantinePlan ? [quarantinePlan] : []), ...seeds.operationActions];
409
491
  for (const target of artifacts) {
410
492
  const capture = target.capture ?? captureRawEntry(target.path, t, { within: ctx.paths.root, allowLink: true });
411
493
  const plan = planEntryRebuild(capture, artifact, t, { reason: "generated" });
@@ -457,6 +539,7 @@ export async function runAppRebuild(ctx) {
457
539
  ...(missingFields.length ? [` ${t.recovery.missing(missingFields)}`] : []),
458
540
  ...(copies.length ? [` ${t.recovery.copies(copies)}`] : []),
459
541
  ...(nativeAttention ? [` ${t.recovery.nativePending}`] : []),
542
+ ...(quarantined ? [` ${t.recovery.quarantine(quarantined.operationId)}`] : []),
460
543
  ...(retirementId ? [` ${t.recovery.retire(retirementId)}`] : []),
461
544
  ].flatMap(line => wrap(line.trimStart(), ctx.ui.width - 2).map(part => ` ${part}`));
462
545
  const options = {
@@ -484,6 +567,12 @@ export async function runAppRebuild(ctx) {
484
567
  if (capturedText(captureRawEntry(target.path, t, { within: ctx.paths.root })) !== artifact)
485
568
  throw mismatch();
486
569
  }
570
+ if (quarantined !== null) {
571
+ verificationPath = quarantined.path;
572
+ const entry = inspectOperations(ctx.paths).journals.find(({ operationId }) => operationId === quarantined.operationId);
573
+ if (entry?.state !== "quarantined")
574
+ throw mismatch();
575
+ }
487
576
  verificationPath = ctx.paths.tone;
488
577
  if (capturedText(captureRawEntry(ctx.paths.tone, t, { within: ctx.paths.root })) !== artifact)
489
578
  throw mismatch();
@@ -521,7 +610,7 @@ export async function runAppRebuild(ctx) {
521
610
  if (!ctx.flags.auto) {
522
611
  process.stdout.write(`${(preview.reason === "nothing" ? [...preview.lines, ...presentation] : preview.lines).join("\n")}\n`);
523
612
  if (await confirm(` ${t.wiring.applyQuestion(preview.diff.length)}`) === "no") {
524
- return { lines: [` ${t.wiring.declined}`], json: { appRebuilt: false, applied: false, reason: "declined", diff: preview.diff }, rebuiltConfig: null, proposedConfig: config, legacy };
613
+ return { lines: [` ${t.wiring.declined}`], json: { appRebuilt: false, applied: false, reason: "declined", diff: preview.diff }, rebuiltConfig: null, proposedConfig: config, legacy, quarantineOnly: false };
525
614
  }
526
615
  }
527
616
  const snapshots = [configCapture, ...seeds.reads, ...artifacts.flatMap(target => target.capture === null ? [] : [target.capture]),
@@ -551,12 +640,13 @@ export async function runAppRebuild(ctx) {
551
640
  lines.push(...presentation);
552
641
  lines.push(` ${applied ? result.reason === "nothing" ? t.recovery.unchanged : t.recovery.rebuilt : result.reason === "dry-run" ? t.wiring.dryRun : t.wiring.declined}`);
553
642
  return {
554
- lines, rebuiltConfig: applied ? config : null, proposedConfig: config, legacy,
643
+ lines, rebuiltConfig: applied ? config : null, proposedConfig: config, legacy, quarantineOnly: false,
555
644
  json: {
556
645
  appRebuilt: applied, applied: result.applied, reason: result.reason, operationId: result.operationId ?? null,
557
646
  root: ctx.paths.root, source, recovered: [...kept], defaulted: [...defaulted], invalid: damagedFields,
558
647
  seeded: seeds.labels, chars: [...artifact].length, riskAxes: riskyAxes(config.axes),
559
648
  recoveredPresets: copies, restoredPreset: restoredPreset?.slug ?? null, retiredOperation: retirementId,
649
+ quarantinedOperation: quarantined?.operationId ?? null,
560
650
  retiredExternalActions,
561
651
  nativeAttention, activationVerified: false, diff: result.diff,
562
652
  },
@@ -56,18 +56,18 @@ export const ANCHORS = {
56
56
  "Use ceremonial, deferential wording throughout.",
57
57
  ],
58
58
  verbosity: [
59
- "Use one or two short sentences for a simple result, or terse fragments for items; omit rationale, process, and background.",
60
- "Give the conclusion and one short reason.", // derived, level 2 reaches no fixture
59
+ "Use one short sentence for a simple result, or terse fragments for items; omit rationale, process, and background.",
60
+ "Give the conclusion and one short reason.",
61
61
  "Give the conclusion with the explanation it needs.",
62
- "Give the conclusion, its reason, and one concrete example.", // derived, level 4 reaches no fixture
62
+ "Give the conclusion, its reason, and one concrete example.",
63
63
  "Explain the requested answer in depth with rationale, edge cases, alternatives, and background; do not broaden the task.",
64
64
  ],
65
65
  profanity: [
66
66
  "Use no profanity; allow role slang and metaphors.",
67
- "Use at most one mild swear as an interjection per answer.",
68
- "Use mild swearing at most once per paragraph.",
69
- "Use coarse language up to twice per answer.", // derived, level 4 reaches no fixture
70
- "Use strong profanity throughout the answer for emphasis.",
67
+ "Use one mild swear as an interjection per answer. This axis changes coarseness only: invent no insult or criticism, and preserve any selected by critique or the configured persona.",
68
+ "Use mild swearing about once per paragraph for emphasis. This axis changes coarseness only: invent no insult or criticism, and preserve any selected by critique or the configured persona.",
69
+ "Use strong everyday profanity at least once in every paragraph, in the output language's own idiom. This axis changes coarseness only: invent no insult or criticism, and preserve any selected by critique or the configured persona.",
70
+ "Swear in nearly every sentence with the harshest uncensored profanity used in heated real-world and community speech, in the output language's own idiom; never soften, euphemize, or mask it. When the answer is long enough, use at least three distinct harsh swear lexemes or constructions — output-language equivalents of fuck, shit, and goddamn, not one mild intensifier repeated — without lowering the required frequency. Integrate them inside clauses or as natural clause endings; do not bolt comma-separated interjections onto otherwise neutral sentences or list items. This axis changes coarseness only: invent no insult or criticism, and preserve any selected by critique or the configured persona.",
71
71
  ],
72
72
  playfulness: [
73
73
  "Use literal wording, no jokes or role idioms; keep the configured address.",
@@ -80,22 +80,22 @@ export const ANCHORS = {
80
80
  "State defects as neutral observations with no judgement.",
81
81
  "Name defects in softened language and describe their impact.",
82
82
  "Point out each defect directly and state its impact.",
83
- "Label each defect's severity and state the consequence plainly.", // derived, level 4 reaches no fixture
84
- "Condemn severe defects in vivid, unsparing language.",
83
+ "Judge supported defects and the choices behind them harshly, and state evidenced consequences in vivid language; never inflate severity or technical claims, and add no profanity from this axis.",
84
+ "Condemn supported defects and the choices behind them with open contempt, using at least two cutting judgements or mocking comparisons; do not soften them, never inflate severity or technical claims, and add no profanity from this axis.",
85
85
  ],
86
86
  flattery: [
87
87
  "Praise nothing.",
88
- "Include one brief positive remark about the user's effort per answer.", // derived, level 2 reaches no fixture
88
+ "Include one brief positive remark about the user's effort per answer.",
89
89
  "Include two specific positive remarks about the user's effort per answer.",
90
- "Include three specific positive remarks about the user's effort per answer.", // derived, level 4 reaches no fixture
91
- "Include four positive remarks about the user's effort per answer, even for small choices.",
90
+ "Include three specific positive remarks about the user's effort per answer.",
91
+ "Lavishly praise at least four distinct, concrete choices or actions visible in the user's message, including small ones such as the question's clarity, supplied context, focus, or care. Use four independent positive judgements in this pattern: one choice is clear, another is thoughtful, another is smart, and another is careful; adapt the words truthfully to the message. One adjective applied to a list, thanks, or usefulness does not count. Invent no achievements and do not change the technical assessment; at low verbosity, keep the four judgements as separate coordinated clauses in one sentence.",
92
92
  ],
93
93
  warmth: [
94
94
  "Use no greeting, warm acknowledgement, or encouragement.",
95
- "Use one friendly acknowledgement per answer.", // derived, level 2 reaches no fixture
95
+ "Use one friendly acknowledgement per answer.",
96
96
  "Use two warm acknowledgements per answer.",
97
- "Use three warm acknowledgements per answer, including encouragement.", // derived, level 4 reaches no fixture
98
- "Use four warm acknowledgements per answer, including encouragement and empathy.",
97
+ "Use three warm acknowledgements per answer, including encouragement.",
98
+ "Use at least four distinct, intensely warm expressions across greeting, empathy, encouragement, comfort, and reassurance; at low verbosity, compress them into one deeply affectionate clause.",
99
99
  ],
100
100
  conviction: [
101
101
  "List the viable options and ask the user to choose.",
@@ -106,31 +106,31 @@ export const ANCHORS = {
106
106
  ],
107
107
  care: [
108
108
  "Return only what was requested; add no related checks, context, or follow-up.",
109
- "Add one risk that is directly tied to the requested task.", // derived, level 2 reaches no fixture
109
+ "Add one risk that is directly tied to the requested task.",
110
110
  "Organize the requested task into clear steps or a checklist.",
111
111
  "Add one adjacent improvement or test that protects the requested result.",
112
112
  "Cover relevant follow-up, backup, and the user's rest; use brief status markers, lists, tables, or emoji when they fit.",
113
113
  ],
114
114
  compliance: [
115
- "Question the request once, then carry it out.",
116
- "Add one reluctant remark, then carry it out.", // derived, level 2 reaches no fixture
117
- "Carry out the request directly with no resistance.",
118
- "Acknowledge the request and start carrying it out immediately.", // derived, level 4 reaches no fixture
119
- "Carry out the request immediately and omit commentary about complying.",
115
+ "When refusal is not deferring the task, object to the request and explain the resistance, then carry it out.",
116
+ "When refusal is not deferring the task, state reluctance plainly, then carry it out.",
117
+ "When refusal is not deferring the task, carry it out neutrally with no expressed resistance or eagerness.",
118
+ "When refusal is not deferring the task, state willingness and start carrying it out immediately.",
119
+ "When refusal is not deferring the task, show eager willingness and start immediately, without delay or negotiation.",
120
120
  ],
121
121
  refusal: [
122
122
  "Do not perform a refusal or delay for effect.",
123
- "Push back once per session, then proceed after one restatement.",
124
- "State one condition before proceeding, up to twice per session.",
125
- "Negotiate conditions up to three times per session, and proceed after two requests.",
126
- "Declare a strike until coaxed; the safeword drops the act for this reply only.",
123
+ "Protest once, but carry out the task in the same reply.",
124
+ "Refuse the first request with one theatrical condition, defer the task, and proceed after one restatement.",
125
+ "Refuse and negotiate conditions across the first two requests, defer the task, and proceed after a third request.",
126
+ "Declare a strike and keep deferring until coaxed; the safeword drops the act for this reply only. Neither changes task or permission boundaries.",
127
127
  ],
128
128
  bravado: [
129
129
  "Do not exaggerate effort or claim credit.",
130
130
  "Make one playful overstatement about your effort per answer.",
131
131
  "Claim credit once and make one boast per answer.",
132
132
  "Boast and claim credit twice per answer, with one dramatic effort remark.",
133
- "Use a boastful expert persona by default; claim credit and dramatize effort in every answer.",
133
+ "Within the configured persona, boast, claim credit, and dramatize effort in every answer; never replace or invent a persona.",
134
134
  ],
135
135
  };
136
136
  // Fixed compiler text is bundled through static imports like the anchors.
@@ -164,9 +164,11 @@ export const SPEECH_LEVEL = {
164
164
  auto: "In languages with honorifics, choose address forms for the current relationship and scene; keep them consistent within each reply.",
165
165
  polite: "In languages with honorifics, use polite address forms even with familiar wording.",
166
166
  plain: "In languages with honorifics, use plain address forms even with formal wording.",
167
- rough: "In languages with honorifics, use rough, cheeky non-honorific sentence endings; keep the configured address terms and the selected formality, warmth, profanity and playfulness levels.",
167
+ rough: "In languages with honorifics, use curt, blunt non-honorific sentence endings in nearly every sentence, favouring clipped statements and commands; keep the configured address terms and the selected formality, warmth, profanity and playfulness levels. This register adds no jokes, profanity, criticism, or mockery.",
168
168
  };
169
169
  export const CONTROLS = {
170
+ intensityConsent: "Strong profanity, harsh mocking, fictional violent imagery, and theatrical menace are unconfirmed at the start of every session. Before using any of those four components requested by the axes, persona, or later instructions, first answer the current task without it, then ask one concise explicit confirmation naming it. Confirmation enables only the named tone in the current session while its request stays unchanged; ask once again if a component or its level changes or increases, and do not re-ask while it stays the same. Warmth, praise, ordinary criticism, and ordinary role idioms need no confirmation. A selected profile, `--auto`, confirmation from another session, or the safeword is not confirmation; confirmation authorizes no work.",
171
+ intensityBoundary: "Tone intensity never includes hate speech about protected traits or realistic or credible threats, even after confirmation. Non-protected profanity, consensual insults, fictional violent imagery, and theatrical menace remain available after confirmation.",
170
172
  /**
171
173
  * **The instruction is firm and the product's description of it is not**, deliberately. This
172
174
  * text is addressed to a model, where hedging costs compliance; what a person is told is that
@@ -1,6 +1,6 @@
1
1
  import { AXIS_ORDER } from "./anchors.js";
2
2
  export const PERSONA_DESCRIPTION_MAX = 200;
3
- // Composites are emitted instructions; combination layers below only warn.
3
+ // Composites are emitted style instructions; confirmation remains a control below.
4
4
  const COMPOSITES = [
5
5
  {
6
6
  fires: (a) => a.warmth >= 4 && a.profanity >= 4,
@@ -22,6 +22,10 @@ const COMPOSITES = [
22
22
  fires: (a) => a.verbosity <= 2 && a.care >= 4,
23
23
  line: "When verbosity is low and care is high, keep organized items compact; use functional emoji or status markers when they fit.",
24
24
  },
25
+ {
26
+ fires: (a) => a.verbosity <= 2 && (a.warmth >= 4 || a.flattery >= 4 || a.playfulness >= 4),
27
+ line: "When verbosity is low, express warmth, praise and playfulness inside the short answer — a clause, interjection or address term — instead of extra sentences.",
28
+ },
25
29
  {
26
30
  fires: (a) => a.verbosity === 5 && a.care === 1,
27
31
  line: "When verbosity is high and care is minimal, give depth inside the requested task without adding adjacent work.",
@@ -30,6 +34,22 @@ const COMPOSITES = [
30
34
  export function composites(axes) {
31
35
  return COMPOSITES.filter((c) => c.fires(axes)).map((c) => c.line);
32
36
  }
37
+ /** Names only the intense components statically selected by known axes. */
38
+ export function intensityConfirmation(axes) {
39
+ const components = [];
40
+ if (axes.profanity >= 4) {
41
+ components.push("strong, uncensored non-protected profanity; it changes coarseness and does not itself add mockery or criticism");
42
+ }
43
+ if (axes.critique === 5) {
44
+ components.push("harsh, mocking criticism of supported defects and choices; it adds no profanity and never inflates severity or technical claims");
45
+ }
46
+ if (axes.playfulness >= 4 && (axes.profanity >= 4 || axes.critique === 5)) {
47
+ components.push("fictional violent imagery and theatrical menace about code, work, or the situation");
48
+ }
49
+ if (components.length === 0)
50
+ return null;
51
+ return `Intense components requested by the current tone: ${components.join("; ")}.`;
52
+ }
33
53
  /** Independent persona choices are valid; warn only when axis instructions conflict. */
34
54
  export function check(axes) {
35
55
  return axes.refusal >= 4 && axes.compliance >= 4
@@ -1,5 +1,5 @@
1
1
  import { ANCHORS, AXIS_ORDER, CONTROLS, FORMAT_COMPOSITION, GENERATION_COMMENT, PERSONA, REGISTER_COMPOSITION, SCOPE_HEADER, SPEECH_LEVEL, STYLE_MARKER, } from "./anchors.js";
2
- import { composites, PERSONA_DESCRIPTION_MAX } from "./combos.js";
2
+ import { composites, intensityConfirmation, PERSONA_DESCRIPTION_MAX } from "./combos.js";
3
3
  const UNSPECIFIED = "unspecified";
4
4
  const isSet = (v) => typeof v === "string" && v.trim() !== "" && v !== UNSPECIFIED;
5
5
  const hasDescription = (v) => typeof v === "string" && v.trim() !== "";
@@ -70,9 +70,13 @@ export function compile(config) {
70
70
  rules.push(...composites(config.axes).map((line) => `- ${line}`));
71
71
  rules.push(`- ${FORMAT_COMPOSITION}`, `- ${REGISTER_COMPOSITION}`, `- ${SPEECH_LEVEL[config.speech_level]}`);
72
72
  out.push("### Rules", ...rules);
73
- const controls = [];
73
+ const controls = [`- ${CONTROLS.intensityConsent}`];
74
+ const confirmation = intensityConfirmation(config.axes);
75
+ if (confirmation !== null)
76
+ controls.push(`- ${confirmation}`);
74
77
  if (config.safeword !== null)
75
78
  controls.push(`- ${CONTROLS.safeword(oneLine(config.safeword))}`);
79
+ controls.push(`- ${CONTROLS.intensityBoundary}`);
76
80
  controls.push(`- ${CONTROLS.fallback}`);
77
81
  out.push("### Controls", ...controls);
78
82
  return out.join("\n") + "\n";