@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.
|
|
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
|
-
|
|
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]
|
|
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
|
-
|
|
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)");
|
package/skills/tasks/SKILL.md
CHANGED
|
@@ -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),
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|