@wirecat/cli-core 0.515.0 → 0.517.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/CHANGELOG.md +41 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/messenger/projects-command.js +4 -3
- package/dist/cli/messenger/search-answers.d.ts +5 -0
- package/dist/cli/messenger/search-answers.js +14 -1
- package/dist/cli/messenger/tasks-command.js +3 -3
- package/dist/cli/project-evidence.d.ts +96 -0
- package/dist/cli/project-evidence.js +14 -5
- package/dist/errors/reasons.js +1 -0
- package/dist/mail/quick.d.ts +2 -0
- package/dist/mail/quick.js +3 -1
- package/dist/mail/sync.d.ts +1 -1
- package/dist/mail/sync.js +10 -3
- package/dist/services/archive.js +1 -1
- package/dist/store/index.d.ts +1 -1
- package/dist/store/index.js +1 -1
- package/dist/store/sqlite/manifest.js +1 -0
- package/dist/store/sqlite/migrations.generated.js +11 -0
- package/dist/store/sqlite/work-tasks.js +13 -6
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/tasks/priority.d.ts +2 -0
- package/dist/tasks/priority.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,47 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.517.0 — 01.11.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`withSyncCommands(found, sync)`** gives each linked chat of a stored project gathering its `syncCommand`: the
|
|
15
|
+
line the text names to sync a chat held in part, or null. tg's and max's `projects evidence --json` carry it as
|
|
16
|
+
`linked_chats[].sync_command`, so an agent reading JSON gets the command the text shows.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **A chat that reaches its first message is no longer "held from <date> only"** in `missing`, `next` and the
|
|
21
|
+
`projects evidence` text: it is "held from its first message, behind since <date>", or "with gaps".
|
|
22
|
+
- **`messages sync` progress says how far back it got as a date** ("back to 2026-09-27"), not as a message key, which
|
|
23
|
+
for MAX was a number of milliseconds.
|
|
24
|
+
|
|
25
|
+
## 0.516.0 — 01.11.2026
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **`syncMail` counts what `max` left unread in `remaining`**, not 0, so a CLI can say how much is left after a run
|
|
30
|
+
cut short by `--limit`.
|
|
31
|
+
- **`tasks update --priority none` clears the priority** instead of failing with "a priority score is 0 to 1":
|
|
32
|
+
Commander stores an option parser's `null` as an empty string. `priorityOption` reads `none` as the word `"none"`
|
|
33
|
+
for a command's own option; a consumer that maps it to `null` (as `--due none`) clears the priority.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- **Store version 42: a task's revision goes up only for a change a person could overwrite** (`minCompatible` stays
|
|
38
|
+
34): its title, description, status, priority, agent, due date, project or parent, an assignee added or removed, or
|
|
39
|
+
a tag added or removed. A claim taken, renewed or released, a follow-up time, a close suggestion or certainty no
|
|
40
|
+
longer bump it, so a board no longer reports a conflict while an agent simply works. Triggers do it, so writes by
|
|
41
|
+
older builds follow the same rule.
|
|
42
|
+
|
|
43
|
+
### Changed — may break callers
|
|
44
|
+
|
|
45
|
+
- **`tasks.move` beside a task in another column moves the task into that column**: without `status` it takes the
|
|
46
|
+
neighbour's status. A `status` that differs from the neighbour's is refused with `validation_error`, reason
|
|
47
|
+
`task.column` (`HRB-2 is in in_progress, not todo: pick a task in the todo column`), with `anchor`, `anchorStatus`
|
|
48
|
+
and `requestedStatus` in the details. A neighbour off every project board, or the task itself, is refused with a
|
|
49
|
+
message that says so.
|
|
50
|
+
|
|
10
51
|
## 0.515.0 — 01.11.2026
|
|
11
52
|
|
|
12
53
|
### Added
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -109,7 +109,7 @@ export { window, withPaging } from "./paging.js";
|
|
|
109
109
|
export { type CommandPermissions, commandPathKey, commandPermissions, knownBeside, knownPermissionKeys, type PermissionKeyOf, type PermissionKeySource, refuseUnknownPermissionKeys, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
|
|
110
110
|
export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
|
|
111
111
|
export { createProgram, type ProgramDefinition, type ProgramOptions, type RunOptions, run } from "./program.js";
|
|
112
|
-
export { type ProjectTextHints, projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf, } from "./project-evidence.js";
|
|
112
|
+
export { type ProjectTextHints, projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf, withSyncCommands, } from "./project-evidence.js";
|
|
113
113
|
export { fieldsOf, projectFields } from "./result-fields.js";
|
|
114
114
|
export { type Config, type Configuration, count, first, flag, fromFile, type GlobalFlags, NONE_NAMED, namedProfile, type ProfileKind, plain, type ResolveOptions, type Settings, type SettingsExtension, type SettingsExtensionContext, type Source, settingsFor, } from "./settings.js";
|
|
115
115
|
export { type PermissionAnswer, readSetupAnswers, type SetupAnswers, setupPermissionCeiling } from "./setup-answers.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -99,7 +99,7 @@ export { window, withPaging } from "./paging.js";
|
|
|
99
99
|
export { commandPathKey, commandPermissions, knownBeside, knownPermissionKeys, refuseUnknownPermissionKeys, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
|
|
100
100
|
export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
|
|
101
101
|
export { createProgram, run } from "./program.js";
|
|
102
|
-
export { projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf, } from "./project-evidence.js";
|
|
102
|
+
export { projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf, withSyncCommands, } from "./project-evidence.js";
|
|
103
103
|
export { fieldsOf, projectFields } from "./result-fields.js";
|
|
104
104
|
export { count, first, flag, fromFile, NONE_NAMED, namedProfile, plain, settingsFor, } from "./settings.js";
|
|
105
105
|
export { readSetupAnswers, setupPermissionCeiling } from "./setup-answers.js";
|
|
@@ -7,7 +7,7 @@ import { gatherStoredProject } from "../../services/stored-project-gathering.js"
|
|
|
7
7
|
import { FETCH } from "../effects.js";
|
|
8
8
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
9
9
|
import { positiveCount } from "../paging.js";
|
|
10
|
-
import { projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf } from "../project-evidence.js";
|
|
10
|
+
import { projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWindowOf, withSyncCommands, } from "../project-evidence.js";
|
|
11
11
|
import { lineOf } from "../steps.js";
|
|
12
12
|
import { messengerContext } from "./context.js";
|
|
13
13
|
const collect = (value, previous) => [...previous, value];
|
|
@@ -120,12 +120,13 @@ export const projectsCommand = (messenger) => {
|
|
|
120
120
|
: undefined;
|
|
121
121
|
const found = await context.withStore((store, account) => gather(store, account, fetcher));
|
|
122
122
|
const parts = projectAnswerParts(options.sinceTime, found);
|
|
123
|
-
|
|
123
|
+
const hints = {
|
|
124
124
|
sync: (one, since) => one.provider === messenger.provider
|
|
125
125
|
? lineOf(this, `projects evidence ${quote(project)} --since-time ${since} --sync-first`)
|
|
126
126
|
: null,
|
|
127
127
|
showDocument: "the notes app that imported it",
|
|
128
|
-
}
|
|
128
|
+
};
|
|
129
|
+
renderAnswer(this, context, itemOf(snakeKeys(withSyncCommands(found, hints.sync)), parts), () => projectEvidenceText(found, hints));
|
|
129
130
|
}));
|
|
130
131
|
return projects;
|
|
131
132
|
};
|
|
@@ -18,6 +18,11 @@ export type HitItem = InferOutput<(typeof ENTITIES)["hit"]>;
|
|
|
18
18
|
/** The page a `--page` asks for, as the offset the search services take. */
|
|
19
19
|
export declare const offsetOf: (page: number | undefined, limit: number) => number | undefined;
|
|
20
20
|
export declare const messageHitItem: (hit: FoundMessage, query?: string) => MessageHitItem;
|
|
21
|
+
/**
|
|
22
|
+
* What the store holds of a chat that is not complete, in words. A chat that reaches its first message is only
|
|
23
|
+
* behind or has gaps: saying "held from <date> only" would claim older history is missing.
|
|
24
|
+
*/
|
|
25
|
+
export declare const heldPhrase: ({ completeness, held }: Pick<ChatCoverage, "completeness" | "held">) => string;
|
|
21
26
|
/**
|
|
22
27
|
* What the archive lacked for this answer, and the step that would fill it. From a search's coverage, or from the
|
|
23
28
|
* chats an answer is about (`since` is the start of the window it was asked for).
|
|
@@ -96,6 +96,19 @@ const metaOf = (accounts, skipped = [], heldInPart = false) => ({
|
|
|
96
96
|
: {}),
|
|
97
97
|
});
|
|
98
98
|
const day = (at) => at?.slice(0, 10) ?? "no date";
|
|
99
|
+
/**
|
|
100
|
+
* What the store holds of a chat that is not complete, in words. A chat that reaches its first message is only
|
|
101
|
+
* behind or has gaps: saying "held from <date> only" would claim older history is missing.
|
|
102
|
+
*/
|
|
103
|
+
export const heldPhrase = ({ completeness, held }) => {
|
|
104
|
+
if (completeness.state === "unknown" || !held.oldestAt)
|
|
105
|
+
return "never fetched";
|
|
106
|
+
if (!completeness.reachesStart)
|
|
107
|
+
return `held from ${day(held.oldestAt)} only`;
|
|
108
|
+
if (completeness.gaps)
|
|
109
|
+
return "held from its first message, with gaps";
|
|
110
|
+
return `held from its first message, behind since ${day(held.newestAt)}`;
|
|
111
|
+
};
|
|
99
112
|
/**
|
|
100
113
|
* The steps for chats the store holds only in part. Bounded first, when the answer's window is known, then the whole
|
|
101
114
|
* chat; both name the first chat only, because a download is the owner's call. Counts and dates only, never text.
|
|
@@ -105,7 +118,7 @@ const chatGaps = (rows, since) => {
|
|
|
105
118
|
const [first] = open;
|
|
106
119
|
if (!first)
|
|
107
120
|
return { next: [], missing: [] };
|
|
108
|
-
const held =
|
|
121
|
+
const held = heldPhrase(first);
|
|
109
122
|
const steps = (args, why) => ({
|
|
110
123
|
...nextStep("messages.sync", { args: { chat: first.chatId, ...args } }),
|
|
111
124
|
why,
|
|
@@ -8,7 +8,7 @@ import { aheadOf, momentOf } from "../../services/moment.js";
|
|
|
8
8
|
import { unlistedNote } from "../../services/task-categories.js";
|
|
9
9
|
import { ARRIVAL_WINDOW, applyTaskRulesToChats } from "../../services/task-rules.js";
|
|
10
10
|
import { CLAIM_LEASE_MS, closedStateOf, TASK_PAIR_QUERY, TASK_QUERY, taskLinkKindOf, taskTypeOf, } from "../../services/tasks.js";
|
|
11
|
-
import { PRIORITY_LABELS,
|
|
11
|
+
import { PRIORITY_LABELS, priorityOption } from "../../tasks/index.js";
|
|
12
12
|
import { actorOf, holderOf as holderIn } from "../author.js";
|
|
13
13
|
import { environmentOf } from "../context.js";
|
|
14
14
|
import { destroysNothing, LOCAL_READ, LOCAL_WRITE, ownerOnlyOption } from "../effects.js";
|
|
@@ -119,7 +119,7 @@ const HOLDER_HELP = "who you are for claims: a session id or a chat id; default
|
|
|
119
119
|
const planFrom = (options) => {
|
|
120
120
|
const plan = {};
|
|
121
121
|
if (options.priority !== undefined)
|
|
122
|
-
plan.priority = options.priority;
|
|
122
|
+
plan.priority = options.priority === "none" ? null : options.priority;
|
|
123
123
|
if (options.parent !== undefined)
|
|
124
124
|
plan.parent = options.parent === "none" ? null : options.parent;
|
|
125
125
|
if (options.category !== undefined)
|
|
@@ -208,7 +208,7 @@ export const tasksCommand = (messenger) => {
|
|
|
208
208
|
.description("update a task's text, schedule, category, assignees, priority or parent; nothing reaches the messenger")
|
|
209
209
|
.argument("<task>", "the task's id or key, as tasks list shows it")
|
|
210
210
|
.option("--category <name>", `${categoryHelp}; a change is kept in the task's history`)
|
|
211
|
-
.option("--priority <n>", `${PRIORITY_LABELS.join(", ")} or none; 0 (urgent) … 4 (low) still read as before`,
|
|
211
|
+
.option("--priority <n>", `${PRIORITY_LABELS.join(", ")} or none; 0 (urgent) … 4 (low) still read as before`, priorityOption)
|
|
212
212
|
.option("--parent <id>", "parent task in the same project; none clears it"), "send Alex the API token").action(async function (id) {
|
|
213
213
|
const context = messengerContext(this, messenger);
|
|
214
214
|
refuseLocalWrite(context, messenger.app.command, "tasks.update");
|
|
@@ -41,6 +41,102 @@ export declare const projectAnswerParts: (sinceTime: string, found: StoredProjec
|
|
|
41
41
|
complete: boolean;
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* The gathering with each linked chat's `syncCommand`: the line that syncs a chat held in part back to the window
|
|
46
|
+
* start, or null when it needs none or no command here can. JSON readers get what the text says per chat.
|
|
47
|
+
*/
|
|
48
|
+
export declare const withSyncCommands: (found: StoredProjectGathering, sync: ProjectTextHints["sync"]) => {
|
|
49
|
+
schemaVersion: 1;
|
|
50
|
+
runKey: string;
|
|
51
|
+
fingerprint: string;
|
|
52
|
+
resumed: boolean;
|
|
53
|
+
project: {
|
|
54
|
+
name: string;
|
|
55
|
+
key: string | null;
|
|
56
|
+
background: string | null;
|
|
57
|
+
aliases: string[];
|
|
58
|
+
};
|
|
59
|
+
window: {
|
|
60
|
+
after: number;
|
|
61
|
+
before: number;
|
|
62
|
+
asOf: number;
|
|
63
|
+
lookbackDays: number | null;
|
|
64
|
+
basis: "explicit" | "lookback";
|
|
65
|
+
timeBasis: "per-item-time-kind";
|
|
66
|
+
};
|
|
67
|
+
scope: {
|
|
68
|
+
origin: "all-connected" | "explicit";
|
|
69
|
+
sources: import("../context/evidence.js").ContextSource[];
|
|
70
|
+
};
|
|
71
|
+
lanes: import("../services/project-gathering-request.js").ProjectLane[];
|
|
72
|
+
fetch: import("../services/project-gathering-request.js").ProjectChatFetched[];
|
|
73
|
+
index: import("../services/project-gathering.js").ProjectIndexCatchUp;
|
|
74
|
+
units: {
|
|
75
|
+
lane: string;
|
|
76
|
+
source: number;
|
|
77
|
+
after: number;
|
|
78
|
+
before: number;
|
|
79
|
+
state: import("../services/project-gathering-request.js").ProjectUnitState;
|
|
80
|
+
pages: number;
|
|
81
|
+
issue: string | null;
|
|
82
|
+
}[];
|
|
83
|
+
items: import("../services/project-gathering.js").ProjectEvidenceItem[];
|
|
84
|
+
people: import("../services/project-gathering.js").ProjectEvidencePerson[];
|
|
85
|
+
peopleOmitted: number;
|
|
86
|
+
oversized: {
|
|
87
|
+
ref: string;
|
|
88
|
+
lanes: string[];
|
|
89
|
+
source: number;
|
|
90
|
+
bytes: number;
|
|
91
|
+
}[];
|
|
92
|
+
matchedAgain: {
|
|
93
|
+
ref: string;
|
|
94
|
+
lanes: string[];
|
|
95
|
+
}[];
|
|
96
|
+
usage: {
|
|
97
|
+
reads: number;
|
|
98
|
+
readBytes: number;
|
|
99
|
+
aroundBytes: number;
|
|
100
|
+
items: number;
|
|
101
|
+
elapsedMs: number;
|
|
102
|
+
};
|
|
103
|
+
totals: {
|
|
104
|
+
reads: number;
|
|
105
|
+
readBytes: number;
|
|
106
|
+
items: number;
|
|
107
|
+
invocations: number;
|
|
108
|
+
};
|
|
109
|
+
stopped: import("../services/project-gathering.js").ProjectGatheringStop;
|
|
110
|
+
complete: boolean;
|
|
111
|
+
completeness: "complete" | "partial" | "unknown";
|
|
112
|
+
checkpoint: string | null;
|
|
113
|
+
projectRef: string | null;
|
|
114
|
+
unreadSources: {
|
|
115
|
+
source: string;
|
|
116
|
+
kind: string;
|
|
117
|
+
reason?: "out-of-scope" | "linked-chat-limit" | "member-limit" | "no-identity" | "unknown-provider";
|
|
118
|
+
}[];
|
|
119
|
+
mentions: import("../services/project-gathering-mentions.js").EvidenceMentions;
|
|
120
|
+
linkedChats: {
|
|
121
|
+
provider: string;
|
|
122
|
+
account: string;
|
|
123
|
+
chat: string;
|
|
124
|
+
ref: string;
|
|
125
|
+
source: number | null;
|
|
126
|
+
lane: string | null;
|
|
127
|
+
state: "in-scope" | "out-of-scope" | "pending" | "unavailable";
|
|
128
|
+
title: string | null;
|
|
129
|
+
completeness: import("../store/exports.js").ChatCompleteness | null;
|
|
130
|
+
held: {
|
|
131
|
+
messages: number;
|
|
132
|
+
oldestAt: string | null;
|
|
133
|
+
newestAt: string | null;
|
|
134
|
+
} | null;
|
|
135
|
+
items?: number | undefined;
|
|
136
|
+
capped?: boolean | undefined;
|
|
137
|
+
syncCommand: string | null;
|
|
138
|
+
}[];
|
|
139
|
+
};
|
|
44
140
|
export interface ProjectTextHints {
|
|
45
141
|
/** The command that syncs one linked chat back to `since` (`YYYY-MM-DD`), or null when no command here can. */
|
|
46
142
|
sync: (chat: StoredProjectGathering["linkedChats"][number], since: string) => string | null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import * as v from "valibot";
|
|
3
3
|
import { dateWindow, momentOf } from "../services/moment.js";
|
|
4
|
-
import { coverageGaps } from "./messenger/search-answers.js";
|
|
4
|
+
import { coverageGaps, heldPhrase } from "./messenger/search-answers.js";
|
|
5
5
|
/** Days back (`90d`) or a start date (`2026-09-01`); either way the run freezes its end, so a resume keeps it. */
|
|
6
6
|
export const projectWindowOf = (value) => {
|
|
7
7
|
const days = /^([1-9]\d{0,2})d$/.exec(value)?.[1];
|
|
@@ -47,6 +47,17 @@ export const projectAnswerParts = (sinceTime, found) => {
|
|
|
47
47
|
},
|
|
48
48
|
};
|
|
49
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* The gathering with each linked chat's `syncCommand`: the line that syncs a chat held in part back to the window
|
|
52
|
+
* start, or null when it needs none or no command here can. JSON readers get what the text says per chat.
|
|
53
|
+
*/
|
|
54
|
+
export const withSyncCommands = (found, sync) => ({
|
|
55
|
+
...found,
|
|
56
|
+
linkedChats: found.linkedChats.map((one) => ({
|
|
57
|
+
...one,
|
|
58
|
+
syncCommand: one.completeness === null || one.completeness.state === "complete" ? null : sync(one, day(found.window.after)),
|
|
59
|
+
})),
|
|
60
|
+
});
|
|
50
61
|
const LABELS = {
|
|
51
62
|
"person-only": " [person only]",
|
|
52
63
|
sender: " [sent by contact]",
|
|
@@ -120,13 +131,11 @@ export const projectEvidenceText = (found, hints) => {
|
|
|
120
131
|
for (const one of found.fetch)
|
|
121
132
|
lines.push(`${one.chat}: ${FETCH_STATES[one.state] ?? one.state} before reading` +
|
|
122
133
|
`${one.reason && one.state !== "skipped" ? ` (${one.reason})` : ""}`);
|
|
123
|
-
const held = (one) => one.completeness?.state === "unknown" || !one.held?.oldestAt
|
|
124
|
-
? "never fetched"
|
|
125
|
-
: `held from ${one.held.oldestAt.slice(0, 10)} only`;
|
|
126
134
|
for (const one of found.linkedChats)
|
|
127
135
|
if (one.completeness !== null && one.completeness.state !== "complete") {
|
|
128
136
|
const sync = hints.sync(one, day(found.window.after));
|
|
129
|
-
|
|
137
|
+
const held = one.held === null ? "never fetched" : heldPhrase({ completeness: one.completeness, held: one.held });
|
|
138
|
+
lines.push(`${one.title ?? one.ref}: ${held}${one.state === "pending" ? ", not read this run" : ""}` +
|
|
130
139
|
(sync ? ` — sync it: ${sync}` : ""));
|
|
131
140
|
}
|
|
132
141
|
const capped = found.linkedChats.filter((one) => one.capped);
|
package/dist/errors/reasons.js
CHANGED
|
@@ -121,6 +121,7 @@ const OTHERS = [
|
|
|
121
121
|
validation("task.claimed", "someone else holds the task"),
|
|
122
122
|
validation("task.closed", "the task is already closed"),
|
|
123
123
|
["task.stale", "configuration_error", "the task changed since it was read; read it again"],
|
|
124
|
+
validation("task.column", "the task beside which it moves is in another column than the status asked for"),
|
|
124
125
|
validation("task.dependency-cycle", "the task would depend on itself"),
|
|
125
126
|
validation("task.duplicate-candidates", "open tasks may be the same job: attach to one, or add it as new"),
|
|
126
127
|
validation("task.certainty-required", "an agent closes a task with its certainty, 0 to 1"),
|
package/dist/mail/quick.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface QuickRead {
|
|
|
33
33
|
}[];
|
|
34
34
|
/** Mail of the folder is left for a later round or run. */
|
|
35
35
|
capped: boolean;
|
|
36
|
+
/** How many of the open mail `max` left for the next run; an unbudgeted run only. */
|
|
37
|
+
left: number;
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
40
|
* One round of a folder whose server gives no cheap message id: UID, flags, date and size of what is not done, then
|
package/dist/mail/quick.js
CHANGED
|
@@ -7,7 +7,7 @@ export const keyOf = (folder, uid) => `${folder}\0${uid}`;
|
|
|
7
7
|
* the raw bytes of the newest of it, whose Message-ID gives the id the full listing would have given.
|
|
8
8
|
*/
|
|
9
9
|
export const readQuick = async (source, since, folder, searched, done, max, plan, located) => {
|
|
10
|
-
const answer = { listing: [], read: [], unreadable: [], capped: false };
|
|
10
|
+
const answer = { listing: [], read: [], unreadable: [], capped: false, left: 0 };
|
|
11
11
|
let items = plan?.quick.get(folder);
|
|
12
12
|
if (items === undefined) {
|
|
13
13
|
// Mail whose location is known is listed for its flags only; it is never read again.
|
|
@@ -40,6 +40,8 @@ export const readQuick = async (source, since, folder, searched, done, max, plan
|
|
|
40
40
|
if (plan !== undefined)
|
|
41
41
|
plan.remaining += open.length - slice.length;
|
|
42
42
|
answer.capped = open.length > slice.length;
|
|
43
|
+
if (plan === undefined)
|
|
44
|
+
answer.left = open.length - slice.length;
|
|
43
45
|
if (slice.length === 0)
|
|
44
46
|
return answer;
|
|
45
47
|
if (plan !== undefined) {
|
package/dist/mail/sync.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface MailSyncResult {
|
|
|
36
36
|
state?: "partial" | "complete";
|
|
37
37
|
/** Why a budgeted run stopped early: its request budget, its deadline, or a cancel. */
|
|
38
38
|
stopped?: BudgetStop;
|
|
39
|
-
/** Messages of the window this run did not get to read
|
|
39
|
+
/** Messages of the window this run did not get to read: left by a budget, or by `max`. */
|
|
40
40
|
remaining?: number;
|
|
41
41
|
/** Requests a budgeted run spent. */
|
|
42
42
|
requests?: number;
|
package/dist/mail/sync.js
CHANGED
|
@@ -121,7 +121,7 @@ export const syncMail = async (source, store, options) => {
|
|
|
121
121
|
deleted: sum((round) => round.deleted),
|
|
122
122
|
complete,
|
|
123
123
|
state: complete ? "complete" : "partial",
|
|
124
|
-
remaining,
|
|
124
|
+
remaining: remaining + (last.remaining ?? 0),
|
|
125
125
|
requests: budget.used,
|
|
126
126
|
...(plan.stopped === null ? {} : { stopped: plan.stopped }),
|
|
127
127
|
...(indexed === undefined ? {} : { indexed }),
|
|
@@ -183,6 +183,7 @@ const syncRound = async (source, store, { account, since, max = 200, maxBodyChar
|
|
|
183
183
|
mboxPoint = point;
|
|
184
184
|
}
|
|
185
185
|
let capped = false;
|
|
186
|
+
let overMax = 0;
|
|
186
187
|
const unreadable = [];
|
|
187
188
|
/** Raw mail read to learn its id, kept for the save so it is not read twice. */
|
|
188
189
|
const rawOf = new Map();
|
|
@@ -258,6 +259,7 @@ const syncRound = async (source, store, { account, since, max = 200, maxBodyChar
|
|
|
258
259
|
refreshed.set(id, { ...refreshed.get(id), ...flags });
|
|
259
260
|
if (round.capped)
|
|
260
261
|
capped = true;
|
|
262
|
+
overMax += round.left;
|
|
261
263
|
for (const { item, raw } of round.read) {
|
|
262
264
|
collected.push(item);
|
|
263
265
|
rawOf.set(item, raw);
|
|
@@ -465,8 +467,10 @@ const syncRound = async (source, store, { account, since, max = 200, maxBodyChar
|
|
|
465
467
|
upgraded.set(item.messageId, boxes);
|
|
466
468
|
pending.push(item);
|
|
467
469
|
}
|
|
468
|
-
else
|
|
470
|
+
else {
|
|
469
471
|
full = true;
|
|
472
|
+
overMax++;
|
|
473
|
+
}
|
|
470
474
|
continue;
|
|
471
475
|
}
|
|
472
476
|
if (known.has(item.messageId)) {
|
|
@@ -491,8 +495,10 @@ const syncRound = async (source, store, { account, since, max = 200, maxBodyChar
|
|
|
491
495
|
}
|
|
492
496
|
if (pending.length < max)
|
|
493
497
|
pending.push(item);
|
|
494
|
-
else
|
|
498
|
+
else {
|
|
495
499
|
full = true;
|
|
500
|
+
overMax++;
|
|
501
|
+
}
|
|
496
502
|
}
|
|
497
503
|
const inMailboxes = (item) => [
|
|
498
504
|
...[...(foundFolders.get(item.messageId) ?? [])].map(mailbox),
|
|
@@ -794,6 +800,7 @@ const syncRound = async (source, store, { account, since, max = 200, maxBodyChar
|
|
|
794
800
|
envelopes,
|
|
795
801
|
deleted,
|
|
796
802
|
complete,
|
|
803
|
+
remaining: overMax,
|
|
797
804
|
...(indexed === undefined ? {} : { indexed }),
|
|
798
805
|
coverage,
|
|
799
806
|
...(attachments.length ? { attachments } : {}),
|
package/dist/services/archive.js
CHANGED
|
@@ -308,7 +308,7 @@ export const fetchInto = async ({ history, store, account, fetching, limit, page
|
|
|
308
308
|
before = byTime
|
|
309
309
|
? new Date(boundary + (fresh > 0 && !(window && fetching.beforeInclusive) ? 1 : 0)).toISOString()
|
|
310
310
|
: String(boundary);
|
|
311
|
-
note(`${fetched} messages so far, back to ${
|
|
311
|
+
note(`${fetched} messages so far, back to ${fromAt.slice(0, 10)}`);
|
|
312
312
|
const wait = fetching.jitter ? pauseMs * (1 + Math.random()) : pauseMs;
|
|
313
313
|
await sleep(wait, undefined, { signal: stop }).catch(() => { });
|
|
314
314
|
}
|
package/dist/store/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type { TagRedirectIndex, TagRedirectResolution, TagRedirectSnapshot } fro
|
|
|
5
5
|
export { createTagRedirectIndex } from "../domain/tag-redirects.js";
|
|
6
6
|
export { note } from "../entities/note.js";
|
|
7
7
|
export type { FindingEvidence, FindingOutcome, MeetingFactFinding, MeetingFinding, MeetingFindingsInput, MeetingFindingsReceipt, MeetingTaskFinding, } from "../meetings/index.js";
|
|
8
|
-
export { CLOSED_STATES, isClosedState, PRIORITY_LABELS, type PriorityLabel, priorityLabel, priorityOf, TASK_STATES, type TaskState, } from "../tasks/index.js";
|
|
8
|
+
export { CLOSED_STATES, isClosedState, PRIORITY_LABELS, type PriorityLabel, priorityLabel, priorityOf, priorityOption, TASK_STATES, type TaskState, } from "../tasks/index.js";
|
|
9
9
|
export type { AccountMail, AccountMeetings, AccountStore } from "./account-store.js";
|
|
10
10
|
export type { DocumentTransferOptions, DocumentTransferPreview, DocumentTransferReceipt, } from "./document-transfer/index.js";
|
|
11
11
|
export { applyDocumentTransfer, previewDocumentTransfer } from "./document-transfer/index.js";
|
package/dist/store/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { FACT_STATUSES, FACT_TYPES, MEMORY_KINDS, MEMORY_SCOPES, MEMORY_STATUSES, TASK_TYPES, WORK_TASK_TYPES, } from "../domain/knowledge.js";
|
|
2
2
|
export { createTagRedirectIndex } from "../domain/tag-redirects.js";
|
|
3
3
|
export { note } from "../entities/note.js";
|
|
4
|
-
export { CLOSED_STATES, isClosedState, PRIORITY_LABELS, priorityLabel, priorityOf, TASK_STATES, } from "../tasks/index.js";
|
|
4
|
+
export { CLOSED_STATES, isClosedState, PRIORITY_LABELS, priorityLabel, priorityOf, priorityOption, TASK_STATES, } from "../tasks/index.js";
|
|
5
5
|
export { applyDocumentTransfer, previewDocumentTransfer } from "./document-transfer/index.js";
|
|
6
6
|
export { PRAGMAS } from "./driver.js";
|
|
7
7
|
export { EVIDENCE_FINDINGS_KIND, validateEvidenceFindings, } from "./evidence-findings.js";
|
|
@@ -55,6 +55,7 @@ export const MANIFEST = [
|
|
|
55
55
|
{ name: "20261030114008_version-39-email-locations", version: 39, minCompatible: 34 },
|
|
56
56
|
{ name: "20261030225520_version-40-note-trigrams", version: 40, minCompatible: 34 },
|
|
57
57
|
{ name: "20261031230945_version-41-task-fields", version: 41, minCompatible: 34 },
|
|
58
|
+
{ name: "20261101002523_version-42-task-revision", version: 42, minCompatible: 34 },
|
|
58
59
|
];
|
|
59
60
|
export const generatedMigrations = (manifest = MANIFEST, generated = GENERATED) => {
|
|
60
61
|
const migrations = [];
|
|
@@ -859,5 +859,16 @@ export const GENERATED = [
|
|
|
859
859
|
"CREATE TRIGGER tasks_priority_score_au AFTER UPDATE OF priority ON tasks WHEN new.priority IS NOT old.priority AND new.priority_score IS old.priority_score BEGIN\n UPDATE tasks SET priority_score = CASE new.priority WHEN 0 THEN 0.875 WHEN 1 THEN 0.625 WHEN 2 THEN 0.375 WHEN 3 THEN 0.1875 WHEN 4 THEN 0.0625 END WHERE id = new.id;\nEND;",
|
|
860
860
|
"-- Every build's write bumps it, older ones too, so a stale copy is caught whoever wrote last.\nCREATE TRIGGER tasks_revision_au AFTER UPDATE ON tasks WHEN new.revision IS old.revision BEGIN\n UPDATE tasks SET revision = old.revision + 1 WHERE id = new.id;\nEND;"
|
|
861
861
|
]
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
"name": "20261101002523_version-42-task-revision",
|
|
865
|
+
"statements": [
|
|
866
|
+
"-- A claim renewed, a follow-up time or a close suggestion is an agent at work, not a change a person could overwrite;\n-- bumping on those made the board report a conflict while an agent simply worked.\nDROP TRIGGER tasks_revision_au;",
|
|
867
|
+
"CREATE TRIGGER tasks_revision_au AFTER UPDATE OF title, description, status, priority, priority_score, agent, due_at, project_id, parent_id ON tasks\n WHEN new.revision IS old.revision AND (new.title IS NOT old.title OR new.description IS NOT old.description\n OR new.status IS NOT old.status OR new.priority IS NOT old.priority OR new.priority_score IS NOT old.priority_score\n OR new.agent IS NOT old.agent OR new.due_at IS NOT old.due_at OR new.project_id IS NOT old.project_id\n OR new.parent_id IS NOT old.parent_id) BEGIN\n UPDATE tasks SET revision = old.revision + 1 WHERE id = new.id;\nEND;",
|
|
868
|
+
"CREATE TRIGGER tasks_revision_assignment_ai AFTER INSERT ON task_assignments BEGIN\n UPDATE tasks SET revision = revision + 1 WHERE id = new.task_id;\nEND;",
|
|
869
|
+
"CREATE TRIGGER tasks_revision_assignment_ad AFTER DELETE ON task_assignments BEGIN\n UPDATE tasks SET revision = revision + 1 WHERE id = old.task_id;\nEND;",
|
|
870
|
+
"CREATE TRIGGER tasks_revision_tagging_ai AFTER INSERT ON taggings WHEN new.taggable_type = 'task' BEGIN\n UPDATE tasks SET revision = revision + 1 WHERE id = new.taggable_id;\nEND;",
|
|
871
|
+
"CREATE TRIGGER tasks_revision_tagging_ad AFTER DELETE ON taggings WHEN old.taggable_type = 'task' BEGIN\n UPDATE tasks SET revision = revision + 1 WHERE id = old.taggable_id;\nEND;"
|
|
872
|
+
]
|
|
862
873
|
}
|
|
863
874
|
];
|
|
@@ -239,20 +239,27 @@ export const workTaskOperationsOver = (context) => {
|
|
|
239
239
|
throw new CliError("validation_error", "give --before or --after, not both");
|
|
240
240
|
if (!database.prepare(`SELECT 1 FROM tasks t WHERE t.id = ? AND ${BOARD_SCOPE}`).get(task))
|
|
241
241
|
throw new CliError("validation_error", "only a project task moves on the board");
|
|
242
|
-
const status = move.status ?? row.status;
|
|
243
|
-
const column = columnOf(task, status);
|
|
244
242
|
const neighbour = move.before ?? move.after;
|
|
245
243
|
if (neighbour === undefined) {
|
|
246
|
-
|
|
244
|
+
const status = move.status ?? row.status;
|
|
245
|
+
if (columnOf(task, status).length > 0 || move.status === undefined)
|
|
247
246
|
throw new CliError("validation_error", "say where it goes: --before <key> or --after <key>");
|
|
248
247
|
const from = priorityLabel(row.priority_score == null ? null : Number(row.priority_score));
|
|
249
248
|
edit(task, { status: move.status }, actor);
|
|
250
249
|
return { label: { from, to: from, changed: false }, renumbered: [] };
|
|
251
250
|
}
|
|
252
251
|
const target = required(neighbour);
|
|
252
|
+
const anchor = database.prepare(`SELECT t.key, t.status FROM tasks t WHERE t.id = ? AND ${BOARD_SCOPE}`).get(target);
|
|
253
|
+
if (target === task)
|
|
254
|
+
throw new CliError("validation_error", "a task moves beside another task, not itself");
|
|
255
|
+
if (!anchor)
|
|
256
|
+
throw new CliError("validation_error", `${neighbour} is not on a project's board`);
|
|
257
|
+
// A card dropped beside one in another column joins that column; a status that names a different one is a mistake.
|
|
258
|
+
const status = anchor.status;
|
|
259
|
+
if (move.status !== undefined && move.status !== status)
|
|
260
|
+
throw new CliError("validation_error", `${String(anchor.key)} is in ${status}, not ${move.status}: pick a task in the ${move.status} column`, { reason: "task.column", anchor: String(anchor.key), anchorStatus: status, requestedStatus: move.status });
|
|
261
|
+
const column = columnOf(task, status);
|
|
253
262
|
const at = column.findIndex(({ id }) => id === target);
|
|
254
|
-
if (at === -1)
|
|
255
|
-
throw new CliError("validation_error", `${move.before ?? move.after} is not a ${status} task of a project; a task moves beside one in its column`);
|
|
256
263
|
const index = move.before === undefined ? at + 1 : at;
|
|
257
264
|
const above = column[index - 1];
|
|
258
265
|
const below = column[index];
|
|
@@ -285,7 +292,7 @@ export const workTaskOperationsOver = (context) => {
|
|
|
285
292
|
renumbered.push(one.key);
|
|
286
293
|
});
|
|
287
294
|
}
|
|
288
|
-
edit(task, { priority: given,
|
|
295
|
+
edit(task, { priority: given, status }, actor);
|
|
289
296
|
const to = priorityLabel(given);
|
|
290
297
|
return { label: { from, to, changed: from !== to }, renumbered };
|
|
291
298
|
};
|
package/dist/tasks/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { CLOSED_STATES, type ClosedState, closeTask, isClosedState, TASK_KINDS, TASK_ORIGINS, TASK_STATES, TASK_VERDICTS, type Task, type TaskKind, type TaskOrigin, type TaskState, type TaskVerdict, } from "./model.js";
|
|
2
|
-
export { checkedScore, LEGACY_PRIORITY_SCORES, legacyPriority, PRIORITY_LABELS, type PriorityLabel, priorityBand, priorityLabel, priorityOf, } from "./priority.js";
|
|
2
|
+
export { checkedScore, LEGACY_PRIORITY_SCORES, legacyPriority, PRIORITY_LABELS, type PriorityLabel, priorityBand, priorityLabel, priorityOf, priorityOption, } from "./priority.js";
|
|
3
3
|
export { type CloseOptions, createTaskService, type GroupStats, type NewTask, type TaskService, type TaskServiceOptions, } from "./service.js";
|
|
4
4
|
export { matches, type TaskFilter, type TaskStore } from "./store.js";
|
package/dist/tasks/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { CLOSED_STATES, closeTask, isClosedState, TASK_KINDS, TASK_ORIGINS, TASK_STATES, TASK_VERDICTS, } from "./model.js";
|
|
2
|
-
export { checkedScore, LEGACY_PRIORITY_SCORES, legacyPriority, PRIORITY_LABELS, priorityBand, priorityLabel, priorityOf, } from "./priority.js";
|
|
2
|
+
export { checkedScore, LEGACY_PRIORITY_SCORES, legacyPriority, PRIORITY_LABELS, priorityBand, priorityLabel, priorityOf, priorityOption, } from "./priority.js";
|
|
3
3
|
export { createTaskService, } from "./service.js";
|
|
4
4
|
export { matches } from "./store.js";
|
package/dist/tasks/priority.d.ts
CHANGED
|
@@ -16,3 +16,5 @@ export declare const checkedScore: (score: number) => number;
|
|
|
16
16
|
* `none` clears it.
|
|
17
17
|
*/
|
|
18
18
|
export declare const priorityOf: (value: string) => number | null;
|
|
19
|
+
/** `priorityOf` for a command-line option: Commander stores a parser's `null` as an empty string, so `none` stays a word. */
|
|
20
|
+
export declare const priorityOption: (value: string) => number | "none";
|
package/dist/tasks/priority.js
CHANGED
|
@@ -35,3 +35,5 @@ export const priorityOf = (value) => {
|
|
|
35
35
|
return LEGACY_PRIORITY_SCORES[Number(word)];
|
|
36
36
|
throw new CliError("validation_error", `a priority is ${PRIORITY_LABELS.join(", ")} or none; 0 (urgent) … 4 (low) still work as critical, high, medium, low, low`);
|
|
37
37
|
};
|
|
38
|
+
/** `priorityOf` for a command-line option: Commander stores a parser's `null` as an empty string, so `none` stays a word. */
|
|
39
|
+
export const priorityOption = (value) => priorityOf(value) ?? "none";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.517.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|