@wirecat/cli-core 0.511.0 → 0.513.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 +70 -0
- package/dist/calendar/mcp/index.d.ts +46 -10
- package/dist/calendar/mcp/index.js +60 -25
- package/dist/cli/list-query.d.ts +4 -2
- package/dist/cli/list-query.js +3 -3
- package/dist/cli/messenger/tasks-command.js +4 -5
- package/dist/entities/index.d.ts +35 -8
- package/dist/entities/task-results.d.ts +22 -7
- package/dist/entities/task-results.js +4 -1
- package/dist/entities/task.d.ts +18 -1
- package/dist/entities/task.js +12 -1
- package/dist/errors/reasons.js +1 -0
- package/dist/services/task-rules.js +2 -2
- package/dist/services/tasks.d.ts +1 -1
- package/dist/services/tasks.js +7 -5
- package/dist/store/index.d.ts +4 -1
- package/dist/store/index.js +2 -0
- package/dist/store/sqlite/knowledge.js +3 -2
- package/dist/store/sqlite/manifest.js +1 -0
- package/dist/store/sqlite/migrations.generated.js +12 -0
- package/dist/store/sqlite/person-preparation.js +1 -1
- package/dist/store/sqlite/schema.d.ts +48 -0
- package/dist/store/sqlite/schema.js +3 -0
- package/dist/store/sqlite/tags.d.ts +1 -2
- package/dist/store/sqlite/tags.js +18 -3
- package/dist/store/sqlite/task-board.d.ts +16 -0
- package/dist/store/sqlite/task-board.js +192 -0
- package/dist/store/sqlite/task-candidates.js +1 -1
- package/dist/store/sqlite/task-category-settings.js +1 -1
- package/dist/store/sqlite/task-close-store.js +6 -4
- package/dist/store/sqlite/task-pairs.js +4 -3
- package/dist/store/sqlite/task-relations.js +3 -2
- package/dist/store/sqlite/tasks.js +16 -5
- package/dist/store/sqlite/work-tasks.d.ts +96 -9
- package/dist/store/sqlite/work-tasks.js +236 -32
- package/dist/tasks/index.d.ts +2 -1
- package/dist/tasks/index.js +2 -1
- package/dist/tasks/model.d.ts +5 -2
- package/dist/tasks/model.js +5 -2
- package/dist/tasks/priority.d.ts +18 -0
- package/dist/tasks/priority.js +37 -0
- package/dist/tasks/service.js +2 -2
- package/package.json +1 -1
|
@@ -7,8 +7,11 @@ export declare const taskCard: v.StrictObjectSchema<{
|
|
|
7
7
|
readonly title: v.StringSchema<undefined>;
|
|
8
8
|
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
9
9
|
readonly type: v.PicklistSchema<readonly ["bug", "feature", "chore", "question", "request", "mention", "promise"], undefined>;
|
|
10
|
-
readonly status: v.PicklistSchema<readonly ["open", "done", "dismissed"], undefined>;
|
|
10
|
+
readonly status: v.PicklistSchema<readonly ["open", "todo", "in_progress", "done", "dismissed"], undefined>;
|
|
11
11
|
readonly priority: v.NullableSchema<v.NumberSchema<undefined>, undefined>;
|
|
12
|
+
readonly priority_label: v.NullableSchema<v.PicklistSchema<readonly ["low", "medium", "high", "critical"], undefined>, undefined>;
|
|
13
|
+
readonly agent: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
14
|
+
readonly revision: v.NumberSchema<undefined>;
|
|
12
15
|
readonly parent: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
13
16
|
readonly source: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
14
17
|
readonly due_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
@@ -69,7 +72,7 @@ export declare const taskCreated: v.StrictObjectSchema<{
|
|
|
69
72
|
readonly account: v.StringSchema<undefined>;
|
|
70
73
|
readonly group: v.StringSchema<undefined>;
|
|
71
74
|
readonly kind: v.PicklistSchema<readonly ["question", "request", "mention", "promise"], undefined>;
|
|
72
|
-
readonly state: v.PicklistSchema<readonly ["open", "done", "dismissed"], undefined>;
|
|
75
|
+
readonly state: v.PicklistSchema<readonly ["open", "todo", "in_progress", "done", "dismissed"], undefined>;
|
|
73
76
|
readonly reason: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
74
77
|
readonly origin: v.PicklistSchema<readonly ["rule", "agent", "owner"], undefined>;
|
|
75
78
|
readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
@@ -108,8 +111,11 @@ export declare const taskCreated: v.StrictObjectSchema<{
|
|
|
108
111
|
readonly title: v.StringSchema<undefined>;
|
|
109
112
|
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
110
113
|
readonly type: v.PicklistSchema<readonly ["bug", "feature", "chore", "question", "request", "mention", "promise"], undefined>;
|
|
111
|
-
readonly status: v.PicklistSchema<readonly ["open", "done", "dismissed"], undefined>;
|
|
114
|
+
readonly status: v.PicklistSchema<readonly ["open", "todo", "in_progress", "done", "dismissed"], undefined>;
|
|
112
115
|
readonly priority: v.NullableSchema<v.NumberSchema<undefined>, undefined>;
|
|
116
|
+
readonly priority_label: v.NullableSchema<v.PicklistSchema<readonly ["low", "medium", "high", "critical"], undefined>, undefined>;
|
|
117
|
+
readonly agent: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
118
|
+
readonly revision: v.NumberSchema<undefined>;
|
|
113
119
|
readonly parent: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
114
120
|
readonly source: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
115
121
|
readonly due_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
@@ -171,7 +177,7 @@ export declare const taskClosed: v.StrictObjectSchema<{
|
|
|
171
177
|
readonly account: v.StringSchema<undefined>;
|
|
172
178
|
readonly group: v.StringSchema<undefined>;
|
|
173
179
|
readonly kind: v.PicklistSchema<readonly ["question", "request", "mention", "promise"], undefined>;
|
|
174
|
-
readonly state: v.PicklistSchema<readonly ["open", "done", "dismissed"], undefined>;
|
|
180
|
+
readonly state: v.PicklistSchema<readonly ["open", "todo", "in_progress", "done", "dismissed"], undefined>;
|
|
175
181
|
readonly reason: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
176
182
|
readonly origin: v.PicklistSchema<readonly ["rule", "agent", "owner"], undefined>;
|
|
177
183
|
readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
@@ -210,8 +216,11 @@ export declare const taskLinks: v.UnionSchema<[v.StrictObjectSchema<{
|
|
|
210
216
|
readonly title: v.StringSchema<undefined>;
|
|
211
217
|
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
212
218
|
readonly type: v.PicklistSchema<readonly ["bug", "feature", "chore", "question", "request", "mention", "promise"], undefined>;
|
|
213
|
-
readonly status: v.PicklistSchema<readonly ["open", "done", "dismissed"], undefined>;
|
|
219
|
+
readonly status: v.PicklistSchema<readonly ["open", "todo", "in_progress", "done", "dismissed"], undefined>;
|
|
214
220
|
readonly priority: v.NullableSchema<v.NumberSchema<undefined>, undefined>;
|
|
221
|
+
readonly priority_label: v.NullableSchema<v.PicklistSchema<readonly ["low", "medium", "high", "critical"], undefined>, undefined>;
|
|
222
|
+
readonly agent: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
223
|
+
readonly revision: v.NumberSchema<undefined>;
|
|
215
224
|
readonly parent: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
216
225
|
readonly source: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
217
226
|
readonly due_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
@@ -340,8 +349,11 @@ export declare const taskMerge: v.StrictObjectSchema<{
|
|
|
340
349
|
readonly title: v.StringSchema<undefined>;
|
|
341
350
|
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
342
351
|
readonly type: v.PicklistSchema<readonly ["bug", "feature", "chore", "question", "request", "mention", "promise"], undefined>;
|
|
343
|
-
readonly status: v.PicklistSchema<readonly ["open", "done", "dismissed"], undefined>;
|
|
352
|
+
readonly status: v.PicklistSchema<readonly ["open", "todo", "in_progress", "done", "dismissed"], undefined>;
|
|
344
353
|
readonly priority: v.NullableSchema<v.NumberSchema<undefined>, undefined>;
|
|
354
|
+
readonly priority_label: v.NullableSchema<v.PicklistSchema<readonly ["low", "medium", "high", "critical"], undefined>, undefined>;
|
|
355
|
+
readonly agent: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
356
|
+
readonly revision: v.NumberSchema<undefined>;
|
|
345
357
|
readonly parent: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
346
358
|
readonly source: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
347
359
|
readonly due_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
@@ -435,8 +447,11 @@ export declare const taskUnmerge: v.StrictObjectSchema<{
|
|
|
435
447
|
readonly title: v.StringSchema<undefined>;
|
|
436
448
|
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
437
449
|
readonly type: v.PicklistSchema<readonly ["bug", "feature", "chore", "question", "request", "mention", "promise"], undefined>;
|
|
438
|
-
readonly status: v.PicklistSchema<readonly ["open", "done", "dismissed"], undefined>;
|
|
450
|
+
readonly status: v.PicklistSchema<readonly ["open", "todo", "in_progress", "done", "dismissed"], undefined>;
|
|
439
451
|
readonly priority: v.NullableSchema<v.NumberSchema<undefined>, undefined>;
|
|
452
|
+
readonly priority_label: v.NullableSchema<v.PicklistSchema<readonly ["low", "medium", "high", "critical"], undefined>, undefined>;
|
|
453
|
+
readonly agent: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
454
|
+
readonly revision: v.NumberSchema<undefined>;
|
|
440
455
|
readonly parent: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
441
456
|
readonly source: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
442
457
|
readonly due_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
2
|
import { TASK_TYPES } from "../domain/knowledge.js";
|
|
3
3
|
import { LINK_KINDS, LINK_SOURCES } from "../store/sqlite/link-kinds.js";
|
|
4
|
-
import { TASK_KINDS, TASK_ORIGINS, TASK_STATES, TASK_VERDICTS } from "../tasks/index.js";
|
|
4
|
+
import { PRIORITY_LABELS, TASK_KINDS, TASK_ORIGINS, TASK_STATES, TASK_VERDICTS } from "../tasks/index.js";
|
|
5
5
|
import { count, id, reference, text, time } from "./fields.js";
|
|
6
6
|
import { closeSuggestion } from "./task.js";
|
|
7
7
|
import { taskPair } from "./task-pair.js";
|
|
@@ -30,6 +30,9 @@ export const taskCard = v.strictObject({
|
|
|
30
30
|
type: v.picklist(TASK_TYPES),
|
|
31
31
|
status: v.picklist(TASK_STATES),
|
|
32
32
|
priority: v.nullable(v.number()),
|
|
33
|
+
priority_label: v.nullable(v.picklist(PRIORITY_LABELS)),
|
|
34
|
+
agent: v.nullable(text),
|
|
35
|
+
revision: v.number(),
|
|
33
36
|
parent: v.nullable(reference),
|
|
34
37
|
source: v.nullable(text),
|
|
35
38
|
due_at: nullableTime,
|
package/dist/entities/task.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare const task: v.StrictObjectSchema<{
|
|
|
16
16
|
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
17
17
|
readonly description: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
18
18
|
readonly kind: v.PicklistSchema<readonly ["bug", "feature", "chore", "question", "request", "mention", "promise"], undefined>;
|
|
19
|
-
readonly state: v.PicklistSchema<readonly ["open", "done", "dismissed"], undefined>;
|
|
19
|
+
readonly state: v.PicklistSchema<readonly ["open", "todo", "in_progress", "done", "dismissed"], undefined>;
|
|
20
20
|
readonly origin: v.OptionalSchema<v.PicklistSchema<readonly ["rule", "agent", "owner"], undefined>, undefined>;
|
|
21
21
|
readonly project: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
22
22
|
readonly account: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -24,7 +24,24 @@ export declare const task: v.StrictObjectSchema<{
|
|
|
24
24
|
readonly chat: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>, undefined>;
|
|
25
25
|
/** The message it came from. */
|
|
26
26
|
readonly source_message: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>, undefined>;
|
|
27
|
+
/** 0 to 1, higher first; `priority_label` reads it as low, medium, high or critical. */
|
|
27
28
|
readonly priority: v.OptionalSchema<v.NullableSchema<v.NumberSchema<undefined>, undefined>, undefined>;
|
|
29
|
+
readonly priority_label: v.OptionalSchema<v.NullableSchema<v.PicklistSchema<readonly ["low", "medium", "high", "critical"], undefined>, undefined>, undefined>;
|
|
30
|
+
/** The agent working on it, apart from its assignees and its claim. */
|
|
31
|
+
readonly agent: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
32
|
+
/** Give it back with `--revision` to refuse a write made on a stale copy. */
|
|
33
|
+
readonly revision: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
34
|
+
readonly assignments: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
|
|
35
|
+
readonly to: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
36
|
+
readonly role: v.PicklistSchema<["assignee", "reviewer", "watcher"], undefined>;
|
|
37
|
+
}, undefined>, undefined>, undefined>;
|
|
38
|
+
/** Open work whose blocked-by tasks are all done. */
|
|
39
|
+
readonly ready: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
40
|
+
/** The blocked-by tasks not done yet: open (any open state), dismissed, or unavailable. */
|
|
41
|
+
readonly blocked_by: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
|
|
42
|
+
readonly task: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
43
|
+
readonly state: v.PicklistSchema<["open", "dismissed", "unavailable"], undefined>;
|
|
44
|
+
}, undefined>, undefined>, undefined>;
|
|
28
45
|
readonly parent: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>, undefined>;
|
|
29
46
|
readonly due_at: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>, undefined>;
|
|
30
47
|
readonly closed_at: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>, undefined>;
|
package/dist/entities/task.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
2
|
import { TASK_TYPES } from "../domain/knowledge.js";
|
|
3
|
-
import { TASK_ORIGINS, TASK_STATES } from "../tasks/index.js";
|
|
3
|
+
import { PRIORITY_LABELS, TASK_ORIGINS, TASK_STATES } from "../tasks/index.js";
|
|
4
4
|
import { id, reference, text, time } from "./fields.js";
|
|
5
5
|
export const closeSuggestion = v.strictObject({
|
|
6
6
|
as: v.picklist(["done", "dismissed"]),
|
|
@@ -27,7 +27,18 @@ export const task = v.strictObject({
|
|
|
27
27
|
chat: v.optional(v.nullable(reference)),
|
|
28
28
|
/** The message it came from. */
|
|
29
29
|
source_message: v.optional(v.nullable(reference)),
|
|
30
|
+
/** 0 to 1, higher first; `priority_label` reads it as low, medium, high or critical. */
|
|
30
31
|
priority: v.optional(v.nullable(v.number())),
|
|
32
|
+
priority_label: v.optional(v.nullable(v.picklist(PRIORITY_LABELS))),
|
|
33
|
+
/** The agent working on it, apart from its assignees and its claim. */
|
|
34
|
+
agent: v.optional(v.nullable(text)),
|
|
35
|
+
/** Give it back with `--revision` to refuse a write made on a stale copy. */
|
|
36
|
+
revision: v.optional(v.number()),
|
|
37
|
+
assignments: v.optional(v.array(v.strictObject({ to: reference, role: v.picklist(["assignee", "reviewer", "watcher"]) }))),
|
|
38
|
+
/** Open work whose blocked-by tasks are all done. */
|
|
39
|
+
ready: v.optional(v.boolean()),
|
|
40
|
+
/** The blocked-by tasks not done yet: open (any open state), dismissed, or unavailable. */
|
|
41
|
+
blocked_by: v.optional(v.array(v.strictObject({ task: reference, state: v.picklist(["open", "dismissed", "unavailable"]) }))),
|
|
31
42
|
parent: v.optional(v.nullable(reference)),
|
|
32
43
|
due_at: v.optional(v.nullable(time)),
|
|
33
44
|
closed_at: v.optional(v.nullable(time)),
|
package/dist/errors/reasons.js
CHANGED
|
@@ -120,6 +120,7 @@ const OTHERS = [
|
|
|
120
120
|
validation("task.category-unknown", "the category is not in the task category list"),
|
|
121
121
|
validation("task.claimed", "someone else holds the task"),
|
|
122
122
|
validation("task.closed", "the task is already closed"),
|
|
123
|
+
["task.stale", "configuration_error", "the task changed since it was read; read it again"],
|
|
123
124
|
validation("task.dependency-cycle", "the task would depend on itself"),
|
|
124
125
|
validation("task.duplicate-candidates", "open tasks may be the same job: attach to one, or add it as new"),
|
|
125
126
|
validation("task.certainty-required", "an agent closes a task with its certainty, 0 to 1"),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formatLocator } from "../domain/locator.js";
|
|
2
|
-
import { createTaskService } from "../tasks/index.js";
|
|
2
|
+
import { createTaskService, isClosedState } from "../tasks/index.js";
|
|
3
3
|
import { questions } from "./questions.js";
|
|
4
4
|
import { categorize } from "./task-categories.js";
|
|
5
5
|
export const taskAccount = ({ provider, account }) => `${provider}:${account}`;
|
|
@@ -57,7 +57,7 @@ export const applyTaskRules = async (review, { store, account, now, categories,
|
|
|
57
57
|
};
|
|
58
58
|
const close = async (message, kind, answer) => {
|
|
59
59
|
for (const task of await store.findBySource(owner, sourceOf(message))) {
|
|
60
|
-
if (task.kind !== kind || task.state
|
|
60
|
+
if (task.kind !== kind || isClosedState(task.state))
|
|
61
61
|
continue;
|
|
62
62
|
result.closed += 1;
|
|
63
63
|
if (dryRun)
|
package/dist/services/tasks.d.ts
CHANGED
|
@@ -200,7 +200,7 @@ export interface TasksService {
|
|
|
200
200
|
export declare const CLAIM_LEASE_MS: number;
|
|
201
201
|
export declare const PREVIEW = 200;
|
|
202
202
|
export declare const taskTypeOf: (typed: string) => "mention" | "promise" | "question" | "request";
|
|
203
|
-
export declare const taskStateOf: (typed: string) => "dismissed" | "done" | "open";
|
|
203
|
+
export declare const taskStateOf: (typed: string) => "dismissed" | "done" | "in_progress" | "open" | "todo";
|
|
204
204
|
export declare const closedStateOf: (typed: string) => "dismissed" | "done";
|
|
205
205
|
export declare const taskLinkKindOf: (typed: string) => "blocked-by" | "created-from" | "discovered-from" | "duplicate-of" | "evidence" | "follow-up-of" | "related-to";
|
|
206
206
|
export declare const taskTypesOf: (typed: string) => TaskKind[];
|
package/dist/services/tasks.js
CHANGED
|
@@ -5,7 +5,7 @@ import { formatReference } from "../domain/references.js";
|
|
|
5
5
|
import { task as taskEntity } from "../entities/task.js";
|
|
6
6
|
import { certaintyOf, TASK_RELATION_KINDS, } from "../store/index.js";
|
|
7
7
|
import { queryFilter } from "../store/store.js";
|
|
8
|
-
import { createTaskService, TASK_KINDS, TASK_STATES, } from "../tasks/index.js";
|
|
8
|
+
import { createTaskService, isClosedState, TASK_KINDS, TASK_STATES, } from "../tasks/index.js";
|
|
9
9
|
import { storedChatId } from "./message-resolution.js";
|
|
10
10
|
import { categorize, taskCategoryOf } from "./task-categories.js";
|
|
11
11
|
import { taskAccount } from "./task-rules.js";
|
|
@@ -163,7 +163,7 @@ export const tasksService = (deps, categories) => {
|
|
|
163
163
|
const planOf = async (store, id, plan, by) => {
|
|
164
164
|
if (plan.category !== undefined)
|
|
165
165
|
plan = { ...plan, category: await categoryOf(store, plan.category) };
|
|
166
|
-
const open = (await store.tasks.details(id)).status
|
|
166
|
+
const open = !isClosedState((await store.tasks.details(id)).status);
|
|
167
167
|
if (!open && (plan.dueAt != null || plan.nextCheckAt != null))
|
|
168
168
|
throw new CliError("validation_error", "a closed task takes no due date or next check; it stays closed");
|
|
169
169
|
const { assignees, ...fields } = plan;
|
|
@@ -199,7 +199,9 @@ export const tasksService = (deps, categories) => {
|
|
|
199
199
|
* A message linked to a task is read by the comparison, so the task may now look like another one: the pairs go to
|
|
200
200
|
* the review list, never to a merge, and the link stands whatever they score. A closed task is not checked.
|
|
201
201
|
*/
|
|
202
|
-
const reviewAfterLink = async (store, id) => (await store.tasks.get(id))?.state
|
|
202
|
+
const reviewAfterLink = async (store, id) => !isClosedState((await store.tasks.get(id))?.state ?? "done")
|
|
203
|
+
? store.tasks.reviewDuplicates(id, { linked: true })
|
|
204
|
+
: 0;
|
|
203
205
|
return {
|
|
204
206
|
list: ({ query, limit, offset, previews }) => inStore(async (store, account) => {
|
|
205
207
|
const groups = new Map();
|
|
@@ -358,7 +360,7 @@ export const tasksService = (deps, categories) => {
|
|
|
358
360
|
},
|
|
359
361
|
close: (id, { as, reason, by, agent, certainty, suggestOnly }) => inStore(async (store, account) => {
|
|
360
362
|
const existing = await owned(store, account, id);
|
|
361
|
-
const suggested = existing.state
|
|
363
|
+
const suggested = !isClosedState(existing.state) ? await store.tasks.closeSuggestion(id) : null;
|
|
362
364
|
const state = as ?? (by === "owner" ? suggested?.as : undefined);
|
|
363
365
|
if (state === undefined)
|
|
364
366
|
throw new CliError("validation_error", "say how the task is closed: --as done, or --as dismissed");
|
|
@@ -368,7 +370,7 @@ export const tasksService = (deps, categories) => {
|
|
|
368
370
|
const { closeCertainty, closeSuggestion } = await store.tasks.details(task.id);
|
|
369
371
|
return { ...(await taskView(store, account, task)), closeCertainty, closeSuggestion };
|
|
370
372
|
};
|
|
371
|
-
if (by === "agent" && existing.state
|
|
373
|
+
if (by === "agent" && !isClosedState(existing.state)) {
|
|
372
374
|
if (certainty === undefined)
|
|
373
375
|
throw new CliError("validation_error", "an agent closes a task with its certainty, 0 to 1: how sure it is the task needs nothing more", { reason: "task.certainty-required" });
|
|
374
376
|
const { allowed, min } = await store.tasks.agentClose();
|
package/dist/store/index.d.ts
CHANGED
|
@@ -5,6 +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
9
|
export type { AccountMail, AccountMeetings, AccountStore } from "./account-store.js";
|
|
9
10
|
export type { DocumentTransferOptions, DocumentTransferPreview, DocumentTransferReceipt, } from "./document-transfer/index.js";
|
|
10
11
|
export { applyDocumentTransfer, previewDocumentTransfer } from "./document-transfer/index.js";
|
|
@@ -54,6 +55,8 @@ export { fillSearchIndex, resetSearchIndex, type SearchIndexFill, type SearchInd
|
|
|
54
55
|
export type { SyncStateSave } from "./sqlite/sync.js";
|
|
55
56
|
export type { TagCreationOptions, TagCreationReview } from "./sqlite/tag-creation.js";
|
|
56
57
|
export type { TagKind, TagRow } from "./sqlite/tags.js";
|
|
58
|
+
export type { TaskSort } from "./sqlite/task-board.js";
|
|
59
|
+
export { PROJECT_TASK_FIELDS, TASK_SORTS } from "./sqlite/task-board.js";
|
|
57
60
|
export type { SimilarSubject, SimilarTask, TaskDraft } from "./sqlite/task-candidates.js";
|
|
58
61
|
export { type AgentCloseSettings, type CloseSuggestion, certaintyOf } from "./sqlite/task-close.js";
|
|
59
62
|
export type { TaskPair, TaskPairDecision, TaskPairSide } from "./sqlite/task-pairs.js";
|
|
@@ -62,6 +65,6 @@ export type { TaskRelation, TaskRelationKind, TaskRelationsStore } from "./sqlit
|
|
|
62
65
|
export { TASK_RELATION_KINDS } from "./sqlite/task-relations.js";
|
|
63
66
|
export type { StoreTaskStore } from "./sqlite/tasks.js";
|
|
64
67
|
export type { TagPreflight, VocabularyAlias, VocabularyAliasReceipt, VocabularyAudit, VocabularyAuditOptions, VocabularyCatalog, VocabularyEntry, VocabularyKind, VocabularyOptions, VocabularyResolution, VocabularyStore, VocabularyTarget, } from "./sqlite/vocabulary.js";
|
|
65
|
-
export type { NewWorkTask, TaskAssignment, TaskDetails, TaskEvent, TaskFields, TaskRole, TaskType, WorkTaskType, } from "./sqlite/work-tasks.js";
|
|
68
|
+
export type { BoardCounts, BoardOptions, NewWorkTask, RevisionCheck, TaskAssignment, TaskDetails, TaskEvent, TaskFields, TaskMove, TaskMoved, TaskRole, TaskType, WorkTaskType, } from "./sqlite/work-tasks.js";
|
|
66
69
|
export { TASK_EVENT_KINDS, TASK_ROLES } from "./sqlite/work-tasks.js";
|
|
67
70
|
export { type AccountAccounts, type AccountAttachments, type AccountCounters, type AccountKey, type AccountMentions, type AccountMessageFilter, type AccountMessages, type AccountPeople, type AccountsStore, type AccountVectors, type AttachmentsStore, type AttachmentTextEntry, type AttachmentView, type ChatStats, type ChatsStore, type ConversationsStore, type CountersStore, type DeletionScope, type Delta, type FileAttachment, type IdentityRef, type IngestedVia, type LinkedIdentity, type LinkOptions, MENTION_QUERY, MENTION_STATUSES, type MentionsStore, type MessageFilter, type MessageStore, type MessagesStore, OTHER_SOURCES, openStore, type PeopleStore, type PersonFacts, type PersonRecord, type QueriesStore, type Range, type RankedEvidence, type RankedStoreFound, type RankedStoreRow, type RankingEvidenceItem, type RankingEvidenceRequest, type RankingRequest, type RosterStore, type SearchCommand, type SearchesStore, type SearchIndexStore, type SearchRecord, type StoredAccount, type StoredChatFilter, type StoredHit, type StoredSearch, type StoredTag, type StoreOptions, type SyncStore, type TagFilter, type TagsStore, type TagTarget, type TaskTagTarget, type TextOrigin, type VectorsStore, } from "./store.js";
|
package/dist/store/index.js
CHANGED
|
@@ -1,6 +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
5
|
export { applyDocumentTransfer, previewDocumentTransfer } from "./document-transfer/index.js";
|
|
5
6
|
export { PRAGMAS } from "./driver.js";
|
|
6
7
|
export { EVIDENCE_FINDINGS_KIND, validateEvidenceFindings, } from "./evidence-findings.js";
|
|
@@ -22,6 +23,7 @@ export { ORGANIZATION_KINDS, PROJECT_TYPES, SCOPES } from "./sqlite/knowledge.js
|
|
|
22
23
|
export { NOTE_FIELDS, } from "./sqlite/note-search.js";
|
|
23
24
|
export { PROPOSAL_STATUSES } from "./sqlite/proposed-actions.js";
|
|
24
25
|
export { fillSearchIndex, resetSearchIndex, searchIndexState, } from "./sqlite/search-index.js";
|
|
26
|
+
export { PROJECT_TASK_FIELDS, TASK_SORTS } from "./sqlite/task-board.js";
|
|
25
27
|
export { certaintyOf } from "./sqlite/task-close.js";
|
|
26
28
|
export { TASK_PAIR_DECISIONS } from "./sqlite/task-pairs.js";
|
|
27
29
|
export { TASK_RELATION_KINDS } from "./sqlite/task-relations.js";
|
|
@@ -5,6 +5,7 @@ import { formatReference, parseReference } from "../../domain/references.js";
|
|
|
5
5
|
import { normalizeTag } from "../../domain/tags.js";
|
|
6
6
|
import { normalizeVocabularyName } from "../../domain/vocabulary.js";
|
|
7
7
|
import { formatMeetingReference } from "../../meetings/index.js";
|
|
8
|
+
import { isClosedState } from "../../tasks/index.js";
|
|
8
9
|
import { fold } from "../normalize.js";
|
|
9
10
|
import { ulid } from "../ulid.js";
|
|
10
11
|
import { ownerPerson } from "./actors.js";
|
|
@@ -267,7 +268,7 @@ export const knowledgeStoreOver = (context) => {
|
|
|
267
268
|
const row = taskRowOf(database, id);
|
|
268
269
|
const open = row !== undefined &&
|
|
269
270
|
taskAccountOf(database, row) === taskAccount(key) &&
|
|
270
|
-
database.prepare("SELECT status FROM tasks WHERE id = ?").get(row)?.status
|
|
271
|
+
!isClosedState(String(database.prepare("SELECT status FROM tasks WHERE id = ?").get(row)?.status ?? "done"));
|
|
271
272
|
if (!open)
|
|
272
273
|
throw new CliError("validation_error", "a reminder requires an open task in this account");
|
|
273
274
|
return row;
|
|
@@ -622,7 +623,7 @@ export const knowledgeStoreOver = (context) => {
|
|
|
622
623
|
throw new CliError("validation_error", "lease takes 1000–3600000 milliseconds");
|
|
623
624
|
const accountId = account(key), at = now();
|
|
624
625
|
const rows = database
|
|
625
|
-
.prepare("SELECT r.id FROM reminders r JOIN tasks t ON t.id = r.task_id WHERE r.account_id=? AND r.due_at<=? AND t.status
|
|
626
|
+
.prepare("SELECT r.id FROM reminders r JOIN tasks t ON t.id = r.task_id WHERE r.account_id=? AND r.due_at<=? AND t.status NOT IN ('done','dismissed') " +
|
|
626
627
|
"AND (r.state='pending' OR (r.state='leased' AND r.lease_until<=?)) ORDER BY r.due_at, r.id LIMIT ?")
|
|
627
628
|
.all(accountId, at, at, limit);
|
|
628
629
|
const claimed = [];
|
|
@@ -54,6 +54,7 @@ export const MANIFEST = [
|
|
|
54
54
|
{ name: "20261029224107_version-38-folded-triggers", version: 38, minCompatible: 34 },
|
|
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
|
+
{ name: "20261031230945_version-41-task-fields", version: 41, minCompatible: 34 },
|
|
57
58
|
];
|
|
58
59
|
export const generatedMigrations = (manifest = MANIFEST, generated = GENERATED) => {
|
|
59
60
|
const migrations = [];
|
|
@@ -847,5 +847,17 @@ export const GENERATED = [
|
|
|
847
847
|
"CREATE TRIGGER document_trigrams_bd BEFORE DELETE ON documents BEGIN\n DELETE FROM document_trigrams WHERE rowid = old.id;\nEND;",
|
|
848
848
|
"-- The trigrams hold JS-normalized text, which SQL cannot produce: fillTrigrams fills the rows stored before this\n-- version, up to `watermark`, and a search reads the rest of them by their text meanwhile.\nINSERT INTO search_index_state (name, watermark, filled_through, terms_through, normalizer_version)\n SELECT 'note_trigrams', coalesce(max(id), 0), 0, 0, 1 FROM notes\n UNION ALL SELECT 'document_trigrams', coalesce(max(id), 0), 0, 0, 1 FROM documents;"
|
|
849
849
|
]
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
"name": "20261031230945_version-41-task-fields",
|
|
853
|
+
"statements": [
|
|
854
|
+
"ALTER TABLE `tasks` ADD `priority_score` real;",
|
|
855
|
+
"ALTER TABLE `tasks` ADD `agent` text;",
|
|
856
|
+
"ALTER TABLE `tasks` ADD `revision` integer DEFAULT 1 NOT NULL;",
|
|
857
|
+
"-- 0 urgent … 4 low land inside the critical, high, medium and low quarters, with room on either side for moves.\nUPDATE tasks SET priority_score = CASE 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 priority IS NOT NULL;",
|
|
858
|
+
"-- A build before version 41 writes only `priority`; these keep the score in step with what it wrote.\nCREATE TRIGGER tasks_priority_score_ai AFTER INSERT ON tasks WHEN new.priority IS NOT NULL AND new.priority_score IS NULL 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;",
|
|
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
|
+
"-- 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
|
+
]
|
|
850
862
|
}
|
|
851
863
|
];
|
|
@@ -52,7 +52,7 @@ export const personPreparation = ({ database }, uid, { accounts, limit }) => {
|
|
|
52
52
|
const tasks = database
|
|
53
53
|
.prepare(`SELECT t.id,t.title,t.due_at,t.project_id,t.source_locator,t.created_at,group_concat(DISTINCT ta.role) AS role
|
|
54
54
|
FROM task_assignments ta JOIN tasks t ON t.id=ta.task_id JOIN main.projects p ON p.id=t.project_id
|
|
55
|
-
WHERE ta.assignee_type='person' AND ta.assignee_id=? AND t.deleted_at IS NULL AND t.status
|
|
55
|
+
WHERE ta.assignee_type='person' AND ta.assignee_id=? AND t.deleted_at IS NULL AND t.status NOT IN ('done','dismissed')
|
|
56
56
|
AND p.deleted_at IS NULL AND (p.account_id IS NULL OR p.account_id IN (SELECT a.id FROM accounts a
|
|
57
57
|
JOIN json_each(?) k ON a.provider=json_extract(k.value,'$.provider')
|
|
58
58
|
AND a.external_id=json_extract(k.value,'$.account')))
|
|
@@ -12683,6 +12683,54 @@ export declare const tasks: import("drizzle-orm/sqlite-core").SQLiteTableWithCol
|
|
|
12683
12683
|
identity: undefined;
|
|
12684
12684
|
generated: undefined;
|
|
12685
12685
|
}, {}>;
|
|
12686
|
+
priorityScore: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
12687
|
+
name: string;
|
|
12688
|
+
tableName: "tasks";
|
|
12689
|
+
dataType: "number double";
|
|
12690
|
+
data: number;
|
|
12691
|
+
driverParam: number;
|
|
12692
|
+
notNull: false;
|
|
12693
|
+
hasDefault: false;
|
|
12694
|
+
isPrimaryKey: false;
|
|
12695
|
+
isAutoincrement: false;
|
|
12696
|
+
hasRuntimeDefault: false;
|
|
12697
|
+
enumValues: undefined;
|
|
12698
|
+
baseColumn: never;
|
|
12699
|
+
identity: undefined;
|
|
12700
|
+
generated: undefined;
|
|
12701
|
+
}, {}>;
|
|
12702
|
+
agent: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
12703
|
+
name: string;
|
|
12704
|
+
tableName: "tasks";
|
|
12705
|
+
dataType: "string";
|
|
12706
|
+
data: string;
|
|
12707
|
+
driverParam: string;
|
|
12708
|
+
notNull: false;
|
|
12709
|
+
hasDefault: false;
|
|
12710
|
+
isPrimaryKey: false;
|
|
12711
|
+
isAutoincrement: false;
|
|
12712
|
+
hasRuntimeDefault: false;
|
|
12713
|
+
enumValues: [string, ...string[]];
|
|
12714
|
+
baseColumn: never;
|
|
12715
|
+
identity: undefined;
|
|
12716
|
+
generated: undefined;
|
|
12717
|
+
}, {}>;
|
|
12718
|
+
revision: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
12719
|
+
name: string;
|
|
12720
|
+
tableName: "tasks";
|
|
12721
|
+
dataType: "number int53";
|
|
12722
|
+
data: number;
|
|
12723
|
+
driverParam: number;
|
|
12724
|
+
notNull: true;
|
|
12725
|
+
hasDefault: true;
|
|
12726
|
+
isPrimaryKey: false;
|
|
12727
|
+
isAutoincrement: false;
|
|
12728
|
+
hasRuntimeDefault: false;
|
|
12729
|
+
enumValues: undefined;
|
|
12730
|
+
baseColumn: never;
|
|
12731
|
+
identity: undefined;
|
|
12732
|
+
generated: undefined;
|
|
12733
|
+
}, {}>;
|
|
12686
12734
|
};
|
|
12687
12735
|
dialect: 'sqlite';
|
|
12688
12736
|
isAlias: false;
|
|
@@ -1266,6 +1266,9 @@ export const tasks = sqliteTable("tasks", {
|
|
|
1266
1266
|
closeSuggestedCertainty: real("close_suggested_certainty"),
|
|
1267
1267
|
closeSuggestedReason: text("close_suggested_reason"),
|
|
1268
1268
|
closeSuggestedAt: integer("close_suggested_at"),
|
|
1269
|
+
priorityScore: real("priority_score"),
|
|
1270
|
+
agent: text("agent"),
|
|
1271
|
+
revision: integer("revision").notNull().default(1),
|
|
1269
1272
|
}, (table) => [
|
|
1270
1273
|
unique().on(table.projectId, table.number),
|
|
1271
1274
|
index("tasks_by_title_folded").on(table.titleFolded),
|
|
@@ -73,10 +73,9 @@ export declare const canonicalTagNamed: (database: CacheDatabase, name: string)
|
|
|
73
73
|
export declare const createTag: (context: StoreContext, name: string, kind: TagKind, by: "owner" | "agent", options?: TagCreationOptions) => TagRow;
|
|
74
74
|
/** The tag of that name, made as a plain tag when no tag or topic holds the name yet. */
|
|
75
75
|
export declare const ensureTag: (context: StoreContext, name: string) => TagRow;
|
|
76
|
-
/** The tags it did not have before, in the order given; a tag the owner adds becomes the owner's. */
|
|
77
76
|
export declare const addTags: (context: StoreContext, thing: Thing, tags: string[], source?: TaggingSource, author?: Actor) => string[];
|
|
78
77
|
/** An explicit removal withdraws the effective label, restricted to the requested origin when supplied. */
|
|
79
|
-
export declare const removeTags: (
|
|
78
|
+
export declare const removeTags: (context: StoreContext, thing: Thing, tags: string[], source?: "manual" | "auto") => string[];
|
|
80
79
|
export interface Label {
|
|
81
80
|
tag: string;
|
|
82
81
|
/** `file` when only a note's file states it; the owner's own label otherwise. */
|
|
@@ -111,13 +111,23 @@ export const ensureTag = (context, name) => {
|
|
|
111
111
|
return canonicalTagNamed(context.database, name) ?? createTag(context, name, "tag", "owner");
|
|
112
112
|
};
|
|
113
113
|
/** The tags it did not have before, in the order given; a tag the owner adds becomes the owner's. */
|
|
114
|
+
/** A task's tag change is part of its history, and of its revision. */
|
|
115
|
+
const taskTagsChanged = (context, thing, changes, author) => {
|
|
116
|
+
if (thing.type !== "task" || [...(changes.added ?? []), ...(changes.removed ?? [])].length === 0)
|
|
117
|
+
return;
|
|
118
|
+
const at = context.now();
|
|
119
|
+
context.database.prepare("UPDATE tasks SET updated_at = ? WHERE id = ?").run(at, thing.id);
|
|
120
|
+
context.database
|
|
121
|
+
.prepare("INSERT INTO task_events (task_id, actor_type, actor_id, kind, changes, created_at) VALUES (?, ?, ?, 'tags', ?, ?)")
|
|
122
|
+
.run(thing.id, author?.type ?? null, author?.id ?? null, JSON.stringify({ tags: [changes.removed ?? [], changes.added ?? []] }), at);
|
|
123
|
+
};
|
|
114
124
|
export const addTags = (context, thing, tags, source = "owner", author) => {
|
|
115
125
|
const { database, now } = context;
|
|
116
126
|
const insert = database.prepare("INSERT INTO taggings (tag_id, taggable_type, taggable_id, main, source, author_type, author_id, created_at, updated_at) " +
|
|
117
127
|
"VALUES (?, ?, ?, 0, ?, ?, ?, ?, ?) ON CONFLICT (tag_id, taggable_type, taggable_id) DO NOTHING");
|
|
118
128
|
const claim = database.prepare("UPDATE taggings SET source = 'owner', updated_at = ? WHERE tag_id = ? AND taggable_type = ? AND taggable_id = ? AND source <> 'owner'");
|
|
119
129
|
const at = now();
|
|
120
|
-
|
|
130
|
+
const fresh = tags.filter((name) => {
|
|
121
131
|
const tag = ensureTag(context, name);
|
|
122
132
|
const held = database
|
|
123
133
|
.prepare("SELECT source,author_type,author_id FROM taggings WHERE tag_id IN (SELECT ? UNION ALL SELECT source_id FROM tag_redirects WHERE survivor_id=?) AND taggable_type=? AND taggable_id=?")
|
|
@@ -129,11 +139,14 @@ export const addTags = (context, thing, tags, source = "owner", author) => {
|
|
|
129
139
|
claim.run(at, tag.id, thing.type, thing.id);
|
|
130
140
|
return added && held.length === 0;
|
|
131
141
|
});
|
|
142
|
+
taskTagsChanged(context, thing, { added: fresh }, author);
|
|
143
|
+
return fresh;
|
|
132
144
|
};
|
|
133
145
|
/** An explicit removal withdraws the effective label, restricted to the requested origin when supplied. */
|
|
134
|
-
export const removeTags = (
|
|
146
|
+
export const removeTags = (context, thing, tags, source) => {
|
|
147
|
+
const { database } = context;
|
|
135
148
|
const remove = database.prepare("DELETE FROM taggings WHERE tag_id=? AND taggable_type=? AND taggable_id=?");
|
|
136
|
-
|
|
149
|
+
const removed = tags.filter((name) => {
|
|
137
150
|
const tag = canonicalTagNamed(database, name);
|
|
138
151
|
if (!tag)
|
|
139
152
|
return false;
|
|
@@ -171,6 +184,8 @@ export const removeTags = ({ database }, thing, tags, source) => {
|
|
|
171
184
|
}
|
|
172
185
|
return changed;
|
|
173
186
|
});
|
|
187
|
+
taskTagsChanged(context, thing, { removed });
|
|
188
|
+
return removed;
|
|
174
189
|
};
|
|
175
190
|
/** A thing's tags by name, with whether only a file states each. */
|
|
176
191
|
/** A tagging of something still there: a deleted document keeps its tags but is not counted or listed by them. */
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type FieldSpec, type QueryAst } from "@wirecat/cli-base/query";
|
|
2
|
+
import type { ColumnMap } from "./query-filter.js";
|
|
3
|
+
export declare const TASK_SORTS: readonly ["priority", "due", "created", "updated", "key", "state"];
|
|
4
|
+
export type TaskSort = (typeof TASK_SORTS)[number];
|
|
5
|
+
/** The query language of the owner's project tasks, every project at once; `text` takes the bare words. */
|
|
6
|
+
export declare const PROJECT_TASK_FIELDS: readonly FieldSpec[];
|
|
7
|
+
/** A `blocked-by` task that is not done holds the task back; so does one deleted or out of reach. */
|
|
8
|
+
export declare const UNFINISHED_BLOCKER: string;
|
|
9
|
+
/** `now` is when overdue is measured; one value per answer, so the list and its counts agree. */
|
|
10
|
+
export declare const projectTaskColumns: (now: number) => ColumnMap;
|
|
11
|
+
/** Live tasks of the owner's projects; an account's inbox is listed by its account, never here. */
|
|
12
|
+
export declare const BOARD_SCOPE = "t.deleted_at IS NULL AND t.project_id IN (SELECT id FROM projects WHERE deleted_at IS NULL AND length(key) <> 10)";
|
|
13
|
+
/** The chosen order first, then priority, age and id, so a page boundary never splits a tie. */
|
|
14
|
+
export declare const boardOrder: (sort: TaskSort, descending: boolean) => string;
|
|
15
|
+
/** The query without its required terms on `name`: a picker counts the choices its own filter hides. */
|
|
16
|
+
export declare const withoutField: (query: QueryAst | undefined, name: string) => QueryAst | undefined;
|