kankaku 0.7.0 → 0.8.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.
- package/README.md +93 -23
- package/dist/adapters/export-writer.d.ts +13 -0
- package/dist/adapters/export-writer.js +28 -0
- package/dist/adapters/hub-actions.d.ts +35 -0
- package/dist/adapters/hub-actions.js +70 -0
- package/dist/adapters/project-config.d.ts +42 -0
- package/dist/adapters/project-config.js +108 -0
- package/dist/adapters/report-data.d.ts +12 -0
- package/dist/adapters/report-data.js +8 -0
- package/dist/adapters/report-views.d.ts +45 -0
- package/dist/adapters/report-views.js +73 -0
- package/dist/adapters/report.d.ts +112 -0
- package/dist/adapters/report.js +236 -0
- package/dist/adapters/sync-runner.d.ts +21 -6
- package/dist/adapters/sync-runner.js +8 -1
- package/dist/domain/hub-entry.d.ts +26 -2
- package/dist/domain/hub-entry.js +50 -6
- package/dist/domain/sync-plan.js +10 -0
- package/dist/domain/work-record.d.ts +17 -0
- package/dist/domain/work-record.js +5 -1
- package/dist/hub/index.d.ts +6 -0
- package/dist/hub/index.js +6 -0
- package/package.json +1 -1
- package/src/adapters/hub-actions.ts +2 -3
- package/src/adapters/kankaku-command.ts +5 -9
- package/src/adapters/panel/kankaku-panel.ts +164 -19
- package/src/adapters/panel/panel-items.ts +13 -3
- package/src/adapters/panel/screens/doctor.ts +4 -0
- package/src/adapters/panel/screens/export.ts +4 -0
- package/src/adapters/panel/screens/report.ts +2 -0
- package/src/adapters/panel/screens/sync.ts +10 -0
- package/src/adapters/panel/screens/target.ts +18 -1
- package/src/adapters/pi-tracker.ts +8 -0
- package/src/adapters/report-data.ts +13 -0
- package/src/adapters/report-views.ts +1 -1
- package/src/adapters/sync-runner.ts +21 -7
- package/src/domain/hub-entry.ts +87 -8
- package/src/domain/panel-model.ts +10 -7
- package/src/domain/sync-plan.ts +10 -0
- package/src/domain/work-record.ts +22 -1
- package/src/hub/index.ts +6 -0
package/src/domain/hub-entry.ts
CHANGED
|
@@ -207,10 +207,12 @@ export interface TaskEntryPayload {
|
|
|
207
207
|
legacy_client_label: string;
|
|
208
208
|
repo_project: string;
|
|
209
209
|
schema: number;
|
|
210
|
+
/** Coding agent that MEASURED this task, lowercase slug — the orchestrator record's own {@link WorkRecordMetadata.agent} when it carries one (who measured, not who syncs), else `ctx.agent` (the syncing process's own identity) as a legacy fallback. See {@link WorkRecordMetadata.agent}. */
|
|
210
211
|
agent: string;
|
|
211
212
|
agent_version?: string;
|
|
212
213
|
/** Non-default session directory, see `TaskView.sessionDir`. A measurement field, not assignment: sent on both create and update. */
|
|
213
214
|
session_dir?: string;
|
|
215
|
+
/** The integration that wrote this task's records, lowercase slug — same record-wins-over-ctx rule as {@link agent}. */
|
|
214
216
|
plugin: string;
|
|
215
217
|
plugin_version?: string;
|
|
216
218
|
waiting_quality: WaitingQuality;
|
|
@@ -218,12 +220,59 @@ export interface TaskEntryPayload {
|
|
|
218
220
|
subagent_linkage: SubagentLinkage;
|
|
219
221
|
}
|
|
220
222
|
|
|
221
|
-
/**
|
|
222
|
-
|
|
223
|
+
/**
|
|
224
|
+
* `TaskEntryPayload` minus the assignment fields — what an update sends. See
|
|
225
|
+
* the module docs' CRITICAL rule.
|
|
226
|
+
*
|
|
227
|
+
* `agent`/`agent_version`/`plugin`/`plugin_version` are optional here (unlike
|
|
228
|
+
* on `TaskEntryPayload`, where they are always sent on create): an update for
|
|
229
|
+
* a task whose orchestrator record carries no who-measured identity (a
|
|
230
|
+
* legacy record, written before this feature existed) omits them entirely,
|
|
231
|
+
* so a re-sync by a DIFFERENT process never overwrites a row's original
|
|
232
|
+
* identity with its own. See {@link buildTaskEntryUpdatePayload}.
|
|
233
|
+
*/
|
|
234
|
+
export type TaskEntryUpdatePayload = Omit<
|
|
235
|
+
TaskEntryPayload,
|
|
236
|
+
"client" | "project" | "task" | "legacy_client_label" | "agent" | "agent_version" | "plugin" | "plugin_version"
|
|
237
|
+
> & {
|
|
238
|
+
agent?: string;
|
|
239
|
+
agent_version?: string;
|
|
240
|
+
plugin?: string;
|
|
241
|
+
plugin_version?: string;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* The who-measured identity (`agent`/`agentVersion`/`plugin`/`pluginVersion`)
|
|
246
|
+
* for a task's payload: the orchestrator record's own fields, taken as a
|
|
247
|
+
* unit, when it carries an `agent` — a missing version on the record is
|
|
248
|
+
* omitted, never backfilled from `ctx` — otherwise `ctx`'s identity (the
|
|
249
|
+
* syncing process's own), exactly as before this feature existed. See
|
|
250
|
+
* `domain/work-record.ts#WorkRecordMetadata.agent`'s doc comment.
|
|
251
|
+
*/
|
|
252
|
+
function resolveTaskIdentity(
|
|
253
|
+
task: TaskView,
|
|
254
|
+
ctx: HubEntryContext,
|
|
255
|
+
): { agent: string; agentVersion?: string; plugin: string; pluginVersion?: string } {
|
|
256
|
+
const orchestrator = task.orchestrator;
|
|
257
|
+
if (orchestrator.agent !== undefined) {
|
|
258
|
+
// The record's four fields are taken as a unit. A record written by an
|
|
259
|
+
// integration that stamped `agent` but not `plugin` gets the syncing
|
|
260
|
+
// context's `plugin` ONLY on create, so the row is never blank; the
|
|
261
|
+
// update payload below never resends a plugin the record does not own.
|
|
262
|
+
return {
|
|
263
|
+
agent: orchestrator.agent,
|
|
264
|
+
agentVersion: orchestrator.agentVersion,
|
|
265
|
+
plugin: orchestrator.plugin ?? ctx.plugin,
|
|
266
|
+
pluginVersion: orchestrator.plugin !== undefined ? orchestrator.pluginVersion : undefined,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return { agent: ctx.agent, agentVersion: ctx.agentVersion, plugin: ctx.plugin, pluginVersion: ctx.pluginVersion };
|
|
270
|
+
}
|
|
223
271
|
|
|
224
272
|
/** Build the full `task_entries` payload for a **create** request — every field, including assignment. */
|
|
225
273
|
export function buildTaskEntryCreatePayload(task: TaskView, ctx: HubEntryContext): TaskEntryPayload {
|
|
226
274
|
const assignment = resolveTaskAssignment(task, ctx.clients, ctx.projects, ctx.tasks);
|
|
275
|
+
const identity = resolveTaskIdentity(task, ctx);
|
|
227
276
|
return {
|
|
228
277
|
task_id: task.id,
|
|
229
278
|
client: assignment.clientId,
|
|
@@ -253,11 +302,11 @@ export function buildTaskEntryCreatePayload(task: TaskView, ctx: HubEntryContext
|
|
|
253
302
|
legacy_client_label: assignment.legacyClientLabel,
|
|
254
303
|
repo_project: task.project,
|
|
255
304
|
schema: task.orchestrator.schema,
|
|
256
|
-
agent:
|
|
257
|
-
...(
|
|
305
|
+
agent: identity.agent,
|
|
306
|
+
...(identity.agentVersion !== undefined ? { agent_version: identity.agentVersion } : {}),
|
|
258
307
|
...(task.sessionDir !== undefined ? { session_dir: task.sessionDir } : {}),
|
|
259
|
-
plugin:
|
|
260
|
-
...(
|
|
308
|
+
plugin: identity.plugin,
|
|
309
|
+
...(identity.pluginVersion !== undefined ? { plugin_version: identity.pluginVersion } : {}),
|
|
261
310
|
waiting_quality: computeWaitingQuality(),
|
|
262
311
|
cost_quality: computeCostQuality(task),
|
|
263
312
|
subagent_linkage: computeSubagentLinkage(task),
|
|
@@ -269,10 +318,40 @@ export function buildTaskEntryCreatePayload(task: TaskView, ctx: HubEntryContext
|
|
|
269
318
|
* fields only — never `client`, `project`, `task` or `legacy_client_label`,
|
|
270
319
|
* so a re-sync can never undo a reassignment made in the web. See the
|
|
271
320
|
* module docs' CRITICAL rule.
|
|
321
|
+
*
|
|
322
|
+
* `agent`/`agent_version`/`plugin`/`plugin_version` are included ONLY when
|
|
323
|
+
* the orchestrator record itself carries a who-measured `agent` — otherwise
|
|
324
|
+
* they are OMITTED entirely (never sent as `ctx`'s own identity), so a
|
|
325
|
+
* re-sync of a legacy record by a different process never overwrites the
|
|
326
|
+
* row's original identity in the hub. See `domain/work-record.ts`'s
|
|
327
|
+
* "Measurement rules" and {@link resolveTaskIdentity}.
|
|
272
328
|
*/
|
|
273
329
|
export function buildTaskEntryUpdatePayload(task: TaskView, ctx: HubEntryContext): TaskEntryUpdatePayload {
|
|
274
|
-
const {
|
|
275
|
-
|
|
330
|
+
const {
|
|
331
|
+
client: _client,
|
|
332
|
+
project: _project,
|
|
333
|
+
task: _task,
|
|
334
|
+
legacy_client_label: _legacy,
|
|
335
|
+
agent,
|
|
336
|
+
agent_version,
|
|
337
|
+
plugin,
|
|
338
|
+
plugin_version,
|
|
339
|
+
...rest
|
|
340
|
+
} = buildTaskEntryCreatePayload(task, ctx);
|
|
341
|
+
|
|
342
|
+
if (task.orchestrator.agent === undefined) return rest;
|
|
343
|
+
|
|
344
|
+
// `plugin`/`plugin_version` are resent only when the record itself carries
|
|
345
|
+
// them; a record with `agent` but no `plugin` must never have the syncing
|
|
346
|
+
// process's plugin written over the row's original one.
|
|
347
|
+
const recordHasPlugin = task.orchestrator.plugin !== undefined;
|
|
348
|
+
return {
|
|
349
|
+
...rest,
|
|
350
|
+
agent,
|
|
351
|
+
...(agent_version !== undefined ? { agent_version } : {}),
|
|
352
|
+
...(recordHasPlugin ? { plugin } : {}),
|
|
353
|
+
...(recordHasPlugin && plugin_version !== undefined ? { plugin_version } : {}),
|
|
354
|
+
};
|
|
276
355
|
}
|
|
277
356
|
|
|
278
357
|
/** One `work_records` row — raw per-`WorkRecord` detail, always `rollup: false`. */
|
|
@@ -83,10 +83,13 @@ const SCREEN_TITLES: Record<Exclude<PanelScreenId, "root">, string> = {
|
|
|
83
83
|
about: "About",
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
-
/**
|
|
86
|
+
/** The prompt glyph that opens every panel title, the owner's mark for kankaku. */
|
|
87
|
+
export const PANEL_TITLE_PREFIX = ">_";
|
|
88
|
+
|
|
89
|
+
/** `>_ kankaku` at root, `>_ kankaku · <Screen>` on every other screen. */
|
|
87
90
|
export function panelTitle(screen: PanelScreenId): string {
|
|
88
|
-
if (screen === "root") return
|
|
89
|
-
return
|
|
91
|
+
if (screen === "root") return `${PANEL_TITLE_PREFIX} kankaku`;
|
|
92
|
+
return `${PANEL_TITLE_PREFIX} kankaku · ${SCREEN_TITLES[screen]}`;
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
/** One clickable/keyboard hint shown in the panel's footer. */
|
|
@@ -97,9 +100,9 @@ export interface PanelHint {
|
|
|
97
100
|
|
|
98
101
|
/**
|
|
99
102
|
* The footer hint row for a screen: navigation hints, an optional search
|
|
100
|
-
* hint when the current body supports it, and how Escape/`q`
|
|
101
|
-
* at root closes the panel outright, so root shows only
|
|
102
|
-
* other screen shows both `esc back` and `q close`.
|
|
103
|
+
* hint when the current body supports it, and how Escape/left arrow/`q`
|
|
104
|
+
* behave — back at root closes the panel outright, so root shows only
|
|
105
|
+
* `esc close`; every other screen shows both `esc/← back` and `q close`.
|
|
103
106
|
*/
|
|
104
107
|
export function footerHints(screen: PanelScreenId, options: { searchable: boolean }): PanelHint[] {
|
|
105
108
|
const hints: PanelHint[] = [{ key: "↑↓", label: "move" }];
|
|
@@ -113,7 +116,7 @@ export function footerHints(screen: PanelScreenId, options: { searchable: boolea
|
|
|
113
116
|
|
|
114
117
|
hints.push({ key: "enter", label: "select" });
|
|
115
118
|
if (options.searchable) hints.push({ key: "/", label: "search" });
|
|
116
|
-
hints.push({ key: "esc", label: "back" }, { key: "q", label: "close" });
|
|
119
|
+
hints.push({ key: "esc/←", label: "back" }, { key: "q", label: "close" });
|
|
117
120
|
return hints;
|
|
118
121
|
}
|
|
119
122
|
|
package/src/domain/sync-plan.ts
CHANGED
|
@@ -134,6 +134,16 @@ export function computeTaskContentHash(task: TaskView): string {
|
|
|
134
134
|
// `undefined` is dropped by the serialiser, so a task that never knew
|
|
135
135
|
// its reasoning effort keeps the hash it had before this field existed.
|
|
136
136
|
thinkingLevel: task.orchestrator.thinkingLevel,
|
|
137
|
+
// A task gaining a who-measured identity
|
|
138
|
+
// (domain/hub-entry.ts#resolveTaskIdentity) must resync, since it
|
|
139
|
+
// changes which agent/plugin the row is attributed to; the version
|
|
140
|
+
// fields are left out on purpose so a version-only bump alone does not
|
|
141
|
+
// force a resync. The keys are added CONDITIONALLY: `stableStringify`
|
|
142
|
+
// renders an `undefined` value as `"agent":undefined`, so an
|
|
143
|
+
// unconditional key would change every legacy task's hash and force a
|
|
144
|
+
// full resync right after upgrading (see the golden-hash test).
|
|
145
|
+
...(task.orchestrator.agent !== undefined ? { agent: task.orchestrator.agent } : {}),
|
|
146
|
+
...(task.orchestrator.plugin !== undefined ? { plugin: task.orchestrator.plugin } : {}),
|
|
137
147
|
}),
|
|
138
148
|
);
|
|
139
149
|
}
|
|
@@ -201,6 +201,23 @@ export interface WorkRecordMetadata {
|
|
|
201
201
|
* (SUBAGENT-REQ-017). Never set on an `orchestrator` record.
|
|
202
202
|
*/
|
|
203
203
|
profile?: string;
|
|
204
|
+
/**
|
|
205
|
+
* Coding agent that MEASURED this record, lowercase slug (e.g. `"pi"`).
|
|
206
|
+
* Who measured, not who syncs — a different process (a standalone
|
|
207
|
+
* `kankaku` TUI, or a session for a different agent sharing the same
|
|
208
|
+
* `worklog.jsonl`) may later push this record to the hub, and must never
|
|
209
|
+
* overwrite this identity with its own. See kankaku-hub `docs/contract.md`
|
|
210
|
+
* "Agent and measurement quality" and `domain/hub-entry.ts`. Optional and
|
|
211
|
+
* additive: `WORK_RECORD_SCHEMA` is unchanged and an older record without
|
|
212
|
+
* it still validates.
|
|
213
|
+
*/
|
|
214
|
+
agent?: string;
|
|
215
|
+
/** The measuring agent's own version, when it could be determined without a hot-path cost. Never guessed — omitted rather than sent wrong. */
|
|
216
|
+
agentVersion?: string;
|
|
217
|
+
/** The integration that wrote this record, lowercase slug (e.g. `"kankaku"`). Same who-measured-not-who-syncs rule as {@link agent}. */
|
|
218
|
+
plugin?: string;
|
|
219
|
+
/** This integration's own version, from its `package.json`, read once. */
|
|
220
|
+
pluginVersion?: string;
|
|
204
221
|
}
|
|
205
222
|
|
|
206
223
|
export type WorkRecord = WorkRecordCore & WorkRecordMetadata;
|
|
@@ -294,6 +311,10 @@ export function isWorkRecord(value: unknown): value is WorkRecord {
|
|
|
294
311
|
(record["roleConfidence"] === undefined || record["roleConfidence"] === "uncertain") &&
|
|
295
312
|
(record["orchestratorRef"] === undefined || isOrchestratorRef(record["orchestratorRef"])) &&
|
|
296
313
|
(record["costObserved"] === undefined || record["costObserved"] === true) &&
|
|
297
|
-
(record["profile"] === undefined || typeof record["profile"] === "string")
|
|
314
|
+
(record["profile"] === undefined || typeof record["profile"] === "string") &&
|
|
315
|
+
(record["agent"] === undefined || typeof record["agent"] === "string") &&
|
|
316
|
+
(record["agentVersion"] === undefined || typeof record["agentVersion"] === "string") &&
|
|
317
|
+
(record["plugin"] === undefined || typeof record["plugin"] === "string") &&
|
|
318
|
+
(record["pluginVersion"] === undefined || typeof record["pluginVersion"] === "string")
|
|
298
319
|
);
|
|
299
320
|
}
|
package/src/hub/index.ts
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* for which ones and why.
|
|
8
8
|
*/
|
|
9
9
|
export * from "../adapters/cached-catalog.ts";
|
|
10
|
+
export * from "../adapters/export-writer.ts";
|
|
10
11
|
export * from "../adapters/file-modes.ts";
|
|
12
|
+
export * from "../adapters/hub-actions.ts";
|
|
11
13
|
export * from "../adapters/hub-credentials.ts";
|
|
12
14
|
export * from "../adapters/jsonl-work-log.ts";
|
|
13
15
|
export * from "../adapters/kankaku-dir.ts";
|
|
@@ -15,5 +17,9 @@ export * from "../adapters/lazy-jsonl-work-log.ts";
|
|
|
15
17
|
export * from "../adapters/pocketbase-catalog.ts";
|
|
16
18
|
export * from "../adapters/pocketbase-client.ts";
|
|
17
19
|
export * from "../adapters/pocketbase-sink.ts";
|
|
20
|
+
export * from "../adapters/project-config.ts";
|
|
21
|
+
export * from "../adapters/report-data.ts";
|
|
22
|
+
export * from "../adapters/report.ts";
|
|
23
|
+
export * from "../adapters/report-views.ts";
|
|
18
24
|
export * from "../adapters/sync-runner.ts";
|
|
19
25
|
export * from "../adapters/sync-state-store.ts";
|