pi-continual-harness 0.15.1 → 0.16.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 +4 -3
- package/package.json +1 -1
- package/src/harness.ts +82 -11
- package/src/index.ts +11 -0
- package/src/list.ts +157 -0
- package/src/tools.ts +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ Durable I/O (round-trip with pi-reflect):
|
|
|
62
62
|
|
|
63
63
|
```
|
|
64
64
|
/harness status # counts + durable layer presence/mtime
|
|
65
|
+
/harness list [-f] [--kind <kind>] [--scope <global|project|all>] # read items — compact rows, -f = full text + evidence
|
|
65
66
|
/harness export [path] # layered export (or full snapshot to a path)
|
|
66
67
|
/harness export-corpus [path] # calibration corpus JSONL for pi-reflex (local-only; dedupe pairs + lifecycle)
|
|
67
68
|
/harness import [--prune] [path] # layered import (or single file from a path)
|
|
@@ -75,9 +76,9 @@ Durable I/O (round-trip with pi-reflect):
|
|
|
75
76
|
/harness setup [--preset <quiet|recommended|power>] [--yes] # guided config wizard
|
|
76
77
|
```
|
|
77
78
|
|
|
78
|
-
Subcommands, flags, `--kind`/`--model`/`--from`
|
|
79
|
-
`keep`/`drop`/`move` item ids all autocomplete in
|
|
80
|
-
`/harness `.
|
|
79
|
+
Subcommands, flags (`-f` included), `--kind`/`--model`/`--from`/`--scope`
|
|
80
|
+
values, `move` scopes, and `keep`/`drop`/`move` item ids all autocomplete in
|
|
81
|
+
the TUI as you type after `/harness `.
|
|
81
82
|
|
|
82
83
|
**Durable layers.** Every item carries its own durable scope: `global`
|
|
83
84
|
(the shared `~/.pi/agent/harness-state.md`) or `project`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-continual-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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/harness.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// /harness — durable I/O. The two-way round-trip seam with pi-reflect.
|
|
2
2
|
//
|
|
3
3
|
// /harness status [path] counts + durable layer presence/mtime
|
|
4
|
+
// /harness list [-f] [--kind] [--scope] read items: compact rows, -f = full text
|
|
4
5
|
// /harness export [path] layered export (or full snapshot to path)
|
|
5
6
|
// /harness import [--prune] [path] layered import (or single file from path)
|
|
6
7
|
// /harness move <id> <global|project> move an item between durable layers
|
|
@@ -59,6 +60,7 @@ import { buildCorpus } from "./corpus.js";
|
|
|
59
60
|
import { buildStatusReport, renderStatusReport } from "./status.js";
|
|
60
61
|
import { getInjectionStats } from "./inject.js";
|
|
61
62
|
import { nextHint } from "./hints.js";
|
|
63
|
+
import { preview, renderListReport, type ListScope } from "./list.js";
|
|
62
64
|
import { handleSetup, PRESET_COMPLETIONS } from "./setup.js";
|
|
63
65
|
|
|
64
66
|
/** One row of the /harness subcommand menu (label = name, value = name). */
|
|
@@ -72,6 +74,7 @@ const SUBCOMMANDS: CompletionEntry[] = [
|
|
|
72
74
|
{ name: "export", description: "Export active items to durable layers (or one file with a path)" },
|
|
73
75
|
{ name: "export-corpus", description: "Export calibration corpus JSONL for pi-reflex (dedupe pairs + lifecycle)" },
|
|
74
76
|
{ name: "status", description: "Show harness status (counts, audit-trail impact, injection stats, durable layers)" },
|
|
77
|
+
{ name: "list", description: "Read items — compact rows, -f for full text (--kind/--scope filter)" },
|
|
75
78
|
{ name: "prune", description: "Decay & prune inactive items (--decay <days>)" },
|
|
76
79
|
{ name: "keep", description: "Bump item importance (+0.1)" },
|
|
77
80
|
{ name: "drop", description: "Lower item importance (−0.1)" },
|
|
@@ -84,6 +87,13 @@ const SUBCOMMANDS: CompletionEntry[] = [
|
|
|
84
87
|
|
|
85
88
|
const KINDS: ComponentKind[] = ["prompt", "memory", "skill", "subagent"];
|
|
86
89
|
|
|
90
|
+
/** `--scope` values /harness list offers (semantics in src/list.ts). */
|
|
91
|
+
const LIST_SCOPES: Array<{ value: ListScope; description: string }> = [
|
|
92
|
+
{ value: "global", description: "Global-scope items only (~/.pi/agent/harness-state.md)" },
|
|
93
|
+
{ value: "project", description: "Project-scoped items (any project; rows carry the slug)" },
|
|
94
|
+
{ value: "all", description: "Every item, foreign-project included" },
|
|
95
|
+
];
|
|
96
|
+
|
|
87
97
|
/** Flags each subcommand accepts, in menu order. */
|
|
88
98
|
const FLAGS: Record<string, CompletionEntry[]> = {
|
|
89
99
|
import: [{ name: "--prune", description: "Drop live items missing from the durable file" }],
|
|
@@ -97,17 +107,18 @@ const FLAGS: Record<string, CompletionEntry[]> = {
|
|
|
97
107
|
{ name: "--from", description: "Source model (provider/id) whose active items are up for adoption" },
|
|
98
108
|
{ name: "--all", description: "Adopt everything; skip the generalizes-vs-stays filter" },
|
|
99
109
|
],
|
|
110
|
+
list: [
|
|
111
|
+
{ name: "-f", description: "Full item text + evidence instead of the compact preview rows" },
|
|
112
|
+
{ name: "--kind", description: `Limit to one kind (${KINDS.join("|")})` },
|
|
113
|
+
{ name: "--scope", description: "global | project | all (default: global + this project)" },
|
|
114
|
+
],
|
|
100
115
|
setup: [
|
|
101
116
|
{ name: "--preset", description: "Apply one preset: quiet | recommended | power" },
|
|
102
117
|
{ name: "--yes", description: "Scripted consent — skip every confirm prompt" },
|
|
103
118
|
],
|
|
104
119
|
};
|
|
105
120
|
|
|
106
|
-
|
|
107
|
-
// Single line + truncated so the menu row stays readable.
|
|
108
|
-
const flat = text.replace(/\s+/g, " ").trim();
|
|
109
|
-
return flat.length > max ? `${flat.slice(0, max)}…` : flat;
|
|
110
|
-
}
|
|
121
|
+
// preview() lives in src/list.ts (shared with /harness list rows).
|
|
111
122
|
|
|
112
123
|
/**
|
|
113
124
|
* Argument autocomplete for /harness (pi calls this with the FULL text after
|
|
@@ -141,7 +152,7 @@ function completions(argumentPrefix: string) {
|
|
|
141
152
|
const sub = before[0]!.toLowerCase();
|
|
142
153
|
// Flag-value completion: the token right before the one being typed.
|
|
143
154
|
const prev = before.at(-1)!;
|
|
144
|
-
if (prev === "--kind" && sub === "push-mem") {
|
|
155
|
+
if (prev === "--kind" && (sub === "push-mem" || sub === "list")) {
|
|
145
156
|
return filterValues(
|
|
146
157
|
KINDS.map((k) => ({ value: k, description: KIND_LABEL[k] })),
|
|
147
158
|
last,
|
|
@@ -162,6 +173,9 @@ function completions(argumentPrefix: string) {
|
|
|
162
173
|
if (prev === "--preset" && sub === "setup") {
|
|
163
174
|
return filterValues(PRESET_COMPLETIONS, last, before);
|
|
164
175
|
}
|
|
176
|
+
if (prev === "--scope" && sub === "list") {
|
|
177
|
+
return filterValues(LIST_SCOPES, last, before);
|
|
178
|
+
}
|
|
165
179
|
// --from completes from the model keys actually present in the store (the
|
|
166
180
|
// adoption source pool), with each owner's active-item count as the help
|
|
167
181
|
// line — the same store-backed completion style as --model above.
|
|
@@ -175,11 +189,12 @@ function completions(argumentPrefix: string) {
|
|
|
175
189
|
);
|
|
176
190
|
}
|
|
177
191
|
|
|
178
|
-
// Flag completion ("
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
192
|
+
// Flag completion ("-…" prefix, or a fresh token for flag-taking subs —
|
|
193
|
+
// the "-" prefix also matches short flags like list's -f). A fresh
|
|
194
|
+
// --kind/--scope/--model value was already completed above (early
|
|
195
|
+
// return), so a fresh token here means "offer the remaining flags".
|
|
196
|
+
if (last.startsWith("-") || (fresh && sub in FLAGS)) {
|
|
197
|
+
const used = new Set(before.filter((t) => t.startsWith("-")));
|
|
183
198
|
const rows = (FLAGS[sub] ?? []).filter((f) => !used.has(f.name));
|
|
184
199
|
const items = rows
|
|
185
200
|
.filter((f) => f.name.startsWith(last))
|
|
@@ -285,6 +300,9 @@ export function registerHarness(pi: ExtensionAPI): void {
|
|
|
285
300
|
case "setup":
|
|
286
301
|
await handleSetup(ctx, rest);
|
|
287
302
|
return;
|
|
303
|
+
case "list":
|
|
304
|
+
await handleList(ctx, rest);
|
|
305
|
+
return;
|
|
288
306
|
case "status":
|
|
289
307
|
default:
|
|
290
308
|
await handleStatus(ctx);
|
|
@@ -300,6 +318,59 @@ function explicitPath(rest: string[]): string | undefined {
|
|
|
300
318
|
return rest.find((a) => !a.startsWith("-"));
|
|
301
319
|
}
|
|
302
320
|
|
|
321
|
+
/** `/harness list [-f] [--kind <kind>] [--scope <global|project|all>]`
|
|
322
|
+
* (issue #31) — the read view: thin glue (flag parsing + one notify); every
|
|
323
|
+
* selection/rendering rule lives in the pure src/list.ts. Read-only: no
|
|
324
|
+
* store mutation, no durable writes. Invalid flag values AND unexpected
|
|
325
|
+
* arguments warn instead of silently falling back — a filter that quietly
|
|
326
|
+
* ignores its own argument would make the listing lie. */
|
|
327
|
+
async function handleList(ctx: ExtensionCommandContext, rest: string[]): Promise<void> {
|
|
328
|
+
const full = rest.includes("-f");
|
|
329
|
+
let kind: ComponentKind | undefined;
|
|
330
|
+
const kindIdx = rest.indexOf("--kind");
|
|
331
|
+
if (kindIdx >= 0) {
|
|
332
|
+
const k = kindIdx + 1 < rest.length ? rest[kindIdx + 1]! : undefined;
|
|
333
|
+
if (!k || k.startsWith("-") || !KINDS.includes(k as ComponentKind)) {
|
|
334
|
+
ctx.ui.notify(`/harness list: --kind expects one of ${KINDS.join("|")}.`, "warning");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
kind = k as ComponentKind;
|
|
338
|
+
}
|
|
339
|
+
let scope: ListScope | undefined;
|
|
340
|
+
const scopeIdx = rest.indexOf("--scope");
|
|
341
|
+
if (scopeIdx >= 0) {
|
|
342
|
+
const s = scopeIdx + 1 < rest.length ? rest[scopeIdx + 1]! : undefined;
|
|
343
|
+
if (s !== "global" && s !== "project" && s !== "all") {
|
|
344
|
+
ctx.ui.notify("/harness list: --scope expects global|project|all.", "warning");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
scope = s;
|
|
348
|
+
}
|
|
349
|
+
// Unexpected arguments (unknown flags like --full, stray positionals,
|
|
350
|
+
// repeated flags) warn too — the same no-silent-fallback rule as invalid
|
|
351
|
+
// values. Anything but -f, --kind <value>, --scope <value> is refused.
|
|
352
|
+
const consumed = new Set<number>();
|
|
353
|
+
if (kindIdx >= 0) { consumed.add(kindIdx); consumed.add(kindIdx + 1); }
|
|
354
|
+
if (scopeIdx >= 0) { consumed.add(scopeIdx); consumed.add(scopeIdx + 1); }
|
|
355
|
+
const unexpected = rest.filter((t, idx) => t !== "-f" && !consumed.has(idx));
|
|
356
|
+
if (unexpected.length > 0) {
|
|
357
|
+
ctx.ui.notify(
|
|
358
|
+
`/harness list: unexpected argument(s): ${unexpected.join(" ")} — expected -f, --kind <kind>, --scope <global|project|all>.`,
|
|
359
|
+
"warning",
|
|
360
|
+
);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
ctx.ui.notify(
|
|
364
|
+
renderListReport(getState().items, {
|
|
365
|
+
...(kind ? { kind } : {}),
|
|
366
|
+
...(scope ? { scope } : {}),
|
|
367
|
+
...(full ? { full } : {}),
|
|
368
|
+
slug: projectSlug(ctx.cwd),
|
|
369
|
+
}),
|
|
370
|
+
"info",
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
303
374
|
async function handleImport(
|
|
304
375
|
pi: ExtensionAPI,
|
|
305
376
|
ctx: ExtensionCommandContext,
|
package/src/index.ts
CHANGED
|
@@ -132,6 +132,17 @@ export {
|
|
|
132
132
|
} from "./hints.js";
|
|
133
133
|
export { getInjectionStats, resetInjectionStats, type InjectionStats } from "./inject.js";
|
|
134
134
|
|
|
135
|
+
// /harness list (issue #31): pure selection + rendering for the read view of
|
|
136
|
+
// the store (compact rows / full text + evidence). Re-exported for tests and
|
|
137
|
+
// companions without reaching into internal paths.
|
|
138
|
+
export {
|
|
139
|
+
preview,
|
|
140
|
+
renderListReport,
|
|
141
|
+
selectListItems,
|
|
142
|
+
type ListOptions,
|
|
143
|
+
type ListScope,
|
|
144
|
+
} from "./list.js";
|
|
145
|
+
|
|
135
146
|
// Injection selection policy (on by default). Re-exported so companion packages
|
|
136
147
|
// and tests can reuse the pure selection/normalization without reaching into
|
|
137
148
|
// internal paths.
|
package/src/list.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// `/harness list` (issue #31) — the read view of the store. One compact row
|
|
2
|
+
// per item (id, kind, importance, scope, preview) or, with `-f`, a
|
|
3
|
+
// harness_list-style full text + evidence rendering (extended with the
|
|
4
|
+
// scope). Read-only: selection
|
|
5
|
+
// and rendering are pure; harness.ts's handleList stays thin glue (flag
|
|
6
|
+
// parsing + one ctx.ui.notify).
|
|
7
|
+
//
|
|
8
|
+
// Scope semantics mirror the durable layers (issue #7): the default view is
|
|
9
|
+
// the session's own layer set — global + the ACTIVE project — the same set
|
|
10
|
+
// layered /harness import READS for this cwd (layered export writes more:
|
|
11
|
+
// every project layer with active items, foreign included — store.ts).
|
|
12
|
+
// Items scoped to a DIFFERENT project are hidden (and counted in a note,
|
|
13
|
+
// listed or not) unless you ask for them:
|
|
14
|
+
// `--scope project` lists every project-scoped item (rows carry the slug) and
|
|
15
|
+
// `--scope all` lists everything. An item's undefined scope counts as global
|
|
16
|
+
// (legacy snapshots / durable files — see types.ts).
|
|
17
|
+
|
|
18
|
+
import type { ComponentKind, HarnessItem } from "./types.js";
|
|
19
|
+
|
|
20
|
+
/** `--scope` values for /harness list. `undefined` (no flag) = the session's
|
|
21
|
+
* layer set: global + the active project. */
|
|
22
|
+
export type ListScope = "global" | "project" | "all";
|
|
23
|
+
|
|
24
|
+
/** Options for selectListItems / renderListReport. */
|
|
25
|
+
export interface ListOptions {
|
|
26
|
+
/** Filter by component kind (mirrors harness_list's kind parameter). */
|
|
27
|
+
kind?: ComponentKind;
|
|
28
|
+
/** Durable-scope filter; undefined = global + the active project. */
|
|
29
|
+
scope?: ListScope;
|
|
30
|
+
/** `-f`: full text + evidence per item (default: one compact row). */
|
|
31
|
+
full?: boolean;
|
|
32
|
+
/** The active project's slug (from the session cwd); selects the default
|
|
33
|
+
* view and labels the header. */
|
|
34
|
+
slug: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Single-line, whitespace-flattened preview truncated to `max` chars — the
|
|
38
|
+
* shared truncation style for one-line rows (menu entries, notifications). */
|
|
39
|
+
export function preview(text: string, max = 60): string {
|
|
40
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
41
|
+
return flat.length > max ? `${flat.slice(0, max)}…` : flat;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** True when an item belongs to the durable layer(s) the options select. */
|
|
45
|
+
function inScope(i: HarnessItem, opts: ListOptions): boolean {
|
|
46
|
+
switch (opts.scope) {
|
|
47
|
+
case "global":
|
|
48
|
+
return i.scope !== "project";
|
|
49
|
+
case "project":
|
|
50
|
+
return i.scope === "project";
|
|
51
|
+
case "all":
|
|
52
|
+
return true;
|
|
53
|
+
default:
|
|
54
|
+
// Session layer set: global + the active project (undefined scope is
|
|
55
|
+
// global; a project item only shows when its slug is the active one).
|
|
56
|
+
return i.scope !== "project" || i.project === opts.slug;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Select the listable items: kind filter, scope filter, then importance
|
|
61
|
+
* desc with ties keeping store order — the same stable ranking policy as
|
|
62
|
+
* injection selection and /harness adopt. Pure. */
|
|
63
|
+
export function selectListItems(items: HarnessItem[], opts: ListOptions): HarnessItem[] {
|
|
64
|
+
return items
|
|
65
|
+
.filter((i) => (opts.kind ? i.kind === opts.kind : true) && inScope(i, opts))
|
|
66
|
+
.map((i, idx) => [i, idx] as const)
|
|
67
|
+
.sort((a, b) => b[0].importance - a[0].importance || a[1] - b[1])
|
|
68
|
+
.map(([i]) => i);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Compact row: `[id] kind importance scope preview` (issue #31's table);
|
|
72
|
+
* inactive items carry an `(inactive)` marker so the row stays one line. */
|
|
73
|
+
function row(i: HarnessItem): string {
|
|
74
|
+
const scope = i.scope === "project" ? `project:${i.project ?? "?"}` : "global";
|
|
75
|
+
return `[${i.id}] ${i.kind.padEnd(8)} ${i.importance.toFixed(2)} ${scope} ${
|
|
76
|
+
i.active ? "" : "(inactive) "
|
|
77
|
+
}${preview(i.content)}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Full row (`-f`): the harness_list rendering, scope-aware — the same
|
|
81
|
+
* parenthetical the split/adopt prompts use, plus the evidence line. */
|
|
82
|
+
function fullRow(i: HarnessItem): string {
|
|
83
|
+
const bits = [
|
|
84
|
+
i.kind,
|
|
85
|
+
`importance ${i.importance.toFixed(2)}`,
|
|
86
|
+
i.active ? "active" : "inactive",
|
|
87
|
+
`model ${i.ownerModel || "(orphan)"}`,
|
|
88
|
+
i.scope === "project" ? `project "${i.project ?? ""}"` : "global",
|
|
89
|
+
];
|
|
90
|
+
return `[${i.id}] (${bits.join(", ")}) ${i.content}\n evidence: ${i.evidence}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Tally `items` by a string key, rendered as "key (n), key (n)". */
|
|
94
|
+
function tally<T>(items: T[], key: (i: T) => string): string {
|
|
95
|
+
const counts = new Map<string, number>();
|
|
96
|
+
for (const i of items) counts.set(key(i), (counts.get(key(i)) ?? 0) + 1);
|
|
97
|
+
return [...counts.entries()].map(([k, n]) => `${k} (${n})`).join(", ");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function scopeDescription(opts: ListOptions): string {
|
|
101
|
+
switch (opts.scope) {
|
|
102
|
+
case "global":
|
|
103
|
+
return "scope global";
|
|
104
|
+
case "project":
|
|
105
|
+
return 'scope project (any project\'s items; rows carry the slug)';
|
|
106
|
+
case "all":
|
|
107
|
+
return "scope all";
|
|
108
|
+
default:
|
|
109
|
+
return `scope global + project "${opts.slug}"`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Render the whole `/harness list` notify string: header (count + active
|
|
114
|
+
* filters), rows (compact by default, full text + evidence with `full`),
|
|
115
|
+
* and distinct empty-state guidance for an empty store vs an empty filter
|
|
116
|
+
* result (the emptyListMessage lesson: never collapse the two). Pure. */
|
|
117
|
+
export function renderListReport(items: HarnessItem[], opts: ListOptions): string {
|
|
118
|
+
const shown = selectListItems(items, opts);
|
|
119
|
+
const header =
|
|
120
|
+
`Harness list: ${shown.length} item(s) — ${scopeDescription(opts)}` +
|
|
121
|
+
(opts.kind ? `, kind ${opts.kind}` : "") +
|
|
122
|
+
` (${opts.full ? "full text" : "compact; -f for full text"}).`;
|
|
123
|
+
// The default view hides foreign-project items on purpose (they are not
|
|
124
|
+
// this session's layers) — say so instead of letting them look missing.
|
|
125
|
+
// Computed ONCE so the note rides along on every non-empty-store result,
|
|
126
|
+
// empty filter results included (a foreign-only store still gets it).
|
|
127
|
+
let foreignNote: string | undefined;
|
|
128
|
+
if (opts.scope === undefined) {
|
|
129
|
+
const foreign = items.filter(
|
|
130
|
+
(i) => i.scope === "project" && i.project !== opts.slug && (opts.kind ? i.kind === opts.kind : true),
|
|
131
|
+
).length;
|
|
132
|
+
if (foreign > 0) {
|
|
133
|
+
foreignNote = `+ ${foreign} foreign-project item(s) hidden — re-run with --scope all to see them.`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (shown.length === 0) {
|
|
137
|
+
if (items.length === 0) {
|
|
138
|
+
return [
|
|
139
|
+
header,
|
|
140
|
+
"No harness items yet. Run `/refine` after a working session (or `/harness import`) — items appear here as they are created.",
|
|
141
|
+
].join("\n");
|
|
142
|
+
}
|
|
143
|
+
// A store whose matching items are ALL foreign must not collapse into
|
|
144
|
+
// generic filter guidance: the hidden note explains where they went
|
|
145
|
+
// (review #32) — the default view hides them, no filter removed them.
|
|
146
|
+
// (No flag echo here: the header already names scope+kind, and a
|
|
147
|
+
// pseudo-flag like `--scope global+project` would not even parse.)
|
|
148
|
+
return [
|
|
149
|
+
header,
|
|
150
|
+
`Nothing matches the current filters. The store holds ${items.length} item(s): ${tally(items, (i) => i.kind)}. Widen --scope or drop --kind.`,
|
|
151
|
+
...(foreignNote ? [foreignNote] : []),
|
|
152
|
+
].join("\n");
|
|
153
|
+
}
|
|
154
|
+
const lines = [header, ...shown.map(opts.full ? fullRow : row)];
|
|
155
|
+
if (foreignNote) lines.push(foreignNote);
|
|
156
|
+
return lines.join("\n");
|
|
157
|
+
}
|
package/src/tools.ts
CHANGED
|
@@ -88,7 +88,7 @@ export function registerTools(pi: ExtensionAPI): void {
|
|
|
88
88
|
name: "harness_list",
|
|
89
89
|
label: "List harness state",
|
|
90
90
|
description:
|
|
91
|
-
"List items in the Continual Harness state (self-improved prompt notes, memory, skill descriptions, sub-agent specs). Optionally filter by kind.",
|
|
91
|
+
"List items in the Continual Harness state (self-improved prompt notes, memory, skill descriptions, sub-agent specs). Optionally filter by kind. (/harness list is the human-facing TUI view: compact rows + durable-scope filters.)",
|
|
92
92
|
promptSnippet: "Read Continual Harness self-improvement state",
|
|
93
93
|
promptGuidelines: [
|
|
94
94
|
"Call harness_list before proposing changes so you operate on current state.",
|