tonedial 0.1.3 → 0.1.4
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 +1 -1
- package/dist/commands/doctor.js +27 -4
- package/dist/commands/rebuild.js +37 -16
- package/dist/core/i18n.js +14 -4
- package/dist/core/locales/es.js +7 -2
- package/dist/core/locales/ja.js +7 -2
- package/dist/core/locales/zh.js +7 -2
- package/dist/core/presets.js +3 -0
- package/dist/core/seeds.js +2 -2
- package/dist/core/skill.js +9 -1
- package/dist/wiring/apply.js +55 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -36,7 +36,7 @@ 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.
|
|
39
|
+
export const VERSION = "0.1.4";
|
|
40
40
|
const FLAG_NAMES = ["project", "check", "explain", "global", "here", "migrateLegacy", "auto", "dryRun", "fix", "rollback", "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 = {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -515,11 +515,34 @@ async function doctorReport(ctx, showRollbackCommands = true, showCodexAction =
|
|
|
515
515
|
operations: inspected.journals.map(({ operationId, state }) => ({ operationId, state })),
|
|
516
516
|
};
|
|
517
517
|
if (inspected.lock.state !== "none" || inspected.journals.some(({ state }) => state === "pending" || state === "invalid")) {
|
|
518
|
+
const owner = inspected.lock.owner;
|
|
519
|
+
const blocking = inspected.journals.filter(({ state }) => state === "pending" || state === "invalid");
|
|
520
|
+
const shown = ctx.flags.verbose ? inspected.journals : inspected.journals.filter(({ operationId, state }) => state === "pending" || state === "invalid" || operationId === owner?.operationId);
|
|
521
|
+
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]}`),
|
|
524
|
+
];
|
|
525
|
+
if (owner !== null && !shown.some(({ operationId }) => operationId === owner.operationId))
|
|
526
|
+
lines.push(` ${owner.operationId}`);
|
|
527
|
+
if (inspected.lock.state === "live" || inspected.lock.state === "unknown" || inspected.lock.state === "invalid") {
|
|
528
|
+
lines.push(...wrap(ctx.t.doctor.rollbackStatus[inspected.lock.state === "live" ? "busy" : "unresolved"], ctx.ui.width - 2)
|
|
529
|
+
.map(line => ` ${line}`));
|
|
530
|
+
}
|
|
531
|
+
else if (inspected.lock.state === "dead" && owner?.kind === "mutation") {
|
|
532
|
+
const entry = inspected.journals.find(({ operationId }) => operationId === owner.operationId);
|
|
533
|
+
if (entry?.journal && entry.journal.owner.nonce === owner.nonce) {
|
|
534
|
+
const selection = inspectRollback(ctx.paths, owner.operationId);
|
|
535
|
+
if (entry.state === "pending" && !selection.synthetic && blocking.every(({ operationId, journal }) => operationId === owner.operationId && journal !== null)) {
|
|
536
|
+
lines.push("", ` ${ctx.t.doctor.resumeOperation}`, ...nextStep(repairCommand(ctx, `doctor --fix --from-operation ${owner.operationId}`), ctx.ui).slice(1));
|
|
537
|
+
}
|
|
538
|
+
if (showRollbackCommands && previewRollback(ctx.paths, owner.operationId, ctx.t).status === "ready") {
|
|
539
|
+
lines.push("", ...receiptLines(ctx.t.recovery.rollback(owner.operationId), ctx.ui.width - 2).map(line => ` ${line}`));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
lines.push(...nextStep(`${repairCommand(ctx, "doctor")} --json`, ctx.ui));
|
|
518
544
|
return {
|
|
519
|
-
lines
|
|
520
|
-
...wrap(ctx.t.doctor.recoveryPending, ctx.ui.width - 2).map((line) => ` ${ctx.ui.warn(line)}`),
|
|
521
|
-
...recovery.operations.map(({ operationId, state }) => ` ${operationId} ${ctx.t.doctor.operationState[state]}`),
|
|
522
|
-
],
|
|
545
|
+
lines,
|
|
523
546
|
// The damaged config is deliberately not parsed here. `operation` is the only safe context
|
|
524
547
|
// fact to expose until the owner-selected rollback or recovery has completed.
|
|
525
548
|
json: {
|
package/dist/commands/rebuild.js
CHANGED
|
@@ -8,7 +8,7 @@ import { compile } from "../core/compile.js";
|
|
|
8
8
|
import { toCompileInput } from "../core/resolve.js";
|
|
9
9
|
import { isLanguage, LANGUAGES } from "../core/i18n.js";
|
|
10
10
|
import { recoverLegacyTone } from "../core/legacy-tone.js";
|
|
11
|
-
import { exportAliasesState, stateOverlapsInstructions } from "../core/paths.js";
|
|
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
14
|
import { inspectOperations, readJournal, retireOperationForAppRebuild, } from "../core/operations.js";
|
|
@@ -254,9 +254,14 @@ export async function runAppRebuild(ctx) {
|
|
|
254
254
|
else if (!ctx.flags.auto && ctx.canPrompt) {
|
|
255
255
|
const input = createInterface({ input: process.stdin, output: process.stdout });
|
|
256
256
|
try {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
257
|
+
const operation = t.recovery.resumeOperation(owner.operationId).split("\n").flatMap(line => line === owner.operationId ? [line] : wrap(line, ctx.ui.width - 2));
|
|
258
|
+
process.stdout.write(`${operation.map(line => ` ${line}`).join("\n")}\n`);
|
|
259
|
+
const question = wrap(t.recovery.resumeQuestion, ctx.ui.width - 3).map(line => ` ${line}`).join("\n");
|
|
260
|
+
const answer = (await input.question(`${question} `)).trim();
|
|
261
|
+
if (answer === "")
|
|
262
|
+
throw new ConfigError(t.recovery.cancelled);
|
|
263
|
+
if (answer !== owner.operationId)
|
|
264
|
+
throw new ConfigError(t.recovery.operationMismatch);
|
|
260
265
|
retirementId = owner.operationId;
|
|
261
266
|
}
|
|
262
267
|
finally {
|
|
@@ -394,6 +399,8 @@ export async function runAppRebuild(ctx) {
|
|
|
394
399
|
}
|
|
395
400
|
const seedsSelection = generatedSeedSelection(ctx.paths, config, ctx.flags.tools !== null && ctx.flags.tools !== "none", t);
|
|
396
401
|
const seeds = planGeneratedSeedRebuild(ctx.paths, config.managedSeeds, seedsSelection, t);
|
|
402
|
+
const generatedSeeds = seedFiles(ctx.paths, seedsSelection);
|
|
403
|
+
const rewrittenGenerated = new Set(generatedSeeds.filter(seed => seeds.operationActions.some(({ action }) => action.path === seed.path || (object(action.after)?.type === "directory" && seed.path.startsWith(`${action.path}${sep}`)))).map(seed => seed.path));
|
|
397
404
|
checks.push(...seeds.checks);
|
|
398
405
|
config.managedSeeds = seeds.records;
|
|
399
406
|
config.seedVersion = SEED_VERSION;
|
|
@@ -457,40 +464,54 @@ export async function runAppRebuild(ctx) {
|
|
|
457
464
|
auto: ctx.flags.auto, dryRun: ctx.flags.dryRun, verbose: ctx.flags.verbose, ui: ctx.ui, t,
|
|
458
465
|
heading: t.recovery.heading,
|
|
459
466
|
interactive: () => ctx.canPrompt, ...(ctx.canPrompt ? {} : { out: () => { } }),
|
|
460
|
-
recoveryPresentation: { lines: presentation,
|
|
467
|
+
recoveryPresentation: { lines: presentation,
|
|
468
|
+
failureMessage: (id, phase, path, code) => `${t.recovery.failure(id, phase, path, code)}\ntonedial doctor ${connectionScopeFlag(ctx.agent)} --json` },
|
|
461
469
|
verify: () => {
|
|
470
|
+
let verificationPath = ctx.paths.config;
|
|
471
|
+
const mismatch = () => new ConfigError(t.recovery.sourceInvalid, { cause: { code: "VERIFY_MISMATCH", path: verificationPath } });
|
|
462
472
|
try {
|
|
463
473
|
const repaired = parseConfig(capturedText(captureRawEntry(ctx.paths.config, t, { within: ctx.paths.root })) ?? "", t);
|
|
464
474
|
if (!isConfigV2(repaired) || serializeConfig(repaired, t) !== serializeConfig(config, t))
|
|
465
|
-
throw
|
|
466
|
-
for (const seed of
|
|
475
|
+
throw mismatch();
|
|
476
|
+
for (const seed of generatedSeeds) {
|
|
477
|
+
verificationPath = seed.path;
|
|
467
478
|
const now = captureRawEntry(seed.path, t, { within: ctx.paths.root });
|
|
468
479
|
if (capturedText(now) !== seed.contents)
|
|
469
|
-
throw
|
|
480
|
+
throw mismatch();
|
|
470
481
|
}
|
|
471
|
-
for (const target of artifacts)
|
|
482
|
+
for (const target of artifacts) {
|
|
483
|
+
verificationPath = target.path;
|
|
472
484
|
if (capturedText(captureRawEntry(target.path, t, { within: ctx.paths.root })) !== artifact)
|
|
473
|
-
throw
|
|
485
|
+
throw mismatch();
|
|
486
|
+
}
|
|
487
|
+
verificationPath = ctx.paths.tone;
|
|
474
488
|
if (capturedText(captureRawEntry(ctx.paths.tone, t, { within: ctx.paths.root })) !== artifact)
|
|
475
|
-
throw
|
|
489
|
+
throw mismatch();
|
|
476
490
|
for (const entry of library.entries) {
|
|
477
491
|
for (const before of entry.selectionReads) {
|
|
478
|
-
|
|
492
|
+
// Rewritten generated entries were checked against their new bytes above.
|
|
493
|
+
if (before.path === restoredPreset?.capture.path || rewrittenGenerated.has(before.path))
|
|
479
494
|
continue;
|
|
495
|
+
verificationPath = before.path;
|
|
480
496
|
const now = captureRawEntry(before.path, t, { within: ctx.paths.root, maxBytes: PRESET_MAX_BYTES });
|
|
481
497
|
if (now.type !== before.type || now.identity !== before.identity || now.mode !== before.mode ||
|
|
482
498
|
(now.bytes === null ? before.bytes !== null : before.bytes === null || !now.bytes.equals(before.bytes))) {
|
|
483
|
-
throw
|
|
499
|
+
throw mismatch();
|
|
484
500
|
}
|
|
485
501
|
}
|
|
486
502
|
}
|
|
487
503
|
for (const slug of [...copies, ...(restoredPreset ? [restoredPreset.slug] : [])]) {
|
|
504
|
+
verificationPath = slug === restoredPreset?.slug ? restoredPreset.capture.path
|
|
505
|
+
: library.recoveryCopies.find(copy => copy.preset.slug === slug).path;
|
|
488
506
|
if (resolvePreset(ctx.paths.presets, slug, t) === null)
|
|
489
|
-
throw
|
|
507
|
+
throw mismatch();
|
|
490
508
|
}
|
|
491
509
|
}
|
|
492
|
-
catch {
|
|
493
|
-
|
|
510
|
+
catch (cause) {
|
|
511
|
+
if (cause instanceof ConfigError && cause.cause?.code === "VERIFY_MISMATCH")
|
|
512
|
+
throw cause;
|
|
513
|
+
const code = cause !== null && typeof cause === "object" && "code" in cause ? cause.code : undefined;
|
|
514
|
+
throw new ConfigError(t.recovery.sourceInvalid, { cause: { code, path: verificationPath, cause } });
|
|
494
515
|
}
|
|
495
516
|
},
|
|
496
517
|
};
|
package/dist/core/i18n.js
CHANGED
|
@@ -126,7 +126,11 @@ const enMessages = {
|
|
|
126
126
|
rollbackOrder: "Restore connections first, then application files:",
|
|
127
127
|
noSnapshot: (id) => `${id}: no valid settings snapshot for this state root`,
|
|
128
128
|
retire: (id) => `Retire stopped operation ${id} as history before a new application rebuild. Its external changes are not certified or undone.`,
|
|
129
|
-
|
|
129
|
+
resumeOperation: (id) => `Stopped operation:\n${id}`,
|
|
130
|
+
resumeQuestion: "Type the full operation ID above to continue; Enter cancels:",
|
|
131
|
+
cancelled: "Recovery cancelled. No files were changed.",
|
|
132
|
+
operationMismatch: "The operation ID did not match. No files were changed.",
|
|
133
|
+
failure: (id, phase, path, code) => `Application recovery failed during ${({ prepare: "preparation", write: "file writes", verify: "verification", finalize: "completion" })[phase]} (${code}).${path ? `\n${path}` : ""}\nSome file changes may already be applied. Keep operation ${id}.`,
|
|
130
134
|
},
|
|
131
135
|
persona: {
|
|
132
136
|
defaults: role("I", "you", "The person requesting help with a task.", "A general-purpose assistant helping with the requested task."),
|
|
@@ -275,7 +279,8 @@ const enMessages = {
|
|
|
275
279
|
seedsOk: (version) => `version ${version}`,
|
|
276
280
|
seedsNotRun: "no bundled files · init has not run here",
|
|
277
281
|
seedsAttention: "bundled files need attention",
|
|
278
|
-
recoveryPending: "An operation is active or needs recovery.
|
|
282
|
+
recoveryPending: "An operation is active or needs recovery. No files were changed by this command.",
|
|
283
|
+
resumeOperation: "Continue application repair:",
|
|
279
284
|
contextCurrent: "AGENTS.md and TONE.md layout is current.",
|
|
280
285
|
contextRepair: "Managed context files need repair. Check the guide and tone-file diagnostics.",
|
|
281
286
|
contextSize: (guide, total) => total === null
|
|
@@ -775,7 +780,11 @@ const koMessages = {
|
|
|
775
780
|
rollbackOrder: "연결 파일을 먼저 되돌린 뒤 앱 파일을 되돌리세요:",
|
|
776
781
|
noSnapshot: (id) => `${id}: 이 상태 경로에서 쓸 수 있는 설정 기록이 없습니다`,
|
|
777
782
|
retire: (id) => `중단된 작업 ${id}를 기록으로 보관한 뒤 새 앱 복구를 시작합니다. 그 작업의 외부 파일 변경은 확인하거나 되돌리지 않습니다.`,
|
|
778
|
-
|
|
783
|
+
resumeOperation: (id) => `중단된 작업:\n${id}`,
|
|
784
|
+
resumeQuestion: "계속하려면 위 작업 ID 전체를 입력하세요. Enter를 누르면 취소합니다:",
|
|
785
|
+
cancelled: "복구를 취소했습니다. 파일은 바꾸지 않았습니다.",
|
|
786
|
+
operationMismatch: "작업 ID가 일치하지 않습니다. 파일은 바꾸지 않았습니다.",
|
|
787
|
+
failure: (id, phase, path, code) => `앱 복구의 ${({ prepare: "준비", write: "파일 쓰기", verify: "결과 검증", finalize: "완료 처리" })[phase]} 단계에서 실패했습니다 (${code}).${path ? `\n${path}` : ""}\n일부 파일 변경이 이미 적용되었을 수 있습니다. 작업 ${id}의 기록을 보존해 주세요.`,
|
|
779
788
|
},
|
|
780
789
|
persona: {
|
|
781
790
|
defaults: role("저", "사용자님", "작업에 필요한 도움을 요청하는 사용자.", "요청한 작업을 돕는 범용 조수."),
|
|
@@ -927,7 +936,8 @@ const koMessages = {
|
|
|
927
936
|
seedsOk: (version) => `버전 ${version}`,
|
|
928
937
|
seedsNotRun: "기본 파일 없음 · init으로 준비하세요",
|
|
929
938
|
seedsAttention: "기본 파일 확인 필요",
|
|
930
|
-
recoveryPending: "진행 중이거나 복구가 필요한 작업이 있습니다.
|
|
939
|
+
recoveryPending: "진행 중이거나 복구가 필요한 작업이 있습니다. 이번 실행에서는 파일을 바꾸지 않았습니다.",
|
|
940
|
+
resumeOperation: "앱 복구를 이어가려면:",
|
|
931
941
|
contextCurrent: "AGENTS.md와 TONE.md 구성이 최신입니다.",
|
|
932
942
|
contextRepair: "관리 중인 컨텍스트 파일을 복구해야 합니다. 안내 파일과 톤 파일의 진단을 확인해 주세요.",
|
|
933
943
|
contextSize: (guide, total) => total === null
|
package/dist/core/locales/es.js
CHANGED
|
@@ -115,7 +115,11 @@ const messages = {
|
|
|
115
115
|
rollbackOrder: "Restaura primero las conexiones y después los archivos de la aplicación:",
|
|
116
116
|
noSnapshot: (id) => `${id}: no hay una instantánea válida para este directorio de estado`,
|
|
117
117
|
retire: (id) => `Archiva la operación detenida ${id} antes de reconstruir. Sus cambios externos no se certifican ni se deshacen.`,
|
|
118
|
-
|
|
118
|
+
resumeOperation: (id) => `Operación detenida:\n${id}`,
|
|
119
|
+
resumeQuestion: "Para continuar, escribe el ID completo de arriba; Enter cancela:",
|
|
120
|
+
cancelled: "Recuperación cancelada. No se cambió ningún archivo.",
|
|
121
|
+
operationMismatch: "El ID de operación no coincide. No se cambió ningún archivo.",
|
|
122
|
+
failure: (id, phase, path, code) => `La recuperación falló durante la ${({ prepare: "preparación", write: "escritura de archivos", verify: "verificación", finalize: "finalización" })[phase]} (${code}).${path ? `\n${path}` : ""}\nEs posible que algunos cambios ya estén aplicados. Conserva el registro de la operación ${id}.`,
|
|
119
123
|
},
|
|
120
124
|
persona: {
|
|
121
125
|
defaults: role("yo", "tú", "La persona que solicita ayuda con una tarea.", "Un asistente general que ayuda con la tarea solicitada."),
|
|
@@ -264,7 +268,8 @@ const messages = {
|
|
|
264
268
|
seedsOk: (version) => `versión ${version}`,
|
|
265
269
|
seedsNotRun: "sin archivos incluidos · aquí no se ha ejecutado init",
|
|
266
270
|
seedsAttention: "los archivos incluidos requieren atención",
|
|
267
|
-
recoveryPending: "Hay una operación
|
|
271
|
+
recoveryPending: "Hay una operación en curso o que necesita recuperación. Esta ejecución no cambió archivos.",
|
|
272
|
+
resumeOperation: "Continuar la reparación de la aplicación:",
|
|
268
273
|
contextCurrent: "La estructura de AGENTS.md y TONE.md está al día.",
|
|
269
274
|
contextRepair: "Los archivos de contexto gestionados necesitan reparación. Revisa el diagnóstico de la guía y del tono.",
|
|
270
275
|
contextSize: (guide, total) => total === null
|
package/dist/core/locales/ja.js
CHANGED
|
@@ -115,7 +115,11 @@ const messages = {
|
|
|
115
115
|
rollbackOrder: "先に接続を復元し、その後アプリケーションのファイルを復元してください:",
|
|
116
116
|
noSnapshot: (id) => `${id}: この状態ルート用の有効な設定スナップショットがありません`,
|
|
117
117
|
retire: (id) => `停止した操作 ${id} を履歴に移してから再構築してください。外部への変更は検証も取り消しもされません。`,
|
|
118
|
-
|
|
118
|
+
resumeOperation: (id) => `停止した操作:\n${id}`,
|
|
119
|
+
resumeQuestion: "続行するには上の操作ID全体を入力してください。Enterで中止:",
|
|
120
|
+
cancelled: "復旧を中止しました。ファイルは変更していません。",
|
|
121
|
+
operationMismatch: "操作IDが一致しません。ファイルは変更していません。",
|
|
122
|
+
failure: (id, phase, path, code) => `アプリ復旧の${({ prepare: "準備", write: "ファイル書き込み", verify: "結果の検証", finalize: "完了処理" })[phase]}で失敗しました (${code})。${path ? `\n${path}` : ""}\n一部のファイル変更が適用済みの場合があります。操作 ${id} の記録を保持してください。`,
|
|
119
123
|
},
|
|
120
124
|
persona: {
|
|
121
125
|
defaults: role("私", "あなた", "作業の支援を求めている利用者。", "依頼された作業を手伝う汎用アシスタント。"),
|
|
@@ -264,7 +268,8 @@ const messages = {
|
|
|
264
268
|
seedsOk: (version) => `バージョン ${version}`,
|
|
265
269
|
seedsNotRun: "同梱ファイルなし · ここでは init 未実行",
|
|
266
270
|
seedsAttention: "同梱ファイルの確認が必要",
|
|
267
|
-
recoveryPending: "
|
|
271
|
+
recoveryPending: "実行中または復旧が必要な操作があります。この実行ではファイルを変更していません。",
|
|
272
|
+
resumeOperation: "アプリ復旧を続けるには:",
|
|
268
273
|
contextCurrent: "AGENTS.md と TONE.md の構成は最新です。",
|
|
269
274
|
contextRepair: "管理対象のコンテキストファイルに修復が必要です。ガイドと口調ファイルの診断を確認してください。",
|
|
270
275
|
contextSize: (guide, total) => total === null
|
package/dist/core/locales/zh.js
CHANGED
|
@@ -115,7 +115,11 @@ const messages = {
|
|
|
115
115
|
rollbackOrder: "请先恢复连接,再恢复应用文件:",
|
|
116
116
|
noSnapshot: (id) => `${id}:没有此状态根目录的有效设置快照`,
|
|
117
117
|
retire: (id) => `重建应用前,请将已停止的操作 ${id} 归档为历史。其外部变更不会被认证或撤销。`,
|
|
118
|
-
|
|
118
|
+
resumeOperation: (id) => `已停止的操作:\n${id}`,
|
|
119
|
+
resumeQuestion: "要继续,请输入上方完整的操作 ID;按 Enter 取消:",
|
|
120
|
+
cancelled: "已取消恢复,未更改文件。",
|
|
121
|
+
operationMismatch: "操作 ID 不匹配,未更改文件。",
|
|
122
|
+
failure: (id, phase, path, code) => `应用恢复在${({ prepare: "准备", write: "写入文件", verify: "验证结果", finalize: "完成处理" })[phase]}阶段失败 (${code})。${path ? `\n${path}` : ""}\n部分文件更改可能已应用。请保留操作 ${id} 的记录。`,
|
|
119
123
|
},
|
|
120
124
|
persona: {
|
|
121
125
|
defaults: role("我", "你", "请求任务协助的用户。", "协助完成所请求任务的通用助手。"),
|
|
@@ -264,7 +268,8 @@ const messages = {
|
|
|
264
268
|
seedsOk: (version) => `版本 ${version}`,
|
|
265
269
|
seedsNotRun: "无内置文件 · 尚未在此运行 init",
|
|
266
270
|
seedsAttention: "内置文件需要处理",
|
|
267
|
-
recoveryPending: "
|
|
271
|
+
recoveryPending: "有操作正在运行或需要恢复。本次执行未更改文件。",
|
|
272
|
+
resumeOperation: "继续应用恢复:",
|
|
268
273
|
contextCurrent: "AGENTS.md 与 TONE.md 布局已是最新。",
|
|
269
274
|
contextRepair: "受管理的上下文文件需要修复,请检查指南和语气文件诊断。",
|
|
270
275
|
contextSize: (guide, total) => total === null
|
package/dist/core/presets.js
CHANGED
|
@@ -274,6 +274,9 @@ export function inspectPresetLibrary(presetsDir, t) {
|
|
|
274
274
|
unavailable.push({ slug: retired.slug, path, reason: read.reason });
|
|
275
275
|
continue;
|
|
276
276
|
}
|
|
277
|
+
// Exact packaged bytes need no copy; edited labels or formatting still stay preserved.
|
|
278
|
+
if (read.text === `${JSON.stringify(retired, null, 2)}\n`)
|
|
279
|
+
continue;
|
|
277
280
|
let preset;
|
|
278
281
|
try {
|
|
279
282
|
preset = parsePreset(read.text, retired.slug, t);
|
package/dist/core/seeds.js
CHANGED
|
@@ -11,7 +11,7 @@ import { captureRawEntry, caseRenameMatchesCurrent, isNoop, planChange, planEntr
|
|
|
11
11
|
import { BUNDLED, builtinPresetDir, LEGACY_BUILTIN_SLUGS, RETIRED_PRESETS } from "./presets.js";
|
|
12
12
|
import { SKILL_MD, SKILL_NAME, SKILL_TRAITS_MD } from "./skill.js";
|
|
13
13
|
// Bump for changed generated content; freshness also checks each managed seed's bytes/provenance.
|
|
14
|
-
export const SEED_VERSION =
|
|
14
|
+
export const SEED_VERSION = 20;
|
|
15
15
|
/** The state-root operating boundaries. The compiler owns the current tone separately. */
|
|
16
16
|
export const GUIDE_MD = `${GUIDE_FORMAT_MARKER}
|
|
17
17
|
ToneDial changes manner only; it never expands task scope or permissions.
|
|
@@ -73,7 +73,7 @@ export function seedFiles(paths, selection) {
|
|
|
73
73
|
};
|
|
74
74
|
const native = [
|
|
75
75
|
reader,
|
|
76
|
-
{ id: "skill/body", version:
|
|
76
|
+
{ id: "skill/body", version: 20, path: join(paths.skill, SKILL_NAME, "SKILL.md"), label: `skill/${SKILL_NAME}/SKILL.md`, contents: SKILL_MD },
|
|
77
77
|
{
|
|
78
78
|
id: "skill/traits",
|
|
79
79
|
version: 11,
|
package/dist/core/skill.js
CHANGED
|
@@ -177,6 +177,14 @@ an active association needs the user's explicit choice and must not reset saved
|
|
|
177
177
|
Do not edit files, change the state root, remove locks or grant hook trust yourself.
|
|
178
178
|
The user chooses native tools and scope separately; foreign provider containers are not
|
|
179
179
|
application outputs. A successful app rebuild can still report native attention.
|
|
180
|
+
If repair fails, report its phase, affected path, safe error code and operation ID.
|
|
181
|
+
Some file changes may already be applied; a failure is not evidence that all files stayed unchanged.
|
|
182
|
+
Preserve the operation record and follow the reported scoped read-only diagnosis.
|
|
183
|
+
Default doctor output focuses on blockers; \`--verbose\` and JSON retain the operation history.
|
|
184
|
+
For an explicitly selected stopped operation, \`doctor --fix --from-operation <id>\` continues
|
|
185
|
+
application repair using valid current settings first; it does not undo the earlier changes.
|
|
186
|
+
Preview that exact ID and scope with \`--dry-run\` before applying the approved repair.
|
|
187
|
+
Interactive selection requires the full displayed ID; Enter cancels without writing.
|
|
180
188
|
\`doctor --fix --dry-run\` previews app and selected native changes without writing or
|
|
181
189
|
printing private prior values. Keep both reported IDs when recovery changes app and native
|
|
182
190
|
files; follow the receipt and roll back native connections before application files.
|
|
@@ -415,7 +423,7 @@ Profanity grades actual swearing; bravado grades effort claims.
|
|
|
415
423
|
* command name stays `/tone` even though the tool is `tonedial`: it is what a person types
|
|
416
424
|
* mid-sentence, and the two extra characters are paid every time.
|
|
417
425
|
*/
|
|
418
|
-
export const TONE_COMMAND_VERSION =
|
|
426
|
+
export const TONE_COMMAND_VERSION = 12;
|
|
419
427
|
export const TONE_COMMAND_MD = `---
|
|
420
428
|
description: Check, change, or reload this agent's tone, preset, or ToneDial settings
|
|
421
429
|
---
|
package/dist/wiring/apply.js
CHANGED
|
@@ -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
|
-
:
|
|
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;
|