shariq-pi-extensions 0.2.15 → 0.2.16

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.
@@ -53,6 +53,8 @@ Replaces standard context compaction with a defensive, high-fidelity continuity
53
53
  Key capabilities include:
54
54
  - **Fail-Closed Validation**: Strictly enforces `stopReason === "stop"`, rejects tool calls and length-truncated output, and requires all 6 section headings.
55
55
  - **Deterministic State Ledger (Schema v3)**: Machine-readable tracking of `touchedReadFiles`, `touchedModifiedFiles`, and asynchronous NUL-delimited Git worktree parsing capturing `activeDirtyFiles`, staged diffs, unstaged diffs, and untracked file previews in `CompactionEntry.details` and `<uncommitted-diff>` context.
56
+ - **Lockfile & Bundle Diff Filtering**: Automatically isolates `package-lock.json`, `Cargo.lock`, `yarn.lock`, and minified assets from raw diffs to preserve token budgets for source code logic.
57
+ - **Active Background Terminal Awareness**: Automatically identifies running background processes and records them under `<active-background-processes>` to prevent duplicate server launches.
56
58
  - **Hierarchical Delta-Merging**: Carries forward immutable goals and user constraints across 10+ compaction cycles while condensing older completed items to prevent summary bloat.
57
59
  - **Classified Retry Ladder**: Distinguishes non-retryable fatal auth/quota errors from transient reasoning/length limits (retrying with reasoning off) and falling back to the active session model.
58
60
  - **Two-Ended Truncation & Credential Redaction**: Retains both head and tail of tool outputs (ensuring final error traces and test results survive) while redacting secrets and sensitive paths.
@@ -48,6 +48,10 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
48
48
  const getManager = (): TerminalManager => {
49
49
  if (manager) return manager;
50
50
  manager = new TerminalManager();
51
+ (globalThis as any).__pi_get_active_terminals = () => {
52
+ if (!manager) return [];
53
+ return manager.list().filter((s) => s.status === "running").map((s) => `${s.id}: "${oneLine(s.title)}" (pid ${s.pid})`);
54
+ };
51
55
  manager.setOnSettled((snapshot) => {
52
56
  if (!modelOwned.delete(snapshot.id)) {
53
57
  ui?.notify(
@@ -393,4 +397,8 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
393
397
  description: "Open the background terminal control center",
394
398
  handler: async (_args, ctx) => openCommand(ctx),
395
399
  });
400
+
401
+ pi.on("session_shutdown", () => {
402
+ delete (globalThis as any).__pi_get_active_terminals;
403
+ });
396
404
  }
@@ -18,11 +18,13 @@ When long-running agent sessions reach context thresholds, standard compaction f
18
18
  4. **💥 Errors, Root Causes & Fixes** — Full error traces, root cause diagnostics, and verified solutions.
19
19
  5. **🧠 Key Decisions & Hypotheses** — Architectural choices, trade-offs, and discarded hypotheses.
20
20
  6. **📍 Resume Anchor & Immediate Next Action** — Verbatim quote or exact resume state with the single immediate next action.
21
- 7. **📂 Deterministic Engineering Ledger** — Programmatic `<read-files>`, `<touched-files>`, `<uncommitted-dirty-files>`, and bounded `<uncommitted-diff>` blocks. Historical touch state, current NUL-delimited porcelain status, and redacted patch data are persisted in versioned compaction details; sensitive paths are omitted.
21
+ 7. **📂 Deterministic Engineering Ledger** — Programmatic `<read-files>`, `<touched-files>`, `<uncommitted-dirty-files>`, `<modified-lockfiles-and-assets>`, `<active-background-processes>`, and bounded `<uncommitted-diff>` blocks. Lockfiles and minified bundles are automatically excluded from raw diffing to preserve token budgets for real source code, while active background terminals/daemons are recorded to prevent port conflicts.
22
22
 
23
23
  ## Defensive Reliability & Multi-Stage Retry Ladder
24
24
 
25
25
  - **Fail-Closed Validation**: Accepts only `stopReason === "stop"` and rejects tool calls, empty output, or summaries missing required section headers.
26
+ - **Lockfile & Bundle Diff Exclusion**: Automatically excludes `package-lock.json`, `Cargo.lock`, `yarn.lock`, `pnpm-lock.yaml`, and minified assets from raw diffs, recording their status under `<modified-lockfiles-and-assets>` to preserve 100% of diff token headroom for source code.
27
+ - **Background Daemon & Terminal Awareness**: Automatically detects running background terminals/processes and injects their status into `<active-background-processes>` so the successor agent never launches duplicate services.
26
28
  - **Retry Ladder**: If an attempt encounters output limits or transient reasoning timeouts:
27
29
  1. Primary configured model with requested reasoning.
28
30
  2. Primary model with reasoning off (unblocks reasoning/token caps).
@@ -25,6 +25,7 @@ export interface GitEngineeringState {
25
25
  files: DirtyFileState[];
26
26
  patch: string;
27
27
  sensitiveFilesOmitted: number;
28
+ lockfilesAndGeneratedAssets: string[];
28
29
  }
29
30
 
30
31
  export interface SmartCompactionDetails {
@@ -41,6 +42,8 @@ export interface SmartCompactionDetails {
41
42
  dirtyStateAvailable: boolean;
42
43
  sensitiveDirtyFilesOmitted: number;
43
44
  sensitiveTouchedFilesOmitted: number;
45
+ activeBackgroundProcesses?: string[];
46
+ lockfilesAndGeneratedAssets?: string[];
44
47
  cycleCount: number;
45
48
  timestamp: number;
46
49
  }
@@ -124,6 +127,42 @@ export function extractPriorFileState(branchEntries?: any[]): {
124
127
  return { touchedReadFiles, touchedModifiedFiles, cycleCount };
125
128
  }
126
129
 
130
+ const GENERATED_OR_LOCKFILE_PATTERNS = [
131
+ /(?:^|\/)package-lock\.json$/i,
132
+ /(?:^|\/)pnpm-lock\.yaml$/i,
133
+ /(?:^|\/)yarn\.lock$/i,
134
+ /(?:^|\/)Cargo\.lock$/i,
135
+ /(?:^|\/)poetry\.lock$/i,
136
+ /(?:^|\/)bun\.lockb?$/i,
137
+ /(?:^|\/)composer\.lock$/i,
138
+ /(?:^|\/)flake\.lock$/i,
139
+ /(?:^|\/)mise\.lock$/i,
140
+ /\.min\.(?:js|css|mjs)$/i,
141
+ /\.map$/i,
142
+ /\.wasm$/i,
143
+ /(?:^|\/)(?:dist|build|out|\.next|\.nuxt|\.turbo|\.parcel-cache)\//i,
144
+ ];
145
+
146
+ export function isGeneratedOrLockfile(filePath: string): boolean {
147
+ const normalized = filePath.replace(/\\/g, "/");
148
+ return GENERATED_OR_LOCKFILE_PATTERNS.some((pattern) => pattern.test(normalized));
149
+ }
150
+
151
+ export function getActiveBackgroundProcesses(): string[] {
152
+ try {
153
+ const fn = (globalThis as any).__pi_get_active_terminals;
154
+ if (typeof fn === "function") {
155
+ const active = fn();
156
+ if (Array.isArray(active)) {
157
+ return active.filter((item): item is string => typeof item === "string" && Boolean(item.trim()));
158
+ }
159
+ }
160
+ } catch {
161
+ // Best-effort inspection.
162
+ }
163
+ return [];
164
+ }
165
+
127
166
  const execFileAsync = promisify(execFile);
128
167
  const GIT_TIMEOUT_MS = 5_000;
129
168
  const GIT_OUTPUT_LIMIT = 2 * 1024 * 1024;
@@ -170,7 +209,7 @@ async function readUntrackedPreviews(
170
209
  const sections: string[] = [];
171
210
  let remaining = DIRTY_PATCH_CHARS;
172
211
  for (const file of files) {
173
- if (file.status !== "??" || isSensitivePath(file.path) || remaining <= 0) continue;
212
+ if (file.status !== "??" || isSensitivePath(file.path) || isGeneratedOrLockfile(file.path) || remaining <= 0) continue;
174
213
  const absolute = path.resolve(root, file.path);
175
214
  const relative = path.relative(root, absolute);
176
215
  if (relative.startsWith("..") || path.isAbsolute(relative)) continue;
@@ -201,19 +240,23 @@ async function readUntrackedPreviews(
201
240
  }
202
241
 
203
242
  export async function getGitEngineeringState(cwd?: string, signal?: AbortSignal): Promise<GitEngineeringState> {
204
- if (!cwd) return { available: false, files: [], patch: "", sensitiveFilesOmitted: 0 };
243
+ if (!cwd) return { available: false, files: [], patch: "", sensitiveFilesOmitted: 0, lockfilesAndGeneratedAssets: [] };
205
244
  try {
206
245
  const root = (await runGit(cwd, ["rev-parse", "--show-toplevel"], signal)).trim();
207
246
  const status = await runGit(root, ["status", "--porcelain=v1", "-z", "--untracked-files=all"], signal);
208
247
  const allFiles = parseGitStatusPorcelainV1Z(status);
209
248
  const sensitiveFilesOmitted = allFiles.filter((file) => isSensitivePath(file.path)).length;
210
249
  const files = allFiles.filter((file) => !isSensitivePath(file.path));
211
- const trackedPaths = files.filter((file) => file.status !== "??").map((file) => file.path).slice(0, 250);
250
+
251
+ const codeFiles = files.filter((file) => !isGeneratedOrLockfile(file.path));
252
+ const lockOrGeneratedFiles = files.filter((file) => isGeneratedOrLockfile(file.path)).map((file) => file.path);
253
+
254
+ const trackedCodePaths = codeFiles.filter((file) => file.status !== "??").map((file) => file.path).slice(0, 250);
212
255
  const stagedArgs = ["diff", "--cached", "--no-ext-diff", "--no-color", "--unified=2"];
213
256
  const unstagedArgs = ["diff", "--no-ext-diff", "--no-color", "--unified=2"];
214
- if (trackedPaths.length > 0) {
215
- stagedArgs.push("--", ...trackedPaths);
216
- unstagedArgs.push("--", ...trackedPaths);
257
+ if (trackedCodePaths.length > 0) {
258
+ stagedArgs.push("--", ...trackedCodePaths);
259
+ unstagedArgs.push("--", ...trackedCodePaths);
217
260
  } else {
218
261
  // An unmatched pathspec avoids reading unrelated or sensitive tracked diffs.
219
262
  stagedArgs.push("--", ":(exclude,top)**");
@@ -222,7 +265,7 @@ export async function getGitEngineeringState(cwd?: string, signal?: AbortSignal)
222
265
  const [staged, unstaged, untracked] = await Promise.all([
223
266
  runGit(root, stagedArgs, signal),
224
267
  runGit(root, unstagedArgs, signal),
225
- readUntrackedPreviews(root, files),
268
+ readUntrackedPreviews(root, codeFiles),
226
269
  ]);
227
270
  const sections = [
228
271
  staged ? `## Staged changes\n${staged}` : "",
@@ -234,10 +277,11 @@ export async function getGitEngineeringState(cwd?: string, signal?: AbortSignal)
234
277
  files,
235
278
  patch: truncatePatch(redactLikelySecrets(sections.join("\n\n"))),
236
279
  sensitiveFilesOmitted,
280
+ lockfilesAndGeneratedAssets: lockOrGeneratedFiles,
237
281
  };
238
282
  } catch (error) {
239
283
  if (signal?.aborted) throw error;
240
- return { available: false, files: [], patch: "", sensitiveFilesOmitted: 0 };
284
+ return { available: false, files: [], patch: "", sensitiveFilesOmitted: 0, lockfilesAndGeneratedAssets: [] };
241
285
  }
242
286
  }
243
287
 
@@ -519,6 +563,7 @@ export async function runSmartCompaction(
519
563
  const touchedModifiedFilesList = allTouchedModifiedFiles.filter((file) => !isSensitivePath(file)).sort();
520
564
  const gitState = await getGitEngineeringState(ctx.cwd, signal);
521
565
  const activeDirtyFilesList = gitState.files.map((file) => file.path);
566
+ const activeBackgroundProcesses = getActiveBackgroundProcesses();
522
567
 
523
568
  const fileOpsXml = formatFileOperationsXml({
524
569
  readFiles: readFilesList,
@@ -527,6 +572,8 @@ export async function runSmartCompaction(
527
572
  dirtyPatch: gitState.patch,
528
573
  dirtyStateAvailable: gitState.available,
529
574
  sensitiveFilesOmitted: gitState.sensitiveFilesOmitted + sensitiveTouchedFilesOmitted,
575
+ activeBackgroundProcesses,
576
+ lockfilesAndGeneratedAssets: gitState.lockfilesAndGeneratedAssets,
530
577
  });
531
578
 
532
579
  const finalSummary = `${finalSummaryText}${fileOpsXml}`;
@@ -546,6 +593,8 @@ export async function runSmartCompaction(
546
593
  dirtyStateAvailable: gitState.available,
547
594
  sensitiveDirtyFilesOmitted: gitState.sensitiveFilesOmitted,
548
595
  sensitiveTouchedFilesOmitted,
596
+ activeBackgroundProcesses: activeBackgroundProcesses.length > 0 ? activeBackgroundProcesses : undefined,
597
+ lockfilesAndGeneratedAssets: gitState.lockfilesAndGeneratedAssets.length > 0 ? gitState.lockfilesAndGeneratedAssets : undefined,
549
598
  cycleCount,
550
599
  timestamp: Date.now(),
551
600
  };
@@ -229,15 +229,21 @@ export function formatFileOperationsXml(options?: {
229
229
  dirtyPatch?: string;
230
230
  dirtyStateAvailable?: boolean;
231
231
  sensitiveFilesOmitted?: number;
232
+ activeBackgroundProcesses?: Iterable<string>;
233
+ lockfilesAndGeneratedAssets?: Iterable<string>;
232
234
  }): string {
233
235
  if (!options) return "";
234
236
  const readSet = new Set(options.readFiles ?? []);
235
237
  const touchedSet = new Set(options.touchedModifiedFiles ?? []);
236
238
  const dirtySet = new Set(options.activeDirtyFiles ?? []);
239
+ const backgroundSet = new Set(options.activeBackgroundProcesses ?? []);
240
+ const lockfilesSet = new Set(options.lockfilesAndGeneratedAssets ?? []);
237
241
 
238
242
  const readOnly = [...readSet].filter((f) => !touchedSet.has(f)).sort();
239
243
  const touched = [...touchedSet].sort();
240
244
  const dirty = [...dirtySet].sort();
245
+ const background = [...backgroundSet].sort();
246
+ const lockfiles = [...lockfilesSet].sort();
241
247
 
242
248
  const sections: string[] = [];
243
249
  if (readOnly.length > 0) {
@@ -249,6 +255,12 @@ export function formatFileOperationsXml(options?: {
249
255
  if (dirty.length > 0) {
250
256
  sections.push(`<uncommitted-dirty-files>\n${dirty.map(escapeXml).join("\n")}\n</uncommitted-dirty-files>`);
251
257
  }
258
+ if (lockfiles.length > 0) {
259
+ sections.push(`<modified-lockfiles-and-assets>\n${lockfiles.map(escapeXml).join("\n")}\n</modified-lockfiles-and-assets>`);
260
+ }
261
+ if (background.length > 0) {
262
+ sections.push(`<active-background-processes>\n${background.map(escapeXml).join("\n")}\n</active-background-processes>`);
263
+ }
252
264
  if (options.dirtyPatch) {
253
265
  sections.push(`<uncommitted-diff>\n${escapeXml(options.dirtyPatch)}\n</uncommitted-diff>`);
254
266
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shariq-pi-extensions",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "Cross-platform extension suite for the Pi coding agent.",
5
5
  "license": "MIT",
6
6
  "author": "Shariq Riaz",