@tenphi/akno 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.
Files changed (147) hide show
  1. package/LICENSE +83 -0
  2. package/README.md +46 -0
  3. package/dist/args.d.ts +18 -0
  4. package/dist/args.d.ts.map +1 -0
  5. package/dist/args.js +92 -0
  6. package/dist/args.js.map +1 -0
  7. package/dist/bin.d.ts +3 -0
  8. package/dist/bin.d.ts.map +1 -0
  9. package/dist/bin.js +142 -0
  10. package/dist/bin.js.map +1 -0
  11. package/dist/commands/adopt-cmd.d.ts +2 -0
  12. package/dist/commands/adopt-cmd.d.ts.map +1 -0
  13. package/dist/commands/adopt-cmd.js +61 -0
  14. package/dist/commands/adopt-cmd.js.map +1 -0
  15. package/dist/commands/answer-cmd.d.ts +2 -0
  16. package/dist/commands/answer-cmd.d.ts.map +1 -0
  17. package/dist/commands/answer-cmd.js +150 -0
  18. package/dist/commands/answer-cmd.js.map +1 -0
  19. package/dist/commands/bench-cmd.d.ts +4 -0
  20. package/dist/commands/bench-cmd.d.ts.map +1 -0
  21. package/dist/commands/bench-cmd.js +1542 -0
  22. package/dist/commands/bench-cmd.js.map +1 -0
  23. package/dist/commands/config-cmd.d.ts +7 -0
  24. package/dist/commands/config-cmd.d.ts.map +1 -0
  25. package/dist/commands/config-cmd.js +80 -0
  26. package/dist/commands/config-cmd.js.map +1 -0
  27. package/dist/commands/context-cmd.d.ts +2 -0
  28. package/dist/commands/context-cmd.d.ts.map +1 -0
  29. package/dist/commands/context-cmd.js +130 -0
  30. package/dist/commands/context-cmd.js.map +1 -0
  31. package/dist/commands/doctor-cmd.d.ts +2 -0
  32. package/dist/commands/doctor-cmd.d.ts.map +1 -0
  33. package/dist/commands/doctor-cmd.js +198 -0
  34. package/dist/commands/doctor-cmd.js.map +1 -0
  35. package/dist/commands/dream-cmd.d.ts +26 -0
  36. package/dist/commands/dream-cmd.d.ts.map +1 -0
  37. package/dist/commands/dream-cmd.js +1167 -0
  38. package/dist/commands/dream-cmd.js.map +1 -0
  39. package/dist/commands/dream-model-status.d.ts +5 -0
  40. package/dist/commands/dream-model-status.d.ts.map +1 -0
  41. package/dist/commands/dream-model-status.js +33 -0
  42. package/dist/commands/dream-model-status.js.map +1 -0
  43. package/dist/commands/dream-notifications.d.ts +24 -0
  44. package/dist/commands/dream-notifications.d.ts.map +1 -0
  45. package/dist/commands/dream-notifications.js +168 -0
  46. package/dist/commands/dream-notifications.js.map +1 -0
  47. package/dist/commands/dream-schedule.d.ts +55 -0
  48. package/dist/commands/dream-schedule.d.ts.map +1 -0
  49. package/dist/commands/dream-schedule.js +160 -0
  50. package/dist/commands/dream-schedule.js.map +1 -0
  51. package/dist/commands/folder-cmd.d.ts +2 -0
  52. package/dist/commands/folder-cmd.d.ts.map +1 -0
  53. package/dist/commands/folder-cmd.js +84 -0
  54. package/dist/commands/folder-cmd.js.map +1 -0
  55. package/dist/commands/graph-cmd.d.ts +2 -0
  56. package/dist/commands/graph-cmd.d.ts.map +1 -0
  57. package/dist/commands/graph-cmd.js +126 -0
  58. package/dist/commands/graph-cmd.js.map +1 -0
  59. package/dist/commands/inbox-cmd.d.ts +2 -0
  60. package/dist/commands/inbox-cmd.d.ts.map +1 -0
  61. package/dist/commands/inbox-cmd.js +69 -0
  62. package/dist/commands/inbox-cmd.js.map +1 -0
  63. package/dist/commands/index-cmd.d.ts +2 -0
  64. package/dist/commands/index-cmd.d.ts.map +1 -0
  65. package/dist/commands/index-cmd.js +102 -0
  66. package/dist/commands/index-cmd.js.map +1 -0
  67. package/dist/commands/ingest-cmd.d.ts +2 -0
  68. package/dist/commands/ingest-cmd.d.ts.map +1 -0
  69. package/dist/commands/ingest-cmd.js +167 -0
  70. package/dist/commands/ingest-cmd.js.map +1 -0
  71. package/dist/commands/init-cmd.d.ts +292 -0
  72. package/dist/commands/init-cmd.d.ts.map +1 -0
  73. package/dist/commands/init-cmd.js +476 -0
  74. package/dist/commands/init-cmd.js.map +1 -0
  75. package/dist/commands/init-prompts.d.ts +51 -0
  76. package/dist/commands/init-prompts.d.ts.map +1 -0
  77. package/dist/commands/init-prompts.js +192 -0
  78. package/dist/commands/init-prompts.js.map +1 -0
  79. package/dist/commands/list-cmd.d.ts +2 -0
  80. package/dist/commands/list-cmd.d.ts.map +1 -0
  81. package/dist/commands/list-cmd.js +77 -0
  82. package/dist/commands/list-cmd.js.map +1 -0
  83. package/dist/commands/maintenance-policy-output.d.ts +4 -0
  84. package/dist/commands/maintenance-policy-output.d.ts.map +1 -0
  85. package/dist/commands/maintenance-policy-output.js +72 -0
  86. package/dist/commands/maintenance-policy-output.js.map +1 -0
  87. package/dist/commands/mutate-cmd.d.ts +5 -0
  88. package/dist/commands/mutate-cmd.d.ts.map +1 -0
  89. package/dist/commands/mutate-cmd.js +224 -0
  90. package/dist/commands/mutate-cmd.js.map +1 -0
  91. package/dist/commands/plan-cmd.d.ts +9 -0
  92. package/dist/commands/plan-cmd.d.ts.map +1 -0
  93. package/dist/commands/plan-cmd.js +411 -0
  94. package/dist/commands/plan-cmd.js.map +1 -0
  95. package/dist/commands/read-cmd.d.ts +2 -0
  96. package/dist/commands/read-cmd.d.ts.map +1 -0
  97. package/dist/commands/read-cmd.js +89 -0
  98. package/dist/commands/read-cmd.js.map +1 -0
  99. package/dist/commands/recall-cmd.d.ts +2 -0
  100. package/dist/commands/recall-cmd.d.ts.map +1 -0
  101. package/dist/commands/recall-cmd.js +176 -0
  102. package/dist/commands/recall-cmd.js.map +1 -0
  103. package/dist/commands/redeploy-cmd.d.ts +43 -0
  104. package/dist/commands/redeploy-cmd.d.ts.map +1 -0
  105. package/dist/commands/redeploy-cmd.js +270 -0
  106. package/dist/commands/redeploy-cmd.js.map +1 -0
  107. package/dist/commands/rules-cmd.d.ts +2 -0
  108. package/dist/commands/rules-cmd.d.ts.map +1 -0
  109. package/dist/commands/rules-cmd.js +72 -0
  110. package/dist/commands/rules-cmd.js.map +1 -0
  111. package/dist/commands/serve-cmd.d.ts +23 -0
  112. package/dist/commands/serve-cmd.d.ts.map +1 -0
  113. package/dist/commands/serve-cmd.js +415 -0
  114. package/dist/commands/serve-cmd.js.map +1 -0
  115. package/dist/commands/timeline-cmd.d.ts +2 -0
  116. package/dist/commands/timeline-cmd.d.ts.map +1 -0
  117. package/dist/commands/timeline-cmd.js +67 -0
  118. package/dist/commands/timeline-cmd.js.map +1 -0
  119. package/dist/commands/write-cmd.d.ts +49 -0
  120. package/dist/commands/write-cmd.d.ts.map +1 -0
  121. package/dist/commands/write-cmd.js +269 -0
  122. package/dist/commands/write-cmd.js.map +1 -0
  123. package/dist/ops-handle.d.ts +67 -0
  124. package/dist/ops-handle.d.ts.map +1 -0
  125. package/dist/ops-handle.js +196 -0
  126. package/dist/ops-handle.js.map +1 -0
  127. package/dist/output.d.ts +28 -0
  128. package/dist/output.d.ts.map +1 -0
  129. package/dist/output.js +87 -0
  130. package/dist/output.js.map +1 -0
  131. package/dist/serve/http.d.ts +19 -0
  132. package/dist/serve/http.d.ts.map +1 -0
  133. package/dist/serve/http.js +135 -0
  134. package/dist/serve/http.js.map +1 -0
  135. package/dist/serve/mcp.d.ts +21 -0
  136. package/dist/serve/mcp.d.ts.map +1 -0
  137. package/dist/serve/mcp.js +79 -0
  138. package/dist/serve/mcp.js.map +1 -0
  139. package/dist/serve/socket.d.ts +15 -0
  140. package/dist/serve/socket.d.ts.map +1 -0
  141. package/dist/serve/socket.js +311 -0
  142. package/dist/serve/socket.js.map +1 -0
  143. package/dist/version.d.ts +2 -0
  144. package/dist/version.d.ts.map +1 -0
  145. package/dist/version.js +15 -0
  146. package/dist/version.js.map +1 -0
  147. package/package.json +40 -0
@@ -0,0 +1,1167 @@
1
+ import { MAINTENANCE_TRANSFORMS, loadConfig, AknoError, parsePhase, } from '@tenphi/akno-core';
2
+ import { openOptionsFrom, parse } from "../args.js";
3
+ import { runMaintenance } from "../ops-handle.js";
4
+ import { heading, json, kv, line, ms, style, truncate } from "../output.js";
5
+ import { loadMaintenanceStatus, printMaintenanceStatus } from "./plan-cmd.js";
6
+ import { inspectDreamSchedule, } from "./dream-schedule.js";
7
+ import { dreamAutoEstimateSummary, dreamModelDegradationSummary, dreamModelUsageSummary, } from "./dream-model-status.js";
8
+ import { printMaintenancePathPolicy } from "./maintenance-policy-output.js";
9
+ import { deliverMaintenanceNotification, missedCycleNotification, scheduledCommandFailureNotification, scheduledRunNotification, } from "./dream-notifications.js";
10
+ const DREAM_HELP = `akno dream [options]
11
+ akno dream status [--run <run_id> | --last <n> | --pending | --explain-policy <path>]
12
+ akno dream resume <--profile | --transform <kind>>
13
+ akno dream notify --schedule-health
14
+
15
+ The maintenance cycle. Phases are selectable and safe to re-run.
16
+
17
+ observe Combine repeated facts into stable patterns. Writes pages under
18
+ observations/ with their evidence, and never restates a fact.
19
+ reflect Plan-backed decision principles built on observation-page evidence.
20
+ Off by default until the observation tier has enough repeated history.
21
+ curate Managed-fragment repair plus page-authorized hygiene or synthesis.
22
+ Runs a draft pass, a verification pass and deterministic guards.
23
+ adopt A page for a document that has none, written beside the file — so its
24
+ text can be returned at all. Honours \`ingest: "file"\`.
25
+ conflicts The thorough pass inline checking cannot do: facts from different
26
+ pages that state different values for the same thing.
27
+ repair Legacy report-only view of exact broken-link proposals. Durable fixes
28
+ are low-risk items in curate audit, review, or auto plans.
29
+ housekeeping Broken links, orphaned documents, rule drift and exact repair disposition.
30
+
31
+ A full/scheduled run resolves the configured maintenance profile. audit plans only,
32
+ review waits for human decisions, autonomous uses a separate curator and applies only
33
+ accepted work. Per-transformation policies may only lower that profile authority.
34
+
35
+ Plain status also inspects the local dev.akno.dream LaunchAgent: installed/loaded
36
+ state, daily interval, next expected run, and a two-hour missed-run window. New
37
+ receipts include content-safe model calls, provider token usage, and degradation.
38
+
39
+ --phase <name> Run one phase instead of every enabled one.
40
+ --mode <policy> audit | review | auto. May lower configured authority for one run;
41
+ it cannot raise it.
42
+ --dry-run Run ephemeral diagnostics; change no knowledge-base files and do not count
43
+ the result as nightly schedule health.
44
+ --private-details
45
+ Include page names, source excerpts, URLs and other private content in
46
+ terminal or JSON output. Default output is safe to retain and share.
47
+ --scheduled Mark a full run as scheduler-owned and deliver configured local notifications.
48
+ --schedule-health Check the expected nightly window and notify once if it was missed.
49
+ --run <run_id> Show one durable, content-safe run receipt.
50
+ --last <n> Show the newest 1–100 run receipts.
51
+ --pending List every nonterminal plan that can still be decided, retried, applied,
52
+ or verified.
53
+ --explain-policy <path>
54
+ Explain the resolved profile, page opt-in, structural eligibility,
55
+ budgets, decision path, and remaining guards for one page.
56
+ --profile With resume, clear a whole-profile automatic-apply pause after inspection.
57
+ --transform <kind>
58
+ With resume, clear one transformation pause or rollback streak.
59
+ --json`;
60
+ export async function dreamCommand(argv) {
61
+ const { values, positionals } = parse(argv, {
62
+ phase: { type: 'string' },
63
+ mode: { type: 'string' },
64
+ run: { type: 'string' },
65
+ last: { type: 'string' },
66
+ pending: { type: 'boolean', default: false },
67
+ 'explain-policy': { type: 'string' },
68
+ 'dry-run': { type: 'boolean', default: false },
69
+ 'private-details': { type: 'boolean', default: false },
70
+ scheduled: { type: 'boolean', default: false },
71
+ 'schedule-health': { type: 'boolean', default: false },
72
+ profile: { type: 'boolean', default: false },
73
+ transform: { type: 'string' },
74
+ });
75
+ if (values.help) {
76
+ line(DREAM_HELP);
77
+ return 0;
78
+ }
79
+ if (positionals[0] === 'resume' && positionals.length === 1) {
80
+ const selected = Number(values.profile) + Number(values.transform !== undefined);
81
+ if (selected !== 1) {
82
+ throw new AknoError('invalid', 'dream resume requires exactly one of --profile or --transform');
83
+ }
84
+ if (values.scheduled ||
85
+ values.phase !== undefined ||
86
+ values.mode !== undefined ||
87
+ values.run !== undefined ||
88
+ values.last !== undefined ||
89
+ values.pending ||
90
+ values['explain-policy'] !== undefined ||
91
+ values['dry-run'] ||
92
+ values['private-details'] ||
93
+ values['schedule-health']) {
94
+ throw new AknoError('invalid', 'dream resume cannot be combined with run or status options');
95
+ }
96
+ const scope = values.profile ? 'profile' : parseMaintenanceTransform(values.transform ?? '');
97
+ const recovery = await runMaintenance('plan', { action: 'resume', scope }, values, openOptionsFrom(values), async (mem) => mem.resumeMaintenance(scope === 'profile' ? { profile: true } : { transform: scope }));
98
+ if (values.json)
99
+ json({ recovery });
100
+ else {
101
+ line(style.green(`resumed ${scope === 'profile' ? 'automatic maintenance' : `${scope} maintenance`}`));
102
+ line(` ${style.grey(`automatic apply: ${recovery.automaticApply}`)}`);
103
+ }
104
+ return 0;
105
+ }
106
+ if (positionals[0] === 'notify' && positionals.length === 1) {
107
+ if (!values['schedule-health']) {
108
+ throw new AknoError('invalid', 'dream notify requires --schedule-health');
109
+ }
110
+ if (values.scheduled ||
111
+ values.phase !== undefined ||
112
+ values.mode !== undefined ||
113
+ values.run !== undefined ||
114
+ values.last !== undefined ||
115
+ values.pending ||
116
+ values['explain-policy'] !== undefined ||
117
+ values['dry-run'] ||
118
+ values['private-details']) {
119
+ throw new AknoError('invalid', 'dream notify --schedule-health cannot be combined with run options');
120
+ }
121
+ return notifyScheduleHealth(values);
122
+ }
123
+ if (positionals[0] === 'status' && positionals.length === 1) {
124
+ if (values['explain-policy'] !== undefined) {
125
+ if (values.run !== undefined || values.last !== undefined || values.pending) {
126
+ throw new AknoError('invalid', 'choose only one of --run, --last, --pending, or --explain-policy');
127
+ }
128
+ const mode = values.mode ? parseMode(values.mode) : undefined;
129
+ const policy = await loadMaintenancePathPolicy(values, values['explain-policy'], mode);
130
+ if (values.json)
131
+ json({ policy });
132
+ else
133
+ printMaintenancePathPolicy(policy);
134
+ return 0;
135
+ }
136
+ if (values.mode !== undefined) {
137
+ throw new AknoError('invalid', '--mode is only meaningful with --explain-policy in status');
138
+ }
139
+ const query = dreamStatusQuery(values);
140
+ const status = await loadMaintenanceStatus(values, query);
141
+ const schedule = Object.keys(query).length === 0 ? inspectDreamSchedule(status.latestFullRun) : null;
142
+ if (values.json)
143
+ json(dreamStatusJson(status, query, schedule));
144
+ else
145
+ printDreamStatus(status, query, schedule);
146
+ return 0;
147
+ }
148
+ if (positionals.length > 0 ||
149
+ values.run ||
150
+ values.last ||
151
+ values.pending ||
152
+ values['schedule-health'] ||
153
+ values.profile ||
154
+ values.transform) {
155
+ line(DREAM_HELP);
156
+ return 1;
157
+ }
158
+ const phase = values.phase ? parsePhase(values.phase) : undefined;
159
+ const mode = values.mode ? parseMode(values.mode) : undefined;
160
+ if (values.scheduled && (phase || mode || values['dry-run'] || values['private-details'])) {
161
+ throw new AknoError('invalid', '--scheduled is only valid for the plain configured full cycle');
162
+ }
163
+ const input = {
164
+ ...(phase ? { phase } : {}),
165
+ ...(mode ? { mode } : {}),
166
+ ...(values['dry-run'] ? { dryRun: true } : {}),
167
+ };
168
+ // Through the service when one is running: it holds the write handle, and the cycle writes.
169
+ // A second request on the same multiplexed connection reads content-free plan state while the
170
+ // model call is pending, so a healthy multi-minute run no longer looks hung.
171
+ const progress = values.json ? null : dreamProgressWriter(Date.now(), phase, mode);
172
+ const commandStartedAt = new Date();
173
+ let report = null;
174
+ let busy = null;
175
+ try {
176
+ report = await runMaintenance('dream', input, values, openOptionsFrom(values), (mem) => mem.dream(input), { onWait: progress?.update, waitEveryMs: 5_000 });
177
+ }
178
+ catch (error) {
179
+ const typed = AknoError.from(error);
180
+ if (typed.code !== 'busy') {
181
+ if (values.scheduled)
182
+ await notifyScheduledFailure(values, typed.code, commandStartedAt);
183
+ throw typed;
184
+ }
185
+ busy = typed;
186
+ }
187
+ finally {
188
+ progress?.done();
189
+ }
190
+ if (busy)
191
+ return reportActiveDream(busy, values);
192
+ if (!report)
193
+ throw new AknoError('internal', 'dream returned no report');
194
+ if (values.scheduled)
195
+ await notifyScheduledRun(values, report.run);
196
+ if (values.json) {
197
+ json(values['private-details'] ? report : safeDreamReport(report));
198
+ return dreamRunExitCode(report.run);
199
+ }
200
+ return printDream(report, values['private-details']);
201
+ }
202
+ function parseMaintenanceTransform(value) {
203
+ if (MAINTENANCE_TRANSFORMS.includes(value)) {
204
+ return value;
205
+ }
206
+ throw new AknoError('invalid', `--transform must be one of: ${MAINTENANCE_TRANSFORMS.join(', ')}`);
207
+ }
208
+ async function notifyScheduledRun(values, run) {
209
+ const config = loadConfig(openOptionsFrom(values));
210
+ if (config.maintenance.notifications === 'off')
211
+ return;
212
+ try {
213
+ const status = await loadMaintenanceStatus(values);
214
+ const history = await loadMaintenanceStatus(values, { last: 10 });
215
+ reportNotificationDelivery(deliverMaintenanceNotification(config.maintenance.notifications, scheduledRunNotification(config.maintenance.notifications, run, status, history.runs), config.stateDir));
216
+ }
217
+ catch (error) {
218
+ reportNotificationDelivery({ status: 'failed', error: AknoError.from(error).code });
219
+ }
220
+ }
221
+ async function notifyScheduledFailure(values, errorCode, startedAt) {
222
+ const config = loadConfig(openOptionsFrom(values));
223
+ if (config.maintenance.notifications === 'off')
224
+ return;
225
+ try {
226
+ const status = await loadMaintenanceStatus(values);
227
+ const latest = status.latestRun;
228
+ if (latest && Date.parse(latest.startedAt) >= startedAt.getTime() - 2_000) {
229
+ const history = await loadMaintenanceStatus(values, { last: 10 });
230
+ reportNotificationDelivery(deliverMaintenanceNotification(config.maintenance.notifications, scheduledRunNotification(config.maintenance.notifications, latest, status, history.runs), config.stateDir));
231
+ return;
232
+ }
233
+ }
234
+ catch {
235
+ // A failed service may also make status unavailable. The typed command failure is still actionable.
236
+ }
237
+ reportNotificationDelivery(deliverMaintenanceNotification(config.maintenance.notifications, scheduledCommandFailureNotification(config.maintenance.notifications, errorCode, startedAt), config.stateDir));
238
+ }
239
+ async function notifyScheduleHealth(values) {
240
+ const config = loadConfig(openOptionsFrom(values));
241
+ const status = await loadMaintenanceStatus(values);
242
+ const schedule = inspectDreamSchedule(status.latestFullRun);
243
+ const delivery = deliverMaintenanceNotification(config.maintenance.notifications, missedCycleNotification(config.maintenance.notifications, schedule), config.stateDir);
244
+ if (values.json)
245
+ json({ notification: delivery, schedule });
246
+ else
247
+ reportNotificationDelivery(delivery);
248
+ return delivery.status === 'failed' ? 2 : 0;
249
+ }
250
+ function reportNotificationDelivery(delivery) {
251
+ if (delivery.status === 'failed') {
252
+ process.stderr.write(style.yellow(`notification delivery failed (${delivery.error ?? 'unknown'})\n`));
253
+ }
254
+ else if (delivery.status === 'sent') {
255
+ process.stderr.write(style.grey('local maintenance notification sent\n'));
256
+ }
257
+ }
258
+ async function loadMaintenancePathPolicy(values, policyPath, mode) {
259
+ if (!policyPath.trim())
260
+ throw new AknoError('invalid', '--explain-policy requires a path');
261
+ return runMaintenance('plan', { action: 'policy', path: policyPath, ...(mode ? { mode } : {}) }, values, openOptionsFrom(values), async (mem) => mem.maintenancePolicy(policyPath, mode), { writable: false });
262
+ }
263
+ export function dreamStatusQuery(values) {
264
+ const selected = Number(values.run !== undefined) + Number(values.last !== undefined) + Number(values.pending);
265
+ if (selected > 1)
266
+ throw new AknoError('invalid', 'choose only one of --run, --last, or --pending');
267
+ if (values.run !== undefined) {
268
+ const runId = values.run.trim();
269
+ if (!runId)
270
+ throw new AknoError('invalid', '--run requires a run id');
271
+ return { runId };
272
+ }
273
+ if (values.last !== undefined) {
274
+ const last = Number(values.last);
275
+ if (!Number.isInteger(last) || last < 1 || last > 100) {
276
+ throw new AknoError('invalid', '--last must be an integer from 1 to 100');
277
+ }
278
+ return { last };
279
+ }
280
+ return values.pending ? { pending: true } : {};
281
+ }
282
+ export function dreamStatusJson(status, query, schedule = null) {
283
+ if (query.runId)
284
+ return { run: status.runs[0] ?? null };
285
+ if (query.last !== undefined)
286
+ return { runs: status.runs };
287
+ if (query.pending) {
288
+ return {
289
+ awaitingHuman: status.awaitingHuman,
290
+ verificationPending: status.verificationPending,
291
+ budgetDeferred: status.budgetDeferred,
292
+ recovery: status.recovery,
293
+ pendingPlans: status.pendingPlans,
294
+ };
295
+ }
296
+ return schedule ? { ...status, schedule } : status;
297
+ }
298
+ function printDreamStatus(status, query, schedule) {
299
+ if (query.runId) {
300
+ const run = status.runs[0];
301
+ if (!run)
302
+ throw new AknoError('not_found', `no maintenance run with id ${query.runId}`);
303
+ printDreamRunReceipt(run);
304
+ return;
305
+ }
306
+ if (query.last !== undefined) {
307
+ printDreamRunHistory(status.runs);
308
+ return;
309
+ }
310
+ if (query.pending) {
311
+ printPendingMaintenance(status);
312
+ return;
313
+ }
314
+ printMaintenanceStatus(status);
315
+ if (schedule)
316
+ printDreamSchedule(schedule);
317
+ }
318
+ function printDreamSchedule(schedule) {
319
+ heading('Nightly schedule');
320
+ kv([
321
+ ['installed', schedule.installed ? 'yes' : 'no'],
322
+ ['loaded', schedule.loaded === null ? 'unsupported' : schedule.loaded ? 'yes' : 'no'],
323
+ [
324
+ 'cadence',
325
+ schedule.hour === null || schedule.minute === null
326
+ ? null
327
+ : `daily at ${String(schedule.hour).padStart(2, '0')}:${String(schedule.minute).padStart(2, '0')} (${schedule.timezone})`,
328
+ ],
329
+ ['health', scheduleHealthLabel(schedule.health)],
330
+ ['previous expected', schedule.previousExpectedAt],
331
+ ['next expected', schedule.nextExpectedAt],
332
+ [
333
+ 'latest full cycle',
334
+ schedule.latestFullRun
335
+ ? `${schedule.latestFullRun.id} · ${schedule.latestFullRun.status} · ${schedule.latestFullRun.startedAt}`
336
+ : null,
337
+ ],
338
+ [
339
+ 'missed-cycle check',
340
+ !schedule.missedCycleCheck.installed
341
+ ? 'not installed'
342
+ : schedule.missedCycleCheck.loaded === false
343
+ ? 'installed but not loaded'
344
+ : schedule.missedCycleCheck.hour === null || schedule.missedCycleCheck.minute === null
345
+ ? 'installed schedule is unreadable'
346
+ : `loaded · daily at ${String(schedule.missedCycleCheck.hour).padStart(2, '0')}:${String(schedule.missedCycleCheck.minute).padStart(2, '0')}`,
347
+ ],
348
+ ]);
349
+ }
350
+ function scheduleHealthLabel(health) {
351
+ switch (health) {
352
+ case 'unsupported':
353
+ return 'launchd status is available on macOS only';
354
+ case 'not_installed':
355
+ return 'not scheduled';
356
+ case 'installed_not_loaded':
357
+ return 'installed but not loaded';
358
+ case 'invalid_schedule':
359
+ return 'installed schedule is unreadable';
360
+ case 'not_due':
361
+ return 'installed after the previous window; not due yet';
362
+ case 'within_window':
363
+ return 'due; still inside the two-hour completion window';
364
+ case 'running':
365
+ return 'full cycle is running';
366
+ case 'on_time':
367
+ return 'healthy';
368
+ case 'last_run_failed':
369
+ return 'latest expected full cycle failed';
370
+ case 'overdue':
371
+ return 'overdue; no full cycle receipt in the expected window';
372
+ }
373
+ }
374
+ function printDreamRunReceipt(run) {
375
+ heading(`Dream run — ${run.status}`);
376
+ kv([
377
+ ['id', run.id],
378
+ ['profile', run.profile],
379
+ ['authority', run.mode],
380
+ ['started', run.startedAt],
381
+ ['finished', run.finishedAt],
382
+ ['duration', run.durationMs === null ? null : ms(run.durationMs)],
383
+ ['error', run.errorCode],
384
+ ['snapshot', run.snapshot.indexRevision.slice(0, 12)],
385
+ ]);
386
+ line('\n model work');
387
+ kv([
388
+ ['model', run.modelUsage.modelId],
389
+ ['summary', dreamModelUsageSummary(run.modelUsage)],
390
+ ['calls succeeded', `${run.modelUsage.successfulCalls}/${run.modelUsage.calls}`],
391
+ ['calls failed', run.modelUsage.failedCalls],
392
+ ['input tokens', run.modelUsage.inputTokens],
393
+ ['output tokens', run.modelUsage.outputTokens],
394
+ ['total tokens', run.modelUsage.totalTokens],
395
+ ['degraded', dreamModelDegradationSummary(run.degraded)],
396
+ ]);
397
+ for (const stage of run.modelUsage.stages) {
398
+ line(` ${stage.stage.padEnd(13)} ${dreamModelUsageSummary({ ...stage, modelId: run.modelUsage.modelId, stages: [] })}`);
399
+ }
400
+ printSemanticMergeMetrics(run.semanticMerge ?? null);
401
+ printRunVerification(run.verification);
402
+ printConflictRefresh(run.conflictRefresh ?? null);
403
+ printAutoEstimate(run.autoEstimate, run.modelUsage);
404
+ line('\n outcomes');
405
+ kv([
406
+ ['observations', run.counts.observations],
407
+ ['curated', run.counts.curated],
408
+ ['managed repairs', run.counts.managedItems?.planned ?? 0],
409
+ ['managed holds', run.counts.managedItems?.held ?? 0],
410
+ ['managed suppressed', run.counts.managedItems?.suppressed ?? 0],
411
+ ['guard rejections', run.counts.rejectedByGuard],
412
+ ['adopted', run.counts.adopted],
413
+ ['conflicts', run.counts.conflicts],
414
+ ['links repaired', run.counts.repairedLinks],
415
+ ['warnings', run.counts.warnings],
416
+ ]);
417
+ if (run.budget) {
418
+ line('\n apply budget');
419
+ kv([
420
+ ['items', `${run.budget.used.items}/${run.budget.limits.maxItems}`],
421
+ ['changed files', `${run.budget.used.filesChanged}/${run.budget.limits.maxFilesChanged}`],
422
+ ['written bytes', `${run.budget.used.bytesWritten}/${run.budget.limits.maxBytesWritten}`],
423
+ ['high-risk items', `${run.budget.used.highRiskItems}/${run.budget.limits.maxHighRiskItems}`],
424
+ ['deferred items', run.budget.deferredItems],
425
+ ]);
426
+ }
427
+ if (run.phases.length > 0) {
428
+ line('\n phases');
429
+ for (const phase of run.phases) {
430
+ line(` ${phase.phase.padEnd(13)} ${phase.ran ? 'ran' : 'skipped'} ${style.grey(ms(phase.durationMs))}`);
431
+ }
432
+ }
433
+ if (run.maintenancePlanIds.length > 0)
434
+ line(`\n plans ${run.maintenancePlanIds.join(', ')}`);
435
+ if (run.changeIds.length > 0)
436
+ line(` changes ${run.changeIds.join(', ')}`);
437
+ }
438
+ function printDreamRunHistory(runs) {
439
+ if (runs.length === 0) {
440
+ line(style.grey('no durable dream runs'));
441
+ return;
442
+ }
443
+ heading(`${runs.length} dream run${runs.length === 1 ? '' : 's'} — newest first`);
444
+ for (const run of runs) {
445
+ const duration = run.durationMs === null ? '-' : ms(run.durationMs);
446
+ const model = run.modelUsage.totalTokens === null
447
+ ? `${run.modelUsage.calls} calls`
448
+ : `${run.modelUsage.totalTokens} tokens`;
449
+ line(` ${style.bold(run.id)} ${run.status.padEnd(19)} ${run.mode.padEnd(6)} ${run.startedAt} ${duration} ${model}`);
450
+ }
451
+ }
452
+ function printSemanticMergeMetrics(metrics) {
453
+ if (!metrics)
454
+ return;
455
+ line('\n semantic merge discovery');
456
+ kv([
457
+ [
458
+ 'pages',
459
+ `${metrics.pagesPrepared}/${metrics.pagesConsidered} prepared` +
460
+ (metrics.pagesSkipped > 0 ? ` · ${metrics.pagesSkipped} skipped` : ''),
461
+ ],
462
+ [
463
+ 'embeddings',
464
+ `${metrics.embeddingCacheHits} cached · ${metrics.embeddingInputs} model input(s) in ${metrics.embeddingCalls} call(s)`,
465
+ ],
466
+ ['pairs', `${metrics.prefilteredPairs}/${metrics.pairsCompared} passed prefilter`],
467
+ [
468
+ 'classifier',
469
+ `${metrics.classifierCacheHits} cached · ${metrics.classifierCalls} call(s) / ${metrics.classifierCandidates} candidate(s)`,
470
+ ],
471
+ ['qualified', metrics.qualifiedPairs],
472
+ ]);
473
+ }
474
+ function printAutoEstimate(estimate, measured) {
475
+ if (!estimate)
476
+ return;
477
+ line('\n autonomous follow-up estimate');
478
+ kv([
479
+ ['initial curator pass', dreamAutoEstimateSummary(estimate)],
480
+ [
481
+ 'curator model',
482
+ estimate.modelConfigured
483
+ ? `${estimate.modelId ?? 'configured'} (configured; health not probed)`
484
+ : 'not configured',
485
+ ],
486
+ ["this audit's measured work", dreamModelUsageSummary(measured)],
487
+ ]);
488
+ if (estimate.status === 'estimated') {
489
+ line(` ${style.grey('Prompt-message tokens use characters/4; output is a hard request ceiling.')}`);
490
+ line(` ${style.grey('A later auto run replans; retry work and provider pricing are not included.')}`);
491
+ }
492
+ }
493
+ function printPendingMaintenance(status) {
494
+ heading('Pending maintenance');
495
+ kv([
496
+ ['awaiting decisions', status.awaitingHuman],
497
+ ['verification pending', status.verificationPending],
498
+ ['budget deferred', status.budgetDeferred],
499
+ ['nonterminal plans', status.pendingPlans.length],
500
+ ]);
501
+ if (status.pendingPlans.length === 0) {
502
+ line(style.grey('\n nothing is waiting for a decision, retry, apply, or verification'));
503
+ return;
504
+ }
505
+ for (const plan of status.pendingPlans) {
506
+ const proposed = plan.counts.proposed;
507
+ const verification = plan.counts.verification_pending;
508
+ line(`\n ${style.bold(plan.id)} ${plan.status} ${plan.mode}/${plan.phase}` +
509
+ ` ${proposed} proposed · ${verification} verification pending`);
510
+ line(` ${style.grey(`created ${plan.createdAt}`)}`);
511
+ }
512
+ }
513
+ async function reportActiveDream(error, values) {
514
+ const status = await loadMaintenanceStatus(values);
515
+ const requestedId = typeof error.details?.run_id === 'string' ? error.details.run_id : null;
516
+ const reportedStart = typeof error.details?.started_at === 'string' ? error.details.started_at : 'unknown';
517
+ const run = status.latestRun?.id === requestedId ? status.latestRun : null;
518
+ const outcome = run?.status === 'running' ? 'already_running' : 'finished_while_waiting';
519
+ if (values.json) {
520
+ json({ outcome, run_id: requestedId, run });
521
+ return 0;
522
+ }
523
+ heading(outcome === 'already_running' ? 'Dream already running' : 'Dream finished while checking');
524
+ kv([
525
+ ['run', requestedId ?? 'unknown'],
526
+ ['status', run?.status ?? 'unknown'],
527
+ ['started', run?.startedAt ?? reportedStart],
528
+ ]);
529
+ line(style.grey('\n No second maintenance run was started.'));
530
+ return 0;
531
+ }
532
+ function printDream(report, privateDetails) {
533
+ const readOnly = dreamRunIsReadOnly(report);
534
+ const authorityLabel = readOnly
535
+ ? style.grey(` (${report.run.dryRun ? 'dry run' : report.run.mode}; no KB writes)`)
536
+ : '';
537
+ heading(`Dream — ${ms(report.durationMs)}${authorityLabel}`);
538
+ kv([
539
+ ['run', report.run.id],
540
+ ['status', report.run.status],
541
+ ['profile', report.run.profile],
542
+ ['authority', report.run.mode],
543
+ ['snapshot', report.run.snapshot.indexRevision.slice(0, 12)],
544
+ ]);
545
+ printRunVerification(report.verification);
546
+ printConflictRefresh(report.conflictRefresh);
547
+ for (const phase of report.phases) {
548
+ const label = phase.ran ? style.green('ran') : style.grey('skipped');
549
+ const detail = phase.skipped ? style.grey(` ${phase.skipped}`) : style.grey(` ${ms(phase.durationMs)}`);
550
+ line(` ${phase.phase.padEnd(13)} ${label}${detail}`);
551
+ }
552
+ printSemanticMergeMetrics(report.semanticMerge);
553
+ if (report.observations.length > 0) {
554
+ const changed = report.observations.filter((entry) => entry.action !== 'unchanged' && entry.action !== 'rejected');
555
+ heading(`${changed.length} inference proposal/result(s)`);
556
+ if (privateDetails) {
557
+ for (const entry of report.observations) {
558
+ const mark = entry.action === 'created'
559
+ ? style.green('new ')
560
+ : entry.action === 'refined'
561
+ ? style.cyan('refined')
562
+ : entry.action === 'would-create' || entry.action === 'would-refine'
563
+ ? style.cyan('planned')
564
+ : entry.action === 'rejected'
565
+ ? style.yellow('rejected')
566
+ : style.grey('same ');
567
+ line(` ${mark} ${entry.slug}`);
568
+ line(` ${truncate(entry.pattern, 96)}`);
569
+ line(` ${style.grey(`evidence: ${entry.evidence.join(', ')}`)}`);
570
+ }
571
+ }
572
+ else {
573
+ const counts = countBy(report.observations, (entry) => entry.action);
574
+ kv([
575
+ ['created', counts.created ?? 0],
576
+ ['refined', counts.refined ?? 0],
577
+ ['planned', (counts['would-create'] ?? 0) + (counts['would-refine'] ?? 0)],
578
+ ['rejected', counts.rejected ?? 0],
579
+ ['unchanged', counts.unchanged ?? 0],
580
+ ]);
581
+ }
582
+ }
583
+ if (report.curated.length > 0) {
584
+ heading(`${report.curated.length} page curation result(s)`);
585
+ if (privateDetails) {
586
+ for (const entry of report.curated) {
587
+ const mark = entry.action === 'updated'
588
+ ? style.green('updated')
589
+ : entry.action === 'would-update'
590
+ ? style.cyan('preview')
591
+ : entry.action === 'rejected'
592
+ ? style.yellow('refused')
593
+ : style.grey('same');
594
+ line(` ${mark} ${entry.slug} ${style.grey(entry.mode)}`);
595
+ if (entry.temporal) {
596
+ line(` ${style.grey(`${entry.temporal.state} event until ${entry.temporal.until}` +
597
+ `${entry.temporal.archival ? ' · archival' : ''} · ${entry.temporal.source}`)}`);
598
+ }
599
+ for (const issue of entry.issues)
600
+ line(` ${style.grey(issue)}`);
601
+ if (entry.splits.length)
602
+ line(` ${style.grey(`splits: ${entry.splits.join(', ')}`)}`);
603
+ if (entry.extractions.length) {
604
+ line(` ${style.grey(`extracts: ${entry.extractions.join(', ')}`)}`);
605
+ }
606
+ if (entry.merges.length)
607
+ line(` ${style.grey(`merges: ${entry.merges.join(', ')}`)}`);
608
+ }
609
+ }
610
+ else {
611
+ const counts = curationCounts(report);
612
+ kv([
613
+ ['updated', counts.updated],
614
+ ['preview', counts.preview],
615
+ ['unchanged', counts.unchanged],
616
+ ['refused', counts.rejected],
617
+ ]);
618
+ for (const guard of curationGuardSummary(report)) {
619
+ line(` ${style.grey(`guardrail: ${guard.category}`)} ${guard.pages} page(s)`);
620
+ }
621
+ line(` ${style.grey('private page names and reasons: rerun with --private-details')}`);
622
+ }
623
+ }
624
+ if (report.managedItems.inspectedMarkers > 0) {
625
+ heading(`${report.managedItems.inspectedMarkers} managed fragment(s) inspected`);
626
+ kv([
627
+ ['valid', report.managedItems.outcomes.valid],
628
+ ['repairable', report.managedItems.outcomes.planned],
629
+ ['held', report.managedItems.outcomes.held],
630
+ ['suppressed', report.managedItems.outcomes.suppressed],
631
+ ['planned pages', report.managedItems.plannedPages],
632
+ ['placement calls', report.managedItems.placement.classifierCalls],
633
+ ['placement cache hits', report.managedItems.placement.cacheHits],
634
+ ['placement kept', report.managedItems.placement.kept],
635
+ ['placement moved', report.managedItems.placement.moved],
636
+ ['placement sections created', report.managedItems.placement.sectionsCreated],
637
+ ['placement uncertain', report.managedItems.placement.uncertain],
638
+ ['placement unavailable', report.managedItems.placement.unavailable],
639
+ ['routing candidates', report.managedItems.routing.candidatesConsidered],
640
+ ['routing calls', report.managedItems.routing.classifierCalls],
641
+ ['routing cache hits', report.managedItems.routing.cacheHits],
642
+ ['cross-page moved', report.managedItems.routing.moved],
643
+ ['routing sections created', report.managedItems.routing.sectionsCreated],
644
+ ['routing deferred', report.managedItems.routing.deferred],
645
+ ['routing uncertain', report.managedItems.routing.uncertain],
646
+ ['routing unavailable', report.managedItems.routing.unavailable],
647
+ ['source calls', report.managedItems.source.classifierCalls],
648
+ ['source cache hits', report.managedItems.source.cacheHits],
649
+ ['source supported', report.managedItems.source.supported],
650
+ ['wording corrected', report.managedItems.source.corrected],
651
+ ['wording uncertain', report.managedItems.source.uncertain],
652
+ ['source unavailable', report.managedItems.source.unavailable],
653
+ ]);
654
+ const actionable = report.managedItems.details.filter((finding) => finding.outcome !== 'valid');
655
+ if (privateDetails) {
656
+ for (const finding of actionable.slice(0, 100)) {
657
+ line(` ${finding.slug}:${finding.line || '?'} ${style.grey(`${finding.code} · ${finding.outcome}`)}`);
658
+ }
659
+ if (actionable.length > 100) {
660
+ line(` ${style.grey(`${actionable.length - 100} more private finding(s) omitted`)}`);
661
+ }
662
+ }
663
+ else if (actionable.length > 0) {
664
+ line(` ${style.grey('private page locations omitted; rerun with --private-details')}`);
665
+ }
666
+ }
667
+ for (const plan of (report.maintenancePlans ?? []).length > 0
668
+ ? report.maintenancePlans
669
+ : report.maintenancePlan
670
+ ? [report.maintenancePlan]
671
+ : []) {
672
+ heading(`Maintenance plan — ${plan.status}`);
673
+ line(` ${style.bold(plan.id)} ${plan.mode}/${plan.phase} ${style.grey(plan.summary)}`);
674
+ const proposed = plan.items.filter((item) => item.status === 'proposed').length;
675
+ const approved = plan.items.filter((item) => item.status === 'approved').length;
676
+ const dependencyDeferred = plan.items.filter((item) => ['dependency_conflict', 'dependency_unmet'].includes(item.statusCode ?? '')).length;
677
+ if (dependencyDeferred > 0) {
678
+ line(` ${style.grey(plan.status === 'superseded'
679
+ ? `${dependencyDeferred} dependent item(s) were replanned from the post-apply state in this run`
680
+ : `${dependencyDeferred} dependent item(s) deferred; they will be replanned on the next run`)}`);
681
+ }
682
+ const snapshotDeferred = plan.items.filter((item) => item.statusCode === 'snapshot_drift').length;
683
+ if (snapshotDeferred > 0) {
684
+ line(` ${style.grey(`${snapshotDeferred} stale item(s) deferred; they will be replanned on the next run`)}`);
685
+ }
686
+ if (proposed > 0) {
687
+ line(` ${style.grey('inspect:')} ${style.bold(`akno plan diff ${plan.id}`)}`);
688
+ line(` ${style.grey('decide:')} ${style.bold(`akno plan decide ${plan.id} --item <item_id> --approve`)}`);
689
+ }
690
+ if (approved > 0)
691
+ line(` ${style.grey('apply:')} ${style.bold(`akno plan apply ${plan.id}`)}`);
692
+ for (const item of plan.items.filter((entry) => entry.changeId)) {
693
+ const label = privateDetails ? item.subject : item.id;
694
+ line(` ${style.grey(`${label} undo:`)} ${style.bold(`akno undo ${item.changeId}`)}`);
695
+ }
696
+ }
697
+ if (report.planPrune && (report.planPrune.payloads.plans > 0 || report.planPrune.receipts.plans > 0)) {
698
+ heading('Plan retention');
699
+ kv([
700
+ ['private payloads pruned', report.planPrune.payloads.plans],
701
+ ['private bytes removed', report.planPrune.payloads.privateBytes],
702
+ ['compact receipts removed', report.planPrune.receipts.plans],
703
+ ]);
704
+ }
705
+ printAutoEstimate(report.autoEstimate, report.modelUsage);
706
+ if (report.rejected.length > 0) {
707
+ // A guardrail firing is the tier working. Silence here would make a prompt edit
708
+ // that removed one indistinguishable from a quiet night.
709
+ heading(`${report.rejected.length} refused by a guardrail`);
710
+ if (privateDetails) {
711
+ for (const entry of report.rejected.slice(0, 10)) {
712
+ line(` ${style.yellow('·')} ${truncate(entry.pattern, 80)}`);
713
+ line(` ${style.grey(entry.reason)}`);
714
+ }
715
+ }
716
+ else {
717
+ line(` ${style.grey('private patterns and reasons omitted; rerun with --private-details')}`);
718
+ }
719
+ }
720
+ if (report.adopted.length > 0) {
721
+ const created = report.adopted.filter((entry) => entry.action === 'created');
722
+ const planned = report.adopted.filter((entry) => entry.action === 'planned');
723
+ const rejected = report.adopted.filter((entry) => entry.action === 'rejected');
724
+ heading(`Document adoption — ${created.length} created, ${planned.length} proposed, ${rejected.length} rejected`);
725
+ if (privateDetails) {
726
+ for (const entry of report.adopted) {
727
+ const mark = entry.action === 'created'
728
+ ? style.green('page ')
729
+ : entry.action === 'planned'
730
+ ? style.yellow('plan ')
731
+ : entry.action === 'rejected'
732
+ ? style.red('refused')
733
+ : style.grey('left ');
734
+ line(` ${mark} ${entry.slug} ${style.grey(entry.files.join(', '))}`);
735
+ if (entry.reason)
736
+ line(` ${style.grey(entry.reason)}`);
737
+ }
738
+ }
739
+ else {
740
+ const counts = countBy(report.adopted, (entry) => entry.action);
741
+ kv([
742
+ ['created', counts.created ?? 0],
743
+ ['proposed', counts.planned ?? 0],
744
+ ['rejected', counts.rejected ?? 0],
745
+ ['left alone', counts.skipped ?? 0],
746
+ ]);
747
+ }
748
+ }
749
+ const actionable = report.conflicts.filter((entry) => ['superseded', 'qualified', 'unresolved', 'unverified'].includes(entry.verdict));
750
+ const cleared = report.conflicts.length - actionable.length;
751
+ if (report.conflicts.length > 0) {
752
+ // A run that examined five candidates and cleared them must not look like a run that
753
+ // found nothing: the second says the pass works, the first says it also judged.
754
+ heading(`${report.conflicts.length} conflict candidate(s) — ${actionable.length} need handling` +
755
+ (cleared > 0 ? `, ${cleared} cleared or time-scoped` : ''));
756
+ if (privateDetails) {
757
+ for (const conflict of actionable) {
758
+ const verdict = conflict.verdict === 'superseded'
759
+ ? style.red('superseded')
760
+ : style.yellow(conflict.verdict.replaceAll('_', ' '));
761
+ line(` ${verdict} ${style.bold(`${conflict.subject} / ${conflict.attribute}`)}`);
762
+ for (const claim of conflict.claims) {
763
+ const current = conflict.likelyCurrent === claim.slug ? style.green(' ← likely current') : '';
764
+ line(` ${style.grey(`${claim.slug}:${claim.line}`)} ${truncate(claim.claim, 76)}${current}`);
765
+ }
766
+ }
767
+ }
768
+ else if (actionable.length > 0) {
769
+ line(` ${style.grey('private claims and source locations omitted; rerun with --private-details')}`);
770
+ }
771
+ if (actionable.length > 0 &&
772
+ !(report.maintenancePlans ?? []).some((plan) => plan.items.some((item) => item.kind === 'contradiction')) &&
773
+ !report.maintenancePlan?.items.some((item) => item.kind === 'contradiction')) {
774
+ line(`\n ${style.grey('no contradiction item was applied; use')} ${style.bold('akno dream --phase curate --mode audit')} ${style.grey('to inspect eligible plans')}`);
775
+ }
776
+ }
777
+ if (report.repaired) {
778
+ const fixed = report.repaired;
779
+ if (fixed.links.length > 0 || fixed.claims.length > 0) {
780
+ const linkActions = countBy(fixed.links, (link) => link.action);
781
+ heading(`Broken-link plans — ${linkActions.applied ?? 0} applied, ${linkActions.planned ?? 0} proposed, ${linkActions.rejected ?? 0} rejected`);
782
+ if (privateDetails) {
783
+ for (const link of fixed.links) {
784
+ line(` ${style.grey(link.from)} [[${link.brokenTarget}]] ${style.green('→')} [[${link.newTarget}]]` +
785
+ style.grey(` (${link.signal.replace('_', ' ')}, ${link.action})`));
786
+ }
787
+ for (const claim of fixed.claims) {
788
+ // Both sides, because this rewrote a sentence in the owner's own notes.
789
+ line(` ${style.grey(`${claim.slug}:${claim.line}`)} superseded by ${style.grey(claim.supersededBy)}`);
790
+ line(` ${style.red('was')} ${truncate(claim.before, 72)}`);
791
+ line(` ${style.green('now')} ${truncate(claim.after, 72)}`);
792
+ }
793
+ }
794
+ else {
795
+ line(` ${style.grey('private paths and before/after text omitted; rerun with --private-details')}`);
796
+ }
797
+ if (report.repairChangeId) {
798
+ line(`\n ${style.grey('all of it undoes together:')} ${style.bold(`akno undo ${report.repairChangeId}`)}`);
799
+ }
800
+ }
801
+ if (fixed.declined.length > 0) {
802
+ // What it would not plan, and why. A planner that skips silently looks like a broken one.
803
+ heading(`${fixed.declined.length} left alone`);
804
+ if (privateDetails) {
805
+ for (const entry of fixed.declined.slice(0, 10)) {
806
+ line(` ${style.grey(truncate(entry.what, 60))} ${entry.reason}`);
807
+ if (entry.candidates?.length) {
808
+ line(` ${style.grey(`candidates: ${entry.candidates.join(', ')}`)}`);
809
+ }
810
+ }
811
+ if (fixed.declined.length > 10)
812
+ line(` ${style.grey(`… and ${fixed.declined.length - 10} more`)}`);
813
+ }
814
+ else {
815
+ line(` ${style.grey('private targets and reasons omitted; rerun with --private-details')}`);
816
+ }
817
+ }
818
+ }
819
+ if (report.housekeeping) {
820
+ const house = report.housekeeping;
821
+ heading('Housekeeping');
822
+ kv([
823
+ ['broken links', housekeepingCount(house.counts.brokenLinks, house.planBacked.brokenLinks)],
824
+ [
825
+ 'orphaned documents',
826
+ housekeepingCount(house.counts.orphanedDocuments, house.planBacked.orphanedDocuments),
827
+ ],
828
+ ['pages off their rules', housekeepingCount(house.counts.drift, house.planBacked.drift)],
829
+ ['rule repairs', ruleRepairSummary(house.ruleRepairs)],
830
+ ['graph review candidates', house.counts.graphCandidates],
831
+ ]);
832
+ if (privateDetails) {
833
+ for (const entry of house.orphanedDocuments.slice(0, 5)) {
834
+ line(` ${style.yellow('·')} ${entry.relPath}`);
835
+ line(` ${style.grey(entry.reason)}`);
836
+ if (entry.plan)
837
+ line(` ${style.grey(housekeepingPlanLabel(entry.plan))}`);
838
+ }
839
+ for (const entry of house.drift.slice(0, 5)) {
840
+ line(` ${style.yellow('·')} ${entry.slug} ${style.grey(`${entry.rule} expects ${entry.expected}, found ${entry.found}`)}`);
841
+ if (entry.plan)
842
+ line(` ${style.grey(housekeepingPlanLabel(entry.plan))}`);
843
+ line(` ${style.grey(`${entry.repair.status}/${entry.repair.code}: ${entry.repair.reason}`)}`);
844
+ }
845
+ if (house.brokenLinks.length > 0) {
846
+ for (const link of house.brokenLinks.slice(0, 5)) {
847
+ const plan = link.plan ? ` · ${housekeepingPlanLabel(link.plan)}` : '';
848
+ line(` ${style.grey(`${link.from} → ${link.to}${plan}`)}`);
849
+ }
850
+ }
851
+ for (const entry of house.graphCandidates.slice(0, 5)) {
852
+ line(` ${style.yellow('·')} [${entry.kind}] ${entry.subject}`);
853
+ line(` ${style.grey(entry.reason)}`);
854
+ if (entry.related.length > 0) {
855
+ line(` ${style.grey(`related: ${entry.related.join(', ')}`)}`);
856
+ }
857
+ }
858
+ }
859
+ }
860
+ if (report.warnings.length > 0) {
861
+ heading(`${report.warnings.length} warning(s)`);
862
+ if (privateDetails) {
863
+ for (const warning of report.warnings.slice(0, 10))
864
+ line(` ${style.yellow('·')} ${warning}`);
865
+ }
866
+ else {
867
+ line(` ${style.grey('private warning details omitted; rerun with --private-details')}`);
868
+ }
869
+ }
870
+ // Said out loud rather than left to be discovered: the flag that turns this on is the one
871
+ // that puts a record of private inferences on disk, and a run should say where it went.
872
+ if (report.logPath) {
873
+ line(`\n ${style.grey(privateDetails ? `this run was written to ${report.logPath}` : 'a private run log was written')}`);
874
+ }
875
+ for (const [what, id] of [
876
+ ['the observations', report.changeId],
877
+ ['the pages it wrote for documents', report.adoptChangeId],
878
+ ]) {
879
+ // Two changes rather than one: undoing a night's inferences should not also undo the pages
880
+ // that made documents searchable, and the other way round.
881
+ if (id)
882
+ line(`\n ${style.grey(`reverse ${what} with`)} ${style.bold(`akno undo ${id}`)}`);
883
+ }
884
+ return dreamRunExitCode(report.run);
885
+ }
886
+ export function dreamRunExitCode(run) {
887
+ return run.status === 'failed' ? 1 : 0;
888
+ }
889
+ function housekeepingCount(total, planBacked) {
890
+ return planBacked > 0 ? `${total} (${planBacked} plan-backed)` : total;
891
+ }
892
+ function housekeepingPlanLabel(plan) {
893
+ const deferred = plan.statusCode ? ` · ${plan.statusCode}` : '';
894
+ return `plan ${plan.planId}/${plan.itemId} · ${plan.policy}/${plan.status}${deferred}`;
895
+ }
896
+ function ruleRepairSummary(counts) {
897
+ return `${counts.planBacked} planned · ${counts.ready} ready · ${counts.held} held · ${counts.reportOnly} report-only`;
898
+ }
899
+ function printRunVerification(verification) {
900
+ if (!verification)
901
+ return;
902
+ line('\n run verification');
903
+ kv([
904
+ ['status', verification.status],
905
+ ['applied items', verification.appliedItems],
906
+ ['affected files', verification.affectedFiles],
907
+ ['unattributed files', verification.unattributedFiles ?? 'not recorded'],
908
+ ['item receipts', verification.checks.appliedItems],
909
+ ['affected paths', verification.checks.affectedPaths],
910
+ ['whole snapshot', verification.checks.wholeSnapshot],
911
+ ['budget accounting', verification.checks.budget],
912
+ ['model accounting', verification.checks.modelUsage],
913
+ ]);
914
+ for (const issue of verification.issues) {
915
+ line(` ${style.grey(`${issue.code}: ${issue.count}`)}`);
916
+ }
917
+ }
918
+ function printConflictRefresh(refresh) {
919
+ if (!refresh)
920
+ return;
921
+ line('\n changed-claim refresh');
922
+ kv([
923
+ ['status', refresh.status],
924
+ ['cause', refresh.cause],
925
+ ['changed files', refresh.changedFiles],
926
+ ['knowledge pages', refresh.knowledgePages],
927
+ ['current pages', refresh.currentPages],
928
+ ['stale pages', refresh.stalePages],
929
+ ['conflicts', refresh.candidates],
930
+ ['unverified', refresh.unverified],
931
+ ]);
932
+ }
933
+ export function dreamRunIsReadOnly(report) {
934
+ return report.run.dryRun || report.run.mode === 'audit' || report.run.mode === 'review';
935
+ }
936
+ function curationCounts(report) {
937
+ const counts = countBy(report.curated, (entry) => entry.action);
938
+ return {
939
+ updated: counts.updated ?? 0,
940
+ preview: counts['would-update'] ?? 0,
941
+ unchanged: counts.unchanged ?? 0,
942
+ rejected: counts.rejected ?? 0,
943
+ };
944
+ }
945
+ export function curationGuardSummary(report) {
946
+ const categories = new Map();
947
+ for (const entry of report.curated) {
948
+ for (const category of new Set(entry.issues.map(guardrailCategory))) {
949
+ const current = categories.get(category) ?? { pages: new Set(), issues: 0 };
950
+ current.pages.add(entry.slug);
951
+ current.issues += entry.issues.filter((issue) => guardrailCategory(issue) === category).length;
952
+ categories.set(category, current);
953
+ }
954
+ }
955
+ return [...categories]
956
+ .map(([category, value]) => ({ category, pages: value.pages.size, issues: value.issues }))
957
+ .sort((left, right) => right.pages - left.pages || left.category.localeCompare(right.category));
958
+ }
959
+ function guardrailCategory(issue) {
960
+ if (/numeric\/date\/value|source body line/i.test(issue))
961
+ return 'value preservation';
962
+ if (/wikilink|markdown link|external url|link target/i.test(issue))
963
+ return 'link integrity';
964
+ if (/stable item|provenance|marker/i.test(issue))
965
+ return 'provenance preservation';
966
+ if (/unresolved|conflict/i.test(issue))
967
+ return 'conflict preservation';
968
+ if (/evidence/i.test(issue))
969
+ return 'evidence relevance';
970
+ if (/archiv|post-event/i.test(issue))
971
+ return 'archival materiality';
972
+ if (/extract|destination|source page/i.test(issue))
973
+ return 'extraction safety';
974
+ if (/cosmetic|material|heading|structur|format|size/i.test(issue))
975
+ return 'materiality';
976
+ if (/timeout|provider|transport|model|json|schema/i.test(issue))
977
+ return 'model response';
978
+ return 'other safety check';
979
+ }
980
+ /** A content-free JSON receipt suitable for CI logs and support bundles. */
981
+ export function safeDreamReport(report) {
982
+ const observations = countBy(report.observations, (entry) => entry.action);
983
+ const adopted = countBy(report.adopted, (entry) => entry.action);
984
+ const conflicts = countBy(report.conflicts, (entry) => entry.verdict);
985
+ return {
986
+ run: report.run,
987
+ durationMs: report.durationMs,
988
+ phases: report.phases.map((phase) => ({
989
+ phase: phase.phase,
990
+ ran: phase.ran,
991
+ skipped: phase.skipped !== undefined,
992
+ durationMs: phase.durationMs,
993
+ })),
994
+ observations: {
995
+ total: report.observations.length,
996
+ created: observations.created ?? 0,
997
+ refined: observations.refined ?? 0,
998
+ planned: (observations['would-create'] ?? 0) + (observations['would-refine'] ?? 0),
999
+ rejected: observations.rejected ?? 0,
1000
+ unchanged: observations.unchanged ?? 0,
1001
+ },
1002
+ curation: {
1003
+ total: report.curated.length,
1004
+ ...curationCounts(report),
1005
+ guardrails: curationGuardSummary(report),
1006
+ },
1007
+ managedItems: safeManagedItemReport(report.managedItems),
1008
+ semanticMerge: report.semanticMerge,
1009
+ verification: report.verification,
1010
+ conflictRefresh: report.conflictRefresh,
1011
+ maintenancePlan: report.maintenancePlan ? safeMaintenancePlan(report.maintenancePlan) : null,
1012
+ maintenancePlans: (report.maintenancePlans ?? []).map(safeMaintenancePlan),
1013
+ planPrune: report.planPrune,
1014
+ rejectedByGuard: report.rejected.length,
1015
+ adopted: {
1016
+ total: report.adopted.length,
1017
+ created: adopted.created ?? 0,
1018
+ planned: adopted.planned ?? 0,
1019
+ rejected: adopted.rejected ?? 0,
1020
+ skipped: adopted.skipped ?? 0,
1021
+ },
1022
+ conflicts: {
1023
+ total: report.conflicts.length,
1024
+ superseded: conflicts.superseded ?? 0,
1025
+ qualified: conflicts.qualified ?? 0,
1026
+ unresolved: conflicts.unresolved ?? 0,
1027
+ timeScoped: conflicts.time_scoped ?? 0,
1028
+ unverified: conflicts.unverified ?? 0,
1029
+ notAConflict: conflicts.not_a_conflict ?? 0,
1030
+ },
1031
+ repair: report.repaired
1032
+ ? {
1033
+ links: report.repaired.links.length,
1034
+ linkActions: countBy(report.repaired.links, (link) => link.action),
1035
+ claims: report.repaired.claims.length,
1036
+ declined: report.repaired.declined.length,
1037
+ }
1038
+ : null,
1039
+ housekeeping: report.housekeeping
1040
+ ? {
1041
+ counts: report.housekeeping.counts,
1042
+ planBacked: report.housekeeping.planBacked,
1043
+ ruleRepairs: report.housekeeping.ruleRepairs,
1044
+ }
1045
+ : null,
1046
+ warnings: report.warnings.length,
1047
+ changes: {
1048
+ observations: report.changeId,
1049
+ adopted: report.adoptChangeId,
1050
+ curated: report.curateChangeId,
1051
+ repaired: report.repairChangeId,
1052
+ },
1053
+ privateLogWritten: report.logPath !== undefined,
1054
+ };
1055
+ }
1056
+ function safeManagedItemReport(report) {
1057
+ const { details: _details, ...safe } = report;
1058
+ return safe;
1059
+ }
1060
+ function safeMaintenancePlan(plan) {
1061
+ return {
1062
+ id: plan.id,
1063
+ createdAt: plan.createdAt,
1064
+ updatedAt: plan.updatedAt,
1065
+ mode: plan.mode,
1066
+ phase: plan.phase,
1067
+ status: plan.status,
1068
+ payloadPrunedAt: plan.payloadPrunedAt,
1069
+ fingerprint: plan.fingerprint,
1070
+ counts: plan.counts,
1071
+ items: plan.items.map((item) => ({
1072
+ id: item.id,
1073
+ kind: item.kind,
1074
+ policy: item.policy,
1075
+ risk: item.risk,
1076
+ componentCount: item.componentCount,
1077
+ status: item.status,
1078
+ statusCode: item.statusCode,
1079
+ decision: item.decision
1080
+ ? { actor: item.decision.actor, outcome: item.decision.outcome, at: item.decision.at }
1081
+ : null,
1082
+ changeId: item.changeId,
1083
+ verification: item.verification ? { status: item.verification.status, at: item.verification.at } : null,
1084
+ })),
1085
+ };
1086
+ }
1087
+ function countBy(values, key) {
1088
+ const counts = {};
1089
+ for (const value of values) {
1090
+ const name = key(value);
1091
+ counts[name] = (counts[name] ?? 0) + 1;
1092
+ }
1093
+ return counts;
1094
+ }
1095
+ function dreamProgressWriter(startedAt, phase, mode) {
1096
+ let lastStage = '';
1097
+ let lastWrittenAt = Number.NEGATIVE_INFINITY;
1098
+ let visible = false;
1099
+ const tty = process.stderr.isTTY;
1100
+ const update = (wait) => {
1101
+ const progress = dreamProgressDescription(wait, startedAt, phase, mode);
1102
+ if (!tty && progress.stage === lastStage && wait.elapsedMs - lastWrittenAt < 30_000)
1103
+ return;
1104
+ const message = `${progress.stage} · ${ms(wait.elapsedMs)} · ${progress.writeState}`;
1105
+ if (tty)
1106
+ process.stderr.write(`\r\u001b[2K${style.grey(message)}`);
1107
+ else
1108
+ process.stderr.write(`${message}\n`);
1109
+ visible = true;
1110
+ lastStage = progress.stage;
1111
+ lastWrittenAt = wait.elapsedMs;
1112
+ };
1113
+ return {
1114
+ update,
1115
+ done: () => {
1116
+ if (tty && visible)
1117
+ process.stderr.write('\r\u001b[2K');
1118
+ },
1119
+ };
1120
+ }
1121
+ export function dreamProgressDescription(wait, startedAt, phase, mode) {
1122
+ const latest = relevantProgressPlan(wait.status, startedAt, mode);
1123
+ if (!latest) {
1124
+ return {
1125
+ stage: phase === 'curate' ? 'curate: planning candidates' : 'dream: running phases',
1126
+ writeState: mode === 'audit'
1127
+ ? 'audit mode; KB writes disabled'
1128
+ : phase === 'curate'
1129
+ ? 'KB write not started'
1130
+ : 'write state will be reported at completion',
1131
+ };
1132
+ }
1133
+ const stage = latest.status === 'deciding'
1134
+ ? 'curate: independent curator'
1135
+ : latest.status === 'applying' || latest.status === 'approved'
1136
+ ? 'curate: applying approved items'
1137
+ : latest.status === 'partially_completed'
1138
+ ? 'curate: verifying or recovering'
1139
+ : latest.status === 'ready' || latest.status === 'awaiting_review'
1140
+ ? 'curate: plan ready for review'
1141
+ : latest.status === 'completed'
1142
+ ? 'curate: plan completed'
1143
+ : `curate: plan ${latest.status}`;
1144
+ const applied = latest.counts.applied;
1145
+ const writeState = applied > 0
1146
+ ? `${applied} item(s) written and verified`
1147
+ : latest.status === 'applying' || latest.status === 'partially_completed'
1148
+ ? 'KB write or verification in progress'
1149
+ : latest.mode === 'audit' || latest.mode === 'review'
1150
+ ? `${latest.mode} mode; KB write not started`
1151
+ : 'KB write not started';
1152
+ return { stage, writeState };
1153
+ }
1154
+ function relevantProgressPlan(status, startedAt, mode) {
1155
+ const latest = status?.latest;
1156
+ if (!latest || (mode && latest.mode !== mode))
1157
+ return null;
1158
+ const unfinished = !['completed', 'failed', 'superseded'].includes(latest.status);
1159
+ const createdDuringRun = Date.parse(latest.createdAt) >= startedAt - 2_000;
1160
+ return unfinished || createdDuringRun ? latest : null;
1161
+ }
1162
+ function parseMode(value) {
1163
+ if (value === 'audit' || value === 'review' || value === 'auto')
1164
+ return value;
1165
+ throw new AknoError('invalid', `unknown maintenance mode '${value}' — expected audit, review or auto`);
1166
+ }
1167
+ //# sourceMappingURL=dream-cmd.js.map