@wirecat/cli-core 0.361.0 → 0.363.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 +30 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/meeting-findings-input.d.ts +2 -1
- package/dist/cli/meeting-findings-input.js +10 -7
- package/dist/cli/program.d.ts +1 -1
- package/dist/cli/program.js +1 -1
- package/dist/context/evidence.d.ts +6 -0
- package/dist/context/evidence.js +4 -0
- package/dist/errors/reasons.js +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/meetings/findings-rules.d.ts +60 -0
- package/dist/meetings/findings-rules.js +117 -0
- package/dist/meetings/findings.d.ts +10 -48
- package/dist/meetings/findings.js +10 -116
- package/dist/meetings/index.d.ts +1 -0
- package/dist/meetings/index.js +1 -0
- package/dist/services/context-evidence.d.ts +2 -1
- package/dist/services/context-evidence.js +1 -0
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.js +2 -1
- package/dist/services/project-gathering.d.ts +1 -0
- package/dist/services/project-gathering.js +3 -0
- package/dist/services/proposal-tasks.js +5 -0
- package/dist/store/evidence-findings.d.ts +63 -0
- package/dist/store/evidence-findings.js +15 -0
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.js +1 -0
- package/dist/store/sqlite/context-evidence.d.ts +9 -1
- package/dist/store/sqlite/context-evidence.js +43 -1
- package/dist/store/sqlite/evidence-findings.d.ts +3 -0
- package/dist/store/sqlite/evidence-findings.js +172 -0
- package/dist/store/sqlite/findings-core.d.ts +17 -0
- package/dist/store/sqlite/findings-core.js +108 -0
- package/dist/store/sqlite/meeting-findings.js +47 -121
- package/dist/store/sqlite/proposed-actions.d.ts +4 -1
- package/dist/store/sqlite/proposed-actions.js +12 -4
- package/dist/store/store.d.ts +5 -1
- package/dist/store/store.js +10 -1
- package/dist/terminal/login-qr.d.ts +15 -11
- package/dist/terminal/login-qr.js +30 -42
- package/dist/terminal/qr.d.ts +7 -5
- package/dist/terminal/qr.js +21 -47
- package/package.json +1 -1
- package/parity.json +1 -7
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,36 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.363.0 — 19.10.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`store.evidenceFindings`: cited facts and follow-ups about one project from stored messages, mail and
|
|
15
|
+
documents, staged by an agent and reviewed** — reviewed meeting findings' rules over any stored reference. A
|
|
16
|
+
review is the owner's or an agent's deciding for the owner (`by: { bot, reason }`), recorded in the receipt.
|
|
17
|
+
Evidence is checked against the current text by fingerprint: project evidence items carry `fingerprint`, and
|
|
18
|
+
`store.contextRecords.read(ref)` reproduces it. Meeting and evidence findings share their validation and review
|
|
19
|
+
code (`validateFindings`); meeting findings behave as before. A host reads a staged batch from snake_case JSON
|
|
20
|
+
with `parseEvidenceFindingsJson` from `./cli`. `./services` also exports `contextEvidenceFingerprint`,
|
|
21
|
+
`ContextRecordReader` and `excerpt`, for a host that shows a cited record's text.
|
|
22
|
+
|
|
23
|
+
## 0.362.0 — 19.10.2026
|
|
24
|
+
|
|
25
|
+
### Changed — may break callers
|
|
26
|
+
|
|
27
|
+
- **Without a terminal, `loginQr` prints the QR code as text an agent pastes into the chat; the PNG is gone.** The
|
|
28
|
+
owner signed in through a PNG an agent opened in an image viewer, a window the person can lose. Now, when the
|
|
29
|
+
diagnostic stream is not a terminal (`terminal: false`, by default whether stderr is one), `show` prints
|
|
30
|
+
`plainQr(link)` on it: half blocks with no colours, the light modules and the quiet zone drawn, so the code reads
|
|
31
|
+
dark on light in a dark terminal theme and every line starts and ends with a block. A WhatsApp-sized code stays
|
|
32
|
+
under 80 columns. Then it notes `PASTE_QR` (paste the block in a code block, exactly as printed; paste a renewed one
|
|
33
|
+
and say the old one expired) and where to scan. With `json: true` each code is also one line on stderr,
|
|
34
|
+
`{"event":"qr","qr":{"text","expires_at","scan"}}`; stdout keeps only the answer. `show` takes the code's expiry as
|
|
35
|
+
a third argument. In a terminal the code is drawn as before.
|
|
36
|
+
- Removed: `loginQr`'s `qrFile` option, `LoginQr.path` and `LoginQr.close()`, `qrPng` and `qrNeedsTerminal`. A CLI
|
|
37
|
+
drops `--qr-file` and its refusal of a QR login without a terminal. `parity.json` no longer expects `--qr-file` on
|
|
38
|
+
`session start` or `setup`.
|
|
39
|
+
|
|
10
40
|
## 0.361.0 — 19.10.2026
|
|
11
41
|
|
|
12
42
|
### Fixed
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export { bufferedInput, type InputPolicy, inputPolicy, MAX_BUFFERED_INPUT, MAX_S
|
|
|
65
65
|
export { answerFields, checkJson, type EntityFields, entityFields, type JsonFieldsResolver, type JsonRequest, liftJson, } from "./json-fields.js";
|
|
66
66
|
export { commandPath, LIST_QUERY_GUIDE, listQuery, nativeField, nativeTerms, pagedList, pageOf, type QueryWords, queryArgument, queryHelp, queryTerms, searchText, termField, timeBounds, typedQuery, unfilteredQuery, withQuery, } from "./list-query.js";
|
|
67
67
|
export { addMeetingBatchCommand, formatMeetingBatch, prepareMeetingBatch } from "./meeting-batch.js";
|
|
68
|
-
export { parseMeetingFindingsJson } from "./meeting-findings-input.js";
|
|
68
|
+
export { parseEvidenceFindingsJson, parseMeetingFindingsJson } from "./meeting-findings-input.js";
|
|
69
69
|
export { meetingPreparationAnswer } from "./meeting-preparation-answer.js";
|
|
70
70
|
export { addMeetingPreparationCommand } from "./meeting-preparation-command.js";
|
|
71
71
|
export { type MeetingPreparationReader, meetingPreparationInput, readMeetingPreparation, } from "./meeting-preparation-read.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -60,7 +60,7 @@ export { bufferedInput, inputPolicy, MAX_BUFFERED_INPUT, MAX_SECRET_INPUT, provi
|
|
|
60
60
|
export { answerFields, checkJson, entityFields, liftJson, } from "./json-fields.js";
|
|
61
61
|
export { commandPath, LIST_QUERY_GUIDE, listQuery, nativeField, nativeTerms, pagedList, pageOf, queryArgument, queryHelp, queryTerms, searchText, termField, timeBounds, typedQuery, unfilteredQuery, withQuery, } from "./list-query.js";
|
|
62
62
|
export { addMeetingBatchCommand, formatMeetingBatch, prepareMeetingBatch } from "./meeting-batch.js";
|
|
63
|
-
export { parseMeetingFindingsJson } from "./meeting-findings-input.js";
|
|
63
|
+
export { parseEvidenceFindingsJson, parseMeetingFindingsJson } from "./meeting-findings-input.js";
|
|
64
64
|
export { meetingPreparationAnswer } from "./meeting-preparation-answer.js";
|
|
65
65
|
export { addMeetingPreparationCommand } from "./meeting-preparation-command.js";
|
|
66
66
|
export { meetingPreparationInput, readMeetingPreparation, } from "./meeting-preparation-read.js";
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import { type MeetingFindingsInput } from "../meetings/index.js";
|
|
1
|
+
import { type FindingsInput, type MeetingFindingsInput } from "../meetings/index.js";
|
|
2
2
|
export declare const parseMeetingFindingsJson: (text: string) => MeetingFindingsInput;
|
|
3
|
+
export declare const parseEvidenceFindingsJson: (text: string) => FindingsInput;
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { validateMeetingFindings } from "../meetings/index.js";
|
|
3
|
+
import { validateEvidenceFindings } from "../store/evidence-findings.js";
|
|
3
4
|
const keys = {
|
|
4
5
|
schema_version: "schemaVersion",
|
|
5
6
|
due_at: "dueAt",
|
|
6
7
|
due_evidence: "dueEvidence",
|
|
7
8
|
assignee_evidence: "assigneeEvidence",
|
|
8
9
|
};
|
|
9
|
-
|
|
10
|
+
const parseFindingsJson = (text, label, validate) => {
|
|
10
11
|
if (Buffer.byteLength(text, "utf8") > 65536)
|
|
11
|
-
throw new CliError("validation_error",
|
|
12
|
+
throw new CliError("validation_error", `${label} take at most 65536 UTF-8 bytes`);
|
|
12
13
|
let raw;
|
|
13
14
|
try {
|
|
14
15
|
raw = JSON.parse(text);
|
|
15
16
|
}
|
|
16
17
|
catch {
|
|
17
|
-
throw new CliError("validation_error",
|
|
18
|
+
throw new CliError("validation_error", `${label} input must be valid JSON`);
|
|
18
19
|
}
|
|
19
20
|
const decode = (value, depth = 0) => {
|
|
20
21
|
if (depth > 16)
|
|
21
|
-
throw new CliError("validation_error",
|
|
22
|
+
throw new CliError("validation_error", `${label} JSON is nested too deeply`);
|
|
22
23
|
if (Array.isArray(value))
|
|
23
24
|
return value.map((item) => decode(item, depth + 1));
|
|
24
25
|
if (value === null || typeof value !== "object")
|
|
@@ -26,13 +27,15 @@ export const parseMeetingFindingsJson = (text) => {
|
|
|
26
27
|
const output = {};
|
|
27
28
|
for (const [key, item] of Object.entries(value)) {
|
|
28
29
|
if (/[A-Z]/.test(key))
|
|
29
|
-
throw new CliError("validation_error",
|
|
30
|
+
throw new CliError("validation_error", `${label} JSON fields use snake_case`);
|
|
30
31
|
const name = keys[key] ?? key;
|
|
31
32
|
if (Object.hasOwn(output, name))
|
|
32
|
-
throw new CliError("validation_error",
|
|
33
|
+
throw new CliError("validation_error", `duplicate ${label} field`);
|
|
33
34
|
Object.defineProperty(output, name, { value: decode(item, depth + 1), enumerable: true });
|
|
34
35
|
}
|
|
35
36
|
return output;
|
|
36
37
|
};
|
|
37
|
-
return
|
|
38
|
+
return validate(decode(raw));
|
|
38
39
|
};
|
|
40
|
+
export const parseMeetingFindingsJson = (text) => parseFindingsJson(text, "meeting findings", validateMeetingFindings);
|
|
41
|
+
export const parseEvidenceFindingsJson = (text) => parseFindingsJson(text, "evidence findings", validateEvidenceFindings);
|
package/dist/cli/program.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface ProgramOptions {
|
|
|
29
29
|
export declare const createProgram: ({ app, commands, configure, firstScreen }: ProgramDefinition, { out, err }?: ProgramOptions) => Command;
|
|
30
30
|
export type RunOptions = BaseEnvironment & Record<string, unknown>;
|
|
31
31
|
/**
|
|
32
|
-
* An interactive command run without a terminal — an agent's `session start
|
|
32
|
+
* An interactive command run without a terminal — an agent's `session start qr` — waits on a person scanning a
|
|
33
33
|
* code somewhere else, which 30 s does not cover.
|
|
34
34
|
*/
|
|
35
35
|
export declare const HEADLESS_INTERACTIVE_MS: number;
|
package/dist/cli/program.js
CHANGED
|
@@ -93,7 +93,7 @@ class Relayed extends Error {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
|
-
* An interactive command run without a terminal — an agent's `session start
|
|
96
|
+
* An interactive command run without a terminal — an agent's `session start qr` — waits on a person scanning a
|
|
97
97
|
* code somewhere else, which 30 s does not cover.
|
|
98
98
|
*/
|
|
99
99
|
export const HEADLESS_INTERACTIVE_MS = 5 * 60_000;
|
|
@@ -63,6 +63,12 @@ export interface ContextIndex {
|
|
|
63
63
|
documents: number;
|
|
64
64
|
}>;
|
|
65
65
|
}
|
|
66
|
+
/** Changes whenever the cited text, its title or its revision does; a staged finding citing an older one is stale. */
|
|
67
|
+
export declare const contextEvidenceFingerprint: (item: Pick<ContextEvidenceItem, "ref" | "title" | "text" | "revision">) => string;
|
|
68
|
+
/** One stored message, email or document as the context reader returns it, by its reference. */
|
|
69
|
+
export interface ContextRecordReader {
|
|
70
|
+
read(ref: string): Promise<Pick<ContextEvidenceItem, "ref" | "title" | "text" | "revision"> | null>;
|
|
71
|
+
}
|
|
66
72
|
export declare const contextCursorScope: (request: Pick<ContextEvidenceRequest, "source" | "query" | "after" | "before">) => string;
|
|
67
73
|
export declare const contextSource: (value: ContextSource) => ContextSource;
|
|
68
74
|
export declare const contextEvidenceRequest: (input: ContextEvidenceRequest) => {
|
package/dist/context/evidence.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { CliError } from "@wirecat/cli-base";
|
|
3
3
|
export const CONTEXT_SOURCE_KINDS = ["message", "mail", "document", "meeting"];
|
|
4
|
+
/** Changes whenever the cited text, its title or its revision does; a staged finding citing an older one is stale. */
|
|
5
|
+
export const contextEvidenceFingerprint = (item) => createHash("sha256")
|
|
6
|
+
.update(JSON.stringify(["context-evidence-v1", item.ref, item.title, item.text, item.revision]))
|
|
7
|
+
.digest("hex");
|
|
4
8
|
export const contextCursorScope = (request) => createHash("sha256")
|
|
5
9
|
.update(JSON.stringify([
|
|
6
10
|
"context-evidence-v1",
|
package/dist/errors/reasons.js
CHANGED
|
@@ -18,6 +18,8 @@ const OTHERS = [
|
|
|
18
18
|
validation("context.read-budget", "the selected context reads exceed their text or request allowance"),
|
|
19
19
|
validation("meeting-findings.stale", "meeting findings changed since they were read; stage a new batch"),
|
|
20
20
|
validation("meeting-findings.dedicated-review", "review meeting findings through the dedicated findings command"),
|
|
21
|
+
validation("evidence-findings.stale", "evidence findings changed since they were read; stage a new batch"),
|
|
22
|
+
validation("evidence-findings.dedicated-review", "review evidence findings through the dedicated findings command"),
|
|
21
23
|
validation("document.changed", "the document changed while it was read; nothing was saved"),
|
|
22
24
|
["document.not-imported", "not_found", "the document is not imported in this source account"],
|
|
23
25
|
["extraction.invalid-proposals", "invalid_response", "the model's memory proposals could not be used"],
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,6 @@ export { defaultRules, ModerationRules, moderationPathFor } from "./moderation/r
|
|
|
22
22
|
export { type OutputOptions, resolveOutput } from "./output.js";
|
|
23
23
|
export { pollLines, type RenderOptions, renderMessage, renderMessages } from "./render/messages.js";
|
|
24
24
|
export { isId, type PeopleLookup, pickChat, pickPerson } from "./resolve.js";
|
|
25
|
-
export { atTerminal, type LoginQr, type LoginQrOptions, loginQr,
|
|
25
|
+
export { atTerminal, type LoginQr, type LoginQrOptions, loginQr, PASTE_QR, } from "./terminal/login-qr.js";
|
|
26
26
|
export { readSecret, type SecretInput } from "./terminal/prompt.js";
|
|
27
|
-
export {
|
|
27
|
+
export { plainQr, terminalQr } from "./terminal/qr.js";
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,6 @@ export { defaultRules, ModerationRules, moderationPathFor } from "./moderation/r
|
|
|
15
15
|
export { resolveOutput } from "./output.js";
|
|
16
16
|
export { pollLines, renderMessage, renderMessages } from "./render/messages.js";
|
|
17
17
|
export { isId, pickChat, pickPerson } from "./resolve.js";
|
|
18
|
-
export { atTerminal, loginQr,
|
|
18
|
+
export { atTerminal, loginQr, PASTE_QR, } from "./terminal/login-qr.js";
|
|
19
19
|
export { readSecret } from "./terminal/prompt.js";
|
|
20
|
-
export {
|
|
20
|
+
export { plainQr, terminalQr } from "./terminal/qr.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { MEETING_FACT_TYPES as FACT_TYPES } from "./meeting-context.js";
|
|
2
|
+
declare const TASK_TYPES: readonly ["bug", "feature", "chore"];
|
|
3
|
+
export interface FindingEvidence {
|
|
4
|
+
reference: string;
|
|
5
|
+
fingerprint: string;
|
|
6
|
+
}
|
|
7
|
+
interface FindingBase {
|
|
8
|
+
id: string;
|
|
9
|
+
project: string;
|
|
10
|
+
evidence: FindingEvidence[];
|
|
11
|
+
}
|
|
12
|
+
export interface FactFinding extends FindingBase {
|
|
13
|
+
kind: "fact";
|
|
14
|
+
type: (typeof FACT_TYPES)[number];
|
|
15
|
+
statement: string;
|
|
16
|
+
}
|
|
17
|
+
export interface TaskFinding extends FindingBase {
|
|
18
|
+
kind: "task";
|
|
19
|
+
type: (typeof TASK_TYPES)[number];
|
|
20
|
+
title: string;
|
|
21
|
+
reason: string;
|
|
22
|
+
commitment: "explicit" | "inferred";
|
|
23
|
+
dueAt?: string;
|
|
24
|
+
dueEvidence?: string[];
|
|
25
|
+
assignee?: string;
|
|
26
|
+
assigneeEvidence?: string[];
|
|
27
|
+
}
|
|
28
|
+
export type Finding = FactFinding | TaskFinding;
|
|
29
|
+
export interface FindingsInput {
|
|
30
|
+
schemaVersion: 1;
|
|
31
|
+
key: string;
|
|
32
|
+
extractor: string;
|
|
33
|
+
findings: Finding[];
|
|
34
|
+
}
|
|
35
|
+
export interface FindingOutcome {
|
|
36
|
+
id: string;
|
|
37
|
+
fingerprint: string;
|
|
38
|
+
status: "accepted" | "rejected";
|
|
39
|
+
reference: string | null;
|
|
40
|
+
reused: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface FindingsReceipt {
|
|
43
|
+
previewFingerprint: string;
|
|
44
|
+
accepted: string[];
|
|
45
|
+
items: FindingOutcome[];
|
|
46
|
+
}
|
|
47
|
+
export declare const findingHash: (value: unknown) => string;
|
|
48
|
+
/** What differs between findings kinds: their wording and what one cited reference must look like. */
|
|
49
|
+
export interface FindingsRules {
|
|
50
|
+
/** "meeting findings" */
|
|
51
|
+
plural: string;
|
|
52
|
+
/** "meeting finding" */
|
|
53
|
+
singular: string;
|
|
54
|
+
/** "cue" */
|
|
55
|
+
cited: string;
|
|
56
|
+
reference: (raw: string) => string;
|
|
57
|
+
}
|
|
58
|
+
export declare const validateFindings: (input: FindingsInput, rules: FindingsRules) => FindingsInput;
|
|
59
|
+
export declare const findingFingerprint: ({ id: _id, ...finding }: Finding) => string;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { CliError } from "@wirecat/cli-base";
|
|
3
|
+
import { MEETING_FACT_TYPES as FACT_TYPES } from "./meeting-context.js";
|
|
4
|
+
import { meetingTimestamp } from "./timestamp.js";
|
|
5
|
+
const TASK_TYPES = ["bug", "feature", "chore"];
|
|
6
|
+
const invalid = (message) => {
|
|
7
|
+
throw new CliError("validation_error", message);
|
|
8
|
+
};
|
|
9
|
+
const text = (value, limit) => {
|
|
10
|
+
if (typeof value !== "string" || !value.trim() || value.includes("\0") || value.length > limit)
|
|
11
|
+
return invalid(`finding text must contain 1–${limit} characters without NUL`);
|
|
12
|
+
return value.trim();
|
|
13
|
+
};
|
|
14
|
+
export const findingHash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
15
|
+
const fields = (value, allowed, noun) => {
|
|
16
|
+
if (Object.keys(value).some((key) => !allowed.includes(key)))
|
|
17
|
+
invalid(`unknown ${noun} field`);
|
|
18
|
+
};
|
|
19
|
+
export const validateFindings = (input, rules) => {
|
|
20
|
+
if (!input || typeof input !== "object" || Buffer.byteLength(JSON.stringify(input), "utf8") > 65536)
|
|
21
|
+
return invalid(`${rules.plural} take at most 65536 UTF-8 bytes`);
|
|
22
|
+
fields(input, ["schemaVersion", "key", "extractor", "findings"], rules.singular);
|
|
23
|
+
if (input.schemaVersion !== 1 ||
|
|
24
|
+
!Array.isArray(input.findings) ||
|
|
25
|
+
input.findings.length < 1 ||
|
|
26
|
+
input.findings.length > 16)
|
|
27
|
+
return invalid(`schemaVersion 1 takes 1–16 ${rules.plural}`);
|
|
28
|
+
const key = text(input.key, 64);
|
|
29
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/.test(key))
|
|
30
|
+
return invalid("a findings key uses letters, digits, dot, colon, underscore or hyphen");
|
|
31
|
+
const ids = new Set();
|
|
32
|
+
const findings = input.findings.map((finding) => {
|
|
33
|
+
if (!finding || typeof finding !== "object")
|
|
34
|
+
return invalid("a finding is an object");
|
|
35
|
+
const id = text(finding.id, 64);
|
|
36
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/.test(id) || ids.has(id))
|
|
37
|
+
return invalid("finding IDs must be unique simple keys");
|
|
38
|
+
ids.add(id);
|
|
39
|
+
const project = text(finding.project, 64);
|
|
40
|
+
if (!/^project:[1-9]\d*$/.test(project))
|
|
41
|
+
return invalid("a finding names a project reference");
|
|
42
|
+
if (!Array.isArray(finding.evidence) || finding.evidence.length < 1 || finding.evidence.length > 8)
|
|
43
|
+
return invalid(`a finding requires 1–8 cited ${rules.cited}s`);
|
|
44
|
+
const evidence = finding.evidence
|
|
45
|
+
.map((item) => {
|
|
46
|
+
if (!item || typeof item !== "object")
|
|
47
|
+
return invalid("finding evidence is a reference and fingerprint");
|
|
48
|
+
fields(item, ["reference", "fingerprint"], rules.singular);
|
|
49
|
+
if (typeof item.reference !== "string" || typeof item.fingerprint !== "string")
|
|
50
|
+
return invalid("evidence requires a reference and fingerprint");
|
|
51
|
+
const reference = rules.reference(item.reference);
|
|
52
|
+
if (!/^[a-f0-9]{64}$/.test(item.fingerprint))
|
|
53
|
+
return invalid(`finding evidence requires exact ${rules.cited} references and fingerprints`);
|
|
54
|
+
return { reference, fingerprint: item.fingerprint };
|
|
55
|
+
})
|
|
56
|
+
.sort((a, b) => a.reference.localeCompare(b.reference));
|
|
57
|
+
if (new Set(evidence.map((item) => item.reference)).size !== evidence.length)
|
|
58
|
+
return invalid(`duplicate evidence ${rules.cited}`);
|
|
59
|
+
const base = { id, project, evidence };
|
|
60
|
+
if (finding.kind === "fact") {
|
|
61
|
+
fields(finding, ["id", "project", "evidence", "kind", "type", "statement"], rules.singular);
|
|
62
|
+
if (!FACT_TYPES.includes(finding.type))
|
|
63
|
+
return invalid("unknown fact finding type");
|
|
64
|
+
return { ...base, kind: "fact", type: finding.type, statement: text(finding.statement, 2000) };
|
|
65
|
+
}
|
|
66
|
+
if (finding.kind !== "task")
|
|
67
|
+
return invalid("a finding is a fact or task");
|
|
68
|
+
fields(finding, [
|
|
69
|
+
"id",
|
|
70
|
+
"project",
|
|
71
|
+
"evidence",
|
|
72
|
+
"kind",
|
|
73
|
+
"type",
|
|
74
|
+
"title",
|
|
75
|
+
"reason",
|
|
76
|
+
"commitment",
|
|
77
|
+
"dueAt",
|
|
78
|
+
"dueEvidence",
|
|
79
|
+
"assignee",
|
|
80
|
+
"assigneeEvidence",
|
|
81
|
+
], rules.singular);
|
|
82
|
+
if (!TASK_TYPES.includes(finding.type))
|
|
83
|
+
return invalid("a task finding type is bug, chore or feature");
|
|
84
|
+
if (!["explicit", "inferred"].includes(finding.commitment))
|
|
85
|
+
return invalid("task commitment must be explicit or inferred");
|
|
86
|
+
const support = (value) => {
|
|
87
|
+
if (!Array.isArray(value) ||
|
|
88
|
+
value.length < 1 ||
|
|
89
|
+
value.length > 8 ||
|
|
90
|
+
value.some((ref) => typeof ref !== "string" || !evidence.some((item) => item.reference === ref)))
|
|
91
|
+
return invalid("date and assignee support must cite the finding's evidence");
|
|
92
|
+
return [...new Set(value)].sort();
|
|
93
|
+
};
|
|
94
|
+
if ((finding.dueAt === undefined) !== (finding.dueEvidence === undefined) ||
|
|
95
|
+
(finding.assignee === undefined) !== (finding.assigneeEvidence === undefined))
|
|
96
|
+
return invalid("optional dates and assignees require their supporting evidence");
|
|
97
|
+
if (finding.assignee !== undefined &&
|
|
98
|
+
(typeof finding.assignee !== "string" || !/^person:[1-9]\d*$/.test(finding.assignee)))
|
|
99
|
+
return invalid("an assignee must be a person reference");
|
|
100
|
+
return {
|
|
101
|
+
...base,
|
|
102
|
+
kind: "task",
|
|
103
|
+
type: finding.type,
|
|
104
|
+
title: text(finding.title, 300),
|
|
105
|
+
reason: text(finding.reason, 2000),
|
|
106
|
+
commitment: finding.commitment,
|
|
107
|
+
...(finding.dueAt === undefined
|
|
108
|
+
? {}
|
|
109
|
+
: { dueAt: meetingTimestamp(finding.dueAt, "finding due date"), dueEvidence: support(finding.dueEvidence) }),
|
|
110
|
+
...(finding.assignee === undefined
|
|
111
|
+
? {}
|
|
112
|
+
: { assignee: finding.assignee, assigneeEvidence: support(finding.assigneeEvidence) }),
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
return { schemaVersion: 1, key, extractor: text(input.extractor, 200), findings };
|
|
116
|
+
};
|
|
117
|
+
export const findingFingerprint = ({ id: _id, ...finding }) => findingHash(finding);
|
|
@@ -1,51 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { type FactFinding, type Finding, type FindingsInput, type FindingsReceipt, findingFingerprint } from "./findings-rules.js";
|
|
2
|
+
export type { FindingEvidence, FindingOutcome } from "./findings-rules.js";
|
|
3
|
+
export { findingHash } from "./findings-rules.js";
|
|
3
4
|
export declare const MEETING_FINDINGS_KIND = "meeting_findings";
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
fingerprint: string;
|
|
7
|
-
}
|
|
8
|
-
interface FindingBase {
|
|
9
|
-
id: string;
|
|
10
|
-
project: string;
|
|
11
|
-
evidence: FindingEvidence[];
|
|
12
|
-
}
|
|
13
|
-
export interface MeetingFactFinding extends FindingBase {
|
|
14
|
-
kind: "fact";
|
|
15
|
-
type: (typeof MEETING_FACT_TYPES)[number];
|
|
16
|
-
statement: string;
|
|
17
|
-
}
|
|
18
|
-
export interface MeetingTaskFinding extends FindingBase {
|
|
5
|
+
export type MeetingFactFinding = FactFinding;
|
|
6
|
+
export type MeetingTaskFinding = Extract<Finding, {
|
|
19
7
|
kind: "task";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
dueAt?: string;
|
|
25
|
-
dueEvidence?: string[];
|
|
26
|
-
assignee?: string;
|
|
27
|
-
assigneeEvidence?: string[];
|
|
28
|
-
}
|
|
29
|
-
export type MeetingFinding = MeetingFactFinding | MeetingTaskFinding;
|
|
30
|
-
export interface MeetingFindingsInput {
|
|
31
|
-
schemaVersion: 1;
|
|
32
|
-
key: string;
|
|
33
|
-
extractor: string;
|
|
34
|
-
findings: MeetingFinding[];
|
|
35
|
-
}
|
|
36
|
-
export interface FindingOutcome {
|
|
37
|
-
id: string;
|
|
38
|
-
fingerprint: string;
|
|
39
|
-
status: "accepted" | "rejected";
|
|
40
|
-
reference: string | null;
|
|
41
|
-
reused: boolean;
|
|
42
|
-
}
|
|
43
|
-
export interface MeetingFindingsReceipt {
|
|
44
|
-
previewFingerprint: string;
|
|
45
|
-
accepted: string[];
|
|
46
|
-
items: FindingOutcome[];
|
|
47
|
-
}
|
|
48
|
-
export declare const findingHash: (value: unknown) => string;
|
|
8
|
+
}>;
|
|
9
|
+
export type MeetingFinding = Finding;
|
|
10
|
+
export type MeetingFindingsInput = FindingsInput;
|
|
11
|
+
export type MeetingFindingsReceipt = FindingsReceipt;
|
|
49
12
|
export declare const validateMeetingFindings: (input: MeetingFindingsInput) => MeetingFindingsInput;
|
|
50
|
-
export declare const meetingFindingFingerprint:
|
|
51
|
-
export {};
|
|
13
|
+
export declare const meetingFindingFingerprint: typeof findingFingerprint;
|
|
@@ -1,119 +1,13 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { CliError } from "@wirecat/cli-base";
|
|
3
|
-
import {
|
|
2
|
+
import { findingFingerprint, validateFindings, } from "./findings-rules.js";
|
|
4
3
|
import { canonicalMeetingReference, parseMeetingReference } from "./reference.js";
|
|
5
|
-
|
|
6
|
-
const MEETING_TASK_TYPES = ["bug", "feature", "chore"];
|
|
4
|
+
export { findingHash } from "./findings-rules.js";
|
|
7
5
|
export const MEETING_FINDINGS_KIND = "meeting_findings";
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export const findingHash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
17
|
-
const fields = (value, allowed) => {
|
|
18
|
-
if (Object.keys(value).some((key) => !allowed.includes(key)))
|
|
19
|
-
invalid("unknown meeting finding field");
|
|
20
|
-
};
|
|
21
|
-
export const validateMeetingFindings = (input) => {
|
|
22
|
-
if (!input || typeof input !== "object" || Buffer.byteLength(JSON.stringify(input), "utf8") > 65536)
|
|
23
|
-
return invalid("meeting findings take at most 65536 UTF-8 bytes");
|
|
24
|
-
fields(input, ["schemaVersion", "key", "extractor", "findings"]);
|
|
25
|
-
if (input.schemaVersion !== 1 ||
|
|
26
|
-
!Array.isArray(input.findings) ||
|
|
27
|
-
input.findings.length < 1 ||
|
|
28
|
-
input.findings.length > 16)
|
|
29
|
-
return invalid("schemaVersion 1 takes 1–16 meeting findings");
|
|
30
|
-
const key = text(input.key, 64);
|
|
31
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/.test(key))
|
|
32
|
-
return invalid("a findings key uses letters, digits, dot, colon, underscore or hyphen");
|
|
33
|
-
const ids = new Set();
|
|
34
|
-
const findings = input.findings.map((finding) => {
|
|
35
|
-
if (!finding || typeof finding !== "object")
|
|
36
|
-
return invalid("a finding is an object");
|
|
37
|
-
const id = text(finding.id, 64);
|
|
38
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9_.:-]*$/.test(id) || ids.has(id))
|
|
39
|
-
return invalid("finding IDs must be unique simple keys");
|
|
40
|
-
ids.add(id);
|
|
41
|
-
const project = text(finding.project, 64);
|
|
42
|
-
if (!/^project:[1-9]\d*$/.test(project))
|
|
43
|
-
return invalid("a finding names a project reference");
|
|
44
|
-
if (!Array.isArray(finding.evidence) || finding.evidence.length < 1 || finding.evidence.length > 8)
|
|
45
|
-
return invalid("a finding requires 1–8 cited cues");
|
|
46
|
-
const evidence = finding.evidence
|
|
47
|
-
.map((item) => {
|
|
48
|
-
if (!item || typeof item !== "object")
|
|
49
|
-
return invalid("finding evidence is a reference and fingerprint");
|
|
50
|
-
fields(item, ["reference", "fingerprint"]);
|
|
51
|
-
if (typeof item.reference !== "string" || typeof item.fingerprint !== "string")
|
|
52
|
-
return invalid("evidence requires a reference and fingerprint");
|
|
53
|
-
const reference = canonicalMeetingReference(item.reference);
|
|
54
|
-
if (parseMeetingReference(reference).cuePosition === undefined || !/^[a-f0-9]{64}$/.test(item.fingerprint))
|
|
55
|
-
return invalid("finding evidence requires exact cue references and fingerprints");
|
|
56
|
-
return { reference, fingerprint: item.fingerprint };
|
|
57
|
-
})
|
|
58
|
-
.sort((a, b) => a.reference.localeCompare(b.reference));
|
|
59
|
-
if (new Set(evidence.map((item) => item.reference)).size !== evidence.length)
|
|
60
|
-
return invalid("duplicate evidence cue");
|
|
61
|
-
const base = { id, project, evidence };
|
|
62
|
-
if (finding.kind === "fact") {
|
|
63
|
-
fields(finding, ["id", "project", "evidence", "kind", "type", "statement"]);
|
|
64
|
-
if (!MEETING_FACT_TYPES.includes(finding.type))
|
|
65
|
-
return invalid("unknown fact finding type");
|
|
66
|
-
return { ...base, kind: "fact", type: finding.type, statement: text(finding.statement, 2000) };
|
|
67
|
-
}
|
|
68
|
-
if (finding.kind !== "task")
|
|
69
|
-
return invalid("a finding is a fact or task");
|
|
70
|
-
fields(finding, [
|
|
71
|
-
"id",
|
|
72
|
-
"project",
|
|
73
|
-
"evidence",
|
|
74
|
-
"kind",
|
|
75
|
-
"type",
|
|
76
|
-
"title",
|
|
77
|
-
"reason",
|
|
78
|
-
"commitment",
|
|
79
|
-
"dueAt",
|
|
80
|
-
"dueEvidence",
|
|
81
|
-
"assignee",
|
|
82
|
-
"assigneeEvidence",
|
|
83
|
-
]);
|
|
84
|
-
if (!MEETING_TASK_TYPES.includes(finding.type))
|
|
85
|
-
return invalid("a task finding type is bug, chore or feature");
|
|
86
|
-
if (!["explicit", "inferred"].includes(finding.commitment))
|
|
87
|
-
return invalid("task commitment must be explicit or inferred");
|
|
88
|
-
const support = (value) => {
|
|
89
|
-
if (!Array.isArray(value) ||
|
|
90
|
-
value.length < 1 ||
|
|
91
|
-
value.length > 8 ||
|
|
92
|
-
value.some((ref) => typeof ref !== "string" || !evidence.some((item) => item.reference === ref)))
|
|
93
|
-
return invalid("date and assignee support must cite the finding's evidence");
|
|
94
|
-
return [...new Set(value)].sort();
|
|
95
|
-
};
|
|
96
|
-
if ((finding.dueAt === undefined) !== (finding.dueEvidence === undefined) ||
|
|
97
|
-
(finding.assignee === undefined) !== (finding.assigneeEvidence === undefined))
|
|
98
|
-
return invalid("optional dates and assignees require their supporting evidence");
|
|
99
|
-
if (finding.assignee !== undefined &&
|
|
100
|
-
(typeof finding.assignee !== "string" || !/^person:[1-9]\d*$/.test(finding.assignee)))
|
|
101
|
-
return invalid("an assignee must be a person reference");
|
|
102
|
-
return {
|
|
103
|
-
...base,
|
|
104
|
-
kind: "task",
|
|
105
|
-
type: finding.type,
|
|
106
|
-
title: text(finding.title, 300),
|
|
107
|
-
reason: text(finding.reason, 2000),
|
|
108
|
-
commitment: finding.commitment,
|
|
109
|
-
...(finding.dueAt === undefined
|
|
110
|
-
? {}
|
|
111
|
-
: { dueAt: meetingTimestamp(finding.dueAt, "finding due date"), dueEvidence: support(finding.dueEvidence) }),
|
|
112
|
-
...(finding.assignee === undefined
|
|
113
|
-
? {}
|
|
114
|
-
: { assignee: finding.assignee, assigneeEvidence: support(finding.assigneeEvidence) }),
|
|
115
|
-
};
|
|
116
|
-
});
|
|
117
|
-
return { schemaVersion: 1, key, extractor: text(input.extractor, 200), findings };
|
|
118
|
-
};
|
|
119
|
-
export const meetingFindingFingerprint = ({ id: _id, ...finding }) => findingHash(finding);
|
|
6
|
+
const cue = (raw) => {
|
|
7
|
+
const reference = canonicalMeetingReference(raw);
|
|
8
|
+
if (parseMeetingReference(reference).cuePosition === undefined)
|
|
9
|
+
throw new CliError("validation_error", "finding evidence requires exact cue references and fingerprints");
|
|
10
|
+
return reference;
|
|
11
|
+
};
|
|
12
|
+
export const validateMeetingFindings = (input) => validateFindings(input, { plural: "meeting findings", singular: "meeting finding", cited: "cue", reference: cue });
|
|
13
|
+
export const meetingFindingFingerprint = findingFingerprint;
|
package/dist/meetings/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { type MeetingCueIdentity, meetingCueFingerprint } from "./cue-fingerprin
|
|
|
5
5
|
export { linkEvent } from "./events.js";
|
|
6
6
|
export { exportMeeting, type MeetingExport, type MeetingExportFormat, type MeetingExportOptions, serializeVtt, } from "./export.js";
|
|
7
7
|
export { type FindingEvidence, type FindingOutcome, findingHash, MEETING_FINDINGS_KIND, type MeetingFactFinding, type MeetingFinding, type MeetingFindingsInput, type MeetingFindingsReceipt, type MeetingTaskFinding, meetingFindingFingerprint, validateMeetingFindings, } from "./findings.js";
|
|
8
|
+
export { type Finding, type FindingsInput, type FindingsReceipt, type FindingsRules, findingFingerprint, validateFindings, } from "./findings-rules.js";
|
|
8
9
|
export { type ImportFile, importFiles } from "./import.js";
|
|
9
10
|
export { MEETING_QUERY, type MeetingItem, type MeetingOwner, meeting, meetingItem } from "./item.js";
|
|
10
11
|
export { createMeetingContext, MEETING_FACT_TYPES, type MeetingContextAction, type MeetingContextChange, type MeetingContextOperation, type MeetingContextPorts, type MeetingContextReceipt, validateMeetingContextChange, } from "./meeting-context.js";
|
package/dist/meetings/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { meetingCueFingerprint } from "./cue-fingerprint.js";
|
|
|
3
3
|
export { linkEvent } from "./events.js";
|
|
4
4
|
export { exportMeeting, serializeVtt, } from "./export.js";
|
|
5
5
|
export { findingHash, MEETING_FINDINGS_KIND, meetingFindingFingerprint, validateMeetingFindings, } from "./findings.js";
|
|
6
|
+
export { findingFingerprint, validateFindings, } from "./findings-rules.js";
|
|
6
7
|
export { importFiles } from "./import.js";
|
|
7
8
|
export { MEETING_QUERY, meeting, meetingItem } from "./item.js";
|
|
8
9
|
export { createMeetingContext, MEETING_FACT_TYPES, validateMeetingContextChange, } from "./meeting-context.js";
|
|
@@ -5,5 +5,6 @@ export interface ScopedContextEvidenceReader extends ContextEvidenceReader {
|
|
|
5
5
|
usage(): ContextReadUsage;
|
|
6
6
|
}
|
|
7
7
|
export declare const createContextEvidenceReader: (port: ContextEvidenceReader, allowed: readonly ContextSource[], limits?: ContextReadLimits) => ScopedContextEvidenceReader;
|
|
8
|
-
export type { ContextEvidenceItem, ContextEvidencePage, ContextEvidenceReader, ContextEvidenceRequest, ContextIndex, ContextSource, } from "../context/evidence.js";
|
|
8
|
+
export type { ContextEvidenceItem, ContextEvidencePage, ContextEvidenceReader, ContextEvidenceRequest, ContextIndex, ContextRecordReader, ContextSource, } from "../context/evidence.js";
|
|
9
|
+
export { contextEvidenceFingerprint } from "../context/evidence.js";
|
|
9
10
|
export type { ContextReadLimits, ContextReadUsage } from "../context/read-budget.js";
|
package/dist/services/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type BotCheckService } from "../botcheck/service.js";
|
|
2
|
-
export type { ContextEvidenceItem, ContextEvidencePage, ContextEvidenceReader, ContextEvidenceRequest, ContextIndex, ContextReadLimits, ContextReadUsage, ContextSource, ScopedContextEvidenceReader, } from "./context-evidence.js";
|
|
3
|
-
export { createContextEvidenceReader } from "./context-evidence.js";
|
|
2
|
+
export type { ContextEvidenceItem, ContextEvidencePage, ContextEvidenceReader, ContextEvidenceRequest, ContextIndex, ContextReadLimits, ContextReadUsage, ContextRecordReader, ContextSource, ScopedContextEvidenceReader, } from "./context-evidence.js";
|
|
3
|
+
export { contextEvidenceFingerprint, createContextEvidenceReader } from "./context-evidence.js";
|
|
4
|
+
export { excerpt } from "./excerpt.js";
|
|
4
5
|
export { type DownloadedModel, downloadImageModel, downloadTextModel, type ModelDownloadOptions, } from "./model-downloads.js";
|
|
5
6
|
export type { ProjectEvidenceItem, ProjectGathering, ProjectGatheringPorts, ProjectGatheringStop, ProjectIndexCatchUp, ProjectSourceFetcher, } from "./project-gathering.js";
|
|
6
7
|
export { gatherProjectEvidence } from "./project-gathering.js";
|
package/dist/services/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { botCheckService } from "../botcheck/service.js";
|
|
2
|
-
export { createContextEvidenceReader } from "./context-evidence.js";
|
|
2
|
+
export { contextEvidenceFingerprint, createContextEvidenceReader } from "./context-evidence.js";
|
|
3
|
+
export { excerpt } from "./excerpt.js";
|
|
3
4
|
export { downloadImageModel, downloadTextModel, } from "./model-downloads.js";
|
|
4
5
|
export { gatherProjectEvidence } from "./project-gathering.js";
|
|
5
6
|
export { storedMessageNeighbours, storedNeighbours } from "./project-gathering-around.js";
|
|
@@ -33,6 +33,7 @@ export interface ProjectEvidenceItem extends Omit<ContextEvidenceItem, "text"> {
|
|
|
33
33
|
textTruncated: boolean;
|
|
34
34
|
lanes: string[];
|
|
35
35
|
context: "project" | "person-only";
|
|
36
|
+
fingerprint: string;
|
|
36
37
|
around?: ProjectNeighbour[] | null;
|
|
37
38
|
aroundIssue?: string | null;
|
|
38
39
|
}
|