pi-bar 0.3.28 → 0.3.29

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 CHANGED
@@ -9,9 +9,9 @@
9
9
  claude-opus-4.7 ❯ think:med ❯ 2.6% / 1.0M ❯ Inspecting package structure ❯ plan:active
10
10
  ```
11
11
 
12
- ![pi-bar with low context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.28/assets/screenshot-green.png)
13
- ![pi-bar with medium context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.28/assets/screenshot-yellow.png)
14
- ![pi-bar with high context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.28/assets/screenshot-red.png)
12
+ ![pi-bar with low context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.29/assets/screenshot-green.png)
13
+ ![pi-bar with medium context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.29/assets/screenshot-yellow.png)
14
+ ![pi-bar with high context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.29/assets/screenshot-red.png)
15
15
 
16
16
  ## Why use it?
17
17
 
@@ -1008,6 +1008,88 @@ class FooterTldrEngine {
1008
1008
  }
1009
1009
  }
1010
1010
 
1011
+ // First-word allow/ban lists are duplicated between the prompt and the
1012
+ // sanitizer fallback. They live here so both stay aligned automatically when
1013
+ // new verbs get added or moved.
1014
+ const ALLOWED_FIRST_WORDS_PROGRESSIVE = [
1015
+ "Reviewing",
1016
+ "Investigating",
1017
+ "Exploring",
1018
+ "Updating",
1019
+ "Refining",
1020
+ "Fixing",
1021
+ "Implementing",
1022
+ "Wrapping up",
1023
+ "Bumping",
1024
+ "Releasing",
1025
+ "Preparing",
1026
+ "Drafting",
1027
+ "Resuming",
1028
+ "Pulling",
1029
+ "Surveying",
1030
+ "Recording",
1031
+ ] as const;
1032
+ const ALLOWED_FIRST_WORDS_PAST = [
1033
+ "Reviewed",
1034
+ "Investigated",
1035
+ "Explored",
1036
+ "Updated",
1037
+ "Refined",
1038
+ "Fixed",
1039
+ "Implemented",
1040
+ "Wrapped up",
1041
+ "Bumped",
1042
+ "Released",
1043
+ "Prepared",
1044
+ "Drafted",
1045
+ "Resumed",
1046
+ "Pulled",
1047
+ "Surveyed",
1048
+ "Recorded",
1049
+ ] as const;
1050
+ const BANNED_FIRST_WORDS = [
1051
+ "Read",
1052
+ "Reading",
1053
+ "Grep",
1054
+ "Grepping",
1055
+ "Listing",
1056
+ "List",
1057
+ "Counting",
1058
+ "Counted",
1059
+ "Extracting",
1060
+ "Extracted",
1061
+ "Displaying",
1062
+ "Displayed",
1063
+ "Editing",
1064
+ "Edited",
1065
+ "Writing",
1066
+ "Wrote",
1067
+ "Running",
1068
+ "Ran",
1069
+ "Publishing",
1070
+ "Published",
1071
+ "Capturing",
1072
+ "Captured",
1073
+ "Verifying",
1074
+ "Verified",
1075
+ "Verify",
1076
+ "Validating",
1077
+ "Validated",
1078
+ "Validate",
1079
+ "Checking",
1080
+ "Checked",
1081
+ "Check",
1082
+ "Confirming",
1083
+ "Confirmed",
1084
+ "Confirm",
1085
+ "Searching",
1086
+ "Searched",
1087
+ "Search",
1088
+ "Finding",
1089
+ "Found",
1090
+ "Find",
1091
+ ] as const;
1092
+
1011
1093
  function checkpointSystemPrompt(job: TldrCheckpointJob): string {
1012
1094
  // Tense + exemplars vary by priority. Final TLDRs describe completed turns;
1013
1095
  // using -ing examples for final caused all final TLDRs to slip back into
@@ -1016,6 +1098,7 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
1016
1098
  // directive instead of generic filler like "Continuing with task".
1017
1099
  let tenseInstruction: string;
1018
1100
  let goodExamples: string;
1101
+ let allowedFirstWords: readonly string[];
1019
1102
  if (job.displayPriority === "final") {
1020
1103
  tenseInstruction = "Start with a past-tense verb describing what was completed.";
1021
1104
  goodExamples = [
@@ -1024,15 +1107,18 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
1024
1107
  "- Refined sanitizer for stray prefixes",
1025
1108
  "- Wrapped up extension release",
1026
1109
  ].join("\n");
1110
+ allowedFirstWords = ALLOWED_FIRST_WORDS_PAST;
1027
1111
  } else if (job.displayPriority === "immediate") {
1028
1112
  tenseInstruction =
1029
- "Rephrase the user's new request as a concise present-progressive task clause. Do not write \"Continuing\", \"Investigating user input\", or any generic filler.";
1113
+ "Rephrase the user's new request as a concise present-progressive task clause. If the request is opaque (e.g. \"continue\", \"go\", \"ok\"), name the carry-over task with a noun (e.g. \"Resuming refactor work\"), not generic filler.";
1030
1114
  goodExamples = [
1031
1115
  "- Reviewing footer summary behavior",
1032
1116
  "- Investigating live TLDR regressions",
1033
1117
  "- Refining sanitizer for stray prefixes",
1034
1118
  "- Preparing extension release",
1119
+ "- Resuming refactor work",
1035
1120
  ].join("\n");
1121
+ allowedFirstWords = ALLOWED_FIRST_WORDS_PROGRESSIVE;
1036
1122
  } else {
1037
1123
  tenseInstruction = "Start with a present-tense -ing verb describing current work.";
1038
1124
  goodExamples = [
@@ -1041,18 +1127,20 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
1041
1127
  "- Refining sanitizer for stray prefixes",
1042
1128
  "- Wrapping up extension release",
1043
1129
  ].join("\n");
1130
+ allowedFirstWords = ALLOWED_FIRST_WORDS_PROGRESSIVE;
1044
1131
  }
1045
1132
 
1046
1133
  // Frame the model as a human developer describing visible work, not an agent
1047
1134
  // summarizing its own mechanics. The banned phrases and few-shot examples
1048
1135
  // fix concrete regressions observed in backtests: tool-name verbs, success
1049
- // suffixes, file path leaks, and markdown formatting.
1136
+ // suffixes, file path leaks, and markdown formatting. The HARD CONSTRAINTS
1137
+ // block sits at the tail of the prompt because instruction-following models
1138
+ // give the most weight to the most-recent directives (recency bias).
1050
1139
  return `Write one plain-English TLDR for a Pi coding agent.
1051
1140
  Describe the work progress as if a human developer were doing it.
1052
1141
  Focus on the task activity and current outcome, not agent mechanics.
1053
1142
  Do not mention tools, tool calls, prompts, messages, model output, or implementation details.
1054
- Do not start with tool-narration verbs such as Read, Reading, Grep, Listing, Counting, Extracting, Displaying, Editing, Writing, Running, Publishing, Capturing, Verifying, Validating, Checking, Confirming, Searching, Finding.
1055
- Use human-developer verbs instead: Reviewing, Investigating, Exploring, Updating, Refining, Fixing, Implementing, Wrapping up, Bumping, Releasing.
1143
+ Use human-developer verbs instead of tool-narration verbs.
1056
1144
  Do not use file paths, file extensions, code identifiers, package names, or version strings.
1057
1145
  Do not use backticks, asterisks, underscores, quotes, or any markdown formatting.
1058
1146
  Do not append filler suffixes such as "with success", "successfully", or "completed successfully".
@@ -1076,10 +1164,13 @@ Bad examples:
1076
1164
  - Reading status-footer file completed successfully.
1077
1165
  - Publishing \`pi-bar@0.3.3\` to npm.
1078
1166
  - Grepping for sanitizeTldrText callers.
1079
- - Continuing with task progression.
1167
+ - Verifying repository status after commit.
1080
1168
  - Investigating user input responses.
1081
1169
 
1082
- ${tenseInstruction}`;
1170
+ HARD CONSTRAINTS (apply last; override anything above that conflicts):
1171
+ - First word MUST be one of: ${allowedFirstWords.join(", ")}.
1172
+ - First word MUST NOT be: ${BANNED_FIRST_WORDS.join(", ")}.
1173
+ - ${tenseInstruction}`;
1083
1174
  }
1084
1175
 
1085
1176
  function extractStopReason(message: unknown): string | undefined {
@@ -1273,18 +1364,87 @@ const DANGLING_TRAILING_PREP_PATTERN =
1273
1364
  /\s+(?:to|at|as|of|by|for|in|on|with|from|version|v)\s*[.!?,;:]?\s*$/i;
1274
1365
  const DANGLING_PREP_CHAIN_PATTERN =
1275
1366
  /\b(to|at|as|of|by|from|version|v)\s+(for|in|on|with|from|after|before|during|to|at|as|of|by|and|but|or)\b/gi;
1367
+ // File-path / identifier strip can leave a verb directly followed by a
1368
+ // preposition: "Reviewing README for image update" -> after path strip ->
1369
+ // "Reviewing for image update". Collapse the bare verb+prep into just the verb.
1370
+ const VERB_BARE_PREP_PATTERN =
1371
+ /\b(Reviewing|Investigating|Updating|Refining|Exploring|Fixing|Implementing|Bumping|Releasing|Preparing|Drafting|Resuming|Pulling|Surveying|Recording)\s+(?:for|in|on|with|after|before|to|at|as|of|by|from)\s+/gi;
1276
1372
 
1277
1373
  function stripDanglingPrepositions(text: string): string {
1278
1374
  let cleaned = text;
1279
1375
  let previous: string;
1280
1376
  do {
1281
1377
  previous = cleaned;
1378
+ cleaned = cleaned.replace(VERB_BARE_PREP_PATTERN, "$1 ");
1282
1379
  cleaned = cleaned.replace(DANGLING_PREP_CHAIN_PATTERN, "$2");
1283
1380
  cleaned = cleaned.replace(DANGLING_TRAILING_PREP_PATTERN, "").trim();
1284
1381
  } while (cleaned !== previous && cleaned.length > 0);
1285
1382
  return cleaned;
1286
1383
  }
1287
1384
 
1385
+ // Last-resort fallback when the model ignored the allow-list and started the
1386
+ // TLDR with a banned tool-narration verb. The map preserves tense (present
1387
+ // vs past). Case of the first letter follows the original word.
1388
+ const BANNED_FIRST_WORD_REWRITES: Record<string, string> = {
1389
+ Reading: "Reviewing",
1390
+ Read: "Reviewed",
1391
+ Grepping: "Investigating",
1392
+ Grep: "Investigated",
1393
+ Listing: "Reviewing",
1394
+ List: "Reviewed",
1395
+ Counting: "Surveying",
1396
+ Counted: "Surveyed",
1397
+ Extracting: "Pulling",
1398
+ Extracted: "Pulled",
1399
+ Displaying: "Reviewing",
1400
+ Displayed: "Reviewed",
1401
+ Editing: "Updating",
1402
+ Edited: "Updated",
1403
+ Writing: "Drafting",
1404
+ Wrote: "Drafted",
1405
+ Running: "Working on",
1406
+ Ran: "Worked on",
1407
+ Publishing: "Releasing",
1408
+ Published: "Released",
1409
+ Capturing: "Recording",
1410
+ Captured: "Recorded",
1411
+ Verifying: "Reviewing",
1412
+ Verified: "Reviewed",
1413
+ Verify: "Review",
1414
+ Validating: "Reviewing",
1415
+ Validated: "Reviewed",
1416
+ Validate: "Review",
1417
+ Checking: "Reviewing",
1418
+ Checked: "Reviewed",
1419
+ Check: "Review",
1420
+ Confirming: "Reviewing",
1421
+ Confirmed: "Reviewed",
1422
+ Confirm: "Review",
1423
+ Searching: "Investigating",
1424
+ Searched: "Investigated",
1425
+ Search: "Investigate",
1426
+ Finding: "Investigating",
1427
+ Found: "Investigated",
1428
+ Find: "Investigate",
1429
+ };
1430
+ const BANNED_FIRST_WORD_PATTERN = new RegExp(
1431
+ `^(${Object.keys(BANNED_FIRST_WORD_REWRITES).join("|")})\\b`,
1432
+ );
1433
+
1434
+ function rewriteBannedFirstWord(text: string): string {
1435
+ const match = BANNED_FIRST_WORD_PATTERN.exec(text);
1436
+ if (!match) return text;
1437
+ const original = match[1];
1438
+ const replacement = BANNED_FIRST_WORD_REWRITES[original];
1439
+ if (!replacement) return text;
1440
+ // Preserve lowercase-start TLDRs (rare but possible) by matching original case.
1441
+ const cased =
1442
+ original[0] === original[0].toLowerCase()
1443
+ ? replacement[0].toLowerCase() + replacement.slice(1)
1444
+ : replacement;
1445
+ return cased + text.slice(original.length);
1446
+ }
1447
+
1288
1448
  // Trailing filler suffixes observed in backtests:
1289
1449
  // "... with success.", "... completed successfully.", "... successfully."
1290
1450
  const SUCCESS_SUFFIX_PATTERN =
@@ -1316,7 +1476,8 @@ function sanitizeTldrText(
1316
1476
  const withoutLeaks = stripIdentifierLeaks(withoutScaffolding) || withoutScaffolding;
1317
1477
  const withoutDangling = stripDanglingPrepositions(withoutLeaks) || withoutLeaks;
1318
1478
  const withoutSuccess = stripSuccessSuffix(withoutDangling) || withoutDangling;
1319
- return truncateText(withoutSuccess, maxChars);
1479
+ const rewritten = rewriteBannedFirstWord(withoutSuccess);
1480
+ return truncateText(rewritten, maxChars);
1320
1481
  }
1321
1482
 
1322
1483
  function shouldShowStatus(key: string, filter: StatusFilter): boolean {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-bar",
3
- "version": "0.3.28",
3
+ "version": "0.3.29",
4
4
  "description": "Pi footer/statusline showing active model, thinking level, context pressure, live TLDR, and extension statuses.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -51,6 +51,6 @@
51
51
  "extensions": [
52
52
  "./extensions/status-footer.ts"
53
53
  ],
54
- "image": "https://cdn.jsdelivr.net/npm/pi-bar@0.3.28/assets/screenshot-yellow.png"
54
+ "image": "https://cdn.jsdelivr.net/npm/pi-bar@0.3.29/assets/screenshot-yellow.png"
55
55
  }
56
56
  }