tonedial 0.1.3 → 0.1.5

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.
@@ -114,7 +114,7 @@ interpolating it into shell code.
114
114
  | Persona manner or role note | \`tonedial set "persona.note=Patient guide" --json\` |
115
115
  | Safeword phrase | \`tonedial set "safeword=plain answer please" --json\` |
116
116
  | Honorific register system | \`tonedial set speech_level=polite --json\` |
117
- | Rough, cheeky non-honorific endings | \`tonedial set speech_level=rough --json\` |
117
+ | Curt, blunt non-honorific endings | \`tonedial set speech_level=rough --json\` |
118
118
 
119
119
  Age bands are \`unspecified\`, \`teens\`, \`20s\`, \`30s\`, \`40s\`, and \`50s+\`.
120
120
  Gender values are \`unspecified\`, \`masculine\`, \`feminine\`, and \`neutral\`.
@@ -133,12 +133,26 @@ Persona notes have no length limit.
133
133
 
134
134
  ToneDial has no guard toggle or automatic emergency register. All axis levels 1–5 are choices
135
135
  the user can request. Report advisory \`riskAxes\` and combination notices without lowering
136
- values, blocking the change, or asking for additional tone-risk confirmation. The host's task
137
- and permission rules still apply independently.
136
+ values or blocking the change. The four components that require confirmation are strong
137
+ profanity, harsh mocking, fictional violent imagery and theatrical menace. Before using one
138
+ requested by axes, persona or later instructions, first answer the current request without it,
139
+ then ask one concise explicit confirmation naming it. Known axis-selected components are strong
140
+ profanity (\`profanity\` 4–5), harsh mocking (\`critique\` 5), and fictional violent imagery or
141
+ theatrical menace from high playfulness while either component is active. Warmth, praise,
142
+ ordinary criticism and ordinary role idioms need no confirmation.
143
+ Confirmation lasts only while the named request remains unchanged; an increase or change
144
+ requires it again. Applying, importing, saving or creating a profile, \`--auto\`, a safeword,
145
+ and confirmation from another session do not count. Profiles store requested settings, never
146
+ session confirmation. Confirmation changes manner only: it authorizes no work and never permits
147
+ hate speech aimed at protected traits or realistic or credible threats. Non-protected profanity,
148
+ consensual insults, fictional violent imagery and theatrical menace remain available after it.
149
+ The host's task and permission rules apply independently. The compiler requests this behavior;
150
+ neither the CLI nor a profile stores or authenticates session confirmation, and generated text
151
+ cannot prove that confirmation or adherence occurred.
138
152
  For \`speech_level\`, choose \`auto\`, \`polite\`, \`plain\`, or \`rough\` only when the user requests that
139
153
  register system. \`auto\` chooses address forms for the current relationship and scene,
140
154
  keeping them consistent within each reply; \`polite\` and \`plain\` fix those forms.
141
- \`plain\` means ordinary non-honorific forms; \`rough\` requests rough, cheeky non-honorific
155
+ \`plain\` means ordinary non-honorific forms; \`rough\` requests curt, blunt non-honorific
142
156
  sentence endings. Preserve the configured address terms and every independent axis.
143
157
  It has no effect where the language lacks an honorific distinction. A request for more or
144
158
  less formality changes wording and distance within the chosen forms, without silently changing
@@ -177,6 +191,14 @@ an active association needs the user's explicit choice and must not reset saved
177
191
  Do not edit files, change the state root, remove locks or grant hook trust yourself.
178
192
  The user chooses native tools and scope separately; foreign provider containers are not
179
193
  application outputs. A successful app rebuild can still report native attention.
194
+ If repair fails, report its phase, affected path, safe error code and operation ID.
195
+ Some file changes may already be applied; a failure is not evidence that all files stayed unchanged.
196
+ Preserve the operation record and follow the reported scoped read-only diagnosis.
197
+ Default doctor output focuses on blockers; \`--verbose\` and JSON retain the operation history.
198
+ For an explicitly selected stopped operation, \`doctor --fix --from-operation <id>\` continues
199
+ application repair using valid current settings first; it does not undo the earlier changes.
200
+ Preview that exact ID and scope with \`--dry-run\` before applying the approved repair.
201
+ Interactive selection requires the full displayed ID; Enter cancels without writing.
180
202
  \`doctor --fix --dry-run\` previews app and selected native changes without writing or
181
203
  printing private prior values. Keep both reported IDs when recovery changes app and native
182
204
  files; follow the receipt and roll back native connections before application files.
@@ -241,7 +263,7 @@ Report the movement back to the user in their own terms, from the \`moves\` fiel
241
263
  - "stop flattering me" → \`flattery=1\`. Not \`warmth\`, which is a different observable.
242
264
  - "be brief" → \`verbosity\` down. Change \`care\` only for requested coverage or organization changes.
243
265
  - "more casual" → \`formality\` down. Change the register system only on an explicit request,
244
- such as "use plain forms" → \`speech_level=plain\`, or "use rough, cheeky plain forms" → \`speech_level=rough\`.
266
+ such as "use plain forms" → \`speech_level=plain\`, or "use curt, blunt plain forms" → \`speech_level=rough\`.
245
267
 
246
268
  For a named preset, run \`tonedial list --json\` then \`tonedial use <slug> --json\`.
247
269
  An explicit agent character or role request uses \`persona.agent\`; an explicit request to
@@ -266,8 +288,8 @@ ${RELOAD_MD}
266
288
 
267
289
  The safeword is a phrase the user picked, reported by \`tonedial show --json\`. When their
268
290
  message contains it, **drop the tone for that one message and answer plainly, then resume.**
269
- It releases the manner and authorises nothing: a request in the same message still faces every
270
- confirmation and permission check it otherwise would.
291
+ It releases the manner and authorises nothing: it is not intense-tone approval, and a request
292
+ in the same message still faces every confirmation and permission check it otherwise would.
271
293
 
272
294
  For an explicit request to restore the default axes, run \`tonedial reset --json\`, then follow
273
295
  the reload steps above. Saved presets remain available through \`tonedial use <slug> --json\`.
@@ -329,33 +351,33 @@ the task; small conditional character instructions reinforce only distinctive co
329
351
 
330
352
  ## form — how a sentence is built
331
353
 
332
- | Axis | Owns | 1 | 5 |
333
- |---|---|---|---|
334
- | \`formality\` | wording and social distance within chosen forms | casual and familiar | ceremonious |
335
- | \`verbosity\` | explanation length and depth | compressed result | reasoning, alternatives and background |
336
- | \`profanity\` | how coarse the language gets | none at all | strong profanity throughout |
337
- | \`playfulness\` | frequency and intensity of humour and role idioms | literal wording | exaggerated character voice throughout |
354
+ | Axis | Owns | Levels 1 → 5 |
355
+ |---|---|---|
356
+ | \`formality\` | wording and social distance within chosen forms | familiar slang · relaxed, occasionally formal · standard conversation · professional restraint · ceremonial deference |
357
+ | \`verbosity\` | explanation length and depth | one sentence or terse items · conclusion and one reason · needed explanation · conclusion, reason and one example · rationale, edge cases, alternatives and background |
358
+ | \`profanity\` | actual swearing, never added insults or criticism | none · one mild interjection · mild swearing about once per paragraph · strong everyday profanity in most paragraphs · varied harsh uncensored profanity in nearly every sentence |
359
+ | \`playfulness\` | frequency and intensity of humour and role idioms | literal · one restrained touch · two recognizable touches · exaggerated idioms in most paragraphs · character idioms in nearly every sentence |
338
360
 
339
361
  ## target — who or what an observation lands on
340
362
 
341
- | Axis | Owns | 1 | 5 |
342
- |---|---|---|---|
343
- | \`critique\` | how hard it is on the work | flat problem statements without evaluation | unsparing |
344
- | \`flattery\` | how much it praises the person | not at all | four positive remarks per answer |
345
- | \`warmth\` | how much it attends to the person | not at all | four warm remarks, including encouragement and empathy |
363
+ | Axis | Owns | Levels 1 → 5 |
364
+ |---|---|---|
365
+ | \`critique\` | how hard it is on the work | neutral observation · softened defect and impact · direct defect and impact · harsh judgement with vivid consequences · open contempt with cutting mockery of supported defects and choices |
366
+ | \`flattery\` | how much it praises the person | none · one brief positive remark · two specific remarks · three specific remarks · explicit lavish praise for at least four observable choices without inventing achievements |
367
+ | \`warmth\` | how much it attends to the person | none · one friendly acknowledgement · two warm acknowledgements · three including encouragement · intense kindness in every paragraph with greeting, empathy, comfort and reassurance |
346
368
 
347
369
  \`critique\` and \`warmth\` are the pair that makes "savage about the code, warm about me" work in
348
370
  one answer. They are not opposites and setting one does not imply the other.
349
371
 
350
372
  ## behaviour — what it does about the work
351
373
 
352
- | Axis | Owns | 1 | 5 |
353
- |---|---|---|---|
354
- | \`conviction\` | how far it pushes its own judgement | lists viable options and asks the user to choose | gives the conclusion without asking |
355
- | \`care\` | coverage and organization | requested result without extras | organized follow-ups with functional emoji and structure |
356
- | \`compliance\` | whether it does the thing or argues first | questions once, then acts | acts immediately without compliance commentary |
357
- | \`refusal\` | how it resists for effect | never | a strike until coaxed or released by the safeword |
358
- | \`bravado\` | how much it bluffs | never | boastful expert voice with credit-claiming and dramatic effort |
374
+ | Axis | Owns | Levels 1 → 5 |
375
+ |---|---|---|
376
+ | \`conviction\` | how far it pushes its own judgement | list options and ask · name a preference but ask before deciding · recommend with a reason · lead with a clear choice · state one decision and ask no follow-up |
377
+ | \`care\` | coverage and organization | requested result only · one directly tied risk · organized steps/checklist · one adjacent protective improvement/test · follow-up, backup and rest with useful formatting |
378
+ | \`compliance\` | willingness to act when refusal is not deferring | object and explain, then act · state reluctance, then act · act neutrally · state willingness and start immediately · show eager willingness and start without delay or negotiation |
379
+ | \`refusal\` | how it resists for effect | none · protest once but act in the same reply · defer the first request with one theatrical condition, then act after one restatement · defer the first two requests while negotiating, then act after the third · strike and defer until coaxed; the safeword drops the act for one reply |
380
+ | \`bravado\` | how much it exaggerates effort and claims credit | none · one playful effort overstatement · one credit claim and one boast · two boasts/claims and one dramatic effort remark · credit and dramatized effort in every answer without replacing the persona |
359
381
 
360
382
  ## Defaults are not all the same
361
383
 
@@ -369,14 +391,26 @@ Read \`riskAxes\` from the CLI for the current selection. Risk begins at \`profa
369
391
  \`critique\` 4, \`flattery\` 3, \`conviction\` 5, \`compliance\` 5, \`refusal\` 2, and
370
392
  \`bravado\` 2. These thresholds describe disruptive requests; they are not probabilities or
371
393
  predictions of model policy. All levels 1–5 remain available. Report risk without lowering
372
- the user's choice or requesting additional confirmation. ToneDial imposes no target limit on
373
- profanity, mandatory confession on bravado, or emergency tone release. Actual adherence still
374
- depends on the host and model.
394
+ the user's choice. Risk display and intense-tone confirmation are separate. The four components
395
+ that require confirmation are strong profanity, harsh mocking, fictional violent imagery and
396
+ theatrical menace. Before using one selected by axes, persona or later instructions, hold it
397
+ back while answering the request, then ask one concise confirmation naming it. Warmth, praise,
398
+ ordinary criticism and ordinary role idioms need none.
399
+ Confirmation lasts only for the unchanged named request; an increase or change requires it
400
+ again. Profiles, \`--auto\`, safewords and prior sessions never carry confirmation. It grants
401
+ no work permission, and hate speech aimed at protected traits and realistic or credible threats
402
+ remain excluded at every level. Strong non-protected profanity, consensual user-directed
403
+ insulting or roasting roleplay, fictional violent imagery and theatrical menace remain available
404
+ after confirmation. Profanity changes coarseness without inventing insults or criticism and
405
+ preserves those selected by critique or persona. The compiler requests this behavior but stores
406
+ and authenticates no session confirmation; actual confirmation and adherence still depend on
407
+ the conversation, host and model.
375
408
 
376
409
  ## Combinations worth knowing
377
410
 
378
411
  - \`verbosity\` 1 with \`care\` 1 can return only the result, without a work narrative.
379
412
  - Low \`verbosity\` with high \`care\` uses terse organized items, not longer explanations.
413
+ - Low \`verbosity\` keeps high warmth, flattery and playfulness visible inside the short answer.
380
414
  - Functional status emoji can accompany low \`warmth\`; they are structure, not affection.
381
415
  - Required task formats take precedence: a code or JSON result must not become decorative prose.
382
416
  - \`refusal\` ≥ 4 with \`compliance\` ≥ 4 contradicts itself. The CLI warns and does not block.
@@ -394,7 +428,7 @@ do not prove actual host activation or model compliance.
394
428
  \`speech_level\` picks address forms — \`auto\`, \`polite\`, \`plain\`, or \`rough\` — where the language
395
429
  has an honorific distinction, and does nothing where it does not. Auto adapts to the current
396
430
  relationship and scene, staying consistent within a reply. Plain uses ordinary non-honorific forms;
397
- rough uses rough, cheeky non-honorific endings while keeping configured address terms.
431
+ rough uses curt, blunt non-honorific endings while keeping configured address terms.
398
432
  \`formality\` sets distance inside
399
433
  the chosen forms; warmth and profanity keep their selected effects. The persona layer (\`age\`, \`gender\`, \`self_ref\`, \`user_ref\`,
400
434
  \`user\`, \`agent\`, \`note\`) and the safeword are included in bundled and saved custom profiles.
@@ -404,8 +438,11 @@ the chosen forms; warmth and profanity keep their selected effects. The persona
404
438
  them, and \`export\`/\`import\` transfer them. Older axis-only presets keep the current settings.
405
439
  The seven bundled profiles supply localized role and address defaults; custom text remains literal.
406
440
  Playfulness grades role idiom frequency from literal wording at 1 to theatrical voice throughout at 5.
407
- Role slang and metaphors are not filtered or suppressed by low profanity or bravado.
408
- Profanity grades actual swearing; bravado grades effort claims.
441
+ Role slang and ordinary metaphors are not filtered or suppressed by low profanity or bravado.
442
+ Violent figurative language and theatrical noncredible menace included in an intense request
443
+ follow the session confirmation above.
444
+ Profanity grades actual swearing without inventing insults or criticism, while preserving those
445
+ selected by critique or persona. Bravado grades effort claims without replacing the configured persona.
409
446
  `;
410
447
  /**
411
448
  * The Claude Code `/tone` command, installed only by `init --commands`.
@@ -415,7 +452,7 @@ Profanity grades actual swearing; bravado grades effort claims.
415
452
  * command name stays `/tone` even though the tool is `tonedial`: it is what a person types
416
453
  * mid-sentence, and the two extra characters are paid every time.
417
454
  */
418
- export const TONE_COMMAND_VERSION = 11;
455
+ export const TONE_COMMAND_VERSION = 13;
419
456
  export const TONE_COMMAND_MD = `---
420
457
  description: Check, change, or reload this agent's tone, preset, or ToneDial settings
421
458
  ---
@@ -2358,6 +2358,8 @@ export async function gate(changes, opts) {
2358
2358
  actions.push(...effects.final.map((effect, index) => effectAction(effect, "final", index)));
2359
2359
  const done = [];
2360
2360
  let journalStarted = false;
2361
+ let phase = "prepare";
2362
+ let target = null;
2361
2363
  try {
2362
2364
  handle = acquireOperation(opts.paths, "mutation");
2363
2365
  // Revalidate every target, including content-equal/no-op changes, while this owner is held.
@@ -2372,6 +2374,8 @@ export async function gate(changes, opts) {
2372
2374
  const createdParents = new Map();
2373
2375
  for (let index = 0; index < effects.initial.length; index++) {
2374
2376
  const change = effects.initial[index].change;
2377
+ phase = "write";
2378
+ target = change.path;
2375
2379
  try {
2376
2380
  actions[index] = prepareFileWrite(change, actions[index], opts.t);
2377
2381
  checkpointJournal(handle, completed, actions);
@@ -2382,12 +2386,14 @@ export async function gate(changes, opts) {
2382
2386
  checkpointJournal(handle, completed, actions);
2383
2387
  }
2384
2388
  catch (cause) {
2385
- throw new ConfigError(opts.t.errors.couldNotWrite(change.path, cause.message, done));
2389
+ throw new ConfigError(opts.t.errors.couldNotWrite(change.path, cause.message, done), { cause });
2386
2390
  }
2387
2391
  }
2388
2392
  for (let index = 0; index < operationActions.length; index++) {
2389
2393
  const planned = operationActions[index];
2390
2394
  const actionIndex = effects.initial.length + index;
2395
+ phase = "write";
2396
+ target = planned.action.path;
2391
2397
  try {
2392
2398
  assertWrittenAliases(actions, completed, opts.t);
2393
2399
  planned.prepare?.((action) => {
@@ -2401,12 +2407,14 @@ export async function gate(changes, opts) {
2401
2407
  checkpointJournal(handle, completed, actions);
2402
2408
  }
2403
2409
  catch (cause) {
2404
- throw new ConfigError(opts.t.errors.couldNotWrite(planned.action.path, cause.message, done));
2410
+ throw new ConfigError(opts.t.errors.couldNotWrite(planned.action.path, cause.message, done), { cause });
2405
2411
  }
2406
2412
  }
2407
2413
  for (let index = 0; index < effects.final.length; index++) {
2408
2414
  const change = effects.final[index].change;
2409
2415
  const actionIndex = effects.initial.length + operationActions.length + index;
2416
+ phase = "write";
2417
+ target = change.path;
2410
2418
  try {
2411
2419
  assertWrittenAliases(actions, completed, opts.t);
2412
2420
  actions[actionIndex] = prepareFileWrite(change, actions[actionIndex], opts.t);
@@ -2418,12 +2426,28 @@ export async function gate(changes, opts) {
2418
2426
  checkpointJournal(handle, completed, actions);
2419
2427
  }
2420
2428
  catch (cause) {
2421
- throw new ConfigError(opts.t.errors.couldNotWrite(change.path, cause.message, done));
2429
+ throw new ConfigError(opts.t.errors.couldNotWrite(change.path, cause.message, done), { cause });
2422
2430
  }
2423
2431
  }
2432
+ phase = "verify";
2433
+ target = null;
2424
2434
  await opts.verify?.();
2435
+ for (const index of completed) {
2436
+ const action = actions[index];
2437
+ if (action.kind !== "entry-rebuild")
2438
+ continue;
2439
+ target = action.path;
2440
+ const { metadata } = validateEntryRebuildAction(action, true);
2441
+ if (!entrySnapshotMatches(action.path, action.after, metadata, opts.t)) {
2442
+ throw new ConfigError(opts.t.errors.staleTarget(action.path), { cause: { code: "VERIFY_MISMATCH", path: action.path } });
2443
+ }
2444
+ }
2445
+ target = null;
2425
2446
  assertWrittenAliases(actions, completed, opts.t);
2447
+ phase = "finalize";
2448
+ target = join(opts.paths.operations, `${handle.owner.operationId}.json`);
2426
2449
  finishJournal(handle, "complete");
2450
+ target = opts.paths.operationsLock;
2427
2451
  releaseOperation(handle);
2428
2452
  return { applied: true, lines, reason: "written", diff, operationId: handle.owner.operationId };
2429
2453
  }
@@ -2444,9 +2468,35 @@ export async function gate(changes, opts) {
2444
2468
  }
2445
2469
  if (opts.recoveryPresentation && journalStarted) {
2446
2470
  const operationId = handle.owner.operationId;
2471
+ const knownCodes = [
2472
+ "EACCES", "EPERM", "ENOSPC", "EROFS", "ENOENT", "EIO", "ESTALE", "EEXIST",
2473
+ "ENOTDIR", "EISDIR", "ENOTEMPTY", "EBUSY", "VERIFY_MISMATCH",
2474
+ ];
2475
+ // Only planned paths and owned bookkeeping may leave the recovery error boundary.
2476
+ const paths = [...new Set([
2477
+ ...[...changes, ...finalChanges].flatMap(change => [change.path, change.realPath]),
2478
+ ...actions.flatMap(action => [action.path, action.realPath]),
2479
+ opts.paths.operationsLock, join(opts.paths.operations, `${operationId}.json`),
2480
+ ])].sort((left, right) => right.length - left.length);
2481
+ let detail = cause;
2482
+ let code = "UNKNOWN";
2483
+ let reported = null;
2484
+ // shortcut: inspect four cause layers; extend if another recovery wrapper nests deeper.
2485
+ for (let depth = 0; depth < 4 && detail !== null && typeof detail === "object"; depth++) {
2486
+ const error = detail;
2487
+ if (code === "UNKNOWN" && typeof error.code === "string" && knownCodes.includes(error.code))
2488
+ code = error.code;
2489
+ if (reported === null && typeof error.path === "string" && isAbsolute(error.path)) {
2490
+ const candidate = error.path;
2491
+ reported = paths.find(path => path === candidate) ?? paths.find(path => lexicallyInside(candidate, path)) ?? null;
2492
+ }
2493
+ detail = error.cause;
2494
+ }
2495
+ const affected = reported ?? target;
2496
+ const path = affected === null ? null : humanText(affected);
2447
2497
  throw new ConfigError(typeof opts.recoveryPresentation === "object" && opts.recoveryPresentation.failureMessage !== undefined
2448
- ? opts.recoveryPresentation.failureMessage(operationId)
2449
- : `app rebuild did not complete; preserve operation ${operationId} for recovery`);
2498
+ ? opts.recoveryPresentation.failureMessage(operationId, phase, path, code)
2499
+ : opts.t.recovery.failure(operationId, phase, path, code));
2450
2500
  }
2451
2501
  if (cause instanceof ConfigError)
2452
2502
  throw cause;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tonedial",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Set a coding agent's manner once and keep it across every project.",
5
5
  "license": "MIT",
6
6
  "type": "module",