@prjct.app/pi-signals 0.2.0 → 0.2.1
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 +4 -0
- package/dist/cli.mjs +9 -7
- package/package.json +2 -2
- package/src/evidence.ts +6 -3
- package/src/repair.ts +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 0.2.1 (2026-10-13)
|
|
2
|
+
|
|
3
|
+
- The `seen` list in `evidenceTool` is now a cell holding an immutable array. Each `execute` builds a new array with the additions appended instead of pushing in place; `contains` reads from the cell.
|
|
4
|
+
|
|
1
5
|
## 0.2.0 (2026-10-09)
|
|
2
6
|
|
|
3
7
|
- `/signals` opens the shared docked panel, like the other prjct extensions. It lists what needs a fix first, then queued, running, flaky and resolved issues, each with component, status and sightings. An issue's detail shows the triage decision, recent observations and runs. The Daily triage row shows schedule, recent days, repositories and the repair setting. `r` queues an issue for triage again. Outside the TUI the same list prints as text instead of JSON.
|
package/dist/cli.mjs
CHANGED
|
@@ -834,7 +834,7 @@ var init_schema = __esm({
|
|
|
834
834
|
// src/evidence.ts
|
|
835
835
|
import { Type as Type2 } from "typebox";
|
|
836
836
|
function evidenceTool(source) {
|
|
837
|
-
const seen = [];
|
|
837
|
+
const seen = { items: [] };
|
|
838
838
|
const tool = {
|
|
839
839
|
name: "signal_evidence",
|
|
840
840
|
label: "Read original signal evidence",
|
|
@@ -844,14 +844,16 @@ function evidenceTool(source) {
|
|
|
844
844
|
const input = raw, offset = input.offset ?? 0, limit = input.limit ?? 1;
|
|
845
845
|
const rows = typeof source === "function" ? source(offset, limit + 1) : source.slice(offset, offset + limit + 1);
|
|
846
846
|
const items = await protectOutboundData(rows.slice(0, limit));
|
|
847
|
+
const additions = [];
|
|
847
848
|
for (const item of items) {
|
|
848
849
|
const payload = item.payload;
|
|
849
|
-
|
|
850
|
+
additions.push(payload?.body ?? JSON.stringify(item));
|
|
850
851
|
}
|
|
852
|
+
if (additions.length) seen.items = [...seen.items, ...additions];
|
|
851
853
|
return { content: [{ type: "text", text: JSON.stringify({ items, nextOffset: rows.length > limit ? offset + limit : null }) }], details: {} };
|
|
852
854
|
}
|
|
853
855
|
};
|
|
854
|
-
return { tool, contains: (quote2) => seen.some((text2) => text2.includes(quote2)) };
|
|
856
|
+
return { tool, contains: (quote2) => seen.items.some((text2) => text2.includes(quote2)) };
|
|
855
857
|
}
|
|
856
858
|
var init_evidence = __esm({
|
|
857
859
|
"src/evidence.ts"() {
|
|
@@ -953,10 +955,10 @@ ${JSON.stringify({ issue })}`,
|
|
|
953
955
|
const source = resolve(path, file), destination = resolve(baselinePath, file);
|
|
954
956
|
if (!source.startsWith(path + sep2) || !destination.startsWith(baselinePath + sep2)) return blocked("Regression file escaped the worktree.");
|
|
955
957
|
for (const root of [path, baselinePath]) {
|
|
956
|
-
|
|
958
|
+
const cursor = { current: root };
|
|
957
959
|
for (const part of file.split("/")) {
|
|
958
|
-
current = join6(current, part);
|
|
959
|
-
const stat = await lstat(current).catch(() => void 0);
|
|
960
|
+
cursor.current = join6(cursor.current, part);
|
|
961
|
+
const stat = await lstat(cursor.current).catch(() => void 0);
|
|
960
962
|
if (stat?.isSymbolicLink()) return blocked("Regression paths must not contain symlinks.");
|
|
961
963
|
}
|
|
962
964
|
}
|
|
@@ -973,7 +975,7 @@ ${JSON.stringify({ issue })}`,
|
|
|
973
975
|
if (await protectOutboundData(diff) !== diff) return blocked("The candidate diff contains data caught by outbound protection; publication is blocked.");
|
|
974
976
|
await must(path, ["git", "commit", "-m", `fix: ${issue.title}`], signal);
|
|
975
977
|
const head = await must(path, ["git", "rev-parse", "HEAD"], signal);
|
|
976
|
-
for (
|
|
978
|
+
for (const _sample of [0, 1, 2]) {
|
|
977
979
|
const before = await command(baselinePath, fix.regressionCommand, signal);
|
|
978
980
|
checks.push(before);
|
|
979
981
|
if (before.code === 0 || !before.output.includes(fix.failurePattern)) return blocked("The unchanged baseline did not consistently reproduce the claimed failure.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prjct.app/pi-signals",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Local failure signals, daily agent-owned triage and verified repair candidates for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"check": "tsc --noEmit",
|
|
54
|
-
"test": "node --import ./tests/isolate.mjs --import tsx --test --test-concurrency=2 tests/*.test.ts",
|
|
54
|
+
"test": "export PI_CODING_AGENT_DIR=\"$(mktemp -d)\" PRJCT_HOME=\"$(mktemp -d)\" && node --import ./tests/isolate.mjs --import tsx --test --test-concurrency=2 tests/*.test.ts",
|
|
55
55
|
"check:package": "npm pack --dry-run --ignore-scripts",
|
|
56
56
|
"build": "node scripts/build.mjs",
|
|
57
57
|
"prepack": "npm run build",
|
package/src/evidence.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { protectOutboundData } from '@prjct.app/pi-secrets/privacy';
|
|
|
5
5
|
export type EvidenceSource = readonly unknown[] | ((offset: number, limit: number) => unknown[]);
|
|
6
6
|
/** Keep full observations in storage; the deciding agent chooses which pages to read. */
|
|
7
7
|
export function evidenceTool(source: EvidenceSource) {
|
|
8
|
-
|
|
8
|
+
// The one cell that changes: it holds the current list, which is always replaced, never edited.
|
|
9
|
+
const seen: { items: readonly string[] } = { items: [] };
|
|
9
10
|
const tool: ToolDefinition = {
|
|
10
11
|
name: 'signal_evidence', label: 'Read original signal evidence',
|
|
11
12
|
description: 'Read original issue observations by page. Nothing is discarded; follow nextOffset to inspect all sightings. Select a smaller limit for large reports.',
|
|
@@ -14,12 +15,14 @@ export function evidenceTool(source: EvidenceSource) {
|
|
|
14
15
|
const input = raw as { offset?: number; limit?: number }, offset = input.offset ?? 0, limit = input.limit ?? 1;
|
|
15
16
|
const rows = typeof source === 'function' ? source(offset, limit + 1) : source.slice(offset, offset + limit + 1);
|
|
16
17
|
const items = await protectOutboundData(rows.slice(0, limit));
|
|
18
|
+
const additions: string[] = [];
|
|
17
19
|
for (const item of items) {
|
|
18
20
|
const payload = (item as { payload?: { body?: string } }).payload;
|
|
19
|
-
|
|
21
|
+
additions.push(payload?.body ?? JSON.stringify(item));
|
|
20
22
|
}
|
|
23
|
+
if (additions.length) seen.items = [...seen.items, ...additions];
|
|
21
24
|
return { content: [{ type: 'text', text: JSON.stringify({ items, nextOffset: rows.length > limit ? offset + limit : null }) }], details: {} };
|
|
22
25
|
},
|
|
23
26
|
};
|
|
24
|
-
return { tool, contains: (quote: string) => seen.some(text => text.includes(quote)) };
|
|
27
|
+
return { tool, contains: (quote: string) => seen.items.some(text => text.includes(quote)) };
|
|
25
28
|
}
|
package/src/repair.ts
CHANGED
|
@@ -43,10 +43,10 @@ export async function repair(issue: LocalIssue, repo: Repository, path: string,
|
|
|
43
43
|
if (!source.startsWith(path + sep) || !destination.startsWith(baselinePath + sep)) return blocked('Regression file escaped the worktree.');
|
|
44
44
|
// Refuse symlinks throughout either path; do not copy arbitrary external files.
|
|
45
45
|
for (const root of [path, baselinePath]) {
|
|
46
|
-
|
|
46
|
+
const cursor = { current: root };
|
|
47
47
|
for (const part of file.split('/')) {
|
|
48
|
-
current = join(current, part);
|
|
49
|
-
const stat = await lstat(current).catch(() => undefined);
|
|
48
|
+
cursor.current = join(cursor.current, part);
|
|
49
|
+
const stat = await lstat(cursor.current).catch(() => undefined);
|
|
50
50
|
if (stat?.isSymbolicLink()) return blocked('Regression paths must not contain symlinks.');
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -62,7 +62,7 @@ export async function repair(issue: LocalIssue, repo: Repository, path: string,
|
|
|
62
62
|
if (await protectOutboundData(diff) !== diff) return blocked('The candidate diff contains data caught by outbound protection; publication is blocked.');
|
|
63
63
|
await must(path, ['git', 'commit', '-m', `fix: ${issue.title}`], signal);
|
|
64
64
|
const head = await must(path, ['git', 'rev-parse', 'HEAD'], signal);
|
|
65
|
-
for (
|
|
65
|
+
for (const _sample of [0, 1, 2]) {
|
|
66
66
|
const before = await command(baselinePath, fix.regressionCommand, signal); checks.push(before);
|
|
67
67
|
if (before.code === 0 || !before.output.includes(fix.failurePattern)) return blocked('The unchanged baseline did not consistently reproduce the claimed failure.');
|
|
68
68
|
const after = await command(path, fix.regressionCommand, signal); checks.push(after);
|