@evoclock/pi-agentic-driver 1.0.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.
|
|
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,
|
|
@@ -505,7 +507,6 @@ function fixedArgv(action, params) {
|
|
|
505
507
|
"agent", "prompt", params.role, promptWithReportRequirement(params.role, params.prompt),
|
|
506
508
|
"--wait",
|
|
507
509
|
"--until", "idle", "--until", "done", "--until", "blocked",
|
|
508
|
-
"--timeout", String(params.timeoutMs),
|
|
509
510
|
];
|
|
510
511
|
case "prompt_async":
|
|
511
512
|
case "submit": {
|
|
@@ -518,14 +519,12 @@ function fixedArgv(action, params) {
|
|
|
518
519
|
: promptWithReportRequirement(params.role, params.prompt);
|
|
519
520
|
return [
|
|
520
521
|
"agent", "prompt", params.role, text,
|
|
521
|
-
"--timeout", String(COMMAND_TIMEOUT_MS),
|
|
522
522
|
];
|
|
523
523
|
}
|
|
524
524
|
case "wait":
|
|
525
525
|
return [
|
|
526
526
|
"agent", "wait", params.role,
|
|
527
527
|
"--until", "idle", "--until", "done", "--until", "blocked",
|
|
528
|
-
"--timeout", String(params.timeoutMs),
|
|
529
528
|
];
|
|
530
529
|
case "read":
|
|
531
530
|
return [
|
|
@@ -1063,10 +1062,12 @@ function allMarkerOccurrences(text, standaloneOnly = false, additionalPair = und
|
|
|
1063
1062
|
? text.length
|
|
1064
1063
|
: (newline > end && text[newline - 1] === "\r" ? newline - 1 : newline);
|
|
1065
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.
|
|
1066
1067
|
const horizontalOnly = Buffer.byteLength(leading, "utf8") <= MAX_MARKER_HORIZONTAL_WHITESPACE
|
|
1067
1068
|
&& Buffer.byteLength(trailing, "utf8") <= MAX_MARKER_HORIZONTAL_WHITESPACE
|
|
1068
1069
|
&& /^[ \t]*$/.test(leading)
|
|
1069
|
-
&& /^[ \t]
|
|
1070
|
+
&& /^[ \t]*(?:│)?$/.test(trailing);
|
|
1070
1071
|
const lineEnd = newline < 0 || text[newline] === "\n";
|
|
1071
1072
|
if (!standaloneOnly || (horizontalOnly && lineEnd)) occurrences.push({ marker, index, end });
|
|
1072
1073
|
from = end;
|
|
@@ -1090,7 +1091,9 @@ function promptContractRange(text, marker, occurrenceIndex, { boundEnd = false }
|
|
|
1090
1091
|
if (deliveryTail) end += deliveryTail[0].length;
|
|
1091
1092
|
if (boundEnd && end > occurrenceIndex) return undefined;
|
|
1092
1093
|
if (Buffer.byteLength(text.slice(anchor, end), "utf8") > MAX_PROMPT_CONTRACT_ECHO_BYTES) return undefined;
|
|
1093
|
-
|
|
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;
|
|
1094
1097
|
const otherMarkers = [...new Set(Object.values(REPORT_MARKERS)
|
|
1095
1098
|
.flatMap((pair) => [pair.open, pair.close]))]
|
|
1096
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/pulse/SKILL.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
---
|
|
2
|
+
description: Use Board Pulse to check Kanban scheduling capacity or work through ready cards.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com> -->
|
|
6
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
3
7
|
|
|
4
8
|
# Pulse
|
|
5
9
|
|
package/skills/tasks/SKILL.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use the session task list to track work and promote owner-authorized tasks to the durable board.
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# tasks — unified session task list + board promotion (v1)
|
|
2
6
|
|
|
3
7
|
Owner-facing skill for the pi-agentic-driver session task list and
|
|
@@ -116,9 +120,11 @@ reverted by re-installing picc-tasks.
|
|
|
116
120
|
`node scripts/enforcement/session_tasks_validate_pi.js --repo-root <root>`
|
|
117
121
|
is read-only. It validates session snapshot shape, taskListId resolution,
|
|
118
122
|
promotion metadata, linkage integrity, board availability (canonical
|
|
119
|
-
`TASKS.md` first; `board.md` never),
|
|
120
|
-
|
|
121
|
-
|
|
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.
|
|
122
128
|
|
|
123
129
|
## Out of scope in v1
|
|
124
130
|
|