@lorekit/cli 1.71.0 → 1.71.2
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/README.md +69 -0
- package/bin/lorekit.mjs +35 -5
- package/package.json +1 -1
- package/src/commands/groom.mjs +4 -1
- package/src/commands/policy.mjs +27 -3
- package/src/shared/flags.mjs +73 -0
- package/src/store/remote.mjs +54 -11
- package/src/surfaces.generated.mjs +134 -1
package/README.md
CHANGED
|
@@ -667,6 +667,75 @@ The server's refusal is printed verbatim with a one-line next step; the CLI make
|
|
|
667
667
|
exactly one request and never retries, splits the sweep, or re-scopes around it.
|
|
668
668
|
Use an unscoped `lk_rw_*` / `lk_wo_*` token for maintenance.
|
|
669
669
|
|
|
670
|
+
### `lorekit groom` / `lorekit policy` / `lorekit protect` (aliases: `pin` / `unpin`)
|
|
671
|
+
|
|
672
|
+
Retention automation — the CLI counterpart to the MCP `groom.*`/`policy.*`/
|
|
673
|
+
`memory.protect` tools and the REST `/groom/*`/`/policies*`/`/protect` routes.
|
|
674
|
+
**Remote only**, matching `purge`: `retention_policies` has no local-store
|
|
675
|
+
equivalent.
|
|
676
|
+
|
|
677
|
+
```bash
|
|
678
|
+
# Preview what a rule would catch, without changing anything
|
|
679
|
+
lorekit groom --scope repo::acme/app --unseen-days 90
|
|
680
|
+
|
|
681
|
+
# Archive the matches (soft-archive, recoverable via `lorekit restore`)
|
|
682
|
+
lorekit groom --scope repo::acme/app --unseen-days 90 --run --yes
|
|
683
|
+
|
|
684
|
+
# Save it as a policy you can re-run by name, or let it sweep nightly
|
|
685
|
+
lorekit policy create --scope repo::acme/app --name "stale repo lore" \
|
|
686
|
+
--unseen-days 90 --mode review
|
|
687
|
+
lorekit policy list
|
|
688
|
+
lorekit policy update <id> --mode auto --enabled
|
|
689
|
+
lorekit policy delete <id>
|
|
690
|
+
|
|
691
|
+
# Exempt one lesson from every rule and every manual sweep
|
|
692
|
+
lorekit pin repo::acme/app::the-load-bearing-lesson
|
|
693
|
+
lorekit unpin repo::acme/app::the-load-bearing-lesson
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
`groom [--policy-id <id> | --scope <s> [conditions…]] [--run] [--yes]` previews
|
|
697
|
+
by default; `--run` archives. Exactly one of `--policy-id`/`--scope` is
|
|
698
|
+
required. `policy <list|create|update|delete>` manages the SAVED rules
|
|
699
|
+
`--policy-id` runs; `delete` removes the rule only, never the lessons it
|
|
700
|
+
matched.
|
|
701
|
+
|
|
702
|
+
Every condition ANDs together: the five age/activity fields
|
|
703
|
+
(`--min-age-days`/`--unseen-days`/`--max-seen-count`/`--max-read-count`/
|
|
704
|
+
`--max-opened-count`) plus the same **eight dimension filters** the Lore
|
|
705
|
+
Explorer's filter bar offers —
|
|
706
|
+
|
|
707
|
+
| Flag | `--<dim>-mode` values (default) |
|
|
708
|
+
|------|----------------------------------|
|
|
709
|
+
| `--tags <a,b>` | `any` \| `all` \| `none` (`any`) |
|
|
710
|
+
| `--kind <a,b>` | `in` \| `nin` (`in`) |
|
|
711
|
+
| `--host <a,b>` | `in` \| `nin` (`in`) |
|
|
712
|
+
| `--trigger <a,b>` | `in` \| `nin` (`in`) |
|
|
713
|
+
| `--source-agent <a,b>` | `in` \| `nin` (`in`) |
|
|
714
|
+
| `--origin-repo <a,b>` | `in` \| `nin` (`in`) |
|
|
715
|
+
| `--origin-branch <a,b>` | `in` \| `nin` (`in`) |
|
|
716
|
+
| `--origin-pr <a,b>` | `in` \| `nin` (`in`) |
|
|
717
|
+
|
|
718
|
+
— all comma-separated, same convention as `write --tags a,b,c`:
|
|
719
|
+
|
|
720
|
+
```bash
|
|
721
|
+
lorekit policy create --scope repo::acme/app --name "stale ci review events" \
|
|
722
|
+
--kind bus --kind-mode in --tags ci::pr-review-state --tags-mode all \
|
|
723
|
+
--min-age-days 30
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
`policy update` pairs every condition — age/count AND every dimension — with
|
|
727
|
+
a `--clear-<field>` boolean (e.g. `--clear-kind`, `--clear-min-age-days`) that
|
|
728
|
+
sends an explicit `null` rather than omitting the field: omitting a field on
|
|
729
|
+
update leaves it unchanged, `null` clears it.
|
|
730
|
+
|
|
731
|
+
```bash
|
|
732
|
+
lorekit policy update <id> --clear-kind --host reviewer,aw
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
`protect <scope::key> [--off]` (or the shorter `pin`/`unpin`) marks a lesson as
|
|
736
|
+
excluded from every policy and every `groom --run`, regardless of which rule
|
|
737
|
+
would otherwise match it — persists until explicitly cleared.
|
|
738
|
+
|
|
670
739
|
### `lorekit hook`
|
|
671
740
|
|
|
672
741
|
The **shared hook engine** behind the Claude Code / Cursor / Codex plugins.
|
package/bin/lorekit.mjs
CHANGED
|
@@ -935,7 +935,10 @@ ${c.bold('Options')}
|
|
|
935
935
|
${c.bold('Usage')}
|
|
936
936
|
lorekit groom --policy-id <id> [--run] [--yes] [--json]
|
|
937
937
|
lorekit groom --scope <s> [--min-age-days <n>] [--unseen-days <n>] [--max-seen-count <n>]
|
|
938
|
-
[--max-read-count <n>] [--max-opened-count <n>]
|
|
938
|
+
[--max-read-count <n>] [--max-opened-count <n>]
|
|
939
|
+
[--tags a,b --tags-mode any|all|none]
|
|
940
|
+
[--kind|--host|--trigger|--source-agent|--origin-repo|--origin-branch|--origin-pr a,b [--<dim>-mode in|nin]]
|
|
941
|
+
[--run] [--yes] [--json]
|
|
939
942
|
|
|
940
943
|
Resolves the SAME candidates a saved policy or an inline condition set would
|
|
941
944
|
catch, via the retention-policy candidate query — a previewed count always
|
|
@@ -960,6 +963,12 @@ ${c.bold('Options')}
|
|
|
960
963
|
the hundreds — a small value matches nothing
|
|
961
964
|
--max-opened-count <n> Match only lessons an agent DELIBERATELY fetched at most n
|
|
962
965
|
times. Bulk reads do NOT count, so 0 means never chosen
|
|
966
|
+
--tags <a,b>, --tags-mode <any|all|none>
|
|
967
|
+
Match lessons carrying these labels (default: any)
|
|
968
|
+
--kind, --host, --trigger, --source-agent,
|
|
969
|
+
--origin-repo, --origin-branch, --origin-pr <a,b>
|
|
970
|
+
--<dim>-mode <in|nin> Match lessons whose dimension is one of these
|
|
971
|
+
values (default: in); comma-separated
|
|
963
972
|
--run Archive the matches instead of previewing
|
|
964
973
|
-y, --yes Confirm --run; required when non-interactive
|
|
965
974
|
--json Machine-readable result
|
|
@@ -974,12 +983,17 @@ ${c.bold('Usage')}
|
|
|
974
983
|
lorekit policy create --scope <s> --name <n> [--mode review|auto] [--enabled]
|
|
975
984
|
[--min-age-days <n>] [--unseen-days <n>] [--max-seen-count <n>]
|
|
976
985
|
[--max-read-count <n>] [--max-opened-count <n>]
|
|
986
|
+
[--tags a,b --tags-mode any|all|none]
|
|
987
|
+
[--kind|--host|--trigger|--source-agent|--origin-repo|--origin-branch|--origin-pr a,b [--<dim>-mode in|nin]]
|
|
977
988
|
lorekit policy update <id> [--name <n>] [--mode review|auto] [--enabled|--disabled]
|
|
978
|
-
[--min-age-days <n>|--clear-min-age-days] [...]
|
|
989
|
+
[--min-age-days <n>|--clear-min-age-days] [...]
|
|
990
|
+
[--tags a,b|--clear-tags] [--<dim> a,b|--clear-<dim>] [--<dim>-mode in|nin] [--json]
|
|
979
991
|
lorekit policy delete <id> [--yes] [--json]
|
|
980
992
|
|
|
981
993
|
A policy is a saved retention rule: a scope plus AND-ed conditions
|
|
982
|
-
(min-age-days / unseen-days / max-seen-count / max-read-count / max-opened-count
|
|
994
|
+
(min-age-days / unseen-days / max-seen-count / max-read-count / max-opened-count,
|
|
995
|
+
plus the eight dimension filters — tags/kind/host/trigger/source-agent/
|
|
996
|
+
origin-repo/origin-branch/origin-pr).
|
|
983
997
|
\`mode: review\` surfaces it for
|
|
984
998
|
you to run by hand with ${c.cyan('lorekit groom --policy-id')}; \`mode: auto\` gets swept
|
|
985
999
|
nightly, but ONLY once you also pass --enabled — auto starts disabled on
|
|
@@ -995,8 +1009,14 @@ ${c.bold('Options')}
|
|
|
995
1009
|
--min-age-days <n>, --unseen-days <n>, --max-seen-count <n>, --max-read-count <n>,
|
|
996
1010
|
--max-opened-count <n>
|
|
997
1011
|
Conditions (create/update)
|
|
1012
|
+
--tags <a,b>, --tags-mode <any|all|none>
|
|
1013
|
+
--kind, --host, --trigger, --source-agent,
|
|
1014
|
+
--origin-repo, --origin-branch, --origin-pr <a,b>
|
|
1015
|
+
--<dim>-mode <in|nin> Dimension conditions (create/update); comma-separated
|
|
998
1016
|
--clear-min-age-days, --clear-unseen-days, --clear-max-seen-count,
|
|
999
|
-
--clear-max-read-count, --clear-max-opened-count
|
|
1017
|
+
--clear-max-read-count, --clear-max-opened-count,
|
|
1018
|
+
--clear-tags, --clear-kind, --clear-host, --clear-trigger,
|
|
1019
|
+
--clear-source-agent, --clear-origin-repo, --clear-origin-branch, --clear-origin-pr
|
|
1000
1020
|
Remove a condition (update only)
|
|
1001
1021
|
-y, --yes Confirm delete; required when non-interactive
|
|
1002
1022
|
--json Machine-readable result
|
|
@@ -1121,6 +1141,16 @@ const KNOWN_FLAGS = [
|
|
|
1121
1141
|
'name', 'mode', 'enabled', 'disabled',
|
|
1122
1142
|
'clear-min-age-days', 'clear-unseen-days', 'clear-max-seen-count', 'clear-max-read-count',
|
|
1123
1143
|
'clear-max-opened-count', 'off',
|
|
1144
|
+
// The eight retention dimension filters' `*-mode` value flags (the value
|
|
1145
|
+
// flags themselves — tags/source-agent/trigger/kind/host/origin-repo/
|
|
1146
|
+
// origin-branch/origin-pr — are already listed above for `write`) plus
|
|
1147
|
+
// their `clear-*` booleans (policy update only). See
|
|
1148
|
+
// `shared/flags.mjs`'s `parseDimensionConditions`, the one place that
|
|
1149
|
+
// owns the flag-name ↔ field-name ↔ mode-enum table these mirror.
|
|
1150
|
+
'tags-mode', 'source-agent-mode', 'trigger-mode', 'kind-mode', 'host-mode',
|
|
1151
|
+
'origin-repo-mode', 'origin-branch-mode', 'origin-pr-mode',
|
|
1152
|
+
'clear-tags', 'clear-source-agent', 'clear-trigger', 'clear-kind', 'clear-host',
|
|
1153
|
+
'clear-origin-repo', 'clear-origin-branch', 'clear-origin-pr',
|
|
1124
1154
|
// `obligations`
|
|
1125
1155
|
'files', 'strict', 'strict-all',
|
|
1126
1156
|
// `update`
|
|
@@ -1137,7 +1167,7 @@ async function main() {
|
|
|
1137
1167
|
const argv = process.argv.slice(2);
|
|
1138
1168
|
const args = parseArgs(argv, {
|
|
1139
1169
|
aliases: { d: 'dir', e: 'endpoint', t: 'token', y: 'yes', h: 'help', v: 'version' },
|
|
1140
|
-
booleans: ['yes', 'force', 'deep', 'apply', 'help', 'version', 'global', 'project', 'no-hooks', 'mcp-json', 'no-origin', 'json', 'remote', 'local', 'link', 'archived', 'clear-ttl', 'telemetry', 'all', 'run', 'enabled', 'disabled', 'off', 'clear-min-age-days', 'clear-unseen-days', 'clear-max-seen-count', 'clear-max-read-count', 'clear-max-opened-count', 'strict', 'strict-all', 'check'],
|
|
1170
|
+
booleans: ['yes', 'force', 'deep', 'apply', 'help', 'version', 'global', 'project', 'no-hooks', 'mcp-json', 'no-origin', 'json', 'remote', 'local', 'link', 'archived', 'clear-ttl', 'telemetry', 'all', 'run', 'enabled', 'disabled', 'off', 'clear-min-age-days', 'clear-unseen-days', 'clear-max-seen-count', 'clear-max-read-count', 'clear-max-opened-count', 'strict', 'strict-all', 'clear-tags', 'clear-source-agent', 'clear-trigger', 'clear-kind', 'clear-host', 'clear-origin-repo', 'clear-origin-branch', 'clear-origin-pr', 'check'],
|
|
1141
1171
|
known: KNOWN_FLAGS,
|
|
1142
1172
|
});
|
|
1143
1173
|
|
package/package.json
CHANGED
package/src/commands/groom.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { resolveProjectRoot } from '../shared/config.mjs';
|
|
|
11
11
|
import { loadControl, resolveDenies } from '../shared/control.mjs';
|
|
12
12
|
import { resolveStores, remoteUnavailableReason } from '../shared/stores.mjs';
|
|
13
13
|
import { log, err, c, select } from '../shared/util.mjs';
|
|
14
|
-
import { parseIntFlag } from '../shared/flags.mjs';
|
|
14
|
+
import { parseIntFlag, parseDimensionConditions } from '../shared/flags.mjs';
|
|
15
15
|
|
|
16
16
|
/** Resolve the groom.preview/groom.run request from CLI args. */
|
|
17
17
|
export function parseGroomRequest(args) {
|
|
@@ -33,6 +33,8 @@ export function parseGroomRequest(args) {
|
|
|
33
33
|
if (maxRead.error) return { error: maxRead.error };
|
|
34
34
|
const maxOpened = parseIntFlag(args['max-opened-count'], 'max-opened-count');
|
|
35
35
|
if (maxOpened.error) return { error: maxOpened.error };
|
|
36
|
+
const dims = parseDimensionConditions(args, { clearable: false });
|
|
37
|
+
if (dims.error) return { error: dims.error };
|
|
36
38
|
|
|
37
39
|
return {
|
|
38
40
|
request: {
|
|
@@ -42,6 +44,7 @@ export function parseGroomRequest(args) {
|
|
|
42
44
|
max_seen_count: maxSeen.value,
|
|
43
45
|
max_read_count: maxRead.value,
|
|
44
46
|
max_opened_count: maxOpened.value,
|
|
47
|
+
...dims.conditions,
|
|
45
48
|
},
|
|
46
49
|
};
|
|
47
50
|
}
|
package/src/commands/policy.mjs
CHANGED
|
@@ -15,7 +15,19 @@ import { resolveProjectRoot } from '../shared/config.mjs';
|
|
|
15
15
|
import { loadControl, resolveDenies } from '../shared/control.mjs';
|
|
16
16
|
import { resolveStores, remoteUnavailableReason } from '../shared/stores.mjs';
|
|
17
17
|
import { log, err, c, select } from '../shared/util.mjs';
|
|
18
|
-
import { parseIntFlag } from '../shared/flags.mjs';
|
|
18
|
+
import { parseIntFlag, parseDimensionConditions } from '../shared/flags.mjs';
|
|
19
|
+
|
|
20
|
+
/** `field` → display label for `formatPolicy`'s dimension summary. */
|
|
21
|
+
const DIMENSION_DISPLAY = [
|
|
22
|
+
['tags', 'tags_mode', 'tags'],
|
|
23
|
+
['source_agent', 'source_agent_mode', 'source_agent'],
|
|
24
|
+
['trigger', 'trigger_mode', 'trigger'],
|
|
25
|
+
['kind', 'kind_mode', 'kind'],
|
|
26
|
+
['host', 'host_mode', 'host'],
|
|
27
|
+
['origin_repo', 'origin_repo_mode', 'origin_repo'],
|
|
28
|
+
['origin_branch', 'origin_branch_mode', 'origin_branch'],
|
|
29
|
+
['origin_pr', 'origin_pr_mode', 'origin_pr'],
|
|
30
|
+
];
|
|
19
31
|
|
|
20
32
|
function pickRemote({ root, env, args }) {
|
|
21
33
|
const { remoteDenied } = resolveDenies(root, { env });
|
|
@@ -32,6 +44,12 @@ function formatPolicy(p) {
|
|
|
32
44
|
if (p.max_seen_count != null) conditions.push(`max_seen_count=${p.max_seen_count}`);
|
|
33
45
|
if (p.max_read_count != null) conditions.push(`max_read_count=${p.max_read_count}`);
|
|
34
46
|
if (p.max_opened_count != null) conditions.push(`max_opened_count=${p.max_opened_count}`);
|
|
47
|
+
for (const [field, modeField, label] of DIMENSION_DISPLAY) {
|
|
48
|
+
const values = p[field];
|
|
49
|
+
if (!Array.isArray(values) || values.length === 0) continue;
|
|
50
|
+
const mode = p[modeField];
|
|
51
|
+
conditions.push(`${label}${mode ? `:${mode}` : ''}=[${values.join(',')}]`);
|
|
52
|
+
}
|
|
35
53
|
const mode = p.mode === 'auto' ? (p.enabled ? c.green('auto (enabled)') : c.dim('auto (disabled)')) : c.dim('review');
|
|
36
54
|
return `${c.cyan(p.id)} ${c.bold(p.name)} ${c.dim(p.scope)} ${mode}${conditions.length ? ` ${c.dim(conditions.join(', '))}` : ''}`;
|
|
37
55
|
}
|
|
@@ -52,7 +70,7 @@ async function list(args, store) {
|
|
|
52
70
|
|
|
53
71
|
async function create(args, store) {
|
|
54
72
|
if (!args.scope || !args.name) {
|
|
55
|
-
err(`${c.red('Usage:')} lorekit policy create --scope <scope> --name <name> [--mode review|auto] [--enabled] [--min-age-days N] [--unseen-days N] [--max-seen-count N] [--max-read-count N] [--max-opened-count N]`);
|
|
73
|
+
err(`${c.red('Usage:')} lorekit policy create --scope <scope> --name <name> [--mode review|auto] [--enabled] [--min-age-days N] [--unseen-days N] [--max-seen-count N] [--max-read-count N] [--max-opened-count N] [--tags a,b --tags-mode any|all|none] [--kind|--host|--trigger|--source-agent|--origin-repo|--origin-branch|--origin-pr a,b [--<dim>-mode in|nin]]`);
|
|
56
74
|
return 1;
|
|
57
75
|
}
|
|
58
76
|
const minAge = parseIntFlag(args['min-age-days'], 'min-age-days');
|
|
@@ -69,6 +87,8 @@ async function create(args, store) {
|
|
|
69
87
|
err(`${c.red('Error:')} --mode must be "review" or "auto"`);
|
|
70
88
|
return 1;
|
|
71
89
|
}
|
|
90
|
+
const dims = parseDimensionConditions(args, { clearable: false });
|
|
91
|
+
if (dims.error) { err(`${c.red('Error:')} ${dims.error}`); return 1; }
|
|
72
92
|
|
|
73
93
|
const res = await store.policyCreate({
|
|
74
94
|
scope: args.scope,
|
|
@@ -80,6 +100,7 @@ async function create(args, store) {
|
|
|
80
100
|
max_seen_count: maxSeen.value,
|
|
81
101
|
max_read_count: maxRead.value,
|
|
82
102
|
max_opened_count: maxOpened.value,
|
|
103
|
+
...dims.conditions,
|
|
83
104
|
});
|
|
84
105
|
if (!res.ok) {
|
|
85
106
|
const msg = res.error?.message ?? res.error ?? res.networkError ?? 'the server rejected the request';
|
|
@@ -95,7 +116,7 @@ async function create(args, store) {
|
|
|
95
116
|
async function update(args, store) {
|
|
96
117
|
const id = args._[2];
|
|
97
118
|
if (!id) {
|
|
98
|
-
err(`${c.red('Usage:')} lorekit policy update <id> [--name N] [--mode review|auto] [--enabled|--disabled] [--min-age-days N] [
|
|
119
|
+
err(`${c.red('Usage:')} lorekit policy update <id> [--name N] [--mode review|auto] [--enabled|--disabled] [--min-age-days N|--clear-min-age-days] [...] [--tags a,b|--clear-tags] [--kind|--host|--trigger|--source-agent|--origin-repo|--origin-branch|--origin-pr a,b|--clear-<dim>] [--<dim>-mode ...]`);
|
|
99
120
|
return 1;
|
|
100
121
|
}
|
|
101
122
|
if (args.mode !== undefined && args.mode !== 'review' && args.mode !== 'auto') {
|
|
@@ -121,6 +142,9 @@ async function update(args, store) {
|
|
|
121
142
|
if (parsed.error) { err(`${c.red('Error:')} ${parsed.error}`); return 1; }
|
|
122
143
|
patch[field] = parsed.value;
|
|
123
144
|
}
|
|
145
|
+
const dims = parseDimensionConditions(args, { clearable: true });
|
|
146
|
+
if (dims.error) { err(`${c.red('Error:')} ${dims.error}`); return 1; }
|
|
147
|
+
Object.assign(patch, dims.conditions);
|
|
124
148
|
if (Object.keys(patch).length === 0) {
|
|
125
149
|
err(`${c.red('Error:')} at least one field to update is required`);
|
|
126
150
|
return 1;
|
package/src/shared/flags.mjs
CHANGED
|
@@ -14,3 +14,76 @@ export function parseIntFlag(raw, name) {
|
|
|
14
14
|
}
|
|
15
15
|
return { value: Number(String(raw).trim()) };
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The eight dimension filters a retention policy / inline groom call can
|
|
20
|
+
* carry (migration 00093) — the CLI's flag-name ↔ field-name ↔ valid-mode
|
|
21
|
+
* table. Mirrors `groomDimensionFilterProperties` in
|
|
22
|
+
* `packages/schemas/src/shared/tool-catalog.ts` and `GROOM_DIMENSION_FIELDS`
|
|
23
|
+
* in `supabase/functions/mcp/tools.ts`; kept as ONE list here (not three) so
|
|
24
|
+
* `policy create`/`policy update`/`groom` cannot describe the dimensions
|
|
25
|
+
* differently.
|
|
26
|
+
*/
|
|
27
|
+
const DIMENSIONS = [
|
|
28
|
+
{ flag: 'tags', field: 'tags', modes: ['any', 'all', 'none'] },
|
|
29
|
+
{ flag: 'source-agent', field: 'source_agent', modes: ['in', 'nin'] },
|
|
30
|
+
{ flag: 'trigger', field: 'trigger', modes: ['in', 'nin'] },
|
|
31
|
+
{ flag: 'kind', field: 'kind', modes: ['in', 'nin'] },
|
|
32
|
+
{ flag: 'host', field: 'host', modes: ['in', 'nin'] },
|
|
33
|
+
{ flag: 'origin-repo', field: 'origin_repo', modes: ['in', 'nin'] },
|
|
34
|
+
{ flag: 'origin-branch', field: 'origin_branch', modes: ['in', 'nin'] },
|
|
35
|
+
{ flag: 'origin-pr', field: 'origin_pr', modes: ['in', 'nin'] },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parse the eight retention dimension filters (`--tags`/`--kind`/`--host`/
|
|
40
|
+
* `--trigger`/`--source-agent`/`--origin-repo`/`--origin-branch`/`--origin-pr`,
|
|
41
|
+
* each with a `--<dim>-mode`) out of a parsed-args object.
|
|
42
|
+
*
|
|
43
|
+
* Comma-separated values — matching the existing `write --tags a,b,c`
|
|
44
|
+
* convention. The minimal flag parser (`bin/lorekit.mjs`'s `parseArgs`) is
|
|
45
|
+
* last-wins on a repeated flag, so comma-splitting is the established
|
|
46
|
+
* multi-value shape in this CLI, not a new one invented here.
|
|
47
|
+
*
|
|
48
|
+
* `--<dim>-mode` is validated against that DIMENSION's own enum (`tags-mode`
|
|
49
|
+
* accepts any/all/none; every other dimension accepts in/nin) — an invalid
|
|
50
|
+
* value is rejected rather than silently forwarded to the RPC as a raw
|
|
51
|
+
* Postgres enum error.
|
|
52
|
+
*
|
|
53
|
+
* With `clearable: true` (policy update), `--clear-<dim>` sends an explicit
|
|
54
|
+
* `null` for the VALUE field only, and skips both the value and mode flags for
|
|
55
|
+
* that dimension entirely — matching how `policy update --clear-min-age-days`
|
|
56
|
+
* already ignores `--min-age-days` when both are passed. The MODE field is
|
|
57
|
+
* left untouched by a clear: it is meaningless once the value is null, and
|
|
58
|
+
* silently dropping it if the caller separately set it would be a second
|
|
59
|
+
* surprise on top of the clear.
|
|
60
|
+
*
|
|
61
|
+
* Returns `{ conditions }` containing only the fields the caller actually
|
|
62
|
+
* named — never a full 16-key object of undefineds, so a caller can spread it
|
|
63
|
+
* straight into a request body without a second `stripUndefined` pass losing
|
|
64
|
+
* an intentional `null` — or `{ error }` naming the offending flag.
|
|
65
|
+
*/
|
|
66
|
+
export function parseDimensionConditions(args, { clearable = false } = {}) {
|
|
67
|
+
const conditions = {};
|
|
68
|
+
for (const { flag, field, modes } of DIMENSIONS) {
|
|
69
|
+
if (clearable && args[`clear-${flag}`]) {
|
|
70
|
+
conditions[field] = null;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const raw = args[flag];
|
|
74
|
+
if (raw !== undefined) {
|
|
75
|
+
conditions[field] = String(raw)
|
|
76
|
+
.split(',')
|
|
77
|
+
.map((s) => s.trim())
|
|
78
|
+
.filter((s) => s.length > 0);
|
|
79
|
+
}
|
|
80
|
+
const modeRaw = args[`${flag}-mode`];
|
|
81
|
+
if (modeRaw !== undefined) {
|
|
82
|
+
if (!modes.includes(modeRaw)) {
|
|
83
|
+
return { error: `--${flag}-mode must be one of ${modes.join('|')}, got ${JSON.stringify(String(modeRaw))}` };
|
|
84
|
+
}
|
|
85
|
+
conditions[`${field}_mode`] = modeRaw;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { conditions };
|
|
89
|
+
}
|
package/src/store/remote.mjs
CHANGED
|
@@ -42,6 +42,20 @@ function stripUndefined(obj) {
|
|
|
42
42
|
return out;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Drop only `undefined` — keeps an explicit `null`. Bare `stripUndefined`
|
|
46
|
+
// above is right for a CREATE body, where an omitted dimension and an
|
|
47
|
+
// explicit `null` mean the same "not filtered". It is wrong for a PATCH,
|
|
48
|
+
// where `null` is the caller's explicit instruction to CLEAR a
|
|
49
|
+
// previously-set condition (`policy update --clear-kind`) — dropping it
|
|
50
|
+
// there means the field silently stays whatever it already was, which is
|
|
51
|
+
// the opposite of what was asked. Named for what it keeps, not what it
|
|
52
|
+
// drops, since "strip" alone reads as "strip everything falsy-ish".
|
|
53
|
+
function stripUndefinedKeepNull(obj) {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const [k, v] of Object.entries(obj || {})) if (v !== undefined) out[k] = v;
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
45
59
|
/**
|
|
46
60
|
* A read that could not be answered — a transport failure or a non-2xx status,
|
|
47
61
|
* as opposed to "the lesson is not there".
|
|
@@ -768,9 +782,20 @@ class RemoteStore {
|
|
|
768
782
|
return { ok: true, entries: Array.isArray(res.data?.entries) ? res.data.entries : [] };
|
|
769
783
|
}
|
|
770
784
|
|
|
771
|
-
// POST /policies → the created policy object.
|
|
772
|
-
|
|
773
|
-
|
|
785
|
+
// POST /policies → the created policy object. `...dims` carries whichever
|
|
786
|
+
// of the eight dimension filters (+ their `_mode` companions) the caller
|
|
787
|
+
// named — see `parseDimensionConditions` in `shared/flags.mjs`, the shared
|
|
788
|
+
// parser policy create/update and groom all go through.
|
|
789
|
+
async policyCreate({
|
|
790
|
+
scope, name, mode, enabled, min_age_days, unseen_days, max_seen_count, max_read_count, max_opened_count,
|
|
791
|
+
tags, tags_mode, source_agent, source_agent_mode, trigger, trigger_mode, kind, kind_mode, host, host_mode,
|
|
792
|
+
origin_repo, origin_repo_mode, origin_branch, origin_branch_mode, origin_pr, origin_pr_mode,
|
|
793
|
+
} = {}) {
|
|
794
|
+
const body = stripUndefined({
|
|
795
|
+
scope, name, mode, enabled, min_age_days, unseen_days, max_seen_count, max_read_count, max_opened_count,
|
|
796
|
+
tags, tags_mode, source_agent, source_agent_mode, trigger, trigger_mode, kind, kind_mode, host, host_mode,
|
|
797
|
+
origin_repo, origin_repo_mode, origin_branch, origin_branch_mode, origin_pr, origin_pr_mode,
|
|
798
|
+
});
|
|
774
799
|
const res = await this._rest('/memories/policies', { method: 'POST', body });
|
|
775
800
|
if (!res.ok) return { ok: false, error: res.error, httpStatus: res.httpStatus, networkError: res.networkError };
|
|
776
801
|
return { ok: true, policy: res.data };
|
|
@@ -778,10 +803,11 @@ class RemoteStore {
|
|
|
778
803
|
|
|
779
804
|
// PATCH /policies/:id → the updated policy object. An omitted field is left
|
|
780
805
|
// unchanged; pass an explicit `null` in the patch to clear a condition —
|
|
781
|
-
// this method does not strip nulls, only `undefined` (
|
|
782
|
-
// that distinction, which is the whole point of the RPC's JSONB-patch
|
|
806
|
+
// this method does not strip nulls, only `undefined` (stripUndefinedKeepNull
|
|
807
|
+
// keeps that distinction, which is the whole point of the RPC's JSONB-patch
|
|
808
|
+
// design and of `policy update --clear-*`).
|
|
783
809
|
async policyUpdate(id, patch = {}) {
|
|
784
|
-
const body =
|
|
810
|
+
const body = stripUndefinedKeepNull(patch);
|
|
785
811
|
const res = await this._rest(`/memories/policies/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
|
786
812
|
if (!res.ok) return { ok: false, error: res.error, httpStatus: res.httpStatus, networkError: res.networkError };
|
|
787
813
|
return { ok: true, policy: res.data };
|
|
@@ -796,9 +822,18 @@ class RemoteStore {
|
|
|
796
822
|
|
|
797
823
|
// POST /groom/preview → { count, keys: [{ scope, key }] } — the SAME
|
|
798
824
|
// candidates a groom() run would archive. Pass either `policy_id` or
|
|
799
|
-
// `scope` (+ optional conditions
|
|
800
|
-
|
|
801
|
-
|
|
825
|
+
// `scope` (+ optional conditions, including the eight dimension filters —
|
|
826
|
+
// see policyCreate's comment), never both.
|
|
827
|
+
async groomPreview({
|
|
828
|
+
policy_id, scope, min_age_days, unseen_days, max_seen_count, max_read_count, max_opened_count,
|
|
829
|
+
tags, tags_mode, source_agent, source_agent_mode, trigger, trigger_mode, kind, kind_mode, host, host_mode,
|
|
830
|
+
origin_repo, origin_repo_mode, origin_branch, origin_branch_mode, origin_pr, origin_pr_mode,
|
|
831
|
+
} = {}) {
|
|
832
|
+
const body = stripUndefined({
|
|
833
|
+
policy_id, scope, min_age_days, unseen_days, max_seen_count, max_read_count, max_opened_count,
|
|
834
|
+
tags, tags_mode, source_agent, source_agent_mode, trigger, trigger_mode, kind, kind_mode, host, host_mode,
|
|
835
|
+
origin_repo, origin_repo_mode, origin_branch, origin_branch_mode, origin_pr, origin_pr_mode,
|
|
836
|
+
});
|
|
802
837
|
const res = await this._rest('/memories/groom/preview', { method: 'POST', body });
|
|
803
838
|
if (!res.ok) return { ok: false, error: res.error, httpStatus: res.httpStatus, networkError: res.networkError };
|
|
804
839
|
return { ok: true, count: res.data?.count ?? 0, keys: Array.isArray(res.data?.keys) ? res.data.keys : [] };
|
|
@@ -806,8 +841,16 @@ class RemoteStore {
|
|
|
806
841
|
|
|
807
842
|
// POST /groom/run → archives every previewed candidate, in one transaction.
|
|
808
843
|
// Soft-archive only (recoverable via restore); never hard-deletes.
|
|
809
|
-
async groomRun({
|
|
810
|
-
|
|
844
|
+
async groomRun({
|
|
845
|
+
policy_id, scope, min_age_days, unseen_days, max_seen_count, max_read_count, max_opened_count,
|
|
846
|
+
tags, tags_mode, source_agent, source_agent_mode, trigger, trigger_mode, kind, kind_mode, host, host_mode,
|
|
847
|
+
origin_repo, origin_repo_mode, origin_branch, origin_branch_mode, origin_pr, origin_pr_mode,
|
|
848
|
+
} = {}) {
|
|
849
|
+
const body = stripUndefined({
|
|
850
|
+
policy_id, scope, min_age_days, unseen_days, max_seen_count, max_read_count, max_opened_count,
|
|
851
|
+
tags, tags_mode, source_agent, source_agent_mode, trigger, trigger_mode, kind, kind_mode, host, host_mode,
|
|
852
|
+
origin_repo, origin_repo_mode, origin_branch, origin_branch_mode, origin_pr, origin_pr_mode,
|
|
853
|
+
});
|
|
811
854
|
const res = await this._rest('/memories/groom/run', { method: 'POST', body });
|
|
812
855
|
if (!res.ok) return { ok: false, error: res.error, httpStatus: res.httpStatus, networkError: res.networkError };
|
|
813
856
|
return { ok: true, archived: res.data?.archived ?? 0, keys: Array.isArray(res.data?.keys) ? res.data.keys : [] };
|
|
@@ -56,7 +56,8 @@ export const ORG_TOOL_NAMES = [
|
|
|
56
56
|
];
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* The `tools/list` payload: name, description
|
|
59
|
+
* The `tools/list` payload: name, description, inputSchema and MCP
|
|
60
|
+
* annotations per op.
|
|
60
61
|
* Identical projection to the edge server's, from the same declaration, so the
|
|
61
62
|
* local stdio server and the hosted server advertise the same contract.
|
|
62
63
|
*/
|
|
@@ -156,6 +157,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
156
157
|
"description": "The lessons that actually shaped this run, as `scope::key` strings — exactly the labels they were injected under. Name only the ones you applied; an empty or omitted list is the honest answer when none were. Silently ignored where a reference names nothing you can see, so a wrong guess costs nothing and the write always succeeds."
|
|
157
158
|
}
|
|
158
159
|
}
|
|
160
|
+
},
|
|
161
|
+
"annotations": {
|
|
162
|
+
"readOnlyHint": false,
|
|
163
|
+
"destructiveHint": true,
|
|
164
|
+
"idempotentHint": false,
|
|
165
|
+
"openWorldHint": false
|
|
159
166
|
}
|
|
160
167
|
},
|
|
161
168
|
{
|
|
@@ -180,6 +187,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
180
187
|
"description": "Batch mode: one or more `scope::key` references, fetched in a single call. Cannot be combined with `scope`/`key`. Each entry is parsed by the same reference grammar `memory.write`'s `cited` field uses (`scope::key`, verbatim scope — never lowercased). Silently truncated past 32 entries."
|
|
181
188
|
}
|
|
182
189
|
}
|
|
190
|
+
},
|
|
191
|
+
"annotations": {
|
|
192
|
+
"readOnlyHint": true,
|
|
193
|
+
"destructiveHint": false,
|
|
194
|
+
"idempotentHint": true,
|
|
195
|
+
"openWorldHint": false
|
|
183
196
|
}
|
|
184
197
|
},
|
|
185
198
|
{
|
|
@@ -242,6 +255,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
242
255
|
"description": "full (default) returns each entry's complete `value`. summary omits `value` and returns `value_bytes` + a 200-character `preview` instead — the cheap discovery read for deciding WHICH lessons to then fetch with `memory.read`."
|
|
243
256
|
}
|
|
244
257
|
}
|
|
258
|
+
},
|
|
259
|
+
"annotations": {
|
|
260
|
+
"readOnlyHint": true,
|
|
261
|
+
"destructiveHint": false,
|
|
262
|
+
"idempotentHint": true,
|
|
263
|
+
"openWorldHint": false
|
|
245
264
|
}
|
|
246
265
|
},
|
|
247
266
|
{
|
|
@@ -272,6 +291,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
272
291
|
"description": "Org slug to delete under (org-owned delete). Omit for a personal memory. Soft-archive requires a member/admin/owner role; hard-delete (force: true) requires admin/owner — verified server-side."
|
|
273
292
|
}
|
|
274
293
|
}
|
|
294
|
+
},
|
|
295
|
+
"annotations": {
|
|
296
|
+
"readOnlyHint": false,
|
|
297
|
+
"destructiveHint": true,
|
|
298
|
+
"idempotentHint": true,
|
|
299
|
+
"openWorldHint": false
|
|
275
300
|
}
|
|
276
301
|
},
|
|
277
302
|
{
|
|
@@ -313,6 +338,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
313
338
|
"description": "Opaque cursor from a previous response's `nextCursor`. Omit to start from the first page."
|
|
314
339
|
}
|
|
315
340
|
}
|
|
341
|
+
},
|
|
342
|
+
"annotations": {
|
|
343
|
+
"readOnlyHint": true,
|
|
344
|
+
"destructiveHint": false,
|
|
345
|
+
"idempotentHint": true,
|
|
346
|
+
"openWorldHint": false
|
|
316
347
|
}
|
|
317
348
|
},
|
|
318
349
|
{
|
|
@@ -334,6 +365,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
334
365
|
"description": "Lesson identifier, unique within the scope. Max 512 characters."
|
|
335
366
|
}
|
|
336
367
|
}
|
|
368
|
+
},
|
|
369
|
+
"annotations": {
|
|
370
|
+
"readOnlyHint": false,
|
|
371
|
+
"destructiveHint": false,
|
|
372
|
+
"idempotentHint": true,
|
|
373
|
+
"openWorldHint": false
|
|
337
374
|
}
|
|
338
375
|
},
|
|
339
376
|
{
|
|
@@ -342,6 +379,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
342
379
|
"inputSchema": {
|
|
343
380
|
"type": "object",
|
|
344
381
|
"properties": {}
|
|
382
|
+
},
|
|
383
|
+
"annotations": {
|
|
384
|
+
"readOnlyHint": true,
|
|
385
|
+
"destructiveHint": false,
|
|
386
|
+
"idempotentHint": true,
|
|
387
|
+
"openWorldHint": false
|
|
345
388
|
}
|
|
346
389
|
},
|
|
347
390
|
{
|
|
@@ -362,6 +405,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
362
405
|
"description": "Maximum entries to return."
|
|
363
406
|
}
|
|
364
407
|
}
|
|
408
|
+
},
|
|
409
|
+
"annotations": {
|
|
410
|
+
"readOnlyHint": true,
|
|
411
|
+
"destructiveHint": false,
|
|
412
|
+
"idempotentHint": true,
|
|
413
|
+
"openWorldHint": false
|
|
365
414
|
}
|
|
366
415
|
},
|
|
367
416
|
{
|
|
@@ -383,6 +432,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
383
432
|
"description": "Lesson identifier, unique within the scope. Max 512 characters."
|
|
384
433
|
}
|
|
385
434
|
}
|
|
435
|
+
},
|
|
436
|
+
"annotations": {
|
|
437
|
+
"readOnlyHint": false,
|
|
438
|
+
"destructiveHint": false,
|
|
439
|
+
"idempotentHint": true,
|
|
440
|
+
"openWorldHint": false
|
|
386
441
|
}
|
|
387
442
|
},
|
|
388
443
|
{
|
|
@@ -399,6 +454,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
399
454
|
"description": "Only purge archived lessons older than this many days."
|
|
400
455
|
}
|
|
401
456
|
}
|
|
457
|
+
},
|
|
458
|
+
"annotations": {
|
|
459
|
+
"readOnlyHint": false,
|
|
460
|
+
"destructiveHint": true,
|
|
461
|
+
"idempotentHint": true,
|
|
462
|
+
"openWorldHint": false
|
|
402
463
|
}
|
|
403
464
|
},
|
|
404
465
|
{
|
|
@@ -407,6 +468,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
407
468
|
"inputSchema": {
|
|
408
469
|
"type": "object",
|
|
409
470
|
"properties": {}
|
|
471
|
+
},
|
|
472
|
+
"annotations": {
|
|
473
|
+
"readOnlyHint": false,
|
|
474
|
+
"destructiveHint": true,
|
|
475
|
+
"idempotentHint": true,
|
|
476
|
+
"openWorldHint": false
|
|
410
477
|
}
|
|
411
478
|
},
|
|
412
479
|
{
|
|
@@ -428,6 +495,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
428
495
|
"description": "Human-readable display name"
|
|
429
496
|
}
|
|
430
497
|
}
|
|
498
|
+
},
|
|
499
|
+
"annotations": {
|
|
500
|
+
"readOnlyHint": false,
|
|
501
|
+
"destructiveHint": false,
|
|
502
|
+
"idempotentHint": false,
|
|
503
|
+
"openWorldHint": false
|
|
431
504
|
}
|
|
432
505
|
},
|
|
433
506
|
{
|
|
@@ -436,6 +509,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
436
509
|
"inputSchema": {
|
|
437
510
|
"type": "object",
|
|
438
511
|
"properties": {}
|
|
512
|
+
},
|
|
513
|
+
"annotations": {
|
|
514
|
+
"readOnlyHint": true,
|
|
515
|
+
"destructiveHint": false,
|
|
516
|
+
"idempotentHint": true,
|
|
517
|
+
"openWorldHint": false
|
|
439
518
|
}
|
|
440
519
|
},
|
|
441
520
|
{
|
|
@@ -457,6 +536,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
457
536
|
"description": "New display name"
|
|
458
537
|
}
|
|
459
538
|
}
|
|
539
|
+
},
|
|
540
|
+
"annotations": {
|
|
541
|
+
"readOnlyHint": false,
|
|
542
|
+
"destructiveHint": true,
|
|
543
|
+
"idempotentHint": true,
|
|
544
|
+
"openWorldHint": false
|
|
460
545
|
}
|
|
461
546
|
},
|
|
462
547
|
{
|
|
@@ -473,6 +558,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
473
558
|
"description": "The org slug to delete"
|
|
474
559
|
}
|
|
475
560
|
}
|
|
561
|
+
},
|
|
562
|
+
"annotations": {
|
|
563
|
+
"readOnlyHint": false,
|
|
564
|
+
"destructiveHint": true,
|
|
565
|
+
"idempotentHint": true,
|
|
566
|
+
"openWorldHint": false
|
|
476
567
|
}
|
|
477
568
|
},
|
|
478
569
|
{
|
|
@@ -481,6 +572,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
481
572
|
"inputSchema": {
|
|
482
573
|
"type": "object",
|
|
483
574
|
"properties": {}
|
|
575
|
+
},
|
|
576
|
+
"annotations": {
|
|
577
|
+
"readOnlyHint": true,
|
|
578
|
+
"destructiveHint": false,
|
|
579
|
+
"idempotentHint": true,
|
|
580
|
+
"openWorldHint": false
|
|
484
581
|
}
|
|
485
582
|
},
|
|
486
583
|
{
|
|
@@ -675,6 +772,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
675
772
|
"description": "How the origin_pr filter combines."
|
|
676
773
|
}
|
|
677
774
|
}
|
|
775
|
+
},
|
|
776
|
+
"annotations": {
|
|
777
|
+
"readOnlyHint": false,
|
|
778
|
+
"destructiveHint": false,
|
|
779
|
+
"idempotentHint": false,
|
|
780
|
+
"openWorldHint": false
|
|
678
781
|
}
|
|
679
782
|
},
|
|
680
783
|
{
|
|
@@ -866,6 +969,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
866
969
|
"description": "How the origin_pr filter combines. Omit to leave unchanged; pass explicit null to clear."
|
|
867
970
|
}
|
|
868
971
|
}
|
|
972
|
+
},
|
|
973
|
+
"annotations": {
|
|
974
|
+
"readOnlyHint": false,
|
|
975
|
+
"destructiveHint": true,
|
|
976
|
+
"idempotentHint": true,
|
|
977
|
+
"openWorldHint": false
|
|
869
978
|
}
|
|
870
979
|
},
|
|
871
980
|
{
|
|
@@ -882,6 +991,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
882
991
|
"description": "The policy id to delete."
|
|
883
992
|
}
|
|
884
993
|
}
|
|
994
|
+
},
|
|
995
|
+
"annotations": {
|
|
996
|
+
"readOnlyHint": false,
|
|
997
|
+
"destructiveHint": true,
|
|
998
|
+
"idempotentHint": true,
|
|
999
|
+
"openWorldHint": false
|
|
885
1000
|
}
|
|
886
1001
|
},
|
|
887
1002
|
{
|
|
@@ -1058,6 +1173,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
1058
1173
|
"description": "How the origin_pr filter combines."
|
|
1059
1174
|
}
|
|
1060
1175
|
}
|
|
1176
|
+
},
|
|
1177
|
+
"annotations": {
|
|
1178
|
+
"readOnlyHint": true,
|
|
1179
|
+
"destructiveHint": false,
|
|
1180
|
+
"idempotentHint": true,
|
|
1181
|
+
"openWorldHint": false
|
|
1061
1182
|
}
|
|
1062
1183
|
},
|
|
1063
1184
|
{
|
|
@@ -1234,6 +1355,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
1234
1355
|
"description": "How the origin_pr filter combines."
|
|
1235
1356
|
}
|
|
1236
1357
|
}
|
|
1358
|
+
},
|
|
1359
|
+
"annotations": {
|
|
1360
|
+
"readOnlyHint": false,
|
|
1361
|
+
"destructiveHint": false,
|
|
1362
|
+
"idempotentHint": true,
|
|
1363
|
+
"openWorldHint": false
|
|
1237
1364
|
}
|
|
1238
1365
|
},
|
|
1239
1366
|
{
|
|
@@ -1260,6 +1387,12 @@ export const MCP_TOOL_DEFS = [
|
|
|
1260
1387
|
"description": "true to protect, false to unprotect."
|
|
1261
1388
|
}
|
|
1262
1389
|
}
|
|
1390
|
+
},
|
|
1391
|
+
"annotations": {
|
|
1392
|
+
"readOnlyHint": false,
|
|
1393
|
+
"destructiveHint": false,
|
|
1394
|
+
"idempotentHint": true,
|
|
1395
|
+
"openWorldHint": false
|
|
1263
1396
|
}
|
|
1264
1397
|
}
|
|
1265
1398
|
];
|