@wirecat/cli-core 0.369.0 → 0.371.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 +27 -0
- package/dist/cli/guide.d.ts +15 -0
- package/dist/cli/guide.js +58 -9
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +1 -1
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.js +2 -0
- package/dist/store/project-transfer/manifest.d.ts +6 -0
- package/dist/store/project-transfer/manifest.js +138 -0
- package/dist/store/project-transfer/plan.d.ts +28 -0
- package/dist/store/project-transfer/plan.js +106 -0
- package/dist/store/project-transfer/schema.d.ts +94 -0
- package/dist/store/project-transfer/schema.js +68 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.371.0 — 20.10.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Project annotation manifest validation from `./store`** checks a bounded, versioned JSON graph of
|
|
15
|
+
project aliases, facts and direct links with explicit external dependencies and omitted facets.
|
|
16
|
+
`decodeProjectAnnotations` validates it, `projectAnnotationsFingerprint` hashes its normalized
|
|
17
|
+
form, and `planProjectAnnotations` checks explicit bindings against a complete target snapshot.
|
|
18
|
+
These helpers do not export a store, restore records or authorize confirmation.
|
|
19
|
+
- **`documentPassage` from `./services`** gives hosts the same bounded paragraph context that project gathering
|
|
20
|
+
uses, so a findings review can show the paragraphs around its cited document without copying that logic.
|
|
21
|
+
|
|
22
|
+
## 0.370.0 — 20.10.2026
|
|
23
|
+
|
|
24
|
+
### Changed — may break callers
|
|
25
|
+
|
|
26
|
+
- **`checkGuide` finds a scenario step a shell would change**, so a tool's guide test that passed may now fail. It
|
|
27
|
+
splits each step as a POSIX shell does and reports `>`, `<`, `|`, `&`, `;`, `` ` ``, `$(` and the globs `*`, `?`,
|
|
28
|
+
`[` left outside quotes (`` ` `` and `$(` also inside double quotes), and a quote never closed. `max review
|
|
29
|
+
date>1d` passed before, though a shell writes a file named `1d` and drops the filter. Quote the word:
|
|
30
|
+
`'date>1d'`. `shellProblems(run)` gives the same check for one step.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **`MEMO_SCENARIOS` holds the titles of memo's cross-tool scenarios** (`personBrief`, `decisionTrail`), so every
|
|
35
|
+
tool's "Works with" list takes them from one place instead of typing its own copy.
|
|
36
|
+
|
|
10
37
|
## 0.369.0 — 20.10.2026
|
|
11
38
|
|
|
12
39
|
### Fixed
|
package/dist/cli/guide.d.ts
CHANGED
|
@@ -18,6 +18,19 @@ export interface GuideReference {
|
|
|
18
18
|
name: string;
|
|
19
19
|
title: string;
|
|
20
20
|
}
|
|
21
|
+
/** memo's cross-tool scenarios: the one source of the titles every tool lists, and memo's files must carry. */
|
|
22
|
+
export declare const MEMO_SCENARIOS: {
|
|
23
|
+
readonly personBrief: {
|
|
24
|
+
readonly tool: "memo";
|
|
25
|
+
readonly name: "person-brief";
|
|
26
|
+
readonly title: "One person across every tool";
|
|
27
|
+
};
|
|
28
|
+
readonly decisionTrail: {
|
|
29
|
+
readonly tool: "memo";
|
|
30
|
+
readonly name: "decision-trail";
|
|
31
|
+
readonly title: "What did we decide about X, across every source";
|
|
32
|
+
};
|
|
33
|
+
};
|
|
21
34
|
export interface GuideOptions {
|
|
22
35
|
/** Absolute path of the tool's scenario files: `fileURLToPath(new URL("../scenarios", import.meta.url))`. */
|
|
23
36
|
directory: string;
|
|
@@ -40,6 +53,8 @@ export declare const loadScenarios: (directory: string) => Promise<{
|
|
|
40
53
|
* program is built. `checkGuide` validates them in the tool's CI.
|
|
41
54
|
*/
|
|
42
55
|
export declare const guideCommand: (app: Pick<AppIdentity, "command">, options: GuideOptions) => Command;
|
|
56
|
+
/** Characters in a step that a shell would act on: the person who copies it would run something else. */
|
|
57
|
+
export declare const shellProblems: (run: string) => string[];
|
|
43
58
|
/** What is wrong with one step on this program: the path, each option and its value, the arguments. Never runs it. */
|
|
44
59
|
export declare const checkStep: (program: Command, run: string) => string[];
|
|
45
60
|
/**
|
package/dist/cli/guide.js
CHANGED
|
@@ -7,6 +7,11 @@ import { environmentOf, outputFor } from "./context.js";
|
|
|
7
7
|
import { NO_EFFECTS } from "./effects.js";
|
|
8
8
|
import { itemOf, listOf, renderAnswer } from "./envelope.js";
|
|
9
9
|
import { PROFILE_FREE } from "./profile.js";
|
|
10
|
+
/** memo's cross-tool scenarios: the one source of the titles every tool lists, and memo's files must carry. */
|
|
11
|
+
export const MEMO_SCENARIOS = {
|
|
12
|
+
personBrief: { tool: "memo", name: "person-brief", title: "One person across every tool" },
|
|
13
|
+
decisionTrail: { tool: "memo", name: "decision-trail", title: "What did we decide about X, across every source" },
|
|
14
|
+
};
|
|
10
15
|
const FRONT_MATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
11
16
|
const FIELDS = new Set(["title", "goal", "steps", "look"]);
|
|
12
17
|
const NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -157,17 +162,49 @@ export const guideCommand = (app, options) => {
|
|
|
157
162
|
guides.set(command, options);
|
|
158
163
|
return command;
|
|
159
164
|
};
|
|
160
|
-
|
|
161
|
-
|
|
165
|
+
const OPERATORS = {
|
|
166
|
+
">": "redirects output to a file",
|
|
167
|
+
"<": "reads input from a file",
|
|
168
|
+
"|": "pipes into another command",
|
|
169
|
+
"&": "runs the command in the background",
|
|
170
|
+
";": "ends the command there",
|
|
171
|
+
"`": "runs it as a command",
|
|
172
|
+
"$(": "runs it as a command",
|
|
173
|
+
"*": "expands it to file names",
|
|
174
|
+
"?": "expands it to file names",
|
|
175
|
+
"[": "expands it to file names",
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* A step split into words as a POSIX shell would — '…' literal, "…" with \ escapes, \ outside quotes — and the
|
|
179
|
+
* characters a shell would act on instead of passing them to the tool. `` ` `` and `$(` still act inside "…".
|
|
180
|
+
*/
|
|
181
|
+
const scan = (line) => {
|
|
162
182
|
const found = [];
|
|
183
|
+
const live = new Set();
|
|
163
184
|
let word;
|
|
164
185
|
let quote;
|
|
165
|
-
for (
|
|
166
|
-
|
|
167
|
-
|
|
186
|
+
for (let index = 0; index < line.length; index++) {
|
|
187
|
+
const char = line[index];
|
|
188
|
+
const next = line[index + 1];
|
|
189
|
+
if (quote === "'") {
|
|
190
|
+
if (char === "'")
|
|
168
191
|
quote = undefined;
|
|
169
192
|
else
|
|
170
|
-
word
|
|
193
|
+
word += char;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (char === "\\" && next !== undefined && (quote === undefined || '$`"\\'.includes(next))) {
|
|
197
|
+
word = (word ?? "") + next;
|
|
198
|
+
index++;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (char === "`" || (char === "$" && next === "("))
|
|
202
|
+
live.add(char === "`" ? "`" : "$(");
|
|
203
|
+
if (quote === '"') {
|
|
204
|
+
if (char === '"')
|
|
205
|
+
quote = undefined;
|
|
206
|
+
else
|
|
207
|
+
word += char;
|
|
171
208
|
}
|
|
172
209
|
else if (char === '"' || char === "'") {
|
|
173
210
|
quote = char;
|
|
@@ -178,12 +215,24 @@ const words = (line) => {
|
|
|
178
215
|
found.push(word);
|
|
179
216
|
word = undefined;
|
|
180
217
|
}
|
|
181
|
-
else
|
|
218
|
+
else {
|
|
219
|
+
if (OPERATORS[char])
|
|
220
|
+
live.add(char);
|
|
182
221
|
word = (word ?? "") + char;
|
|
222
|
+
}
|
|
183
223
|
}
|
|
184
224
|
if (word !== undefined)
|
|
185
225
|
found.push(word);
|
|
186
|
-
return found;
|
|
226
|
+
return { words: found, live: [...live], open: quote !== undefined };
|
|
227
|
+
};
|
|
228
|
+
const words = (line) => scan(line).words;
|
|
229
|
+
/** Characters in a step that a shell would act on: the person who copies it would run something else. */
|
|
230
|
+
export const shellProblems = (run) => {
|
|
231
|
+
const { live, open } = scan(run);
|
|
232
|
+
return [
|
|
233
|
+
...live.map((char) => `a shell acts on ${char}: it ${OPERATORS[char]}; put the word in single quotes`),
|
|
234
|
+
...(open ? ["a quote is never closed"] : []),
|
|
235
|
+
];
|
|
187
236
|
};
|
|
188
237
|
const silent = (command) => command.exitOverride().configureOutput({ writeOut: () => { }, writeErr: () => { }, outputError: () => { } });
|
|
189
238
|
const ownerOf = (chain, flag) => {
|
|
@@ -295,7 +344,7 @@ export const checkGuide = async (build, { packageRoot } = {}) => {
|
|
|
295
344
|
const { scenarios, problems } = await loadScenarios(options.directory);
|
|
296
345
|
for (const scenario of scenarios)
|
|
297
346
|
scenario.steps.forEach((step, index) => {
|
|
298
|
-
for (const problem of checkStep(build(), step.run))
|
|
347
|
+
for (const problem of [...shellProblems(step.run), ...checkStep(build(), step.run)])
|
|
299
348
|
problems.push(`${scenario.name}.md step ${index + 1} (${step.run}): ${problem}`);
|
|
300
349
|
});
|
|
301
350
|
for (const reference of options.worksWith ?? [])
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
|
|
|
56
56
|
export { changesAuthority, destroysNothing, FETCH, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, REMOTE_READ, REMOTE_WRITE, reachesOtherService, refuseUnderReadOnly, } from "./effects.js";
|
|
57
57
|
export { type AnswerOutput, answerSchema, type Body, type ItemBody, itemAnswer, itemOf, jsonlRecords, judgedAnswer, type ListBody, type ListInput, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
|
|
58
58
|
export { withAbort } from "./execution.js";
|
|
59
|
-
export { checkGuide, checkStep, type GuideOptions, type GuideReference, type GuideStep, guideCommand, loadScenarios, type Scenario, } from "./guide.js";
|
|
59
|
+
export { checkGuide, checkStep, type GuideOptions, type GuideReference, type GuideStep, guideCommand, loadScenarios, MEMO_SCENARIOS, type Scenario, shellProblems, } from "./guide.js";
|
|
60
60
|
export { type CommandHelp, commandHelp, commandHelpMarkdown, commandHelpText, exampleReport, type NextChoice, nextUsually, } from "./help/command-help.js";
|
|
61
61
|
export { commandsText } from "./help/commands-text.js";
|
|
62
62
|
export { type FirstScreen, type FirstScreenLine, firstScreen, installHelp, WIRECAT_TOOLS, } from "./help/first-screen.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -51,7 +51,7 @@ export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
|
|
|
51
51
|
export { changesAuthority, destroysNothing, FETCH, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, REMOTE_READ, REMOTE_WRITE, reachesOtherService, refuseUnderReadOnly, } from "./effects.js";
|
|
52
52
|
export { answerSchema, itemAnswer, itemOf, jsonlRecords, judgedAnswer, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
|
|
53
53
|
export { withAbort } from "./execution.js";
|
|
54
|
-
export { checkGuide, checkStep, guideCommand, loadScenarios, } from "./guide.js";
|
|
54
|
+
export { checkGuide, checkStep, guideCommand, loadScenarios, MEMO_SCENARIOS, shellProblems, } from "./guide.js";
|
|
55
55
|
export { commandHelp, commandHelpMarkdown, commandHelpText, exampleReport, nextUsually, } from "./help/command-help.js";
|
|
56
56
|
export { commandsText } from "./help/commands-text.js";
|
|
57
57
|
export { firstScreen, installHelp, WIRECAT_TOOLS, } from "./help/first-screen.js";
|
package/dist/services/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { type DownloadedModel, downloadImageModel, downloadTextModel, type Model
|
|
|
6
6
|
export type { ProjectEvidenceItem, ProjectGathering, ProjectGatheringPorts, ProjectGatheringStop, ProjectIndexCatchUp, ProjectSourceFetcher, } from "./project-gathering.js";
|
|
7
7
|
export { gatherProjectEvidence } from "./project-gathering.js";
|
|
8
8
|
export type { ProjectNeighbour, ProjectNeighbourReader } from "./project-gathering-around.js";
|
|
9
|
-
export { storedMessageNeighbours, storedNeighbours } from "./project-gathering-around.js";
|
|
9
|
+
export { documentPassage, storedMessageNeighbours, storedNeighbours } from "./project-gathering-around.js";
|
|
10
10
|
export type { EvidenceMention, EvidenceMentions } from "./project-gathering-mentions.js";
|
|
11
11
|
export { findEvidenceMentions } from "./project-gathering-mentions.js";
|
|
12
12
|
export type { ProjectFetchState, ProjectGatheringBudgets, ProjectGatheringRequest, ProjectLane, ProjectLaneKind, ProjectUnitState, } from "./project-gathering-request.js";
|
package/dist/services/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export { contextEvidenceFingerprint, createContextEvidenceReader } from "./conte
|
|
|
3
3
|
export { excerpt } from "./excerpt.js";
|
|
4
4
|
export { downloadImageModel, downloadTextModel, } from "./model-downloads.js";
|
|
5
5
|
export { gatherProjectEvidence } from "./project-gathering.js";
|
|
6
|
-
export { storedMessageNeighbours, storedNeighbours } from "./project-gathering-around.js";
|
|
6
|
+
export { documentPassage, storedMessageNeighbours, storedNeighbours } from "./project-gathering-around.js";
|
|
7
7
|
export { findEvidenceMentions } from "./project-gathering-mentions.js";
|
|
8
8
|
import { accountService } from "./account.js";
|
|
9
9
|
import { adminService } from "./admin.js";
|
package/dist/store/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type { MeetingFindingsBatch, MeetingFindingsStore } from "./meeting-findi
|
|
|
14
14
|
export { MIGRATIONS, type Migration, migrate } from "./migrations.js";
|
|
15
15
|
export { openCache } from "./open.js";
|
|
16
16
|
export { storePath } from "./path.js";
|
|
17
|
+
export { decodeProjectAnnotations, PROJECT_ANNOTATIONS_MAX_BYTES, type ProjectAnnotations, projectAnnotationsFingerprint, } from "./project-transfer/manifest.js";
|
|
18
|
+
export { type ProjectAnnotationsPlan, type ProjectAnnotationsTarget, planProjectAnnotations, } from "./project-transfer/plan.js";
|
|
17
19
|
export type { AgentAction, AgentActionInput, AgentActionsStore } from "./sqlite/agent-actions.js";
|
|
18
20
|
export { resetAttachmentWords } from "./sqlite/attachment-texts.js";
|
|
19
21
|
export { backfillNormalized, pendingNormalization } from "./sqlite/backfill.js";
|
package/dist/store/index.js
CHANGED
|
@@ -6,6 +6,8 @@ export { EVIDENCE_FINDINGS_KIND, validateEvidenceFindings, } from "./evidence-fi
|
|
|
6
6
|
export { MIGRATIONS, migrate } from "./migrations.js";
|
|
7
7
|
export { openCache } from "./open.js";
|
|
8
8
|
export { storePath } from "./path.js";
|
|
9
|
+
export { decodeProjectAnnotations, PROJECT_ANNOTATIONS_MAX_BYTES, projectAnnotationsFingerprint, } from "./project-transfer/manifest.js";
|
|
10
|
+
export { planProjectAnnotations, } from "./project-transfer/plan.js";
|
|
9
11
|
export { resetAttachmentWords } from "./sqlite/attachment-texts.js";
|
|
10
12
|
export { backfillNormalized, pendingNormalization } from "./sqlite/backfill.js";
|
|
11
13
|
export { CATALOG_QUERY } from "./sqlite/catalog.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ProjectAnnotations } from "./schema.js";
|
|
2
|
+
export type { ProjectAnnotations } from "./schema.js";
|
|
3
|
+
export declare const PROJECT_ANNOTATIONS_MAX_BYTES: number;
|
|
4
|
+
/** Decode bounded JSON, never a row/column importer or an implicit whole-project backup. */
|
|
5
|
+
export declare const decodeProjectAnnotations: (json: string) => ProjectAnnotations;
|
|
6
|
+
export declare const projectAnnotationsFingerprint: (json: string) => string;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { CliError } from "@wirecat/cli-base";
|
|
3
|
+
import * as v from "valibot";
|
|
4
|
+
import { canonicalReference, parseReference } from "../../domain/references.js";
|
|
5
|
+
import { normalizeVocabularyName } from "../../domain/vocabulary.js";
|
|
6
|
+
import { canonicalMeetingReference } from "../../meetings/index.js";
|
|
7
|
+
import { projectAnnotationsSchema } from "./schema.js";
|
|
8
|
+
export const PROJECT_ANNOTATIONS_MAX_BYTES = 4 * 1024 * 1024;
|
|
9
|
+
const invalid = (field) => {
|
|
10
|
+
throw new CliError("validation_error", `invalid project annotations.${field}`);
|
|
11
|
+
};
|
|
12
|
+
const canonical = (ref) => {
|
|
13
|
+
try {
|
|
14
|
+
return ref.startsWith("meeting:") ? canonicalMeetingReference(ref) : canonicalReference(ref);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return invalid("reference");
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const graph = (manifest) => {
|
|
21
|
+
const owned = new Set([manifest.project.ref]);
|
|
22
|
+
const external = new Set();
|
|
23
|
+
const used = new Set();
|
|
24
|
+
const kind = (ref, kinds) => {
|
|
25
|
+
if (canonical(ref) !== ref || !kinds.includes(parseReference(ref).type) || !/^[a-z]+:[1-9]\d*$/.test(ref))
|
|
26
|
+
invalid("reference kind");
|
|
27
|
+
};
|
|
28
|
+
kind(manifest.project.ref, ["project"]);
|
|
29
|
+
for (const fact of manifest.facts) {
|
|
30
|
+
kind(fact.ref, ["fact"]);
|
|
31
|
+
if (owned.has(fact.ref))
|
|
32
|
+
invalid("facts.duplicate");
|
|
33
|
+
owned.add(fact.ref);
|
|
34
|
+
kind(fact.author, ["person", "bot"]);
|
|
35
|
+
if (fact.confirmed_by !== null)
|
|
36
|
+
kind(fact.confirmed_by, ["person", "bot"]);
|
|
37
|
+
if (fact.updated_at < fact.created_at)
|
|
38
|
+
invalid("facts.updated_at");
|
|
39
|
+
if (fact.type !== "decision" && (fact.supersedes !== null || ["superseded", "reversed"].includes(fact.status)))
|
|
40
|
+
invalid("facts.lifecycle");
|
|
41
|
+
if (fact.supersedes === fact.ref)
|
|
42
|
+
invalid("facts.supersedes");
|
|
43
|
+
}
|
|
44
|
+
for (const item of manifest.external) {
|
|
45
|
+
if (canonical(item.ref) !== item.ref || owned.has(item.ref) || external.has(item.ref))
|
|
46
|
+
invalid("external.ref");
|
|
47
|
+
external.add(item.ref);
|
|
48
|
+
const body = /^(msg|document|note|meeting):/.test(item.ref);
|
|
49
|
+
if (body !== (item.content_sha256 !== null))
|
|
50
|
+
invalid("external.content_sha256");
|
|
51
|
+
}
|
|
52
|
+
const dependency = (ref) => {
|
|
53
|
+
if (ref === null)
|
|
54
|
+
return;
|
|
55
|
+
if (canonical(ref) !== ref || (!owned.has(ref) && !external.has(ref)))
|
|
56
|
+
invalid("missing dependency");
|
|
57
|
+
used.add(ref);
|
|
58
|
+
};
|
|
59
|
+
if (manifest.project.organization !== null)
|
|
60
|
+
kind(manifest.project.organization, ["organization"]);
|
|
61
|
+
dependency(manifest.project.organization);
|
|
62
|
+
const aliases = new Set([manifest.project.name, manifest.project.key].map(normalizeVocabularyName));
|
|
63
|
+
for (const alias of manifest.aliases) {
|
|
64
|
+
const name = normalizeVocabularyName(alias);
|
|
65
|
+
if (aliases.has(name))
|
|
66
|
+
invalid("aliases.duplicate");
|
|
67
|
+
aliases.add(name);
|
|
68
|
+
}
|
|
69
|
+
for (const fact of manifest.facts) {
|
|
70
|
+
for (const ref of [fact.author, fact.confirmed_by, fact.subject, fact.supersedes])
|
|
71
|
+
dependency(ref);
|
|
72
|
+
if (fact.supersedes !== null) {
|
|
73
|
+
kind(fact.supersedes, ["fact"]);
|
|
74
|
+
if (!manifest.facts.some((previous) => previous.ref === fact.supersedes && previous.type === "decision"))
|
|
75
|
+
invalid("facts.supersedes outside project");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const previous = new Map(manifest.facts.map((fact) => [fact.ref, fact.supersedes]));
|
|
79
|
+
for (const fact of manifest.facts) {
|
|
80
|
+
const path = new Set();
|
|
81
|
+
let ref = fact.ref;
|
|
82
|
+
while (ref) {
|
|
83
|
+
if (path.has(ref))
|
|
84
|
+
invalid("facts.supersedes cycle");
|
|
85
|
+
path.add(ref);
|
|
86
|
+
ref = previous.get(ref);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const links = new Set();
|
|
90
|
+
for (const link of manifest.links) {
|
|
91
|
+
if (!/^link:[1-9]\d*$/.test(link.ref) || links.has(link.ref))
|
|
92
|
+
invalid("links.ref");
|
|
93
|
+
links.add(link.ref);
|
|
94
|
+
if (!owned.has(link.from) && (link.to === null || !owned.has(link.to)))
|
|
95
|
+
invalid("links.outside project");
|
|
96
|
+
if (link.to === null && link.target_text === null)
|
|
97
|
+
invalid("links.target_text");
|
|
98
|
+
if (link.updated_at < link.created_at)
|
|
99
|
+
invalid("links.updated_at");
|
|
100
|
+
for (const ref of [link.from, link.to])
|
|
101
|
+
dependency(ref);
|
|
102
|
+
}
|
|
103
|
+
for (const ref of external)
|
|
104
|
+
if (!used.has(ref))
|
|
105
|
+
invalid("external.unused");
|
|
106
|
+
};
|
|
107
|
+
/** Decode bounded JSON, never a row/column importer or an implicit whole-project backup. */
|
|
108
|
+
export const decodeProjectAnnotations = (json) => {
|
|
109
|
+
if (typeof json !== "string" || Buffer.byteLength(json) > PROJECT_ANNOTATIONS_MAX_BYTES)
|
|
110
|
+
invalid("bytes");
|
|
111
|
+
let input;
|
|
112
|
+
try {
|
|
113
|
+
input = JSON.parse(json);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return invalid("json");
|
|
117
|
+
}
|
|
118
|
+
const parsed = v.safeParse(projectAnnotationsSchema, input);
|
|
119
|
+
if (!parsed.success)
|
|
120
|
+
return invalid(parsed.issues[0]?.path?.map((part) => String(part.key)).join(".") ?? "manifest");
|
|
121
|
+
const manifest = parsed.output;
|
|
122
|
+
if (manifest.aliases.length + manifest.facts.length + manifest.links.length + manifest.external.length > 1000)
|
|
123
|
+
invalid("record budget");
|
|
124
|
+
graph(manifest);
|
|
125
|
+
return manifest;
|
|
126
|
+
};
|
|
127
|
+
export const projectAnnotationsFingerprint = (json) => {
|
|
128
|
+
const manifest = decodeProjectAnnotations(json);
|
|
129
|
+
const normalized = {
|
|
130
|
+
...manifest,
|
|
131
|
+
aliases: manifest.aliases.toSorted(),
|
|
132
|
+
facts: manifest.facts.toSorted((a, b) => (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0)),
|
|
133
|
+
links: manifest.links.toSorted((a, b) => a.ref.localeCompare(b.ref)),
|
|
134
|
+
external: manifest.external.toSorted((a, b) => a.ref.localeCompare(b.ref)),
|
|
135
|
+
};
|
|
136
|
+
// Strict decoding fixes object key order; record arrays have identity, unlike text and timestamps.
|
|
137
|
+
return createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
|
|
138
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
declare const targetSchema: v.StrictObjectSchema<{
|
|
3
|
+
readonly project: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>;
|
|
4
|
+
readonly revision_sha256: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>;
|
|
5
|
+
readonly annotation_count: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.SafeIntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
6
|
+
readonly external: v.SchemaWithPipe<readonly [v.ArraySchema<v.StrictObjectSchema<{
|
|
7
|
+
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, 2048, undefined>]>;
|
|
8
|
+
readonly content_sha256: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
|
|
9
|
+
}, undefined>, undefined>, v.MaxLengthAction<{
|
|
10
|
+
ref: string;
|
|
11
|
+
content_sha256: string | null;
|
|
12
|
+
}[], 1000, undefined>]>;
|
|
13
|
+
}, undefined>;
|
|
14
|
+
export type ProjectAnnotationsTarget = v.InferOutput<typeof targetSchema>;
|
|
15
|
+
export interface ProjectAnnotationsPlan {
|
|
16
|
+
fingerprint: string;
|
|
17
|
+
manifest_fingerprint: string;
|
|
18
|
+
target_revision_sha256: string;
|
|
19
|
+
missing_bindings: string[];
|
|
20
|
+
conflicts: {
|
|
21
|
+
reference: string;
|
|
22
|
+
reason: "missing" | "wrong_kind" | "changed" | "populated" | "duplicate";
|
|
23
|
+
}[];
|
|
24
|
+
ready: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Pure planning over an adapter's complete target snapshot; a ready plan is not write authorization. */
|
|
27
|
+
export declare const planProjectAnnotations: (json: string, target: ProjectAnnotationsTarget, bindings: Record<string, string>) => ProjectAnnotationsPlan;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { CliError } from "@wirecat/cli-base";
|
|
3
|
+
import * as v from "valibot";
|
|
4
|
+
import { canonicalReference, parseReference } from "../../domain/references.js";
|
|
5
|
+
import { canonicalMeetingReference } from "../../meetings/index.js";
|
|
6
|
+
import { decodeProjectAnnotations, projectAnnotationsFingerprint } from "./manifest.js";
|
|
7
|
+
const hash = v.pipe(v.string(), v.regex(/^[a-f0-9]{64}$/));
|
|
8
|
+
const ref = v.pipe(v.string(), v.minLength(1), v.maxLength(2048));
|
|
9
|
+
const targetSchema = v.strictObject({
|
|
10
|
+
project: v.pipe(v.string(), v.regex(/^project:[1-9]\d*$/)),
|
|
11
|
+
revision_sha256: hash,
|
|
12
|
+
annotation_count: v.pipe(v.number(), v.safeInteger(), v.minValue(0)),
|
|
13
|
+
external: v.pipe(v.array(v.strictObject({ ref, content_sha256: v.nullable(hash) })), v.maxLength(1000)),
|
|
14
|
+
});
|
|
15
|
+
const kind = (ref) => {
|
|
16
|
+
if (ref.startsWith("meeting:")) {
|
|
17
|
+
if (canonicalMeetingReference(ref) !== ref)
|
|
18
|
+
throw new Error("noncanonical");
|
|
19
|
+
return `meeting:${ref.split("/").length}`;
|
|
20
|
+
}
|
|
21
|
+
if (canonicalReference(ref) !== ref)
|
|
22
|
+
throw new Error("noncanonical");
|
|
23
|
+
const parsed = parseReference(ref);
|
|
24
|
+
return "provider" in parsed ? `${parsed.type}:${parsed.provider}` : parsed.type;
|
|
25
|
+
};
|
|
26
|
+
const invalid = (field) => {
|
|
27
|
+
throw new CliError("validation_error", `invalid project annotation plan.${field}`);
|
|
28
|
+
};
|
|
29
|
+
/** Pure planning over an adapter's complete target snapshot; a ready plan is not write authorization. */
|
|
30
|
+
export const planProjectAnnotations = (json, target, bindings) => {
|
|
31
|
+
const manifest = decodeProjectAnnotations(json);
|
|
32
|
+
const checked = v.safeParse(targetSchema, target);
|
|
33
|
+
if (!checked.success)
|
|
34
|
+
return invalid(checked.issues[0]?.path?.map((p) => String(p.key)).join(".") ?? "target");
|
|
35
|
+
if (!bindings || typeof bindings !== "object" || Array.isArray(bindings) || Object.keys(bindings).length > 1001)
|
|
36
|
+
return invalid("bindings");
|
|
37
|
+
if (Object.getPrototypeOf(bindings) !== Object.prototype && Object.getPrototypeOf(bindings) !== null)
|
|
38
|
+
return invalid("bindings");
|
|
39
|
+
const required = new Set([manifest.project.ref, ...manifest.external.map((item) => item.ref)]);
|
|
40
|
+
for (const [source, destination] of Object.entries(bindings))
|
|
41
|
+
if (!required.has(source) || typeof destination !== "string" || destination.length > 2048)
|
|
42
|
+
return invalid("bindings");
|
|
43
|
+
const destinations = new Map();
|
|
44
|
+
for (const item of checked.output.external) {
|
|
45
|
+
try {
|
|
46
|
+
kind(item.ref);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return invalid("target.external.ref");
|
|
50
|
+
}
|
|
51
|
+
if (destinations.has(item.ref) || item.ref === target.project)
|
|
52
|
+
return invalid("target.external.duplicate");
|
|
53
|
+
destinations.set(item.ref, item.content_sha256);
|
|
54
|
+
}
|
|
55
|
+
const missing_bindings = [], conflicts = [];
|
|
56
|
+
const used = new Set();
|
|
57
|
+
for (const source of required) {
|
|
58
|
+
const destination = bindings[source];
|
|
59
|
+
if (destination === undefined) {
|
|
60
|
+
missing_bindings.push(source);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
if (kind(source) !== kind(destination)) {
|
|
65
|
+
conflicts.push({ reference: source, reason: "wrong_kind" });
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return invalid("bindings.reference");
|
|
71
|
+
}
|
|
72
|
+
if (used.has(destination))
|
|
73
|
+
conflicts.push({ reference: source, reason: "duplicate" });
|
|
74
|
+
used.add(destination);
|
|
75
|
+
if (source === manifest.project.ref) {
|
|
76
|
+
if (destination !== target.project)
|
|
77
|
+
conflicts.push({ reference: source, reason: "missing" });
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (!destinations.has(destination))
|
|
81
|
+
conflicts.push({ reference: source, reason: "missing" });
|
|
82
|
+
else if (destinations.get(destination) !== manifest.external.find((item) => item.ref === source)?.content_sha256)
|
|
83
|
+
conflicts.push({ reference: source, reason: "changed" });
|
|
84
|
+
}
|
|
85
|
+
if (target.annotation_count > 0)
|
|
86
|
+
conflicts.push({ reference: target.project, reason: "populated" });
|
|
87
|
+
const manifest_fingerprint = projectAnnotationsFingerprint(json);
|
|
88
|
+
const fingerprint = createHash("sha256")
|
|
89
|
+
.update(JSON.stringify({
|
|
90
|
+
manifest_fingerprint,
|
|
91
|
+
target: {
|
|
92
|
+
...checked.output,
|
|
93
|
+
external: checked.output.external.toSorted((a, b) => (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0)),
|
|
94
|
+
},
|
|
95
|
+
bindings: Object.fromEntries(Object.entries(bindings).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))),
|
|
96
|
+
}))
|
|
97
|
+
.digest("hex");
|
|
98
|
+
return {
|
|
99
|
+
fingerprint,
|
|
100
|
+
manifest_fingerprint,
|
|
101
|
+
target_revision_sha256: target.revision_sha256,
|
|
102
|
+
missing_bindings,
|
|
103
|
+
conflicts,
|
|
104
|
+
ready: missing_bindings.length === 0 && conflicts.length === 0,
|
|
105
|
+
};
|
|
106
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
export declare const projectAnnotationsSchema: v.StrictObjectSchema<{
|
|
3
|
+
readonly format: v.LiteralSchema<"wirecat.project-annotations", undefined>;
|
|
4
|
+
readonly version: v.LiteralSchema<1, undefined>;
|
|
5
|
+
readonly project: v.StrictObjectSchema<{
|
|
6
|
+
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
7
|
+
readonly key: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>;
|
|
8
|
+
readonly name: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
9
|
+
readonly type: v.PicklistSchema<["work", "client", "personal", "oss", "other"], undefined>;
|
|
10
|
+
readonly scope: v.PicklistSchema<readonly ["personal", "work"], undefined>;
|
|
11
|
+
readonly organization: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
12
|
+
readonly created_at: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.SafeIntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
13
|
+
}, undefined>;
|
|
14
|
+
readonly coverage: v.StrictObjectSchema<{
|
|
15
|
+
readonly aliases: v.LiteralSchema<"complete", undefined>;
|
|
16
|
+
readonly facts: v.LiteralSchema<"complete", undefined>;
|
|
17
|
+
readonly links: v.LiteralSchema<"complete", undefined>;
|
|
18
|
+
readonly memories: v.LiteralSchema<"omitted", undefined>;
|
|
19
|
+
readonly tasks: v.LiteralSchema<"omitted", undefined>;
|
|
20
|
+
readonly labels: v.LiteralSchema<"omitted", undefined>;
|
|
21
|
+
readonly findings: v.LiteralSchema<"omitted", undefined>;
|
|
22
|
+
}, undefined>;
|
|
23
|
+
readonly aliases: v.SchemaWithPipe<readonly [v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>, v.MaxLengthAction<string[], 1000, undefined>]>;
|
|
24
|
+
readonly facts: v.SchemaWithPipe<readonly [v.ArraySchema<v.StrictObjectSchema<{
|
|
25
|
+
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
26
|
+
readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
|
|
27
|
+
readonly statement: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
28
|
+
readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed"], undefined>;
|
|
29
|
+
readonly subject: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
30
|
+
readonly occurred_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.SafeIntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
31
|
+
readonly supersedes: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
32
|
+
readonly author: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
33
|
+
readonly model: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
34
|
+
readonly confirmed_by: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
35
|
+
readonly scope: v.NullableSchema<v.PicklistSchema<readonly ["personal", "work"], undefined>, undefined>;
|
|
36
|
+
readonly source: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
37
|
+
readonly created_at: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.SafeIntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
38
|
+
readonly updated_at: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.SafeIntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
39
|
+
}, undefined>, undefined>, v.MaxLengthAction<{
|
|
40
|
+
ref: string;
|
|
41
|
+
type: "decision" | "fact" | "promise" | "question" | "statement";
|
|
42
|
+
statement: string;
|
|
43
|
+
status: "accepted" | "proposed" | "reversed" | "superseded";
|
|
44
|
+
subject: string | null;
|
|
45
|
+
occurred_at: number | null;
|
|
46
|
+
supersedes: string | null;
|
|
47
|
+
author: string;
|
|
48
|
+
model: string | null;
|
|
49
|
+
confirmed_by: string | null;
|
|
50
|
+
scope: "personal" | "work" | null;
|
|
51
|
+
source: string;
|
|
52
|
+
created_at: number;
|
|
53
|
+
updated_at: number;
|
|
54
|
+
}[], 1000, undefined>]>;
|
|
55
|
+
readonly links: v.SchemaWithPipe<readonly [v.ArraySchema<v.StrictObjectSchema<{
|
|
56
|
+
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
57
|
+
readonly from: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
58
|
+
readonly to: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
59
|
+
readonly kind: v.PicklistSchema<readonly ["links-to", "blocked-by", "follow-up-of", "about", "member-of", "labelled", "answered-by", "evidence", "created-from", "duplicate-of", "related-to", "assigned-to"], undefined>;
|
|
60
|
+
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message"], undefined>;
|
|
61
|
+
readonly anchor: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
62
|
+
readonly target_text: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
63
|
+
readonly role: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
64
|
+
readonly evidence: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
65
|
+
readonly confirmed: v.BooleanSchema<undefined>;
|
|
66
|
+
readonly provenance: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
67
|
+
readonly created_at: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.SafeIntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
68
|
+
readonly updated_at: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.SafeIntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
69
|
+
readonly author: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
70
|
+
}, undefined>, undefined>, v.MaxLengthAction<{
|
|
71
|
+
ref: string;
|
|
72
|
+
from: string;
|
|
73
|
+
to: string | null;
|
|
74
|
+
kind: "about" | "answered-by" | "assigned-to" | "blocked-by" | "created-from" | "duplicate-of" | "evidence" | "follow-up-of" | "labelled" | "links-to" | "member-of" | "related-to";
|
|
75
|
+
origin: "file" | "message" | "owner" | "suggested";
|
|
76
|
+
anchor: string | null;
|
|
77
|
+
target_text: string | null;
|
|
78
|
+
role: string | null;
|
|
79
|
+
evidence: string | null;
|
|
80
|
+
confirmed: boolean;
|
|
81
|
+
provenance: string | null;
|
|
82
|
+
created_at: number;
|
|
83
|
+
updated_at: number;
|
|
84
|
+
author: string | null;
|
|
85
|
+
}[], 1000, undefined>]>;
|
|
86
|
+
readonly external: v.SchemaWithPipe<readonly [v.ArraySchema<v.StrictObjectSchema<{
|
|
87
|
+
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
88
|
+
readonly content_sha256: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
|
|
89
|
+
}, undefined>, undefined>, v.MaxLengthAction<{
|
|
90
|
+
ref: string;
|
|
91
|
+
content_sha256: string | null;
|
|
92
|
+
}[], 1000, undefined>]>;
|
|
93
|
+
}, undefined>;
|
|
94
|
+
export type ProjectAnnotations = v.InferOutput<typeof projectAnnotationsSchema>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
import { FACT_STATUSES, FACT_TYPES, MEMORY_SCOPES } from "../../domain/knowledge.js";
|
|
3
|
+
import { LINK_KINDS } from "../sqlite/link-kinds.js";
|
|
4
|
+
import { LINK_ORIGINS } from "../sqlite/notes.js";
|
|
5
|
+
const text = (max) => v.pipe(v.string(), v.minLength(1), v.maxLength(max));
|
|
6
|
+
const ref = text(2048);
|
|
7
|
+
const timestamp = v.pipe(v.number(), v.safeInteger(), v.minValue(0));
|
|
8
|
+
const nullableRef = v.nullable(ref);
|
|
9
|
+
const bounded = (schema) => v.pipe(v.array(schema), v.maxLength(1000));
|
|
10
|
+
export const projectAnnotationsSchema = v.strictObject({
|
|
11
|
+
format: v.literal("wirecat.project-annotations"),
|
|
12
|
+
version: v.literal(1),
|
|
13
|
+
project: v.strictObject({
|
|
14
|
+
ref,
|
|
15
|
+
key: v.pipe(v.string(), v.regex(/^[A-Z][A-Z0-9]{2,3}$/)),
|
|
16
|
+
name: text(200),
|
|
17
|
+
type: v.picklist(["work", "client", "personal", "oss", "other"]),
|
|
18
|
+
scope: v.picklist(MEMORY_SCOPES),
|
|
19
|
+
organization: nullableRef,
|
|
20
|
+
created_at: timestamp,
|
|
21
|
+
}),
|
|
22
|
+
coverage: v.strictObject({
|
|
23
|
+
aliases: v.literal("complete"),
|
|
24
|
+
facts: v.literal("complete"),
|
|
25
|
+
links: v.literal("complete"),
|
|
26
|
+
memories: v.literal("omitted"),
|
|
27
|
+
tasks: v.literal("omitted"),
|
|
28
|
+
labels: v.literal("omitted"),
|
|
29
|
+
findings: v.literal("omitted"),
|
|
30
|
+
}),
|
|
31
|
+
aliases: bounded(text(200)),
|
|
32
|
+
facts: bounded(v.strictObject({
|
|
33
|
+
ref,
|
|
34
|
+
type: v.picklist(FACT_TYPES),
|
|
35
|
+
statement: text(2000),
|
|
36
|
+
status: v.picklist(FACT_STATUSES),
|
|
37
|
+
subject: nullableRef,
|
|
38
|
+
occurred_at: v.nullable(timestamp),
|
|
39
|
+
supersedes: nullableRef,
|
|
40
|
+
author: ref,
|
|
41
|
+
model: v.nullable(text(200)),
|
|
42
|
+
confirmed_by: nullableRef,
|
|
43
|
+
scope: v.nullable(v.picklist(MEMORY_SCOPES)),
|
|
44
|
+
source: text(200),
|
|
45
|
+
created_at: timestamp,
|
|
46
|
+
updated_at: timestamp,
|
|
47
|
+
})),
|
|
48
|
+
links: bounded(v.strictObject({
|
|
49
|
+
ref: text(200),
|
|
50
|
+
from: ref,
|
|
51
|
+
to: nullableRef,
|
|
52
|
+
kind: v.picklist(LINK_KINDS),
|
|
53
|
+
origin: v.picklist(LINK_ORIGINS),
|
|
54
|
+
anchor: v.nullable(text(2000)),
|
|
55
|
+
target_text: v.nullable(text(2000)),
|
|
56
|
+
role: v.nullable(text(200)),
|
|
57
|
+
evidence: v.nullable(text(2000)),
|
|
58
|
+
confirmed: v.boolean(),
|
|
59
|
+
provenance: v.nullable(text(2000)),
|
|
60
|
+
created_at: timestamp,
|
|
61
|
+
updated_at: timestamp,
|
|
62
|
+
author: v.nullable(text(200)),
|
|
63
|
+
})),
|
|
64
|
+
external: bounded(v.strictObject({
|
|
65
|
+
ref,
|
|
66
|
+
content_sha256: v.nullable(v.pipe(v.string(), v.regex(/^[a-f0-9]{64}$/))),
|
|
67
|
+
})),
|
|
68
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.371.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",
|