pi-continual-harness 0.4.0 → 0.5.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/README.md +48 -13
- package/package.json +1 -1
- package/src/config.ts +13 -0
- package/src/harness.ts +61 -2
- package/src/index.ts +5 -0
- package/src/outcome.ts +104 -0
package/README.md
CHANGED
|
@@ -16,8 +16,9 @@ with:
|
|
|
16
16
|
The durable markdown file (`.pi/harness-state.md`) is the composition seam,
|
|
17
17
|
and it is **two-way**: `/refine --commit` and `/harness export` write it;
|
|
18
18
|
`/harness import` parses it back and merges into the live store (offline edits
|
|
19
|
-
win on conflict), so refinements pi-reflect makes flow back online.
|
|
20
|
-
|
|
19
|
+
win on conflict), so refinements pi-reflect makes flow back online. `/harness push-mem`
|
|
20
|
+
pushes active items into pi-mem's semantic store (see
|
|
21
|
+
[Composing with pi-mem](#composing-with-pi-mem)).
|
|
21
22
|
|
|
22
23
|
## Why
|
|
23
24
|
|
|
@@ -40,7 +41,7 @@ leaving offline and storage to the packages that already do them well.
|
|
|
40
41
|
## Install
|
|
41
42
|
|
|
42
43
|
```
|
|
43
|
-
pi install
|
|
44
|
+
pi install npm:pi-continual-harness
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
Or drop `src/index.ts` into `~/.pi/agent/extensions/`.
|
|
@@ -63,6 +64,7 @@ Durable I/O (round-trip with pi-reflect):
|
|
|
63
64
|
/harness prune [--decay <days>] # drop items below the importance floor
|
|
64
65
|
/harness keep <id> # nudge importance up (+0.1)
|
|
65
66
|
/harness drop <id> # nudge importance down (−0.1)
|
|
67
|
+
/harness push-mem [--all|--kind <kind>] # persist active items to pi-mem (save_memory)
|
|
66
68
|
```
|
|
67
69
|
|
|
68
70
|
`import` reconciles the file into the live store: items whose id matches an
|
|
@@ -84,6 +86,24 @@ The model-facing tools:
|
|
|
84
86
|
Active items are injected into the system prompt each turn as a structured
|
|
85
87
|
block, appended to (never replacing) the base prompt.
|
|
86
88
|
|
|
89
|
+
## Composing with pi-mem
|
|
90
|
+
|
|
91
|
+
`/harness push-mem` copies active harness items into
|
|
92
|
+
[pi-mem](https://github.com/georgebashi/pi-mem)'s semantic memory store, so they
|
|
93
|
+
become searchable across sessions. It works by **steering** the agent to call
|
|
94
|
+
pi-mem's `save_memory` tool — there is **no dependency** on pi-mem: it is a
|
|
95
|
+
soft-fail composition. If pi-mem (or any memory tool) is not installed, the
|
|
96
|
+
agent tells you so rather than fabricating one.
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
pi install npm:pi-mem # optional companion
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
By default only **memory**-kind items are pushed (the clean 1:1 mapping); use
|
|
103
|
+
`--all` for every active item or `--kind prompt|skill|subagent` for a specific
|
|
104
|
+
kind. The harness store itself is unchanged by a push — pi-mem gets a separate
|
|
105
|
+
copy.
|
|
106
|
+
|
|
87
107
|
## Configuration
|
|
88
108
|
|
|
89
109
|
Optional config at `~/.pi/agent/harness.json` (missing or malformed → defaults):
|
|
@@ -93,7 +113,8 @@ Optional config at `~/.pi/agent/harness.json` (missing or malformed → defaults
|
|
|
93
113
|
"durableScope": "global",
|
|
94
114
|
"proposer": "steering",
|
|
95
115
|
"remindRefine": { "enabled": false, "everyTurns": 50 },
|
|
96
|
-
"autoRefine": { "enabled": false, "everyTurns": 100, "commit": false }
|
|
116
|
+
"autoRefine": { "enabled": false, "everyTurns": 100, "commit": false },
|
|
117
|
+
"outcomeImportance": { "enabled": false, "bump": 0.03 }
|
|
97
118
|
}
|
|
98
119
|
```
|
|
99
120
|
|
|
@@ -109,10 +130,19 @@ Optional config at `~/.pi/agent/harness.json` (missing or malformed → defaults
|
|
|
109
130
|
informational only — it never mutates state.
|
|
110
131
|
- **`autoRefine`** — opt-in autonomous self-improvement (**off by default**).
|
|
111
132
|
When `enabled`, the agent runs `/refine` itself every `everyTurns` turns
|
|
112
|
-
(default 100).
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
133
|
+
(default 100). It is one of the package's opt-in autonomous paths: it reuses
|
|
134
|
+
the exact `/refine` routine (audited `REFINE_ENTRY` tagged `source: "auto"`,
|
|
135
|
+
branch-local, `/tree` rollback) and notifies before firing. `commit: true`
|
|
136
|
+
also flushes durable state on each run.
|
|
137
|
+
- **`outcomeImportance`** — opt-in autonomous **promotion** loop (**off by
|
|
138
|
+
default**). When `enabled`, a `turn_end` hook bumps (+`bump`, default 0.03)
|
|
139
|
+
the importance of any active item the agent cited by its `[h_xxxx]` tag in the
|
|
140
|
+
turn's output. Referenced items gain importance and get their `updatedAt`
|
|
141
|
+
touched (so they survive time-based decay); ignored items keep decaying. This
|
|
142
|
+
is the package's second opt-in autonomous path — promotion only, never
|
|
143
|
+
deletes, persisted/branchable like `harness_mutate`. Autonomous demotion from
|
|
144
|
+
outcomes is intentionally NOT done (high false-positive); use `/harness drop`,
|
|
145
|
+
`prune --decay`, or the `dedupe` proposer for that.
|
|
116
146
|
|
|
117
147
|
## How it works
|
|
118
148
|
|
|
@@ -180,16 +210,19 @@ Then `"my-proposer"` is selectable via `/refine --proposer my-proposer` or
|
|
|
180
210
|
|
|
181
211
|
## Status
|
|
182
212
|
|
|
183
|
-
0.
|
|
213
|
+
0.5.x. Implemented:
|
|
184
214
|
|
|
185
215
|
- Unified harness-state store with branch-local snapshots (`/tree` rollback).
|
|
186
216
|
- Online `/refine` + `harness_mutate` / `harness_list` tools.
|
|
187
217
|
- Two-way durable round-trip with pi-reflect (`/harness import|export|status`).
|
|
188
218
|
- Importance hygiene: `/harness prune [--decay <days>]` and `/harness keep|drop
|
|
189
219
|
<id>`.
|
|
220
|
+
- **pi-mem composition**: `/harness push-mem [--all|--kind]` steers the agent
|
|
221
|
+
to persist active items into pi-mem (soft-fail; no dependency).
|
|
190
222
|
- Optional config (`~/.pi/agent/harness.json`): project-local durable scope, an
|
|
191
|
-
opt-in `turn_end` reminder,
|
|
192
|
-
|
|
223
|
+
opt-in `turn_end` reminder, opt-in `turn_end` auto-refine, and an opt-in
|
|
224
|
+
`turn_end` outcome-importance loop — the package's two opt-in autonomous
|
|
225
|
+
paths (both off by default).
|
|
193
226
|
- **Pluggable delta proposers** with a registry: `steering` (default) and
|
|
194
227
|
`dedupe` (rule-based) shipped; `registerProposer()` for custom ones.
|
|
195
228
|
|
|
@@ -197,8 +230,10 @@ Open extension points (see `docs/ROADMAP.md`):
|
|
|
197
230
|
|
|
198
231
|
- A dedicated-model proposer (interface ready; the hidden-model-spend tradeoff
|
|
199
232
|
is a separate decision).
|
|
200
|
-
-
|
|
201
|
-
|
|
233
|
+
- Correction-side outcome signals (promotion is shipped; autonomous demotion
|
|
234
|
+
is high-false-positive, so it is served by `/harness drop`, `prune --decay`,
|
|
235
|
+
and the `dedupe` proposer — a fuzzy `corrections` proposer is the natural
|
|
236
|
+
future extension).
|
|
202
237
|
|
|
203
238
|
## License
|
|
204
239
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-continual-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Online self-improvement layer for pi. A unified, in-trajectory harness-state store (prompt notes, memory, skill descriptions, sub-agent specs) with a manual /refine that proposes evidence-backed structured CRUD deltas. Composes with pi-reflect (offline) and pi-mem (storage); owns only the online optimizer layer. Inspired by Continual Harness (arXiv 2605.09998) and ACE (arXiv 2510.04618).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/src/config.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { DEFAULT_DURABLE_PATH } from "./store.js";
|
|
|
17
17
|
|
|
18
18
|
export const DEFAULT_EVERY_TURNS = 50;
|
|
19
19
|
export const DEFAULT_AUTO_EVERY_TURNS = 100;
|
|
20
|
+
/** Default per-reference importance bump for the opt-in outcome loop. */
|
|
21
|
+
export const DEFAULT_REF_BUMP = 0.03;
|
|
20
22
|
|
|
21
23
|
export interface HarnessConfig {
|
|
22
24
|
durableScope?: "global" | "project";
|
|
@@ -31,6 +33,12 @@ export interface HarnessConfig {
|
|
|
31
33
|
};
|
|
32
34
|
/** Delta proposer name (see proposer.ts registry). Defaults to "steering". */
|
|
33
35
|
proposer?: string;
|
|
36
|
+
/** Opt-in turn_end outcome loop: promote importance of items the agent
|
|
37
|
+
* references by their [h_xxxx] tag. Off by default (autonomous mutation). */
|
|
38
|
+
outcomeImportance?: {
|
|
39
|
+
enabled?: boolean;
|
|
40
|
+
bump?: number;
|
|
41
|
+
};
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
export const DEFAULT_CONFIG: HarnessConfig = {
|
|
@@ -38,6 +46,7 @@ export const DEFAULT_CONFIG: HarnessConfig = {
|
|
|
38
46
|
remindRefine: { enabled: false, everyTurns: DEFAULT_EVERY_TURNS },
|
|
39
47
|
autoRefine: { enabled: false, everyTurns: DEFAULT_AUTO_EVERY_TURNS, commit: false },
|
|
40
48
|
proposer: "steering",
|
|
49
|
+
outcomeImportance: { enabled: false, bump: DEFAULT_REF_BUMP },
|
|
41
50
|
};
|
|
42
51
|
|
|
43
52
|
export const CONFIG_PATH = join(homedir(), ".pi", "agent", "harness.json");
|
|
@@ -58,6 +67,10 @@ function mergeConfig(over: Partial<HarnessConfig>): HarnessConfig {
|
|
|
58
67
|
commit: over.autoRefine?.commit ?? false,
|
|
59
68
|
},
|
|
60
69
|
proposer: over.proposer ?? "steering",
|
|
70
|
+
outcomeImportance: {
|
|
71
|
+
enabled: over.outcomeImportance?.enabled ?? false,
|
|
72
|
+
bump: over.outcomeImportance?.bump ?? DEFAULT_REF_BUMP,
|
|
73
|
+
},
|
|
61
74
|
};
|
|
62
75
|
}
|
|
63
76
|
|
package/src/harness.ts
CHANGED
|
@@ -22,14 +22,15 @@ import {
|
|
|
22
22
|
reconstructFromDurable,
|
|
23
23
|
} from "./store.js";
|
|
24
24
|
import { loadConfig, resolveDurablePath } from "./config.js";
|
|
25
|
-
import type { ComponentKind } from "./types.js";
|
|
25
|
+
import type { ComponentKind, HarnessItem } from "./types.js";
|
|
26
26
|
|
|
27
27
|
export function registerHarness(pi: ExtensionAPI): void {
|
|
28
28
|
pi.registerCommand("harness", {
|
|
29
29
|
description:
|
|
30
30
|
"Durable harness-state I/O + importance hygiene. Subcommands: " +
|
|
31
31
|
"import [--prune] [path] · export [path] · status [path] · " +
|
|
32
|
-
"prune [--decay <days>] · keep <id> · drop <id
|
|
32
|
+
"prune [--decay <days>] · keep <id> · drop <id> · " +
|
|
33
|
+
"push-mem [--all|--kind <kind>] (persist active items to pi-mem).",
|
|
33
34
|
handler: async (args, ctx) => {
|
|
34
35
|
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
35
36
|
const sub = (parts[0] ?? "status").toLowerCase();
|
|
@@ -50,6 +51,9 @@ export function registerHarness(pi: ExtensionAPI): void {
|
|
|
50
51
|
case "drop":
|
|
51
52
|
await handleBump(pi, ctx, rest, -0.1, "drop");
|
|
52
53
|
return;
|
|
54
|
+
case "push-mem":
|
|
55
|
+
await handlePushMem(pi, ctx, rest);
|
|
56
|
+
return;
|
|
53
57
|
case "status":
|
|
54
58
|
default:
|
|
55
59
|
await handleStatus(ctx, rest);
|
|
@@ -160,6 +164,61 @@ async function handleBump(
|
|
|
160
164
|
ctx.ui.notify(`${label}: "${preview}" → importance ${item.importance.toFixed(2)}`, "info");
|
|
161
165
|
}
|
|
162
166
|
|
|
167
|
+
/** Compose a steering message that asks the agent to persist the given active
|
|
168
|
+
* items to long-term memory via a memory tool (pi-mem's save_memory).
|
|
169
|
+
* Tool-agnostic: if no memory tool is present the agent says so; we never
|
|
170
|
+
* fabricate one. No dependency on pi-mem — soft-fail composition. */
|
|
171
|
+
function buildPushMemPrompt(items: HarnessItem[], scope: string): string {
|
|
172
|
+
const lines = [
|
|
173
|
+
`/harness push-mem — persist ${items.length} Continual Harness ${scope} to long-term memory`,
|
|
174
|
+
"",
|
|
175
|
+
"For EACH item below, call your memory tool (pi-mem exposes `save_memory`) once, mapping it as shown. This copies harness state into the semantic memory store so it is searchable across sessions.",
|
|
176
|
+
"",
|
|
177
|
+
"If you do NOT have a memory tool, do not fabricate one: tell the user to install pi-mem (`pi install npm:pi-mem`) and stop.",
|
|
178
|
+
"",
|
|
179
|
+
];
|
|
180
|
+
items.forEach((i, n) => {
|
|
181
|
+
const title = `${i.id} ${i.content.slice(0, 48)}`;
|
|
182
|
+
const text = `${i.content} (evidence: ${i.evidence})`;
|
|
183
|
+
lines.push(
|
|
184
|
+
`${n + 1}. [${i.id}] ${i.content}`,
|
|
185
|
+
` evidence: ${i.evidence}`,
|
|
186
|
+
` → save_memory({ title: ${JSON.stringify(title)}, text: ${JSON.stringify(text)}, concepts: ["${i.kind}", "continual-harness"] })`,
|
|
187
|
+
"",
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
return lines.join("\n");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function handlePushMem(
|
|
194
|
+
pi: ExtensionAPI,
|
|
195
|
+
ctx: ExtensionCommandContext,
|
|
196
|
+
rest: string[],
|
|
197
|
+
): Promise<void> {
|
|
198
|
+
const all = rest.includes("--all");
|
|
199
|
+
const kindIdx = rest.indexOf("--kind");
|
|
200
|
+
let kind: ComponentKind | undefined;
|
|
201
|
+
if (kindIdx >= 0 && kindIdx + 1 < rest.length) {
|
|
202
|
+
const k = rest[kindIdx + 1]!;
|
|
203
|
+
if (k === "prompt" || k === "memory" || k === "skill" || k === "subagent") kind = k;
|
|
204
|
+
}
|
|
205
|
+
// Default: memory kind (the clean 1:1 mapping). --all or --kind override.
|
|
206
|
+
const items = getState().items.filter((i) =>
|
|
207
|
+
i.active && (all || (kind ? i.kind === kind : i.kind === "memory")),
|
|
208
|
+
);
|
|
209
|
+
if (items.length === 0) {
|
|
210
|
+
ctx.ui.notify(
|
|
211
|
+
"No active items to push (default: memory kind; use --all or --kind <kind>).",
|
|
212
|
+
"warning",
|
|
213
|
+
);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const scope = all ? "item(s)" : `${kind ?? "memory"} item(s)`;
|
|
217
|
+
const msg = buildPushMemPrompt(items, scope);
|
|
218
|
+
pi.sendUserMessage(msg);
|
|
219
|
+
ctx.ui.notify(`Steering agent to persist ${items.length} ${scope} to pi-mem.`, "info");
|
|
220
|
+
}
|
|
221
|
+
|
|
163
222
|
async function handleStatus(ctx: ExtensionCommandContext, rest: string[]): Promise<void> {
|
|
164
223
|
const path = await resolvePath(rest, ctx);
|
|
165
224
|
const items = getState().items;
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { registerRefine } from "./refine.js";
|
|
|
13
13
|
import { registerTools } from "./tools.js";
|
|
14
14
|
import { registerReminder } from "./remind.js";
|
|
15
15
|
import { registerAutoRefine } from "./auto-refine.js";
|
|
16
|
+
import { registerOutcome } from "./outcome.js";
|
|
16
17
|
import { getState, reconstruct, STATE_ENTRY } from "./store.js";
|
|
17
18
|
|
|
18
19
|
export default function continualHarness(pi: ExtensionAPI): void {
|
|
@@ -32,6 +33,10 @@ export default function continualHarness(pi: ExtensionAPI): void {
|
|
|
32
33
|
registerRefine(pi);
|
|
33
34
|
registerHarness(pi);
|
|
34
35
|
registerReminder(pi);
|
|
36
|
+
// Register outcome BEFORE auto-refine: the test fake overwrites turn_end on
|
|
37
|
+
// each registration (Map.set), and the auto-refine integration tests assume
|
|
38
|
+
// auto-refine is the surviving handler. In production pi, all three run.
|
|
39
|
+
registerOutcome(pi);
|
|
35
40
|
registerAutoRefine(pi);
|
|
36
41
|
}
|
|
37
42
|
|
package/src/outcome.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// turn_end outcome loop (Phase 5 / limit B-phase-2). The package's SECOND
|
|
2
|
+
// autonomous-mutation path (after auto-refine), so — like auto-refine — it is
|
|
3
|
+
// locked behind an explicit opt-in:
|
|
4
|
+
//
|
|
5
|
+
// ~/.pi/agent/harness.json
|
|
6
|
+
// { "outcomeImportance": { "enabled": true, "bump": 0.03 } }
|
|
7
|
+
//
|
|
8
|
+
// What it does: when the agent's own output cites an injected item by its
|
|
9
|
+
// [h_xxxx] tag, that is unambiguous POSITIVE evidence the item was useful, so
|
|
10
|
+
// we nudge its importance up (+bump). This closes the outcome half of the
|
|
11
|
+
// fitness loop that manual /harness keep|drop (B-phase-1) and /harness prune
|
|
12
|
+
// --decay (A) started: useful items gain importance AND get their updatedAt
|
|
13
|
+
// touched (so they survive time-based decay); ignored items keep decaying.
|
|
14
|
+
//
|
|
15
|
+
// Hard scope cut — CORRECTION/demotion from outcomes is genuinely fuzzy and
|
|
16
|
+
// high-false-positive, so it is intentionally NOT autonomized here. Demotion is
|
|
17
|
+
// served by the existing audited, reviewable primitives: /harness drop,
|
|
18
|
+
// /harness prune --decay, and the dedupe proposer. (See ROADMAP.md.)
|
|
19
|
+
//
|
|
20
|
+
// Safety properties (promotion only, never deletes): clamped to [0,1]; persisted
|
|
21
|
+
// via the same harness-state entries as harness_mutate (branch-local, /tree
|
|
22
|
+
// rollback); visible (notify per turn). Pure reference extraction is factored
|
|
23
|
+
// into findReferences() so it is unit-testable without a live pi runtime.
|
|
24
|
+
|
|
25
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
26
|
+
import { DEFAULT_REF_BUMP, type HarnessConfig, loadConfig } from "./config.js";
|
|
27
|
+
import { bumpImportance, getState } from "./store.js";
|
|
28
|
+
|
|
29
|
+
// Minimal entry shape; kept loose to avoid coupling to pi's internal types.
|
|
30
|
+
type AnyEntry = {
|
|
31
|
+
type?: string;
|
|
32
|
+
message?: {
|
|
33
|
+
role?: string;
|
|
34
|
+
content?: Array<{ type?: string; text?: string }>;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Matches [h_xxxx] citation tags (the same form inject.ts emits).
|
|
39
|
+
const REF_RE = /\[(h_[a-z0-9_]+)\]/gi;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extract distinct harness-item ids the agent cited by their [h_xxxx] tag,
|
|
43
|
+
* keeping only those that are currently known (active or not). Pure.
|
|
44
|
+
*/
|
|
45
|
+
export function findReferences(text: string, knownIds: Set<string>): string[] {
|
|
46
|
+
const found = new Set<string>();
|
|
47
|
+
for (const m of text.matchAll(REF_RE)) {
|
|
48
|
+
const id = m[1]!.toLowerCase();
|
|
49
|
+
if (knownIds.has(id)) found.add(id);
|
|
50
|
+
}
|
|
51
|
+
return [...found];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Number of assistant message-entries already consumed. -1 = unseen; the first
|
|
55
|
+
// observed turn_end seeds the cursor (so enabling mid-session never bumps
|
|
56
|
+
// retroactively for the whole history).
|
|
57
|
+
let scannedMessages = -1;
|
|
58
|
+
|
|
59
|
+
/** Test hook: reset internal cursor. */
|
|
60
|
+
export function resetOutcome(): void {
|
|
61
|
+
scannedMessages = -1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Subscribe to turn_end and promote importance of referenced items (opt-in). */
|
|
65
|
+
export function registerOutcome(pi: ExtensionAPI): void {
|
|
66
|
+
pi.on("turn_end", async (_event, ctx) => {
|
|
67
|
+
const config = await loadConfig();
|
|
68
|
+
if (!config.outcomeImportance?.enabled) return;
|
|
69
|
+
const bump = config.outcomeImportance.bump ?? DEFAULT_REF_BUMP;
|
|
70
|
+
if (bump <= 0) return;
|
|
71
|
+
|
|
72
|
+
const entries = ctx.sessionManager.getBranch() as AnyEntry[];
|
|
73
|
+
const asst = entries.filter((e) => e.type === "message" && e.message?.role === "assistant");
|
|
74
|
+
if (scannedMessages < 0) {
|
|
75
|
+
scannedMessages = asst.length; // seed: no retroactive bump
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const fresh = asst.slice(scannedMessages);
|
|
79
|
+
scannedMessages = asst.length;
|
|
80
|
+
|
|
81
|
+
const text = fresh
|
|
82
|
+
.map((e) => (e.message?.content ?? []).map((c) => c.text ?? "").join(" "))
|
|
83
|
+
.join("\n");
|
|
84
|
+
if (!text.trim()) return;
|
|
85
|
+
|
|
86
|
+
const activeIds = new Set(getState().items.filter((i) => i.active).map((i) => i.id));
|
|
87
|
+
const refs = findReferences(text, activeIds);
|
|
88
|
+
if (refs.length === 0) return;
|
|
89
|
+
|
|
90
|
+
let bumped = 0;
|
|
91
|
+
for (const id of refs) {
|
|
92
|
+
const item = bumpImportance(id, bump, (snapshot, ver) => {
|
|
93
|
+
pi.appendEntry("harness-state", { state: snapshot, version: ver });
|
|
94
|
+
});
|
|
95
|
+
if (item) bumped += 1;
|
|
96
|
+
}
|
|
97
|
+
if (bumped > 0) {
|
|
98
|
+
ctx.ui.notify(
|
|
99
|
+
`Outcome: +${bump} importance for ${bumped} referenced item(s): ${refs.join(", ")}`,
|
|
100
|
+
"info",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|