@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 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
- seen.push(payload?.body ?? JSON.stringify(item));
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
- let current = root;
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 (let sample = 0; sample < 3; sample++) {
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.0",
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
- const seen: string[] = [];
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
- seen.push(payload?.body ?? JSON.stringify(item));
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
- let current = root;
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 (let sample = 0; sample < 3; sample++) {
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);