@evoclock/pi-agentic-driver 1.1.0 → 1.1.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.
@@ -7,6 +7,10 @@ import {
7
7
  } from "../scripts/enforcement/session_tasks_core_pi.js";
8
8
  import { createHash } from "node:crypto";
9
9
 
10
+ // Pi discovers every packaged extensions/*.ts file. This helper also needs a
11
+ // harmless default factory so discovery can load it without registering tools.
12
+ export default function taskStoreAdapter() {}
13
+
10
14
  // Match TaskList's branch-first/high-water-mark observation in tasks.ts.
11
15
  // The session mirror is a fallback; neither reader changes either source.
12
16
  function observeSessionTasks(ctx) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoclock/pi-agentic-driver",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Guardrail extensions for Agentic Driver: advisory review, bounded Herdr communication, and guarded worker lifecycle.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -58,7 +58,9 @@ const MAX_PROCESS_OUTPUT_BYTES = 128 * 1024;
58
58
  const MAX_FAILURE_DIAGNOSTIC_BYTES = 4 * 1024;
59
59
  const MAX_REPORT_BYTES = 32 * 1024;
60
60
  const MAX_IDENTITY_FIELD_BYTES = 4 * 1024;
61
- const MAX_MARKER_HORIZONTAL_WHITESPACE = 128;
61
+ // Herdr pads a standalone marker to the terminal width (observed 206 spaces
62
+ // after the opening marker). Keep a finite cap without rejecting that padding.
63
+ const MAX_MARKER_HORIZONTAL_WHITESPACE = 512;
62
64
  const MAX_PROMPT_CONTRACT_ECHO_BYTES = 4 * 1024;
63
65
  const MAX_READ_LINES = 400;
64
66
  // A valid 32 KiB report can contain more than 400 short lines. A second,
@@ -1060,10 +1062,12 @@ function allMarkerOccurrences(text, standaloneOnly = false, additionalPair = und
1060
1062
  ? text.length
1061
1063
  : (newline > end && text[newline - 1] === "\r" ? newline - 1 : newline);
1062
1064
  const trailing = text.slice(end, trailingEnd);
1065
+ // Herdr's recent-unwrapped snapshot can retain Pi's right-hand box
1066
+ // border after a padded marker, even for a real completed report.
1063
1067
  const horizontalOnly = Buffer.byteLength(leading, "utf8") <= MAX_MARKER_HORIZONTAL_WHITESPACE
1064
1068
  && Buffer.byteLength(trailing, "utf8") <= MAX_MARKER_HORIZONTAL_WHITESPACE
1065
1069
  && /^[ \t]*$/.test(leading)
1066
- && /^[ \t]*$/.test(trailing);
1070
+ && /^[ \t]*(?:│)?$/.test(trailing);
1067
1071
  const lineEnd = newline < 0 || text[newline] === "\n";
1068
1072
  if (!standaloneOnly || (horizontalOnly && lineEnd)) occurrences.push({ marker, index, end });
1069
1073
  from = end;
@@ -1087,7 +1091,9 @@ function promptContractRange(text, marker, occurrenceIndex, { boundEnd = false }
1087
1091
  if (deliveryTail) end += deliveryTail[0].length;
1088
1092
  if (boundEnd && end > occurrenceIndex) return undefined;
1089
1093
  if (Buffer.byteLength(text.slice(anchor, end), "utf8") > MAX_PROMPT_CONTRACT_ECHO_BYTES) return undefined;
1090
- if (!/^\s*$/.test(text.slice(markerStart, open)) || !/^\s*$/.test(text.slice(open + marker.open.length, close))) return undefined;
1094
+ // The same box border may appear in the echoed report template. Only
1095
+ // whitespace and that exact border are allowed between its markers.
1096
+ if (!/^[\s│]*$/.test(text.slice(markerStart, open)) || !/^[\s│]*$/.test(text.slice(open + marker.open.length, close))) return undefined;
1091
1097
  const otherMarkers = [...new Set(Object.values(REPORT_MARKERS)
1092
1098
  .flatMap((pair) => [pair.open, pair.close]))]
1093
1099
  .filter((value) => value !== marker.open && value !== marker.close);
@@ -18,7 +18,7 @@ import {
18
18
  promotionIdempotencyKey, findExistingPromotedCard, resolveCallerOrigin, hasTasksCapability,
19
19
  TASKS_CAPABILITY_ENV,
20
20
  } from "./session_tasks_core_pi.js";
21
- import { validateBoard } from "./task_board_core_pi.js";
21
+ import { validateBoard, projectionPath, parseBoard, serializeBoard, readClaimsState } from "./task_board_core_pi.js";
22
22
 
23
23
  function readJsonIfExists(path) {
24
24
  if (!existsSync(path)) return null;
@@ -134,6 +134,48 @@ export function validateTasksSetup({
134
134
  checks.linkage = { ok: brokenLinks.length === 0, brokenLinks };
135
135
  errors.push(...brokenLinks);
136
136
  }
137
+ // Compare parsed semantics against the writer's existing projection format.
138
+ // A missing projection is a warning only (the writer recreates it on write).
139
+ const projection = projectionPath(resolvedBoardPath);
140
+ if (existsSync(projection)) {
141
+ try {
142
+ const parsed = parseBoard(readFileSync(projection, "utf8"), { surface: "obsidian" });
143
+ if (!parsed.ok) {
144
+ checks.projection = { ok: false, path: projection, reason: "the projection does not parse as an Obsidian board" };
145
+ errors.push(`projection invalid: ${parsed.errors?.join("; ") ?? "unparseable"}`);
146
+ } else {
147
+ const claims = readClaimsState(resolvedBoardPath);
148
+ if (!claims.ok) {
149
+ checks.projection = { ok: false, path: projection, reason: `claims state unavailable: ${claims.reason}` };
150
+ errors.push(`projection ${projection} cannot be verified: ${claims.reason}`);
151
+ } else {
152
+ const active = new Map(claims.state.claims.map((claim) => [claim.cardId, claim.role]));
153
+ const expected = parseBoard(serializeBoard(validated.cards.map((card) =>
154
+ active.has(card.cardId) ? { ...card, activeClaim: active.get(card.cardId) } : card
155
+ ), { surface: "obsidian" }), { surface: "obsidian" });
156
+ // Compare the parsed, writer-promised fields, not Markdown formatting.
157
+ // Include multiplicity: duplicate IDs cannot disappear into a Set.
158
+ const fields = ["cardId", "lane", "title", "priority", "done", "flags", "hash",
159
+ "dependencies", "base", "due", "role", "capabilities", "stoppingPoint",
160
+ "specHash", "dodHash", "specText", "dodText", "scope", "unchangedPaths",
161
+ "repositories", "tags", "provenance", "importedId", "authoritySource",
162
+ "authorityWriterHmac", "description", "activeClaim"];
163
+ const signature = (cards) => cards.map((card) => fields.map((field) =>
164
+ field === "activeClaim" ? card.fields?.active ?? null : card[field]
165
+ )).sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
166
+ const stale = JSON.stringify(signature(expected.cards)) !== JSON.stringify(signature(parsed.cards));
167
+ checks.projection = { ok: !stale, path: projection, cards: parsed.cards.length, stale };
168
+ if (stale) errors.push(`projection ${projection} is stale: content does not match the canonical board and claims (recomputed on the next trusted write)`);
169
+ }
170
+ }
171
+ } catch (error) {
172
+ checks.projection = { ok: false, path: projection, reason: String(error?.message || error).slice(0, 200) };
173
+ errors.push(`projection ${projection} could not be read`);
174
+ }
175
+ } else {
176
+ checks.projection = { ok: true, path: projection, present: false };
177
+ warnings.push(`no board.md projection at ${projection} (recomputed on the next trusted write; Obsidian rendering unavailable until then)`);
178
+ }
137
179
  } else {
138
180
  checks.board = { ok: false, path: resolvedBoardPath, reason: "no TASKS.md in this workspace" };
139
181
  warnings.push("no TASKS.md found for the workspace; promotion is unavailable until a board exists (a write tool call bootstraps one)");
@@ -120,9 +120,11 @@ reverted by re-installing picc-tasks.
120
120
  `node scripts/enforcement/session_tasks_validate_pi.js --repo-root <root>`
121
121
  is read-only. It validates session snapshot shape, taskListId resolution,
122
122
  promotion metadata, linkage integrity, board availability (canonical
123
- `TASKS.md` first; `board.md` never), and promotion target configuration.
124
- Exit 0 only when ready; nonzero with a machine-readable error list
125
- otherwise. It never mutates state, never writes the board, never promotes.
123
+ `TASKS.md` first; `board.md` never), projection consistency when present,
124
+ and promotion target configuration. A missing `board.md` is a warning;
125
+ a present but stale or mismatched projection is an error. Exit 0 only when
126
+ operational; otherwise return a machine-readable error list. It never
127
+ mutates state, writes the board, or promotes.
126
128
 
127
129
  ## Out of scope in v1
128
130