@wirecat/cli-core 0.552.0 → 0.553.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 +24 -0
- package/dist/cli/compact.d.ts +4 -0
- package/dist/cli/compact.js +65 -7
- package/dist/cli/messenger/context.d.ts +3 -0
- package/dist/cli/messenger/projects-command.js +38 -11
- package/dist/cli/messenger/serve-relay.d.ts +1 -0
- package/dist/cli/project-evidence.d.ts +33 -4
- package/dist/cli/project-evidence.js +38 -4
- package/dist/cli/project-status-rows.js +11 -1
- package/dist/cli/project-status.d.ts +24 -0
- package/dist/cli/project-status.js +52 -9
- package/dist/services/index.d.ts +4 -3
- package/dist/services/index.js +4 -3
- package/dist/services/link-scorers/index.d.ts +1 -1
- package/dist/services/link-scorers/index.js +1 -1
- package/dist/services/link-scorers/scan.d.ts +0 -1
- package/dist/services/link-scorers/scan.js +1 -1
- package/dist/services/project-purposes.d.ts +28 -0
- package/dist/services/project-purposes.js +41 -0
- package/dist/services/project-status/index.d.ts +22 -8
- package/dist/services/project-status/index.js +17 -9
- package/dist/services/stored-project-gathering.d.ts +79 -4
- package/dist/services/stored-project-gathering.js +171 -22
- package/dist/store/messages.d.ts +2 -1
- package/dist/store/messages.js +2 -2
- package/dist/store/sqlite/reads.d.ts +1 -1
- package/dist/store/sqlite/reads.js +2 -2
- package/package.json +1 -1
- package/parity.json +10 -0
|
@@ -49,7 +49,16 @@ export const projectStatusSchema = v.looseObject({
|
|
|
49
49
|
attention: v.number(),
|
|
50
50
|
why: v.nullable(v.string()),
|
|
51
51
|
})),
|
|
52
|
-
|
|
52
|
+
purposes: v.union([v.literal("all"), v.array(v.string())]),
|
|
53
|
+
out_of_scope: v.optional(v.array(v.strictObject({
|
|
54
|
+
chat: v.string(),
|
|
55
|
+
reason: v.string(),
|
|
56
|
+
purpose: v.optional(v.string()),
|
|
57
|
+
messages: v.optional(v.number()),
|
|
58
|
+
first_at: v.optional(v.nullable(v.string())),
|
|
59
|
+
last_at: v.optional(v.nullable(v.string())),
|
|
60
|
+
read_command: v.optional(v.nullable(v.string())),
|
|
61
|
+
}))),
|
|
53
62
|
failed: v.optional(v.array(v.strictObject({ signal: v.string(), why: v.string() }))),
|
|
54
63
|
...Object.fromEntries(Object.keys(STATUS_SIGNAL_ROWS).map((name) => [listKey(name.slice(7)), v.optional(v.array(statusItem))])),
|
|
55
64
|
});
|
|
@@ -78,9 +87,21 @@ const nextOf = (found, hints) => {
|
|
|
78
87
|
const reads = top.flatMap(({ chat, why }) => partial(found, chat) ? [] : because(hints.read(chat, readFrom(found, chat.ref)), why));
|
|
79
88
|
return [...syncs, ...reads].slice(0, 2);
|
|
80
89
|
};
|
|
81
|
-
const
|
|
90
|
+
const leftOutOf = (found) => found.outOfScope.filter((one) => one.reason === "purpose");
|
|
91
|
+
const readLine = (found, one, hints) => {
|
|
92
|
+
const step = hints.readChat?.({ ref: one.chat, account: one.account, chatId: one.chatId }, day(found.window.since));
|
|
93
|
+
return step ? hints.line(step) : null;
|
|
94
|
+
};
|
|
95
|
+
/** `12 messages 2026-10-03…2026-11-01`, or that the window holds none. */
|
|
96
|
+
export const heldInWindow = (one) => one.messages && one.firstAt && one.lastAt
|
|
97
|
+
? `${one.messages} message${one.messages === 1 ? "" : "s"} ${day(one.firstAt)}…${day(one.lastAt)}`
|
|
98
|
+
: "no messages in the window";
|
|
99
|
+
const leftOutWhy = (one, read) => `${one.chat}, purpose ${one.purpose}, not read: ${heldInWindow(one)}` +
|
|
100
|
+
(read ? `; read it: ${read}` : "; add its purpose to --purpose to read it");
|
|
101
|
+
const missingOf = (found, hints) => {
|
|
82
102
|
const held = found.chats.filter((chat) => partial(found, chat));
|
|
83
103
|
const [first] = held;
|
|
104
|
+
const others = found.outOfScope.filter((one) => one.reason !== "purpose");
|
|
84
105
|
return [
|
|
85
106
|
...(first
|
|
86
107
|
? [
|
|
@@ -92,16 +113,20 @@ const missingOf = (found) => {
|
|
|
92
113
|
},
|
|
93
114
|
]
|
|
94
115
|
: []),
|
|
95
|
-
...(
|
|
116
|
+
...(others.length
|
|
96
117
|
? [
|
|
97
118
|
{
|
|
98
119
|
field: "chats",
|
|
99
|
-
why: `${
|
|
100
|
-
...new Set(
|
|
120
|
+
why: `${others.length} linked chats were not read here (${[
|
|
121
|
+
...new Set(others.map((one) => one.reason)),
|
|
101
122
|
].join(", ")}); memo projects status reads every account`,
|
|
102
123
|
},
|
|
103
124
|
]
|
|
104
125
|
: []),
|
|
126
|
+
...leftOutOf(found).map((one) => {
|
|
127
|
+
const read = readLine(found, one, hints);
|
|
128
|
+
return { field: "chats", why: leftOutWhy(one, read), ...(read ? { step: "messages.list" } : {}) };
|
|
129
|
+
}),
|
|
105
130
|
...found.failed.map((one) => ({ field: `signal:${one.signal}`, why: one.why })),
|
|
106
131
|
];
|
|
107
132
|
};
|
|
@@ -145,11 +170,26 @@ export const projectStatusAnswer = (found, hints) => {
|
|
|
145
170
|
attention: weights.get(chat.ref)?.weight ?? 0,
|
|
146
171
|
why: weights.get(chat.ref)?.why ?? null,
|
|
147
172
|
})),
|
|
148
|
-
|
|
173
|
+
purposes: found.purposes,
|
|
174
|
+
...(found.outOfScope.length
|
|
175
|
+
? {
|
|
176
|
+
outOfScope: found.outOfScope.map((one) => one.reason === "purpose"
|
|
177
|
+
? {
|
|
178
|
+
chat: one.chat,
|
|
179
|
+
reason: one.reason,
|
|
180
|
+
purpose: one.purpose,
|
|
181
|
+
messages: one.messages,
|
|
182
|
+
firstAt: one.firstAt,
|
|
183
|
+
lastAt: one.lastAt,
|
|
184
|
+
readCommand: readLine(found, one, hints),
|
|
185
|
+
}
|
|
186
|
+
: one),
|
|
187
|
+
}
|
|
188
|
+
: {}),
|
|
149
189
|
...(found.failed.length ? { failed: found.failed } : {}),
|
|
150
190
|
...lists,
|
|
151
191
|
});
|
|
152
|
-
const missing = missingOf(found);
|
|
192
|
+
const missing = missingOf(found, hints);
|
|
153
193
|
const complete = found.failed.length === 0 && !found.chats.some((chat) => partial(found, chat));
|
|
154
194
|
return {
|
|
155
195
|
item,
|
|
@@ -235,8 +275,11 @@ export const projectStatusText = (found, hints) => {
|
|
|
235
275
|
const cut = one.data.cut;
|
|
236
276
|
lines.push(` ${cut ? `${data.child} not read (${cut})` : `${data.key ?? data.child} · ${data.name} · ${data.chats} chats`}`);
|
|
237
277
|
}
|
|
238
|
-
|
|
239
|
-
|
|
278
|
+
const others = found.outOfScope.filter((one) => one.reason !== "purpose");
|
|
279
|
+
if (others.length)
|
|
280
|
+
lines.push(`Not read here: ${others.map((one) => `${one.chat} (${one.reason})`).join(", ")}`);
|
|
281
|
+
for (const one of leftOutOf(found))
|
|
282
|
+
lines.push(`Left out: ${leftOutWhy(one, readLine(found, one, hints))}`);
|
|
240
283
|
for (const one of found.failed)
|
|
241
284
|
lines.push(`Signal ${one.signal} failed: ${one.why}`);
|
|
242
285
|
if (next.length)
|
package/dist/services/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { type ChatCoverage, chatCoverage } from "./chat-coverage.js";
|
|
|
6
6
|
export type { ContextEvidenceItem, ContextEvidencePage, ContextEvidenceReader, ContextEvidenceRequest, ContextEvidenceSender, ContextIndex, ContextReadLimits, ContextReadUsage, ContextRecordReader, ContextSource, ScopedContextEvidenceReader, } from "./context-evidence.js";
|
|
7
7
|
export { contextEvidenceFingerprint, createContextEvidenceReader } from "./context-evidence.js";
|
|
8
8
|
export { excerpt } from "./excerpt.js";
|
|
9
|
-
export { aliasHits,
|
|
9
|
+
export { aliasHits, chatSenders, chatTitle, combinedScore, DEFAULT_LIMIT as DEFAULT_LINK_SCAN_LIMIT, DEFAULT_THRESHOLD as DEFAULT_LINK_SCAN_THRESHOLD, emailDomain, type LinkCandidate, type LinkScorer, mailThread, memberChats, memberDialog, PERSON_SCORERS, PERSON_THRESHOLD as PERSON_LINK_SCAN_THRESHOLD, SCAN_BOT, SCORERS, type ScanInput as LinkScanInput, type ScannedProject, type ScannedProposal, type ScanResult as LinkScanResult, type ScorerInput, type Signal as LinkSignal, scanProjectLinks, } from "./link-scorers/index.js";
|
|
10
10
|
export type { MediaArchiveOptions, MediaArchivePorts } from "./media-archive.js";
|
|
11
11
|
export { archiveMedia, checkMediaArchive } from "./media-archive.js";
|
|
12
12
|
export type { MediaArchiveEntry, MediaIdentity } from "./media-archive-manifest.js";
|
|
@@ -21,9 +21,10 @@ export { documentPassage, storedMessageNeighbours, storedNeighbours } from "./pr
|
|
|
21
21
|
export type { EvidenceMention, EvidenceMentions } from "./project-gathering-mentions.js";
|
|
22
22
|
export { findEvidenceMentions } from "./project-gathering-mentions.js";
|
|
23
23
|
export type { ProjectChatFetched, ProjectGatheringBudgets, ProjectGatheringRequest, ProjectLane, ProjectLaneKind, ProjectLinkedChat, ProjectPerson, ProjectSender, ProjectUnitState, } from "./project-gathering-request.js";
|
|
24
|
-
export {
|
|
24
|
+
export { BUILT_IN_PURPOSE_CONFIG, BUILT_IN_PURPOSES, DEFAULT_PURPOSES, LINK_PURPOSE, type PurposeChoice, type PurposeConfig, purposeList, purposeRead, purposesOf, } from "./project-purposes.js";
|
|
25
|
+
export { type MoneyTotal, type OutOfScope, type ProjectStatus, type ProjectStatusInput, projectStatus, STATUS_CHATS, STATUS_SIGNALS, STATUS_WINDOW, type StatusAttention, type StatusChat, type StatusChatSummary, type StatusContext, type StatusItem, type StatusSignal, } from "./project-status/index.js";
|
|
25
26
|
export { questions } from "./questions.js";
|
|
26
|
-
export { canonicalProjectRef, gatherStoredProject, type LinkedChat, linkedChatsOf, type StoredProjectGathering, type StoredProjectInput, } from "./stored-project-gathering.js";
|
|
27
|
+
export { type ChatCount, type ChatCounter, canonicalProjectRef, gatherStoredProject, type LeftOutChat, type LinkedChat, linkedChatsOf, type ProjectMember, type StoredProjectGathering, type StoredProjectInput, storedChatCount, } from "./stored-project-gathering.js";
|
|
27
28
|
import { type AccountService } from "./account.js";
|
|
28
29
|
import { type AdminService } from "./admin.js";
|
|
29
30
|
import { type AdminStatisticsService } from "./admin-statistics.js";
|
package/dist/services/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { AGENDA_DEFAULTS } from "./agenda.js";
|
|
|
5
5
|
export { chatCoverage } from "./chat-coverage.js";
|
|
6
6
|
export { contextEvidenceFingerprint, createContextEvidenceReader } from "./context-evidence.js";
|
|
7
7
|
export { excerpt } from "./excerpt.js";
|
|
8
|
-
export { aliasHits,
|
|
8
|
+
export { aliasHits, chatSenders, chatTitle, combinedScore, DEFAULT_LIMIT as DEFAULT_LINK_SCAN_LIMIT, DEFAULT_THRESHOLD as DEFAULT_LINK_SCAN_THRESHOLD, emailDomain, mailThread, memberChats, memberDialog, PERSON_SCORERS, PERSON_THRESHOLD as PERSON_LINK_SCAN_THRESHOLD, SCAN_BOT, SCORERS, scanProjectLinks, } from "./link-scorers/index.js";
|
|
9
9
|
export { archiveMedia, checkMediaArchive } from "./media-archive.js";
|
|
10
10
|
export { moveMediaArchive } from "./media-archive-move.js";
|
|
11
11
|
export { downloadImageModel, downloadTextModel, } from "./model-downloads.js";
|
|
@@ -13,9 +13,10 @@ export { MONEY_DONE, MONEY_REQUEST, MONEY_WORDS, moneyClassesOf, PROMISE } from
|
|
|
13
13
|
export { gatherProjectEvidence } from "./project-gathering.js";
|
|
14
14
|
export { documentPassage, storedMessageNeighbours, storedNeighbours } from "./project-gathering-around.js";
|
|
15
15
|
export { findEvidenceMentions } from "./project-gathering-mentions.js";
|
|
16
|
-
export { DEFAULT_PURPOSES,
|
|
16
|
+
export { BUILT_IN_PURPOSE_CONFIG, BUILT_IN_PURPOSES, DEFAULT_PURPOSES, LINK_PURPOSE, purposeList, purposeRead, purposesOf, } from "./project-purposes.js";
|
|
17
|
+
export { projectStatus, STATUS_CHATS, STATUS_SIGNALS, STATUS_WINDOW, } from "./project-status/index.js";
|
|
17
18
|
export { questions } from "./questions.js";
|
|
18
|
-
export { canonicalProjectRef, gatherStoredProject, linkedChatsOf, } from "./stored-project-gathering.js";
|
|
19
|
+
export { canonicalProjectRef, gatherStoredProject, linkedChatsOf, storedChatCount, } from "./stored-project-gathering.js";
|
|
19
20
|
import { accountService } from "./account.js";
|
|
20
21
|
import { adminService } from "./admin.js";
|
|
21
22
|
import { adminStatisticsService } from "./admin-statistics.js";
|
|
@@ -5,5 +5,5 @@ export { emailDomain } from "./email-domain.js";
|
|
|
5
5
|
export { mailThread } from "./mail-thread.js";
|
|
6
6
|
export { memberChats } from "./member-chats.js";
|
|
7
7
|
export { memberDialog } from "./member-dialog.js";
|
|
8
|
-
export {
|
|
8
|
+
export { combinedScore, DEFAULT_LIMIT, DEFAULT_THRESHOLD, PERSON_SCORERS, PERSON_THRESHOLD, SCAN_BOT, SCORERS, type ScanInput, scanProjectLinks, } from "./scan.js";
|
|
9
9
|
export type { LinkCandidate, LinkScorer, ScannedProject, ScannedProposal, ScanResult, ScorerInput, Signal, } from "./types.js";
|
|
@@ -5,4 +5,4 @@ export { emailDomain } from "./email-domain.js";
|
|
|
5
5
|
export { mailThread } from "./mail-thread.js";
|
|
6
6
|
export { memberChats } from "./member-chats.js";
|
|
7
7
|
export { memberDialog } from "./member-dialog.js";
|
|
8
|
-
export {
|
|
8
|
+
export { combinedScore, DEFAULT_LIMIT, DEFAULT_THRESHOLD, PERSON_SCORERS, PERSON_THRESHOLD, SCAN_BOT, SCORERS, scanProjectLinks, } from "./scan.js";
|
|
@@ -10,7 +10,6 @@ export declare const SCAN_BOT: {
|
|
|
10
10
|
readonly bot: "memo-scan";
|
|
11
11
|
readonly kind: "rule";
|
|
12
12
|
};
|
|
13
|
-
export declare const BUILT_IN_PURPOSES: readonly ["work", "sales", "support", "other"];
|
|
14
13
|
export declare const DEFAULT_THRESHOLD = 0.5;
|
|
15
14
|
/** A person ↔ person scan keeps the old rule's reach: one weak signal is enough to ask the owner. */
|
|
16
15
|
export declare const PERSON_THRESHOLD = 0.3;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { parseReference } from "../../domain/references.js";
|
|
3
|
+
import { BUILT_IN_PURPOSES } from "../project-purposes.js";
|
|
3
4
|
import { canonicalProjectRef } from "../stored-project-gathering.js";
|
|
4
5
|
import { aliasHits } from "./alias-hits.js";
|
|
5
6
|
import { chatSenders } from "./chat-senders.js";
|
|
@@ -15,7 +16,6 @@ export const SCORERS = [chatTitle, aliasHits, memberChats, memberDialog, chatSen
|
|
|
15
16
|
export const PERSON_SCORERS = [emailDomain];
|
|
16
17
|
/** The rule bot every scan proposal is written by: only the owner decides its proposals. */
|
|
17
18
|
export const SCAN_BOT = { bot: "memo-scan", kind: "rule" };
|
|
18
|
-
export const BUILT_IN_PURPOSES = ["work", "sales", "support", "other"];
|
|
19
19
|
export const DEFAULT_THRESHOLD = 0.5;
|
|
20
20
|
/** A person ↔ person scan keeps the old rule's reach: one weak signal is enough to ask the owner. */
|
|
21
21
|
export const PERSON_THRESHOLD = 0.3;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** What a chat can be for in a project, when the host's config names none. */
|
|
2
|
+
export declare const BUILT_IN_PURPOSES: readonly string[];
|
|
3
|
+
/** What `projects evidence` and `projects status` read when no purpose is asked for (built in; memo's config may change it). */
|
|
4
|
+
export declare const DEFAULT_PURPOSES: readonly string[];
|
|
5
|
+
/** A link with no purpose is a work chat. */
|
|
6
|
+
export declare const LINK_PURPOSE = "work";
|
|
7
|
+
/** The purposes a host's config lists; core knows only the built-in ones and takes the rest as given. */
|
|
8
|
+
export interface PurposeConfig {
|
|
9
|
+
purposes: readonly string[];
|
|
10
|
+
/** Read when no `--purpose` is given. */
|
|
11
|
+
defaults: readonly string[];
|
|
12
|
+
/** A project's own defaults, by its key or `project:<id>`. */
|
|
13
|
+
projects?: Readonly<Record<string, readonly string[]>>;
|
|
14
|
+
}
|
|
15
|
+
export declare const BUILT_IN_PURPOSE_CONFIG: PurposeConfig;
|
|
16
|
+
/** The purposes one run reads: `all`, or a set. */
|
|
17
|
+
export type PurposeChoice = "all" | ReadonlySet<string>;
|
|
18
|
+
/**
|
|
19
|
+
* The purposes a run reads: the asked ones, each checked against the config's list, else the project's own defaults,
|
|
20
|
+
* else the config's. `all` reads every purpose, a link's unlisted one too.
|
|
21
|
+
*/
|
|
22
|
+
export declare const purposesOf: (asked: readonly string[] | undefined, config?: PurposeConfig, project?: {
|
|
23
|
+
ref: string | null;
|
|
24
|
+
key: string | null;
|
|
25
|
+
}) => PurposeChoice;
|
|
26
|
+
export declare const purposeRead: (choice: PurposeChoice, purpose: string | null) => boolean;
|
|
27
|
+
/** `a,b` and repeated flags as one list. */
|
|
28
|
+
export declare const purposeList: (value: string, previous?: string[]) => string[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
/** What a chat can be for in a project, when the host's config names none. */
|
|
3
|
+
export const BUILT_IN_PURPOSES = ["work", "sales", "support", "other"];
|
|
4
|
+
/** What `projects evidence` and `projects status` read when no purpose is asked for (built in; memo's config may change it). */
|
|
5
|
+
export const DEFAULT_PURPOSES = ["work"];
|
|
6
|
+
/** A link with no purpose is a work chat. */
|
|
7
|
+
export const LINK_PURPOSE = "work";
|
|
8
|
+
export const BUILT_IN_PURPOSE_CONFIG = { purposes: BUILT_IN_PURPOSES, defaults: DEFAULT_PURPOSES };
|
|
9
|
+
const unknown = (asked, config) => {
|
|
10
|
+
const wrong = asked.filter((one) => !config.purposes.includes(one));
|
|
11
|
+
if (wrong.length)
|
|
12
|
+
throw new CliError("validation_error", `unknown purpose ${wrong.join(", ")}; the configured ones are ${config.purposes.join(", ")}, or all`, { param: "purpose", purposes: [...config.purposes] });
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The purposes a run reads: the asked ones, each checked against the config's list, else the project's own defaults,
|
|
16
|
+
* else the config's. `all` reads every purpose, a link's unlisted one too.
|
|
17
|
+
*/
|
|
18
|
+
export const purposesOf = (asked, config = BUILT_IN_PURPOSE_CONFIG, project) => {
|
|
19
|
+
const words = (asked ?? []).map((one) => one.trim()).filter(Boolean);
|
|
20
|
+
if (words.includes("all")) {
|
|
21
|
+
if (words.length > 1)
|
|
22
|
+
throw new CliError("validation_error", "--purpose all reads every purpose; give it alone");
|
|
23
|
+
return "all";
|
|
24
|
+
}
|
|
25
|
+
if (words.length) {
|
|
26
|
+
unknown(words, config);
|
|
27
|
+
return new Set(words);
|
|
28
|
+
}
|
|
29
|
+
const own = (project?.key ? config.projects?.[project.key] : undefined) ??
|
|
30
|
+
(project?.ref ? config.projects?.[project.ref] : undefined);
|
|
31
|
+
return new Set(own ?? config.defaults);
|
|
32
|
+
};
|
|
33
|
+
export const purposeRead = (choice, purpose) => choice === "all" || choice.has(purpose ?? LINK_PURPOSE);
|
|
34
|
+
/** `a,b` and repeated flags as one list. */
|
|
35
|
+
export const purposeList = (value, previous = []) => [
|
|
36
|
+
...previous,
|
|
37
|
+
...value
|
|
38
|
+
.split(",")
|
|
39
|
+
.map((one) => one.trim())
|
|
40
|
+
.filter(Boolean),
|
|
41
|
+
];
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { Id } from "../../domain/models.js";
|
|
1
2
|
import type { AccountKey, MessageStore } from "../../store/store.js";
|
|
3
|
+
import { type PurposeConfig } from "../project-purposes.js";
|
|
4
|
+
import { type ChatCount, type ChatCounter } from "../stored-project-gathering.js";
|
|
2
5
|
import { STATUS_SIGNALS } from "./signals/index.js";
|
|
3
6
|
import type { StatusAttention, StatusChat, StatusItem, StatusProject, StatusSignal } from "./types.js";
|
|
4
7
|
export type * from "./types.js";
|
|
@@ -7,8 +10,6 @@ export { STATUS_SIGNALS };
|
|
|
7
10
|
export declare const STATUS_WINDOW = 300;
|
|
8
11
|
/** Linked chats read at most, as `projects evidence` reads. */
|
|
9
12
|
export declare const STATUS_CHATS = 16;
|
|
10
|
-
/** What `projects status` reads when no purpose is asked for (built in; memo's config may change it). */
|
|
11
|
-
export declare const DEFAULT_PURPOSES: readonly string[];
|
|
12
13
|
export interface ProjectStatusInput {
|
|
13
14
|
/** An id, key, name or alias, as `canonicalProjectRef` takes it. */
|
|
14
15
|
project: string;
|
|
@@ -18,12 +19,27 @@ export interface ProjectStatusInput {
|
|
|
18
19
|
after?: number;
|
|
19
20
|
asOf?: number;
|
|
20
21
|
contextQueries?: string[];
|
|
21
|
-
/** The chat purposes read; `all` reads every one.
|
|
22
|
+
/** The chat purposes read; `["all"]` reads every one. Left out, the config's defaults. */
|
|
22
23
|
purposes?: readonly string[];
|
|
24
|
+
/** The host's purposes; core's built-in ones when left out. */
|
|
25
|
+
purposeConfig?: PurposeConfig;
|
|
26
|
+
/** How many messages a chat left out by purpose holds in the window; the store's own count when left out. */
|
|
27
|
+
count?: ChatCounter;
|
|
23
28
|
/** Default `STATUS_SIGNALS`; tests pass their own. */
|
|
24
29
|
signals?: readonly StatusSignal[];
|
|
25
30
|
}
|
|
26
31
|
export type StatusChatSummary = Omit<StatusChat, "messages" | "account">;
|
|
32
|
+
/** A linked chat not read; one left out by its purpose says how much it holds in the window. */
|
|
33
|
+
export type OutOfScope = {
|
|
34
|
+
chat: string;
|
|
35
|
+
reason: "other-account" | "linked-chat-limit";
|
|
36
|
+
} | ({
|
|
37
|
+
chat: string;
|
|
38
|
+
reason: "purpose";
|
|
39
|
+
purpose: string;
|
|
40
|
+
account: AccountKey;
|
|
41
|
+
chatId: Id;
|
|
42
|
+
} & ChatCount);
|
|
27
43
|
export interface MoneyTotal {
|
|
28
44
|
direction: string;
|
|
29
45
|
currency: string | null;
|
|
@@ -48,11 +64,9 @@ export interface ProjectStatus {
|
|
|
48
64
|
money: {
|
|
49
65
|
open: MoneyTotal[];
|
|
50
66
|
};
|
|
51
|
-
outOfScope:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
purpose?: string;
|
|
55
|
-
}[];
|
|
67
|
+
outOfScope: OutOfScope[];
|
|
68
|
+
/** The purposes read: `all`, or the list. */
|
|
69
|
+
purposes: "all" | string[];
|
|
56
70
|
failed: {
|
|
57
71
|
signal: string;
|
|
58
72
|
why: string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { formatReference } from "../../domain/references.js";
|
|
3
3
|
import { chatCoverage } from "../chat-coverage.js";
|
|
4
|
-
import {
|
|
4
|
+
import { LINK_PURPOSE, purposeRead, purposesOf } from "../project-purposes.js";
|
|
5
|
+
import { canonicalProjectRef, linkedChatsOf, storedChatCount, } from "../stored-project-gathering.js";
|
|
5
6
|
import { centsText } from "./signals/amounts.js";
|
|
6
7
|
import { STATUS_SIGNALS } from "./signals/index.js";
|
|
7
8
|
export { STATUS_SIGNALS };
|
|
@@ -12,8 +13,6 @@ export const STATUS_WINDOW = 300;
|
|
|
12
13
|
export const STATUS_CHATS = 16;
|
|
13
14
|
const CHILD_DEPTH = 2;
|
|
14
15
|
const CHILDREN = 8;
|
|
15
|
-
/** What `projects status` reads when no purpose is asked for (built in; memo's config may change it). */
|
|
16
|
-
export const DEFAULT_PURPOSES = ["work"];
|
|
17
16
|
const projectsCatalog = async (store) => new Map((await store.knowledge.vocabulary({ kind: "project" })).entries.map((entry) => [`project:${entry.id}`, entry]));
|
|
18
17
|
/** Confirmed `member-of` links from another project; depth two, eight at most, a project seen twice skipped. */
|
|
19
18
|
const childrenOf = async (store, root, catalog) => {
|
|
@@ -92,13 +91,14 @@ export const projectStatus = async (store, input) => {
|
|
|
92
91
|
if (!(since < now))
|
|
93
92
|
throw new CliError("validation_error", "the window must start before it ends");
|
|
94
93
|
const signals = input.signals ?? STATUS_SIGNALS;
|
|
95
|
-
const purposes = new Set(input.purposes ?? DEFAULT_PURPOSES);
|
|
96
94
|
const catalog = await projectsCatalog(store);
|
|
97
95
|
const ref = await canonicalProjectRef(store, input.project);
|
|
98
96
|
const entry = catalog.get(ref);
|
|
99
97
|
if (!entry)
|
|
100
98
|
throw new CliError("not_found", "the owner project is unavailable");
|
|
101
99
|
const root = { ref, key: entry.key, name: entry.name };
|
|
100
|
+
const purposes = purposesOf(input.purposes, input.purposeConfig, root);
|
|
101
|
+
const count = input.count ?? storedChatCount(store);
|
|
102
102
|
const { children, cut } = await childrenOf(store, root, catalog);
|
|
103
103
|
const outOfScope = [];
|
|
104
104
|
const linked = new Map();
|
|
@@ -110,10 +110,17 @@ export const projectStatus = async (store, input) => {
|
|
|
110
110
|
}
|
|
111
111
|
const chosen = [];
|
|
112
112
|
for (const [chat, link] of linked) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
if (!purposeRead(purposes, link.purpose)) {
|
|
114
|
+
const account = { provider: link.provider, account: link.account };
|
|
115
|
+
outOfScope.push({
|
|
116
|
+
chat,
|
|
117
|
+
reason: "purpose",
|
|
118
|
+
purpose: link.purpose ?? LINK_PURPOSE,
|
|
119
|
+
account,
|
|
120
|
+
chatId: link.chat,
|
|
121
|
+
...(await count(account, link.chat, { since, until: now })),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
117
124
|
else if (!inAccounts(input.accounts, link.provider, link.account))
|
|
118
125
|
outOfScope.push({ chat, reason: "other-account" });
|
|
119
126
|
else if (chosen.length >= STATUS_CHATS)
|
|
@@ -134,7 +141,7 @@ export const projectStatus = async (store, input) => {
|
|
|
134
141
|
title: coverage?.title ?? null,
|
|
135
142
|
kind: (await store.chats.chatKind(account, link.chat)) ?? "unknown",
|
|
136
143
|
role: link.role,
|
|
137
|
-
purpose:
|
|
144
|
+
purpose: link.purpose ?? LINK_PURPOSE,
|
|
138
145
|
project: link.project,
|
|
139
146
|
coverage: coverage,
|
|
140
147
|
messages: page.items.filter((message) => Date.parse(message.timestamp) <= now),
|
|
@@ -189,6 +196,7 @@ export const projectStatus = async (store, input) => {
|
|
|
189
196
|
items,
|
|
190
197
|
money: { open: totalsOf(items) },
|
|
191
198
|
outOfScope,
|
|
199
|
+
purposes: purposes === "all" ? "all" : [...purposes],
|
|
192
200
|
failed,
|
|
193
201
|
attention,
|
|
194
202
|
signals: signals.map(({ id, version }) => ({ id, version })),
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ContextSource } from "../context/evidence.js";
|
|
2
|
-
import type { MessageStore } from "../store/store.js";
|
|
2
|
+
import type { AccountKey, MessageStore } from "../store/store.js";
|
|
3
3
|
import { gatherProjectEvidence, type ProjectChatFetcher, type ProjectGatheringPorts } from "./project-gathering.js";
|
|
4
4
|
import { type EvidenceMentions } from "./project-gathering-mentions.js";
|
|
5
|
+
import { type PurposeConfig } from "./project-purposes.js";
|
|
5
6
|
export interface StoredProjectInput {
|
|
6
7
|
project: string;
|
|
7
8
|
/** The sources the host resolved from its `--source`, and those it could not read. */
|
|
@@ -31,19 +32,81 @@ export interface StoredProjectInput {
|
|
|
31
32
|
maxFetchMs?: number;
|
|
32
33
|
onFetchProgress?: ProjectGatheringPorts["onFetchProgress"];
|
|
33
34
|
signal?: AbortSignal;
|
|
35
|
+
/** The linked chats' purposes to read; `["all"]` reads every one. Left out, the config's defaults. */
|
|
36
|
+
purposes?: readonly string[];
|
|
37
|
+
/** The host's purposes; core's built-in ones when left out. */
|
|
38
|
+
purposeConfig?: PurposeConfig;
|
|
39
|
+
/** Proposed `member-of` links count as linked chats and members too, after the confirmed ones, every item marked. */
|
|
40
|
+
includeProposed?: boolean;
|
|
41
|
+
/** How many messages a left-out chat holds in the window; the store's own count when left out. */
|
|
42
|
+
count?: ChatCounter;
|
|
34
43
|
}
|
|
44
|
+
export interface ChatCount {
|
|
45
|
+
messages: number;
|
|
46
|
+
firstAt: string | null;
|
|
47
|
+
lastAt: string | null;
|
|
48
|
+
}
|
|
49
|
+
export type ChatCounter = (account: AccountKey, chatId: string, window: {
|
|
50
|
+
since: number;
|
|
51
|
+
until: number;
|
|
52
|
+
}) => Promise<ChatCount>;
|
|
53
|
+
/** The store's count of one chat's messages in a window, `until` exclusive, and the first and last of them. */
|
|
54
|
+
export declare const storedChatCount: (store: MessageStore) => ChatCounter;
|
|
35
55
|
/** `project:<id>`, or the one project a key, name or alias names; several or none is refused, never guessed. */
|
|
36
56
|
export declare const canonicalProjectRef: (store: MessageStore, reference: string) => Promise<string>;
|
|
57
|
+
export interface ProjectMember {
|
|
58
|
+
person: string;
|
|
59
|
+
state: "read" | "member-limit" | "no-identity";
|
|
60
|
+
/** The proposal that names this person a member; read only with `includeProposed`. */
|
|
61
|
+
proposed?: string;
|
|
62
|
+
score?: number;
|
|
63
|
+
}
|
|
37
64
|
export interface LinkedChat {
|
|
38
65
|
provider: string;
|
|
39
66
|
account: string;
|
|
40
67
|
chat: string;
|
|
41
68
|
/** The link's free role (`source report`, `what worked`), if the owner set one. */
|
|
42
69
|
role: string | null;
|
|
70
|
+
/** What the chat is for in the project; `null` means `work`. */
|
|
71
|
+
purpose: string | null;
|
|
72
|
+
/** The confirmed link's id; null for a proposed one. */
|
|
73
|
+
relationship: string | null;
|
|
74
|
+
/** The proposal that links the chat; only with `includeProposed`. */
|
|
75
|
+
proposed?: string;
|
|
76
|
+
score?: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Links the owner confirmed, then, when asked, the proposed ones: a suggestion would put a whole chat into the
|
|
80
|
+
* evidence on a guess, so it is read only on request and marked.
|
|
81
|
+
*/
|
|
82
|
+
export declare const linkedChatsOf: (store: MessageStore, project: string | null, options?: {
|
|
83
|
+
includeProposed?: boolean;
|
|
84
|
+
}) => Promise<LinkedChat[]>;
|
|
85
|
+
/** A linked chat whose purpose this run did not read: counted, never listed. */
|
|
86
|
+
export interface LeftOutChat {
|
|
87
|
+
ref: string;
|
|
88
|
+
provider: string;
|
|
89
|
+
account: string;
|
|
90
|
+
chat: string;
|
|
91
|
+
title: string | null;
|
|
92
|
+
purpose: string;
|
|
93
|
+
relationship: string | null;
|
|
94
|
+
proposed?: string;
|
|
95
|
+
messages: number;
|
|
96
|
+
firstAt: string | null;
|
|
97
|
+
lastAt: string | null;
|
|
43
98
|
}
|
|
44
|
-
|
|
45
|
-
export
|
|
46
|
-
|
|
99
|
+
type Gathered = Awaited<ReturnType<typeof gatherProjectEvidence>>;
|
|
100
|
+
export type StoredProjectGathering = Omit<Gathered, "items" | "linkedChats"> & {
|
|
101
|
+
/** `proposed`: the proposal whose link alone brought the item in; only with `includeProposed`. */
|
|
102
|
+
items: (Gathered["items"][number] & {
|
|
103
|
+
proposed?: string;
|
|
104
|
+
})[];
|
|
105
|
+
linkedChats: (Gathered["linkedChats"][number] & {
|
|
106
|
+
purpose: string;
|
|
107
|
+
proposed?: string;
|
|
108
|
+
score?: number;
|
|
109
|
+
})[];
|
|
47
110
|
projectRef: string | null;
|
|
48
111
|
unreadSources: {
|
|
49
112
|
source: string;
|
|
@@ -51,9 +114,21 @@ export type StoredProjectGathering = Awaited<ReturnType<typeof gatherProjectEvid
|
|
|
51
114
|
reason?: "out-of-scope" | "linked-chat-limit" | "member-limit" | "no-identity" | "unknown-provider";
|
|
52
115
|
}[];
|
|
53
116
|
mentions: EvidenceMentions;
|
|
117
|
+
/** The purposes read: `all`, or the list. */
|
|
118
|
+
purposes: "all" | string[];
|
|
119
|
+
leftOut: LeftOutChat[];
|
|
120
|
+
members: ProjectMember[];
|
|
121
|
+
/** One linked chat with no purpose set gave most of this run's items; a first run only, so it is said once. */
|
|
122
|
+
purposeHint: {
|
|
123
|
+
chat: string;
|
|
124
|
+
relationship: string;
|
|
125
|
+
items: number;
|
|
126
|
+
of: number;
|
|
127
|
+
} | null;
|
|
54
128
|
};
|
|
55
129
|
/**
|
|
56
130
|
* `projects evidence` over a whole store: the project's names, key and aliases, the given words and contacts, its
|
|
57
131
|
* confirmed person members and its confirmed linked chats, in the sources the host resolved.
|
|
58
132
|
*/
|
|
59
133
|
export declare const gatherStoredProject: (store: MessageStore, input: StoredProjectInput) => Promise<StoredProjectGathering>;
|
|
134
|
+
export {};
|