@wirecat/cli-core 0.516.0 → 0.518.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 +31 -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/project-evidence.d.ts +96 -0
- package/dist/cli/project-evidence.js +14 -5
- package/dist/entities/fact.d.ts +2 -0
- package/dist/entities/fact.js +2 -0
- package/dist/entities/index.d.ts +3 -0
- package/dist/entities/memory.d.ts +3 -0
- package/dist/entities/memory.js +3 -0
- package/dist/services/archive.js +1 -1
- package/dist/store/evidence-findings.d.ts +3 -0
- package/dist/store/sqlite/evidence-findings.js +3 -1
- package/dist/store/sqlite/evidence-sources.d.ts +2 -0
- package/dist/store/sqlite/evidence-sources.js +35 -11
- package/dist/store/sqlite/facts.d.ts +1 -0
- package/dist/store/sqlite/facts.js +2 -2
- package/dist/store/sqlite/manifest.js +1 -0
- package/dist/store/sqlite/meeting-findings.js +1 -1
- package/dist/store/sqlite/memories.d.ts +19 -0
- package/dist/store/sqlite/memories.js +28 -4
- package/dist/store/sqlite/migrations.generated.js +7 -0
- package/dist/store/sqlite/proposed-actions.js +10 -4
- package/dist/store/sqlite/schema.d.ts +16 -0
- package/dist/store/sqlite/schema.js +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,37 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.518.0 — 01.11.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **A memory names its project (store version 43)**: `memories.add({ project })`, `memories.list({ project })` and
|
|
15
|
+
`memories.update(ref, { project })`, which moves a memory to another project or to none with `null`. A memory and a
|
|
16
|
+
fact carry `project` and `projectName` (`project` and `project_name` in the `memory` and `fact` entities), so a reader
|
|
17
|
+
needs no second lookup for the name.
|
|
18
|
+
- **`evidenceFindings.sources(id)`** reads each reference a findings batch cites back once, in the shape of
|
|
19
|
+
`memories.sources`: `kind`, `title`, `sender`, `at`, an excerpt of at most 280 characters, `tool` and `missing`.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **A findings batch closed with nothing accepted reads as `rejected`** in `proposedActions.get` and `list`, and
|
|
24
|
+
`list({ status: "rejected" })` finds it. The store still writes it `approved`, so older builds read it as before.
|
|
25
|
+
|
|
26
|
+
## 0.517.0 — 01.11.2026
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **`withSyncCommands(found, sync)`** gives each linked chat of a stored project gathering its `syncCommand`: the
|
|
31
|
+
line the text names to sync a chat held in part, or null. tg's and max's `projects evidence --json` carry it as
|
|
32
|
+
`linked_chats[].sync_command`, so an agent reading JSON gets the command the text shows.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **A chat that reaches its first message is no longer "held from <date> only"** in `missing`, `next` and the
|
|
37
|
+
`projects evidence` text: it is "held from its first message, behind since <date>", or "with gaps".
|
|
38
|
+
- **`messages sync` progress says how far back it got as a date** ("back to 2026-09-27"), not as a message key, which
|
|
39
|
+
for MAX was a number of milliseconds.
|
|
40
|
+
|
|
10
41
|
## 0.516.0 — 01.11.2026
|
|
11
42
|
|
|
12
43
|
### Fixed
|
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,
|
|
@@ -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/entities/fact.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export declare const fact: v.StrictObjectSchema<{
|
|
|
6
6
|
readonly text: v.StringSchema<undefined>;
|
|
7
7
|
readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed", "rejected"], undefined>;
|
|
8
8
|
readonly project: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
9
|
+
/** What the project is called. */
|
|
10
|
+
readonly project_name: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
9
11
|
readonly subject: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
10
12
|
/** What the subject is called; `null` for the owner and for a row with no name. */
|
|
11
13
|
readonly subject_name: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
package/dist/entities/fact.js
CHANGED
|
@@ -8,6 +8,8 @@ export const fact = v.strictObject({
|
|
|
8
8
|
text,
|
|
9
9
|
status: v.picklist(FACT_STATUSES),
|
|
10
10
|
project: v.nullable(text),
|
|
11
|
+
/** What the project is called. */
|
|
12
|
+
project_name: v.optional(v.nullable(text)),
|
|
11
13
|
subject: v.nullable(reference),
|
|
12
14
|
/** What the subject is called; `null` for the owner and for a row with no name. */
|
|
13
15
|
subject_name: v.optional(v.nullable(text)),
|
package/dist/entities/index.d.ts
CHANGED
|
@@ -5369,6 +5369,7 @@ export declare const ENTITIES: {
|
|
|
5369
5369
|
readonly text: v.StringSchema<undefined>;
|
|
5370
5370
|
readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed", "rejected"], undefined>;
|
|
5371
5371
|
readonly project: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5372
|
+
readonly project_name: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
5372
5373
|
readonly subject: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
5373
5374
|
readonly subject_name: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
5374
5375
|
readonly subject_is_owner: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
@@ -5408,6 +5409,8 @@ export declare const ENTITIES: {
|
|
|
5408
5409
|
readonly subject: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
|
|
5409
5410
|
readonly subject_name: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
5410
5411
|
readonly subject_is_owner: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
5412
|
+
readonly project: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>, undefined>;
|
|
5413
|
+
readonly project_name: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
5411
5414
|
readonly author: v.StrictObjectSchema<{
|
|
5412
5415
|
readonly type: v.PicklistSchema<["person", "bot"], undefined>;
|
|
5413
5416
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
@@ -8,6 +8,9 @@ export declare const memory: v.StrictObjectSchema<{
|
|
|
8
8
|
/** What the subject is called; `null` for the owner and for a row with no name. */
|
|
9
9
|
readonly subject_name: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
10
10
|
readonly subject_is_owner: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
11
|
+
/** The project it belongs to, and what that project is called. */
|
|
12
|
+
readonly project: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>, undefined>;
|
|
13
|
+
readonly project_name: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
11
14
|
readonly author: v.StrictObjectSchema<{
|
|
12
15
|
readonly type: v.PicklistSchema<["person", "bot"], undefined>;
|
|
13
16
|
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
package/dist/entities/memory.js
CHANGED
|
@@ -10,6 +10,9 @@ export const memory = v.strictObject({
|
|
|
10
10
|
/** What the subject is called; `null` for the owner and for a row with no name. */
|
|
11
11
|
subject_name: v.optional(v.nullable(text)),
|
|
12
12
|
subject_is_owner: v.optional(v.boolean()),
|
|
13
|
+
/** The project it belongs to, and what that project is called. */
|
|
14
|
+
project: v.optional(v.nullable(reference)),
|
|
15
|
+
project_name: v.optional(v.nullable(text)),
|
|
13
16
|
author,
|
|
14
17
|
model: v.nullable(text),
|
|
15
18
|
confidence: v.nullable(v.number()),
|
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
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type FindingOutcome, type FindingsInput, type FindingsReceipt } from "../meetings/index.js";
|
|
2
|
+
import type { EvidenceSource } from "./sqlite/evidence-sources.js";
|
|
2
3
|
export declare const EVIDENCE_FINDINGS_KIND = "evidence_findings";
|
|
3
4
|
export declare const validateEvidenceFindings: (input: FindingsInput) => FindingsInput;
|
|
4
5
|
/** Who decided: the owner, or an agent the host let decide for the owner, with its reason. */
|
|
@@ -60,4 +61,6 @@ export interface EvidenceFindingsStore {
|
|
|
60
61
|
accept: string[];
|
|
61
62
|
by?: EvidenceFindingsDecider;
|
|
62
63
|
}): Promise<EvidenceFindingsBatch>;
|
|
64
|
+
/** Each reference the batch cites, read back once; one gone from the store since is `missing`. */
|
|
65
|
+
sources(id: string): Promise<EvidenceSource[]>;
|
|
63
66
|
}
|
|
@@ -3,6 +3,7 @@ import { contextEvidenceFingerprint } from "../../context/evidence.js";
|
|
|
3
3
|
import { findingFingerprint, findingHash } from "../../meetings/index.js";
|
|
4
4
|
import { EVIDENCE_FINDINGS_KIND, validateEvidenceFindings, } from "../evidence-findings.js";
|
|
5
5
|
import { contextRecordOf } from "./context-evidence.js";
|
|
6
|
+
import { sourcesOfReferences } from "./evidence-sources.js";
|
|
6
7
|
import { findingsAgent, findingsCoreOver } from "./findings-core.js";
|
|
7
8
|
import { authorActor } from "./memories.js";
|
|
8
9
|
import { proposalOperationsOver } from "./proposed-actions.js";
|
|
@@ -34,7 +35,7 @@ export const evidenceFindingsStoreOver = (context) => {
|
|
|
34
35
|
};
|
|
35
36
|
const view = (proposal) => {
|
|
36
37
|
const input = proposal.payload;
|
|
37
|
-
if (!input || !proposal.target || !["proposed", "approved"].includes(proposal.status))
|
|
38
|
+
if (!input || !proposal.target || !["proposed", "approved", "rejected"].includes(proposal.status))
|
|
38
39
|
return invalid("invalid stored findings batch");
|
|
39
40
|
return {
|
|
40
41
|
id: proposal.id,
|
|
@@ -123,6 +124,7 @@ export const evidenceFindingsStoreOver = (context) => {
|
|
|
123
124
|
});
|
|
124
125
|
},
|
|
125
126
|
get: async (id) => view(load(id)),
|
|
127
|
+
sources: async (id) => sourcesOfReferences(database, view(load(id)).input.findings.flatMap((finding) => finding.evidence.map((one) => one.reference))),
|
|
126
128
|
list: async ({ project, limit = 20, offset = 0, status } = {}) => {
|
|
127
129
|
if (status !== undefined && status !== "proposed" && status !== "reviewed")
|
|
128
130
|
return invalid("finding status is proposed or reviewed");
|
|
@@ -21,3 +21,5 @@ export declare const evidenceSourcesOf: (database: CacheDatabase, from: {
|
|
|
21
21
|
type: string;
|
|
22
22
|
id: number;
|
|
23
23
|
}) => EvidenceSource[];
|
|
24
|
+
/** Each distinct reference read back, in order; one the store no longer resolves keeps its reference, `missing`. */
|
|
25
|
+
export declare const sourcesOfReferences: (database: CacheDatabase, references: readonly string[]) => EvidenceSource[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { linkKind } from "./link-kinds.js";
|
|
2
|
-
import { referenceOfThing, stateOfThing, storedThing } from "./things.js";
|
|
2
|
+
import { canonicalThingReference, referenceOfThing, stateOfThing, storedThing, thingOf } from "./things.js";
|
|
3
3
|
const EXCERPT_CHARS = 280;
|
|
4
4
|
const excerptOf = (text) => {
|
|
5
5
|
if (text == null)
|
|
@@ -32,16 +32,7 @@ const KIND = {
|
|
|
32
32
|
meeting_transcript: "meeting",
|
|
33
33
|
meeting_transcript_row: "meeting",
|
|
34
34
|
};
|
|
35
|
-
|
|
36
|
-
* Every `evidence` link from a memory or fact, in the order they were written, including one whose target was
|
|
37
|
-
* deleted since: `evidenceOf` drops those, so a reader would never learn the record lost a source.
|
|
38
|
-
*/
|
|
39
|
-
export const evidenceSourcesOf = (database, from) => database
|
|
40
|
-
.prepare(`SELECT to_type, to_id FROM links WHERE from_type = ? AND from_id = ? AND kind = ${linkKind("evidence")} ` +
|
|
41
|
-
"AND to_id IS NOT NULL ORDER BY id")
|
|
42
|
-
.all(from.type, from.id)
|
|
43
|
-
.map((link) => {
|
|
44
|
-
const thing = storedThing(link.to_type, link.to_id);
|
|
35
|
+
const sourceOf = (database, thing) => {
|
|
45
36
|
const state = stateOfThing(database, thing);
|
|
46
37
|
const read = READS[thing.type];
|
|
47
38
|
const row = read === undefined || state === "unavailable" ? undefined : database.prepare(read).get(thing.id);
|
|
@@ -56,4 +47,37 @@ export const evidenceSourcesOf = (database, from) => database
|
|
|
56
47
|
tool: textOf(row?.tool),
|
|
57
48
|
missing: gone,
|
|
58
49
|
};
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Every `evidence` link from a memory or fact, in the order they were written, including one whose target was
|
|
53
|
+
* deleted since: `evidenceOf` drops those, so a reader would never learn the record lost a source.
|
|
54
|
+
*/
|
|
55
|
+
export const evidenceSourcesOf = (database, from) => database
|
|
56
|
+
.prepare(`SELECT to_type, to_id FROM links WHERE from_type = ? AND from_id = ? AND kind = ${linkKind("evidence")} ` +
|
|
57
|
+
"AND to_id IS NOT NULL ORDER BY id")
|
|
58
|
+
.all(from.type, from.id)
|
|
59
|
+
.map((link) => sourceOf(database, storedThing(link.to_type, link.to_id)));
|
|
60
|
+
const thingNamed = (database, reference) => {
|
|
61
|
+
try {
|
|
62
|
+
return thingOf(database, canonicalThingReference(reference));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
/** Each distinct reference read back, in order; one the store no longer resolves keeps its reference, `missing`. */
|
|
69
|
+
export const sourcesOfReferences = (database, references) => [...new Set(references)].map((reference) => {
|
|
70
|
+
const thing = thingNamed(database, reference);
|
|
71
|
+
if (thing)
|
|
72
|
+
return { ...sourceOf(database, thing), ref: reference };
|
|
73
|
+
return {
|
|
74
|
+
ref: reference,
|
|
75
|
+
kind: reference.startsWith("msg:") ? "message" : reference.split(":")[0] || "unknown",
|
|
76
|
+
title: null,
|
|
77
|
+
sender: null,
|
|
78
|
+
at: null,
|
|
79
|
+
excerpt: null,
|
|
80
|
+
tool: null,
|
|
81
|
+
missing: true,
|
|
82
|
+
};
|
|
59
83
|
});
|
|
@@ -12,6 +12,7 @@ export interface Fact {
|
|
|
12
12
|
/** Proposed, then accepted or rejected; a correction supersedes it; only a decision is reversed. */
|
|
13
13
|
status: (typeof FACT_STATUSES)[number];
|
|
14
14
|
project: string | null;
|
|
15
|
+
projectName: string | null;
|
|
15
16
|
subject: string | null;
|
|
16
17
|
subjectName: string | null;
|
|
17
18
|
subjectIsOwner: boolean;
|
|
@@ -3,7 +3,7 @@ import { FACT_TYPES, MEMORY_SCOPES } from "../../domain/knowledge.js";
|
|
|
3
3
|
import { calendarMoment } from "../../domain/moment.js";
|
|
4
4
|
import { evidenceSourcesOf } from "./evidence-sources.js";
|
|
5
5
|
import { linkKind } from "./link-kinds.js";
|
|
6
|
-
import { authorActor, authorOf, dropLinksOf, erase, evidenceOf, keepHighestId, linkEvidence, nextId, subjectOf, } from "./memories.js";
|
|
6
|
+
import { authorActor, authorOf, dropLinksOf, erase, evidenceOf, keepHighestId, linkEvidence, nextId, projectOf, subjectOf, } from "./memories.js";
|
|
7
7
|
import { requiredThing } from "./notes.js";
|
|
8
8
|
import { write } from "./transaction.js";
|
|
9
9
|
const NAMED = "SELECT f.*, b.name AS author_name FROM facts f LEFT JOIN bots b ON f.author_type = 'bot' AND b.id = f.author_id";
|
|
@@ -21,7 +21,7 @@ export const factOperationsOver = (context) => {
|
|
|
21
21
|
type: row.type,
|
|
22
22
|
statement: String(row.statement),
|
|
23
23
|
status: row.status,
|
|
24
|
-
|
|
24
|
+
...projectOf(database, row),
|
|
25
25
|
...subjectOf(database, row),
|
|
26
26
|
occurredAt: row.occurred_at == null ? null : iso(row.occurred_at),
|
|
27
27
|
supersedes: row.supersedes_id == null ? null : `fact:${row.supersedes_id}`,
|
|
@@ -56,6 +56,7 @@ export const MANIFEST = [
|
|
|
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
58
|
{ name: "20261101002523_version-42-task-revision", version: 42, minCompatible: 34 },
|
|
59
|
+
{ name: "20261101011859_version-43-memory-project", version: 43, minCompatible: 34 },
|
|
59
60
|
];
|
|
60
61
|
export const generatedMigrations = (manifest = MANIFEST, generated = GENERATED) => {
|
|
61
62
|
const migrations = [];
|
|
@@ -43,7 +43,7 @@ export const meetingFindingsStoreOver = (context) => {
|
|
|
43
43
|
};
|
|
44
44
|
const view = (proposal) => {
|
|
45
45
|
const input = proposal.payload;
|
|
46
|
-
if (!input || !proposal.account || !["proposed", "approved"].includes(proposal.status))
|
|
46
|
+
if (!input || !proposal.account || !["proposed", "approved", "rejected"].includes(proposal.status))
|
|
47
47
|
return invalid("invalid stored findings batch");
|
|
48
48
|
return {
|
|
49
49
|
id: proposal.id,
|
|
@@ -19,6 +19,9 @@ export interface Memory {
|
|
|
19
19
|
subjectName: string | null;
|
|
20
20
|
/** The subject is the owner. */
|
|
21
21
|
subjectIsOwner: boolean;
|
|
22
|
+
/** The project it belongs to, as `project:<id>`. */
|
|
23
|
+
project: string | null;
|
|
24
|
+
projectName: string | null;
|
|
22
25
|
/** `name` is a bot's handle; `null` for a person. */
|
|
23
26
|
author: {
|
|
24
27
|
type: Actor["type"];
|
|
@@ -45,6 +48,7 @@ export interface MemoryInput {
|
|
|
45
48
|
evidence: string[];
|
|
46
49
|
author: Author;
|
|
47
50
|
subject?: string;
|
|
51
|
+
project?: string;
|
|
48
52
|
confidence?: number;
|
|
49
53
|
status?: "proposed" | "confirmed";
|
|
50
54
|
/** The memory this one replaces; it becomes `superseded`. */
|
|
@@ -55,11 +59,16 @@ export interface MemoriesStore {
|
|
|
55
59
|
get(reference: string): Promise<Memory>;
|
|
56
60
|
list(options?: {
|
|
57
61
|
subject?: string;
|
|
62
|
+
project?: string;
|
|
58
63
|
kind?: Memory["kind"];
|
|
59
64
|
status?: Memory["status"];
|
|
60
65
|
scope?: Memory["scope"];
|
|
61
66
|
limit?: number;
|
|
62
67
|
}): Promise<Memory[]>;
|
|
68
|
+
/** Moves it to another project, or to none with `null`. */
|
|
69
|
+
update(reference: string, change: {
|
|
70
|
+
project: string | null;
|
|
71
|
+
}): Promise<Memory>;
|
|
63
72
|
/** The owner stands behind it; its evidence was checked now. */
|
|
64
73
|
confirm(reference: string): Promise<Memory>;
|
|
65
74
|
/** Its evidence no longer holds. */
|
|
@@ -100,6 +109,16 @@ export declare const evidenceOf: (context: StoreContext, from: {
|
|
|
100
109
|
}) => string[];
|
|
101
110
|
/** What a memory's or fact's subject is called, for a reader who should not have to look the reference up. */
|
|
102
111
|
export declare const subjectOf: (database: StoreContext["database"], row: Row) => Pick<Memory, "subject" | "subjectName" | "subjectIsOwner">;
|
|
112
|
+
/** A memory's or fact's project, with its name, so a reader does not look the project up. */
|
|
113
|
+
export declare const projectOf: (database: StoreContext["database"], row: Row) => {
|
|
114
|
+
project: null;
|
|
115
|
+
projectName: null;
|
|
116
|
+
} | {
|
|
117
|
+
project: string;
|
|
118
|
+
projectName: string | null;
|
|
119
|
+
};
|
|
120
|
+
/** A `project:<id>` the store holds, as its row id. */
|
|
121
|
+
export declare const projectId: (database: StoreContext["database"], reference: string) => number;
|
|
103
122
|
type Numbered = "memories" | "facts";
|
|
104
123
|
/**
|
|
105
124
|
* The id the next row takes: above every row there is and every one forgotten. SQLite hands out the highest id + 1,
|
|
@@ -64,6 +64,20 @@ export const subjectOf = (database, row) => {
|
|
|
64
64
|
subjectIsOwner: Number(named?.owner) === 1,
|
|
65
65
|
};
|
|
66
66
|
};
|
|
67
|
+
/** A memory's or fact's project, with its name, so a reader does not look the project up. */
|
|
68
|
+
export const projectOf = (database, row) => {
|
|
69
|
+
if (row.project_id == null)
|
|
70
|
+
return { project: null, projectName: null };
|
|
71
|
+
const named = database.prepare(SUBJECT_NAMES.project).get(Number(row.project_id));
|
|
72
|
+
return { project: `project:${row.project_id}`, projectName: named?.name == null ? null : String(named.name) };
|
|
73
|
+
};
|
|
74
|
+
/** A `project:<id>` the store holds, as its row id. */
|
|
75
|
+
export const projectId = (database, reference) => {
|
|
76
|
+
const project = requiredThing(database, reference, "the project");
|
|
77
|
+
if (project.type !== "project")
|
|
78
|
+
throw new CliError("validation_error", `${reference} is not a project`);
|
|
79
|
+
return project.id;
|
|
80
|
+
};
|
|
67
81
|
const highestKey = (table) => `ids.${table}.highest`;
|
|
68
82
|
/**
|
|
69
83
|
* The id the next row takes: above every row there is and every one forgotten. SQLite hands out the highest id + 1,
|
|
@@ -118,6 +132,7 @@ export const memoriesStoreOver = (context) => {
|
|
|
118
132
|
kind: row.kind,
|
|
119
133
|
body: String(row.body),
|
|
120
134
|
...subjectOf(database, row),
|
|
135
|
+
...projectOf(database, row),
|
|
121
136
|
author: authorOf(row),
|
|
122
137
|
model: row.model == null ? null : String(row.model),
|
|
123
138
|
confidence: row.confidence == null ? null : Number(row.confidence),
|
|
@@ -167,12 +182,14 @@ export const memoriesStoreOver = (context) => {
|
|
|
167
182
|
const id = write(database, () => {
|
|
168
183
|
const { actor, model } = authorActor(context, input.author);
|
|
169
184
|
const subject = input.subject === undefined ? undefined : requiredThing(database, input.subject, "the subject");
|
|
185
|
+
const project = input.project === undefined ? null : projectId(database, input.project);
|
|
170
186
|
const replaced = input.supersedes === undefined ? undefined : Number(rowOf(input.supersedes).id);
|
|
171
187
|
const at = now();
|
|
172
188
|
const made = Number(database
|
|
173
189
|
.prepare("INSERT INTO memories (id, kind, body, subject_type, subject_id, author_type, author_id, model, confidence, status, " +
|
|
174
|
-
"last_verified_at, supersedes_id, scope, created_at, updated_at)
|
|
175
|
-
|
|
190
|
+
"last_verified_at, supersedes_id, scope, project_id, created_at, updated_at) " +
|
|
191
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id")
|
|
192
|
+
.get(nextId(database, "memories"), kind, body, subject?.type ?? null, subject?.id ?? null, actor.type, actor.id, model, input.confidence ?? null, status, status === "confirmed" ? at : null, replaced ?? null, scope, project, at, at)?.id);
|
|
176
193
|
linkEvidence(context, { type: "memory", id: made }, input.evidence, input.author === "owner" ? "owner" : "agent");
|
|
177
194
|
if (replaced !== undefined)
|
|
178
195
|
database.prepare("UPDATE memories SET status = 'superseded', updated_at = ? WHERE id = ?").run(at, replaced);
|
|
@@ -183,13 +200,20 @@ export const memoriesStoreOver = (context) => {
|
|
|
183
200
|
get: async (reference) => memoryOf(rowOf(reference)),
|
|
184
201
|
list: async (options = {}) => {
|
|
185
202
|
const subject = options.subject === undefined ? undefined : requiredThing(database, options.subject);
|
|
203
|
+
const project = options.project === undefined ? null : projectId(database, options.project);
|
|
186
204
|
return database
|
|
187
205
|
.prepare(`${NAMED} WHERE m.deleted_at IS NULL AND (? IS NULL OR (m.subject_type = ? AND m.subject_id = ?)) ` +
|
|
188
|
-
"AND (? IS NULL OR m.kind = ?) AND (? IS NULL OR m.status = ?) AND (? IS NULL OR m.scope = ?) " +
|
|
206
|
+
"AND (? IS NULL OR m.project_id = ?) AND (? IS NULL OR m.kind = ?) AND (? IS NULL OR m.status = ?) AND (? IS NULL OR m.scope = ?) " +
|
|
189
207
|
"ORDER BY m.created_at DESC, m.id DESC LIMIT ?")
|
|
190
|
-
.all(subject?.type ?? null, subject?.type ?? null, subject?.id ?? null, options.kind ?? null, options.kind ?? null, options.status ?? null, options.status ?? null, options.scope ?? null, options.scope ?? null, Math.min(Math.max(options.limit ?? 100, 1), 500))
|
|
208
|
+
.all(subject?.type ?? null, subject?.type ?? null, subject?.id ?? null, project, project, options.kind ?? null, options.kind ?? null, options.status ?? null, options.status ?? null, options.scope ?? null, options.scope ?? null, Math.min(Math.max(options.limit ?? 100, 1), 500))
|
|
191
209
|
.map(memoryOf);
|
|
192
210
|
},
|
|
211
|
+
update: async (reference, change) => write(database, () => {
|
|
212
|
+
const id = Number(rowOf(reference).id);
|
|
213
|
+
const project = change.project === null ? null : projectId(database, change.project);
|
|
214
|
+
database.prepare("UPDATE memories SET project_id = ?, updated_at = ? WHERE id = ?").run(project, now(), id);
|
|
215
|
+
return memoryOf(rowOf(`memory:${id}`));
|
|
216
|
+
}),
|
|
193
217
|
confirm: async (reference) => setStatus(reference, "confirmed", true),
|
|
194
218
|
markStale: async (reference) => setStatus(reference, "stale", false),
|
|
195
219
|
remove: async (reference) => {
|
|
@@ -870,5 +870,12 @@ export const GENERATED = [
|
|
|
870
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
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
872
|
]
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
"name": "20261101011859_version-43-memory-project",
|
|
876
|
+
"statements": [
|
|
877
|
+
"ALTER TABLE `memories` ADD `project_id` integer REFERENCES projects(id);",
|
|
878
|
+
"CREATE INDEX `memories_by_project_id` ON `memories` (`project_id`);"
|
|
879
|
+
]
|
|
873
880
|
}
|
|
874
881
|
];
|
|
@@ -8,9 +8,15 @@ import { write } from "./transaction.js";
|
|
|
8
8
|
export const PROPOSAL_STATUSES = ["proposed", "approved", "rejected", "executed", "failed"];
|
|
9
9
|
export const PROPOSAL_VERDICTS = ["useful", "not_useful"];
|
|
10
10
|
const iso = (value) => (value == null ? null : new Date(Number(value)).toISOString());
|
|
11
|
-
const NAMED = "SELECT p.*, b.name AS proposer_name FROM proposed_actions p " +
|
|
12
|
-
"LEFT JOIN bots b ON p.proposed_by_type = 'bot' AND b.id = p.proposed_by_id";
|
|
13
11
|
const json = (value) => (value == null ? null : JSON.parse(String(value)));
|
|
12
|
+
/**
|
|
13
|
+
* A findings batch closed with nothing accepted is stored `approved`, as older builds read it; it reads as
|
|
14
|
+
* `rejected`, the decision the owner made.
|
|
15
|
+
*/
|
|
16
|
+
const STATUS = `CASE WHEN p.status = 'approved' AND p.kind IN ('${MEETING_FINDINGS_KIND}', '${EVIDENCE_FINDINGS_KIND}') ` +
|
|
17
|
+
"AND json_array_length(p.result, '$.accepted') = 0 THEN 'rejected' ELSE p.status END";
|
|
18
|
+
const NAMED = `SELECT p.*, ${STATUS} AS shown_status, b.name AS proposer_name FROM proposed_actions p ` +
|
|
19
|
+
"LEFT JOIN bots b ON p.proposed_by_type = 'bot' AND b.id = p.proposed_by_id";
|
|
14
20
|
export const proposalOperationsOver = (context) => {
|
|
15
21
|
const { database, now } = context;
|
|
16
22
|
const actionOf = (row) => {
|
|
@@ -26,7 +32,7 @@ export const proposalOperationsOver = (context) => {
|
|
|
26
32
|
: (referenceOfThing(database, storedThing(row.target_type, row.target_id)) ?? null),
|
|
27
33
|
payload: json(row.payload),
|
|
28
34
|
reason: row.reason == null ? null : String(row.reason),
|
|
29
|
-
status: row.
|
|
35
|
+
status: row.shown_status,
|
|
30
36
|
proposedBy: {
|
|
31
37
|
type: row.proposed_by_type,
|
|
32
38
|
id: String(row.proposed_by_id),
|
|
@@ -106,7 +112,7 @@ export const proposalOperationsOver = (context) => {
|
|
|
106
112
|
if (status !== undefined && !PROPOSAL_STATUSES.includes(status))
|
|
107
113
|
throw new CliError("validation_error", `a status is one of ${PROPOSAL_STATUSES.join(", ")}`);
|
|
108
114
|
return database
|
|
109
|
-
.prepare(`${NAMED} WHERE (? IS NULL OR
|
|
115
|
+
.prepare(`${NAMED} WHERE (? IS NULL OR ${STATUS} = ?) ORDER BY p.created_at DESC, p.id DESC LIMIT ?`)
|
|
110
116
|
.all(status ?? null, status ?? null, Math.min(Math.max(limit, 1), 500))
|
|
111
117
|
.map(actionOf);
|
|
112
118
|
},
|
|
@@ -8226,6 +8226,22 @@ export declare const memories: import("drizzle-orm/sqlite-core").SQLiteTableWith
|
|
|
8226
8226
|
identity: undefined;
|
|
8227
8227
|
generated: undefined;
|
|
8228
8228
|
}, {}>;
|
|
8229
|
+
projectId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
8230
|
+
name: string;
|
|
8231
|
+
tableName: "memories";
|
|
8232
|
+
dataType: "number int53";
|
|
8233
|
+
data: number;
|
|
8234
|
+
driverParam: number;
|
|
8235
|
+
notNull: false;
|
|
8236
|
+
hasDefault: false;
|
|
8237
|
+
isPrimaryKey: false;
|
|
8238
|
+
isAutoincrement: false;
|
|
8239
|
+
hasRuntimeDefault: false;
|
|
8240
|
+
enumValues: undefined;
|
|
8241
|
+
baseColumn: never;
|
|
8242
|
+
identity: undefined;
|
|
8243
|
+
generated: undefined;
|
|
8244
|
+
}, {}>;
|
|
8229
8245
|
};
|
|
8230
8246
|
dialect: 'sqlite';
|
|
8231
8247
|
isAlias: false;
|
|
@@ -872,10 +872,12 @@ export const memories = sqliteTable("memories", {
|
|
|
872
872
|
createdAt: integer("created_at").notNull(),
|
|
873
873
|
updatedAt: integer("updated_at").notNull(),
|
|
874
874
|
deletedAt: integer("deleted_at"),
|
|
875
|
+
projectId: integer("project_id").references(() => projects.id),
|
|
875
876
|
}, (table) => [
|
|
876
877
|
index("memories_by_subject_type_subject_id").on(table.subjectType, table.subjectId),
|
|
877
878
|
index("memories_by_author_type_author_id").on(table.authorType, table.authorId),
|
|
878
879
|
index("memories_by_supersedes_id").on(table.supersedesId),
|
|
880
|
+
index("memories_by_project_id").on(table.projectId),
|
|
879
881
|
]);
|
|
880
882
|
export const memoryIndexPending = sqliteTable("memory_index_pending", {
|
|
881
883
|
id: integer("id").notNull(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.518.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",
|