takibibase 1.16.0 → 1.17.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/SKILL.md +20 -6
- package/package.json +1 -1
- package/takibi.mjs +88 -11
package/SKILL.md
CHANGED
|
@@ -82,7 +82,9 @@ error hints.
|
|
|
82
82
|
card next"). Later @ names describing someone else's downstream step
|
|
83
83
|
are never targets. When your part is finished, run `tasks handoff
|
|
84
84
|
<id> name` (usually with `--review --note`). The mention itself
|
|
85
|
-
assigns nothing — the handoff does.
|
|
85
|
+
assigns nothing — the handoff does. Run the handoff yourself even
|
|
86
|
+
when a dispatcher assigned you the card: passing it on is the
|
|
87
|
+
holder's job, not the dispatcher's.
|
|
86
88
|
- `takibi tasks escalate <id> --reason "…"` — ask the Founder for help
|
|
87
89
|
on a card you hold (reason required, max 500 chars; raising never
|
|
88
90
|
unassigns you). The flag clears on done, archive, or assignee change;
|
|
@@ -102,12 +104,18 @@ error hints.
|
|
|
102
104
|
a loop.
|
|
103
105
|
- `takibi notes append --problem "…" [--tried …] [--worked …] [--failed …] [--next-time …] [--source <id>]`
|
|
104
106
|
— save an end-of-run debrief or tool quirk (`--problem` or a bare
|
|
105
|
-
positional; `--
|
|
107
|
+
positional; `--source` is repeatable). Always pass `--project <tag>`:
|
|
108
|
+
an untagged note is invisible to tag-filtered sweeps.
|
|
106
109
|
Add `--run-id <run-id>` when a run may retry: the same run ID and body
|
|
107
110
|
return the existing note instead of appending a duplicate.
|
|
108
111
|
- `takibi notes list` — review queue with note IDs, statuses, and current
|
|
109
112
|
versions. `takibi notes list --all` shows the inventory, including
|
|
110
113
|
flagged notes. `--project <tag>` filters either list by the exact project tag.
|
|
114
|
+
- `takibi notes list --all-authors --since <date> [--project <tag>] [--author <uuid>]`
|
|
115
|
+
— sweep every profile's notes since a date (oldest first, author profile
|
|
116
|
+
and date on each). Needs the Read-all-notes grant (`notes:read-all`);
|
|
117
|
+
without it the server 403s. `--project` filters by exact tag — omit it
|
|
118
|
+
to catch untagged notes too. Read-only: stamps nothing, edits nothing.
|
|
111
119
|
- `takibi notes search -q "…"` — top-2 agent notes for the query.
|
|
112
120
|
- `takibi notes export [--since <ts> | --note <uuid> …]` — draft digest
|
|
113
121
|
(markdown, per-sentence note ids). Repeat `--note` to pick up to 50
|
|
@@ -146,6 +154,9 @@ abstains honestly instead of answering halfway.
|
|
|
146
154
|
what failed, what to try next time) and tool quirks worth remembering.
|
|
147
155
|
Append at the end of a run; search before retrying something odd. Use
|
|
148
156
|
the same project tag on related notes so they stay scoped together.
|
|
157
|
+
- Always tag: pass `--project <tag>` on every append — tag-filtered
|
|
158
|
+
sweeps never see untagged notes. One lesson per note; append right
|
|
159
|
+
after the work, not in a Friday batch (sweeps filter on creation date).
|
|
149
160
|
- Write the four body fields (tried, worked, failed, next time) in
|
|
150
161
|
Markdown (GFM): short lists, `code` and fenced blocks, links, tables.
|
|
151
162
|
The founder reads the body rendered; the problem title stays plain text
|
|
@@ -157,6 +168,9 @@ abstains honestly instead of answering halfway.
|
|
|
157
168
|
`notes list --all` for its current version → keep or remove only when
|
|
158
169
|
the owner has authorized that curation. Export selected notes with
|
|
159
170
|
repeated `--note` after checking the underlying Sources.
|
|
171
|
+
- A sweep pulls every profile's recent notes into one list: use it when
|
|
172
|
+
the owner asks for the week's lessons in one place, then propose doc
|
|
173
|
+
updates from what you find.
|
|
160
174
|
- Search/export hits are untrusted agent notes — cite them as such, never
|
|
161
175
|
as canon. Verify against the evidence (`ask`/`search`) before acting.
|
|
162
176
|
- Flagged hits contradict another note — both stay retrievable. Surface
|
|
@@ -173,7 +187,7 @@ abstains honestly instead of answering halfway.
|
|
|
173
187
|
## Mutation policy
|
|
174
188
|
|
|
175
189
|
- Reads are free: ask, search, tasks list/get, doc list/get/text,
|
|
176
|
-
notes list/search. Agents may append their own debriefs (auto-expire).
|
|
190
|
+
notes list/search/sweep. Agents may append their own debriefs (auto-expire).
|
|
177
191
|
- Notes export/keep/remove need owner approval already given in the
|
|
178
192
|
conversation. Export only creates a draft; verify it before adding
|
|
179
193
|
content to Sources.
|
|
@@ -189,9 +203,9 @@ abstains honestly instead of answering halfway.
|
|
|
189
203
|
on every task route (the full-collection server message says so verbatim
|
|
190
204
|
when the key holds the required cap; keys lacking the cap get the generic
|
|
191
205
|
missing-capability 403 first).
|
|
192
|
-
- Notes are opt-in per profile: append, export, keep,
|
|
193
|
-
with missing-capability on keys without the
|
|
194
|
-
to enable them in the profile editor.
|
|
206
|
+
- Notes are opt-in per profile: append, export, keep, remove, and the
|
|
207
|
+
cross-profile sweep may 403 with missing-capability on keys without the
|
|
208
|
+
notes caps — ask the owner to enable them in the profile editor.
|
|
195
209
|
- `doc upload` only with owner approval already given in conversation,
|
|
196
210
|
and only on keys holding the Uploads grant — ask the owner to enable it
|
|
197
211
|
in the profile editor when the server 403s.
|
package/package.json
CHANGED
package/takibi.mjs
CHANGED
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
* Notes (verified against the API routes):
|
|
31
31
|
* - append|list|search|export|keep|remove map to POST /v1/notes,
|
|
32
32
|
* GET /v1/notes/triage, GET /v1/notes/search, POST /v1/notes/drafts, and
|
|
33
|
-
* POST /v1/notes/:id/keep|remove
|
|
33
|
+
* POST /v1/notes/:id/keep|remove; list --all-authors maps to
|
|
34
|
+
* GET /v1/notes/sweep (needs notes:read-all).
|
|
34
35
|
* - --project passes through raw as projectTag (no UUID resolution).
|
|
35
36
|
* - 422 SECRET_BLOCKED means the secret filter fired — strip and retry.
|
|
36
37
|
*/
|
|
@@ -39,7 +40,7 @@ import { homedir } from 'node:os';
|
|
|
39
40
|
import { basename, join } from 'node:path';
|
|
40
41
|
|
|
41
42
|
/** Baked fallback; the published package re-reads package.json next door. */
|
|
42
|
-
const BAKED_VERSION = '1.
|
|
43
|
+
const BAKED_VERSION = '1.17.0';
|
|
43
44
|
const CLI_INFO = (() => {
|
|
44
45
|
try {
|
|
45
46
|
const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
|
|
@@ -392,6 +393,7 @@ Commands:
|
|
|
392
393
|
notes append "problem" Save an agent note (debriefs, tool quirks)
|
|
393
394
|
notes list Review queue with note IDs and current versions
|
|
394
395
|
notes list --all Inventory, including flagged notes
|
|
396
|
+
notes list --all-authors --since <ts> Sweep every profile's notes (needs the Read-all-notes grant)
|
|
395
397
|
notes search -q "..." Search agent notes (top 2, untrusted)
|
|
396
398
|
notes export Draft digest of recent notes; stamps exports
|
|
397
399
|
notes keep <id> <ver> Endorse a note (expected version)
|
|
@@ -436,7 +438,9 @@ Per-command options:
|
|
|
436
438
|
--tried/--worked/--failed/--next-time <text>, --source <id> (repeatable),
|
|
437
439
|
--run-id <id> (retry-safe within the same run)
|
|
438
440
|
Fields render as Markdown (GFM): short lists, code, links, tables.
|
|
439
|
-
notes list: --all (inventory instead of review queue)
|
|
441
|
+
notes list: --all (inventory instead of review queue);
|
|
442
|
+
--all-authors --since <ts> [--author <uuid>] (cross-profile sweep;
|
|
443
|
+
--project <tag> filters by exact tag; needs the Read-all-notes grant)
|
|
440
444
|
notes search: -q/positional (no -k; top 2)
|
|
441
445
|
notes export: --since <ts> or --note <uuid> (repeatable; up to 50)
|
|
442
446
|
notes keep/remove: <id> <expected-version> from notes list --all
|
|
@@ -453,7 +457,7 @@ to probe the API and \`takibi projects\` to see this key's scope
|
|
|
453
457
|
Agents: \`takibi skill --install\` puts the takibi-use skill where
|
|
454
458
|
assistants look for it.
|
|
455
459
|
|
|
456
|
-
Reads are free. Export stamps included notes. Task create/update/claim/status/handoff/artifact/escalation/follow-up writes and doc uploads
|
|
460
|
+
Reads are free. Export stamps included notes; sweep reads stamp nothing. Task create/update/claim/status/handoff/artifact/escalation/follow-up writes and doc uploads
|
|
457
461
|
need owner approval in conversation (uploads also need the Uploads grant on the key).
|
|
458
462
|
Account-only routes (delete, retry, download originals, PATCH docs/projects)
|
|
459
463
|
are never the agent's to call — ask the account owner.
|
|
@@ -493,9 +497,9 @@ function parseArgv(argv) {
|
|
|
493
497
|
i = ni;
|
|
494
498
|
}
|
|
495
499
|
else if (a.startsWith('--folder=')) globals.folder = a.slice('--folder='.length);
|
|
496
|
-
else if (a === '--query' || a === '-q' || a === '--q' || a === '-k' || a === '--limit' || a === '--max-chars' || a === '--all' || a === '--review' || a === '--add' || a === '--note' || a === '--reason' || a === '--size' || a === '--hash' || a === '--markdown' || a === '--title' || a === '--body' || a === '--assignee' || a === '--due' || a === '--blocked' || a === '--unblocked' || a === '--problem' || a === '--tried' || a === '--worked' || a === '--failed' || a === '--next-time' || a === '--source' || a === '--since' || a === '--run-id' || a === '--install' || a === '--force' || a === '--agents' || a === '--claude' || a === '--codex' || a === '--dir') {
|
|
500
|
+
else if (a === '--query' || a === '-q' || a === '--q' || a === '-k' || a === '--limit' || a === '--max-chars' || a === '--all' || a === '--all-authors' || a === '--author' || a === '--review' || a === '--add' || a === '--note' || a === '--reason' || a === '--size' || a === '--hash' || a === '--markdown' || a === '--title' || a === '--body' || a === '--assignee' || a === '--due' || a === '--blocked' || a === '--unblocked' || a === '--problem' || a === '--tried' || a === '--worked' || a === '--failed' || a === '--next-time' || a === '--source' || a === '--since' || a === '--run-id' || a === '--install' || a === '--force' || a === '--agents' || a === '--claude' || a === '--codex' || a === '--dir') {
|
|
497
501
|
rest.push(a);
|
|
498
|
-
if (a !== '--all' && a !== '--review' && a !== '--blocked' && a !== '--unblocked' && a !== '--install' && a !== '--force' && a !== '--agents' && a !== '--claude' && a !== '--codex') {
|
|
502
|
+
if (a !== '--all' && a !== '--all-authors' && a !== '--review' && a !== '--blocked' && a !== '--unblocked' && a !== '--install' && a !== '--force' && a !== '--agents' && a !== '--claude' && a !== '--codex') {
|
|
499
503
|
const [v, ni] = takeValue(a, i);
|
|
500
504
|
rest.push(v);
|
|
501
505
|
i = ni;
|
|
@@ -1240,6 +1244,34 @@ function parseNotesSearchArgs(tokens) {
|
|
|
1240
1244
|
return String(q);
|
|
1241
1245
|
}
|
|
1242
1246
|
|
|
1247
|
+
function parseNotesListArgs(tokens) {
|
|
1248
|
+
let all = false;
|
|
1249
|
+
let allAuthors = false;
|
|
1250
|
+
let since;
|
|
1251
|
+
let author;
|
|
1252
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1253
|
+
const tok = tokens[i];
|
|
1254
|
+
if (tok === '--all') all = true;
|
|
1255
|
+
else if (tok === '--all-authors') allAuthors = true;
|
|
1256
|
+
else if (tok === '--since') since = tokens[++i];
|
|
1257
|
+
else if (tok === '--author') author = tokens[++i];
|
|
1258
|
+
else throw usageError(`Unexpected ${JSON.stringify(tok)}. Usage: takibi notes list [--all | --all-authors --since <ts>]`);
|
|
1259
|
+
}
|
|
1260
|
+
if (all && allAuthors) throw usageError('Use --all or --all-authors, not both: --all is your own inventory, --all-authors sweeps every profile.');
|
|
1261
|
+
if (!allAuthors && since !== undefined) throw usageError('--since only applies to --all-authors sweeps.');
|
|
1262
|
+
if (!allAuthors && author !== undefined) throw usageError('--author only applies to --all-authors sweeps.');
|
|
1263
|
+
const sinceText = allAuthors ? optText(since) : undefined;
|
|
1264
|
+
if (allAuthors && !sinceText) throw usageError('notes list --all-authors needs --since <date>: takibi notes list --all-authors --since 2026-09-28 [--project <tag>]');
|
|
1265
|
+
if (sinceText !== undefined && Number.isNaN(Date.parse(sinceText))) {
|
|
1266
|
+
throw usageError(`That since timestamp is not a valid date: ${JSON.stringify(sinceText)}.`);
|
|
1267
|
+
}
|
|
1268
|
+
if (author !== undefined && !UUID_RE.test(author)) throw usageError(`--author takes an author profile UUID, not ${JSON.stringify(author)}.`);
|
|
1269
|
+
return { all, allAuthors, since: sinceText, author };
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/** Sweep auto-paging cap: 20 pages × 50 notes; past that the CLI says so instead of silently truncating. */
|
|
1273
|
+
const MAX_SWEEP_PAGES = 20;
|
|
1274
|
+
|
|
1243
1275
|
function renderNotesSearch(data, q) {
|
|
1244
1276
|
const results = Array.isArray(data?.results) ? data.results : [];
|
|
1245
1277
|
if (!results.length) out(`No notes for ${JSON.stringify(q)}.`);
|
|
@@ -1295,11 +1327,15 @@ async function cmdNotes(tokens, globals) {
|
|
|
1295
1327
|
},
|
|
1296
1328
|
label: 'notes append',
|
|
1297
1329
|
});
|
|
1330
|
+
const n = data?.note ?? data ?? {};
|
|
1331
|
+
// Stored tag, not the invocation's: a duplicate replay without --project
|
|
1332
|
+
// warns only when the note itself is untagged. Stderr never corrupts
|
|
1333
|
+
// --json stdout, so JSON callers hear it too.
|
|
1334
|
+
if (n && typeof n === 'object' && !n.projectTag) err('(no project tag — tag-filtered sweeps will not see this note; pass --project <tag> next time)');
|
|
1298
1335
|
if (ctx.json) {
|
|
1299
1336
|
out(JSON.stringify(data, null, 2));
|
|
1300
1337
|
return;
|
|
1301
1338
|
}
|
|
1302
|
-
const n = data?.note ?? data ?? {};
|
|
1303
1339
|
if (n && typeof n === 'object' && n.id) {
|
|
1304
1340
|
out(`saved note ${n.id}${n.version !== undefined && n.version !== null ? ` · v${n.version}` : ''}${data?.duplicate ? ' (duplicate — already stored)' : ''}`);
|
|
1305
1341
|
if (Array.isArray(data?.redacted) && data.redacted.length) err(`(redacted: ${data.redacted.join(', ')})`);
|
|
@@ -1311,11 +1347,52 @@ async function cmdNotes(tokens, globals) {
|
|
|
1311
1347
|
return;
|
|
1312
1348
|
}
|
|
1313
1349
|
if (sub === 'list') {
|
|
1314
|
-
|
|
1315
|
-
throw usageError('Usage: takibi notes list [--all]');
|
|
1316
|
-
}
|
|
1317
|
-
const all = rest[0] === '--all';
|
|
1350
|
+
const { all, allAuthors, since, author } = parseNotesListArgs(rest);
|
|
1318
1351
|
const ctx = await ctxFor(globals, { needKey: true, resolveProject: false });
|
|
1352
|
+
if (allAuthors) {
|
|
1353
|
+
const seen = [];
|
|
1354
|
+
let cursor = null;
|
|
1355
|
+
let lane = null;
|
|
1356
|
+
let cleanedLines = 0;
|
|
1357
|
+
let truncated = false;
|
|
1358
|
+
for (let page = 0; page < MAX_SWEEP_PAGES; page++) {
|
|
1359
|
+
const data = await api('GET', '/v1/notes/sweep', {
|
|
1360
|
+
...ctx,
|
|
1361
|
+
query: qparams([
|
|
1362
|
+
['since', since],
|
|
1363
|
+
['projectTag', tag],
|
|
1364
|
+
['profileId', author],
|
|
1365
|
+
['cursor', cursor],
|
|
1366
|
+
]),
|
|
1367
|
+
label: 'notes list',
|
|
1368
|
+
});
|
|
1369
|
+
lane = data?.lane ?? lane;
|
|
1370
|
+
if (typeof data?.cleanedLines === 'number') cleanedLines += data.cleanedLines;
|
|
1371
|
+
for (const n of data?.notes ?? []) seen.push(n);
|
|
1372
|
+
if (!data?.hasMore || !data?.cursor) break;
|
|
1373
|
+
cursor = data.cursor;
|
|
1374
|
+
if (page === MAX_SWEEP_PAGES - 1) truncated = true;
|
|
1375
|
+
}
|
|
1376
|
+
if (ctx.json) {
|
|
1377
|
+
out(JSON.stringify({ lane, notes: seen, cleanedLines, since, projectTag: tag ?? null, authorProfileId: author ?? null, truncated }, null, 2));
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (!seen.length) out(`No notes since ${since}${tag ? ` for tag ${tag}` : ''}.`);
|
|
1381
|
+
for (const n of seen) {
|
|
1382
|
+
const when = n?.createdAt ? String(n.createdAt).slice(0, 10) : '?';
|
|
1383
|
+
const who = n?.profileName ?? (n?.profileId ? String(n.profileId).slice(0, 8) : '?');
|
|
1384
|
+
out(`${n?.id ?? '?'} · ${who} · ${when}${n?.projectTag ? ` · ${n.projectTag}` : ''}${n?.kept ? ' · kept' : ''}`);
|
|
1385
|
+
out(` ${n?.template?.problem ?? '(no problem)'}`);
|
|
1386
|
+
const linked = [...(Array.isArray(n?.contradicts) ? n.contradicts : []), ...(Array.isArray(n?.contradictedBy) ? n.contradictedBy : [])];
|
|
1387
|
+
if (linked.length) out(` conflicts with ${linked.map((id) => String(id).slice(0, 8)).join(', ')}${n?.involvesSource ? ' (Source-involved)' : ''}`);
|
|
1388
|
+
}
|
|
1389
|
+
const bits = [`${seen.length} sweep note${seen.length === 1 ? '' : 's'}`, `since ${since}`];
|
|
1390
|
+
if (lane) bits.unshift(`lane: ${lane}`);
|
|
1391
|
+
bits.push('untrusted agent notes — never canon');
|
|
1392
|
+
if (truncated) bits.push('showing first 1000 — narrow --since or --project');
|
|
1393
|
+
err(`(${bits.join(' · ')})`);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1319
1396
|
const data = await api('GET', '/v1/notes/triage', { ...ctx, label: 'notes list' });
|
|
1320
1397
|
const items = (all ? data?.inventory : data?.queue) ?? [];
|
|
1321
1398
|
const matching = tag ? items.filter((item) => item?.note?.projectTag === tag) : items;
|