novahiz 0.3.2 → 0.3.3
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/bin/novahiz.mjs +30 -2
- package/dist/autodocs.js +170 -0
- package/dist/catalog.js +245 -0
- package/dist/classify.js +181 -0
- package/dist/cli.js +218 -0
- package/dist/commands/autodocs.js +218 -0
- package/dist/commands/clean.js +106 -0
- package/dist/commands/context.js +167 -0
- package/dist/commands/doctor.js +221 -0
- package/dist/commands/gate.js +218 -0
- package/dist/commands/graft.js +146 -0
- package/dist/commands/init.js +325 -0
- package/dist/commands/inspect.js +314 -0
- package/dist/commands/report.js +87 -0
- package/dist/commands/task.js +354 -0
- package/dist/commands/tokens.js +69 -0
- package/dist/complexity.js +265 -0
- package/dist/content.js +99 -0
- package/dist/db.js +176 -0
- package/dist/deps.js +24 -0
- package/dist/exec.js +108 -0
- package/dist/gate-repair.js +76 -0
- package/dist/gate.js +493 -0
- package/dist/graft.js +257 -0
- package/dist/ledger.js +431 -0
- package/dist/memory.js +441 -0
- package/dist/prompt-rewriter.js +481 -0
- package/dist/providers.js +47 -0
- package/dist/relevance.js +58 -0
- package/dist/render.js +90 -0
- package/dist/spec.js +208 -0
- package/dist/targets.js +375 -0
- package/install/bootstrap.mjs +25 -6
- package/install/install.mjs +32 -44
- package/install/lib.mjs +41 -0
- package/package.json +4 -3
- package/src/exec.ts +6 -1
- package/src/spec.ts +4 -1
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { asString, dbPathFor, numberFlag, parse, print, readStdin, splitList } from "./context.js";
|
|
3
|
+
import { loadSpec, NovahizHome } from "../spec.js";
|
|
4
|
+
import { openDb } from "../db.js";
|
|
5
|
+
import { activeTask, addTodos, amendTodo, blockTodo, completeTodo, createTask, dropTask, dropTodo, getTask, getTodo, insertTodo, ledgerSummary, recordTodoDone, reorderTodos, resume, reviewDue, reviewTask, revisionSignals, startTodo } from "../ledger.js";
|
|
6
|
+
const TODO_KINDS = ["read", "edit", "verify", "delegate"];
|
|
7
|
+
export function normalizeTodoInput(item) {
|
|
8
|
+
const record = (item ?? {});
|
|
9
|
+
const owners = Array.isArray(record.owner)
|
|
10
|
+
? record.owner.map(String)
|
|
11
|
+
: typeof record.owner === "string"
|
|
12
|
+
? record.owner.split(",").map((part) => part.trim()).filter(Boolean)
|
|
13
|
+
: [];
|
|
14
|
+
const dependsRaw = record.dependsOn ?? record.depends_on;
|
|
15
|
+
const dependsOn = Array.isArray(dependsRaw) ? dependsRaw.map(String) : [];
|
|
16
|
+
const maxIterations = Number(record.maxIterations ?? record.max_iterations);
|
|
17
|
+
const kind = record.kind ? String(record.kind) : "edit";
|
|
18
|
+
if (!TODO_KINDS.includes(kind)) {
|
|
19
|
+
throw new Error(`invalid todo kind: ${kind} (expected one of ${TODO_KINDS.join(", ")})`);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
label: String(record.label ?? record.title ?? "").trim(),
|
|
23
|
+
kind: kind,
|
|
24
|
+
acceptance: record.acceptance ? String(record.acceptance) : undefined,
|
|
25
|
+
owner: owners.length > 0 ? owners.join(",") : undefined,
|
|
26
|
+
dependsOn,
|
|
27
|
+
maxIterations: Number.isFinite(maxIterations) && maxIterations > 0 ? maxIterations : undefined
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function commandTask(parsed) {
|
|
31
|
+
const root = NovahizHome();
|
|
32
|
+
const spec = loadSpec(root);
|
|
33
|
+
const action = parsed.positionals[1] ?? "status";
|
|
34
|
+
const session = asString(parsed.flags.session);
|
|
35
|
+
const db = openDb(dbPathFor(root, spec));
|
|
36
|
+
try {
|
|
37
|
+
if (action === "new")
|
|
38
|
+
return taskNew(parsed, db, session, spec);
|
|
39
|
+
if (action === "plan")
|
|
40
|
+
return taskPlan(parsed, db, session, spec);
|
|
41
|
+
if (action === "todo")
|
|
42
|
+
return taskTodo(parsed, db, session, spec);
|
|
43
|
+
const id = asString(parsed.flags.id) || parsed.positionals[2] || "";
|
|
44
|
+
// NOTE: review enforcement is asymmetric by design. `start` checks and
|
|
45
|
+
// blocks if a review is due (prevents starting new work on stale context).
|
|
46
|
+
// `done` records the completion without requiring a review first — the
|
|
47
|
+
// review is enforced on the *next* `start` or `review` call. This keeps
|
|
48
|
+
// the "finish what I'm doing" flow unblocked while still catching stale
|
|
49
|
+
// context before new work begins.
|
|
50
|
+
if (action === "start")
|
|
51
|
+
return taskStart(parsed, db, session, spec);
|
|
52
|
+
if (action === "done")
|
|
53
|
+
return taskDone(parsed, db, session, spec);
|
|
54
|
+
if (action === "block")
|
|
55
|
+
return taskBlock(parsed, db, session, spec);
|
|
56
|
+
if (action === "signals")
|
|
57
|
+
return taskSignals(parsed, db, session, spec);
|
|
58
|
+
if (action === "review")
|
|
59
|
+
return taskReview(parsed, db, session, spec);
|
|
60
|
+
if (action === "amend")
|
|
61
|
+
return taskAmend(parsed, db, session, spec);
|
|
62
|
+
if (action === "insert")
|
|
63
|
+
return taskInsert(parsed, db, session, spec);
|
|
64
|
+
if (action === "drop")
|
|
65
|
+
return taskDrop(parsed, db, session, spec);
|
|
66
|
+
if (action === "reorder")
|
|
67
|
+
return taskReorder(parsed, db, session, spec);
|
|
68
|
+
if (action === "read")
|
|
69
|
+
return taskRead(parsed, db, session, spec);
|
|
70
|
+
if (action === "status")
|
|
71
|
+
return taskRead(parsed, db, session, spec);
|
|
72
|
+
if (action === "resume")
|
|
73
|
+
return taskResume(db, session);
|
|
74
|
+
if (action === "current")
|
|
75
|
+
return taskCurrent(db, session);
|
|
76
|
+
print({ error: `unknown task action: ${action}`, actions: ["new", "plan", "todo", "start", "done", "block", "review", "amend", "insert", "drop", "reorder", "signals", "status", "resume", "current"] });
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
print({ error: String(error?.message ?? error) });
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
db.close();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function taskRead(parsed, db, session, spec) {
|
|
88
|
+
const state = resume(db, session || undefined);
|
|
89
|
+
const summary = ledgerSummary(state);
|
|
90
|
+
let review = null;
|
|
91
|
+
const signals = state.task ? revisionSignals(db, state.task.id) : [];
|
|
92
|
+
if (state.task && spec.config.ledger?.enabled !== false) {
|
|
93
|
+
review = reviewDue(db, state.task.id, spec.config.ledger.review);
|
|
94
|
+
summary.push(` review: ${review.due ? "DUE" : "ok"} (edits ${review.edits}/${review.policy.edits}, todos ${review.todos}/${review.policy.todos})`);
|
|
95
|
+
if (review.due)
|
|
96
|
+
summary.push(` -> reconcile with: novahiz task review --task ${state.task.id} --reason "<what changed>"`);
|
|
97
|
+
for (const signal of signals)
|
|
98
|
+
summary.push(` signal ${signal.type}: ${signal.detail}`);
|
|
99
|
+
}
|
|
100
|
+
print({ ...state, summary, review, signals });
|
|
101
|
+
}
|
|
102
|
+
function taskResume(db, session) {
|
|
103
|
+
const state = resume(db, session || undefined);
|
|
104
|
+
const summary = ledgerSummary(state);
|
|
105
|
+
const next = state.todos.find((todo) => todo.status === "pending");
|
|
106
|
+
print({
|
|
107
|
+
...state,
|
|
108
|
+
summary,
|
|
109
|
+
next: next ? { id: next.id, seq: next.seq, label: next.label, kind: next.kind } : null
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function taskCurrent(db, session) {
|
|
113
|
+
const state = resume(db, session || undefined);
|
|
114
|
+
print({ task: state.task, current: state.current, todos: state.todos.length });
|
|
115
|
+
}
|
|
116
|
+
function taskReorder(parsed, db, session, spec) {
|
|
117
|
+
const taskId = asString(parsed.flags.task) || activeTask(db, session || undefined)?.id;
|
|
118
|
+
if (!taskId) {
|
|
119
|
+
print({ error: "task reorder requires --task or an active task" });
|
|
120
|
+
process.exitCode = 1;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const requested = splitList(parsed.flags.order);
|
|
124
|
+
const order = requested.length > 0 ? requested : parsed.positionals.slice(2);
|
|
125
|
+
print({ task: getTask(db, taskId), todos: reorderTodos(db, taskId, order) });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
function taskDrop(parsed, db, session, spec) {
|
|
129
|
+
const id = asString(parsed.flags.id) || parsed.positionals[2] || "";
|
|
130
|
+
if (!id) {
|
|
131
|
+
print({
|
|
132
|
+
error: "task drop requires --id",
|
|
133
|
+
usage: "novahiz task drop --id <task_id|todo_id> --reason \"why\""
|
|
134
|
+
});
|
|
135
|
+
process.exitCode = 1;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const reason = asString(parsed.flags.reason);
|
|
139
|
+
if (getTask(db, id)) {
|
|
140
|
+
print({ task: dropTask(db, id, reason) });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (getTodo(db, id)) {
|
|
144
|
+
print({ todo: dropTodo(db, id, reason) });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
print({
|
|
148
|
+
error: `unknown id: ${id}`,
|
|
149
|
+
hint: "expected a task id or a todo id (pass --reason to abandon an active task)"
|
|
150
|
+
});
|
|
151
|
+
process.exitCode = 1;
|
|
152
|
+
}
|
|
153
|
+
function taskInsert(parsed, db, session, spec) {
|
|
154
|
+
const taskId = asString(parsed.flags.task) || activeTask(db, session || undefined)?.id;
|
|
155
|
+
if (!taskId) {
|
|
156
|
+
print({ error: "task insert requires --task or an active task" });
|
|
157
|
+
process.exitCode = 1;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const label = (asString(parsed.flags.label) || parsed.positionals.slice(2).join(" ")).trim();
|
|
161
|
+
if (!label) {
|
|
162
|
+
print({ error: "task insert requires --label" });
|
|
163
|
+
process.exitCode = 1;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const item = normalizeTodoInput({
|
|
167
|
+
label,
|
|
168
|
+
kind: asString(parsed.flags.kind) || "edit",
|
|
169
|
+
acceptance: asString(parsed.flags.acceptance) || undefined,
|
|
170
|
+
owner: splitList(parsed.flags.owner),
|
|
171
|
+
maxIterations: numberFlag(parsed, "max-iterations", { min: 1, integer: true }) || undefined
|
|
172
|
+
});
|
|
173
|
+
const positionRaw = asString(parsed.flags.position);
|
|
174
|
+
const position = positionRaw === "" ? "end" : /^\d+$/.test(positionRaw) ? Number(positionRaw) : positionRaw;
|
|
175
|
+
print({ todo: insertTodo(db, taskId, item, position), task: getTask(db, taskId) });
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
function taskAmend(parsed, db, session, spec) {
|
|
179
|
+
const id = asString(parsed.flags.id) || parsed.positionals[2] || "";
|
|
180
|
+
if (!id) {
|
|
181
|
+
print({ error: "task amend requires --id" });
|
|
182
|
+
process.exitCode = 1;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const patch = {};
|
|
186
|
+
const label = asString(parsed.flags.label);
|
|
187
|
+
if (label)
|
|
188
|
+
patch.label = label;
|
|
189
|
+
const kind = asString(parsed.flags.kind);
|
|
190
|
+
if (kind && !TODO_KINDS.includes(kind)) {
|
|
191
|
+
print({ error: `invalid todo kind: ${kind} (expected one of ${TODO_KINDS.join(", ")})` });
|
|
192
|
+
process.exitCode = 1;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (kind)
|
|
196
|
+
patch.kind = kind;
|
|
197
|
+
if (parsed.flags.acceptance !== undefined)
|
|
198
|
+
patch.acceptance = asString(parsed.flags.acceptance);
|
|
199
|
+
const owners = splitList(parsed.flags.owner);
|
|
200
|
+
if (owners.length > 0)
|
|
201
|
+
patch.owner = owners.join(",");
|
|
202
|
+
const maxIterations = numberFlag(parsed, "max-iterations", { min: 1, integer: true });
|
|
203
|
+
if (maxIterations !== undefined && maxIterations > 0)
|
|
204
|
+
patch.maxIterations = maxIterations;
|
|
205
|
+
print({ todo: amendTodo(db, id, patch) });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
function taskReview(parsed, db, session, spec) {
|
|
209
|
+
const taskId = asString(parsed.flags.task) || activeTask(db, session || undefined)?.id;
|
|
210
|
+
if (!taskId) {
|
|
211
|
+
print({ error: "task review requires --task or an active task" });
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const raw = (asString(parsed.flags.json) || readStdin()).trim();
|
|
216
|
+
let diff = {};
|
|
217
|
+
if (raw.length > 0) {
|
|
218
|
+
try {
|
|
219
|
+
diff = JSON.parse(raw);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
print({ error: "invalid JSON review diff" });
|
|
223
|
+
process.exitCode = 1;
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const outcome = reviewTask(db, { taskId, ...diff });
|
|
228
|
+
print({ ...outcome, reason: asString(parsed.flags.reason) });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
function taskSignals(parsed, db, session, spec) {
|
|
232
|
+
const taskId = asString(parsed.flags.task) || activeTask(db, session || undefined)?.id;
|
|
233
|
+
if (!taskId) {
|
|
234
|
+
print({ error: "task signals requires --task or an active task" });
|
|
235
|
+
process.exitCode = 1;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
print({ task: getTask(db, taskId), signals: revisionSignals(db, taskId) });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
function taskBlock(parsed, db, session, spec) {
|
|
242
|
+
const id = asString(parsed.flags.id) || parsed.positionals[2] || "";
|
|
243
|
+
if (!id) {
|
|
244
|
+
print({ error: "task block requires --id" });
|
|
245
|
+
process.exitCode = 1;
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
print({ todo: blockTodo(db, id, asString(parsed.flags.reason)) });
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
function taskDone(parsed, db, session, spec) {
|
|
252
|
+
const id = asString(parsed.flags.id) || parsed.positionals[2] || "";
|
|
253
|
+
if (!id) {
|
|
254
|
+
print({ error: "task done requires --id" });
|
|
255
|
+
process.exitCode = 1;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const todo = completeTodo(db, id, asString(parsed.flags.proof));
|
|
259
|
+
if (spec.config.ledger?.enabled !== false)
|
|
260
|
+
recordTodoDone(db, todo.task_id);
|
|
261
|
+
print({ todo });
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
function taskStart(parsed, db, session, spec) {
|
|
265
|
+
const id = asString(parsed.flags.id) || parsed.positionals[2] || "";
|
|
266
|
+
if (!id) {
|
|
267
|
+
print({ error: "task start requires --id" });
|
|
268
|
+
process.exitCode = 1;
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const target = getTodo(db, id);
|
|
272
|
+
if (target && spec.config.ledger?.enabled !== false) {
|
|
273
|
+
const due = reviewDue(db, target.task_id, spec.config.ledger.review);
|
|
274
|
+
if (due.due) {
|
|
275
|
+
print({ error: due.reason, task: target.task_id });
|
|
276
|
+
process.exitCode = 1;
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
print({ todo: startTodo(db, id) });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
function taskTodo(parsed, db, session, spec) {
|
|
284
|
+
const id = asString(parsed.flags.id) || parsed.positionals[2] || "";
|
|
285
|
+
const taskId = asString(parsed.flags.task) || activeTask(db, session || undefined)?.id;
|
|
286
|
+
if (!taskId) {
|
|
287
|
+
print({ error: "task todo requires --task or an active task" });
|
|
288
|
+
process.exitCode = 1;
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const label = (asString(parsed.flags.label) || parsed.positionals.slice(2).join(" ")).trim();
|
|
292
|
+
if (label.length === 0) {
|
|
293
|
+
print({ error: "task todo requires --label" });
|
|
294
|
+
process.exitCode = 1;
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const maxIterations = numberFlag(parsed, "max-iterations", { min: 1, integer: true });
|
|
298
|
+
const owners = splitList(parsed.flags.owner);
|
|
299
|
+
const todos = addTodos(db, taskId, [
|
|
300
|
+
{
|
|
301
|
+
label,
|
|
302
|
+
kind: (asString(parsed.flags.kind) || "edit"),
|
|
303
|
+
acceptance: asString(parsed.flags.acceptance) || undefined,
|
|
304
|
+
owner: owners.length > 0 ? owners.join(",") : undefined,
|
|
305
|
+
maxIterations: maxIterations !== undefined && maxIterations > 0 ? maxIterations : undefined
|
|
306
|
+
}
|
|
307
|
+
]);
|
|
308
|
+
print({ task: getTask(db, taskId), todos });
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
function taskPlan(parsed, db, session, spec) {
|
|
312
|
+
const id = asString(parsed.flags.id) || parsed.positionals[2] || "";
|
|
313
|
+
const taskId = asString(parsed.flags.task) || activeTask(db, session || undefined)?.id;
|
|
314
|
+
if (!taskId) {
|
|
315
|
+
print({ error: "task plan requires --task or an active task" });
|
|
316
|
+
process.exitCode = 1;
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const raw = (asString(parsed.flags.json) || readStdin()).trim();
|
|
320
|
+
if (raw.length === 0 && parsed.flags.json === undefined) {
|
|
321
|
+
print({ error: "task plan requires --json <array> or JSON on stdin" });
|
|
322
|
+
process.exitCode = 1;
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
let parsedItems;
|
|
326
|
+
try {
|
|
327
|
+
parsedItems = JSON.parse(raw.length > 0 ? raw : "[]");
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
print({ error: "invalid JSON plan" });
|
|
331
|
+
process.exitCode = 1;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (!Array.isArray(parsedItems)) {
|
|
335
|
+
print({ error: "plan must be a JSON array of todos" });
|
|
336
|
+
process.exitCode = 1;
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const todos = addTodos(db, taskId, parsedItems.map(normalizeTodoInput));
|
|
340
|
+
print({ task: getTask(db, taskId), todos });
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
function taskNew(parsed, db, session, spec) {
|
|
344
|
+
const title = (asString(parsed.flags.title) || parsed.positionals.slice(2).join(" ")).trim();
|
|
345
|
+
if (title.length === 0) {
|
|
346
|
+
print({ error: "task new requires --title" });
|
|
347
|
+
process.exitCode = 1;
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const id = asString(parsed.flags.id) || undefined;
|
|
351
|
+
const task = createTask(db, { title, id, sessionId: session || undefined });
|
|
352
|
+
print({ task, todos: [] });
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { dbPathFor, emit, flagOn } from "./context.js";
|
|
2
|
+
import { loadSpec, NovahizHome } from "../spec.js";
|
|
3
|
+
import { openDb } from "../db.js";
|
|
4
|
+
import * as ui from "../render.js";
|
|
5
|
+
const AVG_LINE_BYTES = 80;
|
|
6
|
+
const AVG_TOKENS_PER_LINE = 25;
|
|
7
|
+
export function commandTokens(parsed) {
|
|
8
|
+
const root = NovahizHome();
|
|
9
|
+
const spec = loadSpec(root);
|
|
10
|
+
const db = openDb(dbPathFor(root, spec));
|
|
11
|
+
const enforcementEvents = db.prepare("SELECT COUNT(*) AS n FROM enforcement_log").get().n;
|
|
12
|
+
const invocationEvents = db.prepare("SELECT COUNT(*) AS n FROM skill_invocations").get().n;
|
|
13
|
+
const roadmapEvents = db.prepare("SELECT COUNT(*) AS n FROM roadmap_progress").get().n;
|
|
14
|
+
const taskEvents = db.prepare("SELECT COUNT(*) AS n FROM tasks").get().n;
|
|
15
|
+
const todoEvents = db.prepare("SELECT COUNT(*) AS n FROM todos").get().n;
|
|
16
|
+
const blocked = db.prepare("SELECT COUNT(*) AS n FROM enforcement_log WHERE decision = 'block'").get().n;
|
|
17
|
+
const blockedBytes = blocked * AVG_LINE_BYTES;
|
|
18
|
+
const blockedTokens = Math.round((blocked * AVG_LINE_BYTES) / AVG_TOKENS_PER_LINE);
|
|
19
|
+
const byTool = db.prepare("SELECT tool, COUNT(*) AS n FROM enforcement_log GROUP BY tool ORDER BY n DESC").all();
|
|
20
|
+
const byDecision = db.prepare("SELECT decision, COUNT(*) AS n FROM enforcement_log GROUP BY decision ORDER BY n DESC").all();
|
|
21
|
+
db.close();
|
|
22
|
+
const events = enforcementEvents + invocationEvents + roadmapEvents + taskEvents + todoEvents;
|
|
23
|
+
const value = {
|
|
24
|
+
events,
|
|
25
|
+
enforcement: enforcementEvents,
|
|
26
|
+
invocations: invocationEvents,
|
|
27
|
+
roadmap: roadmapEvents,
|
|
28
|
+
tasks: taskEvents,
|
|
29
|
+
todos: todoEvents,
|
|
30
|
+
blocked,
|
|
31
|
+
estimatedBytesSaved: blockedBytes,
|
|
32
|
+
estimatedTokensSaved: blockedTokens,
|
|
33
|
+
byTool,
|
|
34
|
+
byDecision
|
|
35
|
+
};
|
|
36
|
+
const calibrate = flagOn(parsed, "calibrate");
|
|
37
|
+
emit(parsed, value, () => {
|
|
38
|
+
const lines = [
|
|
39
|
+
ui.heading("novahiz tokens"),
|
|
40
|
+
"",
|
|
41
|
+
ui.kv([
|
|
42
|
+
["events:", String(events)],
|
|
43
|
+
["enforcement:", String(enforcementEvents)],
|
|
44
|
+
["invocations:", String(invocationEvents)],
|
|
45
|
+
["roadmap:", String(roadmapEvents)],
|
|
46
|
+
["tasks:", String(taskEvents)],
|
|
47
|
+
["todos:", String(todoEvents)],
|
|
48
|
+
["blocked:", String(blocked)],
|
|
49
|
+
["est. bytes saved (~±5x):", ui.bytes(blockedBytes)],
|
|
50
|
+
["est. tokens saved (~±5x):", String(blockedTokens)]
|
|
51
|
+
]),
|
|
52
|
+
"",
|
|
53
|
+
ui.heading("By tool"),
|
|
54
|
+
ui.table(["tool", "n"], byTool.map((row) => [row.tool, String(row.n)])),
|
|
55
|
+
"",
|
|
56
|
+
ui.heading("By decision"),
|
|
57
|
+
ui.table(["decision", "n"], byDecision.map((row) => [row.decision, String(row.n)]))
|
|
58
|
+
];
|
|
59
|
+
if (calibrate) {
|
|
60
|
+
const bytesPerToken = AVG_LINE_BYTES / AVG_TOKENS_PER_LINE;
|
|
61
|
+
lines.push("", ui.heading("Calibration"), ui.kv([
|
|
62
|
+
["avg line bytes:", String(AVG_LINE_BYTES)],
|
|
63
|
+
["avg tokens/line:", String(AVG_TOKENS_PER_LINE)],
|
|
64
|
+
["bytes/token:", bytesPerToken.toFixed(1)]
|
|
65
|
+
]));
|
|
66
|
+
}
|
|
67
|
+
return lines.join("\n");
|
|
68
|
+
});
|
|
69
|
+
}
|