@evoclock/pi-agentic-driver 0.8.0 → 0.8.2
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/extensions/task-board.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
|
-
// BOARD-1 provider extension
|
|
5
|
-
//
|
|
6
|
-
// no
|
|
4
|
+
// BOARD-1 provider extension. Both tools register at startup; the board is
|
|
5
|
+
// resolved per tool call from the calling session's working directory. A
|
|
6
|
+
// workspace with no board file gets a structured board-unavailable result —
|
|
7
|
+
// nothing is created and nothing else changes.
|
|
7
8
|
|
|
8
9
|
import { existsSync } from "node:fs";
|
|
9
10
|
import { join } from "node:path";
|
|
10
11
|
|
|
11
|
-
// Documented board locations (design §2): workspace-level Obsidian Kanban
|
|
12
|
-
// Markdown, or vogelkop's simpler TASKS.md shape.
|
|
13
12
|
const BOARD_FILENAMES = ["board.md", "TASKS.md"];
|
|
14
13
|
|
|
15
14
|
export function resolveBoardPath(cwd) {
|
|
@@ -23,9 +22,5 @@ export function resolveBoardPath(cwd) {
|
|
|
23
22
|
|
|
24
23
|
export default async function taskBoardPi(pi) {
|
|
25
24
|
const module = await import(new URL("../scripts/enforcement/task_board_core_pi.js", import.meta.url).href);
|
|
26
|
-
|
|
27
|
-
if (boardPath === null) {
|
|
28
|
-
return { registered: [], observation: { present: false, boardPath: null } };
|
|
29
|
-
}
|
|
30
|
-
return module.registerKanbanBoardTools(pi, { boardPath });
|
|
25
|
+
return module.registerKanbanBoardTools(pi, { resolveBoardPath });
|
|
31
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evoclock/pi-agentic-driver",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
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-or-later",
|
|
@@ -988,11 +988,12 @@ export function observeBoardProvider({ boardPath }) {
|
|
|
988
988
|
return { present, boardPath: present ? boardPath : null };
|
|
989
989
|
}
|
|
990
990
|
|
|
991
|
-
export function registerKanbanBoardTools(pi, { boardPath } = {}) {
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
//
|
|
995
|
-
//
|
|
991
|
+
export function registerKanbanBoardTools(pi, { resolveBoardPath, boardPath } = {}) {
|
|
992
|
+
// Tools register unconditionally; the board is resolved per call from the
|
|
993
|
+
// calling session's working directory (Pi extensions have no ctx at
|
|
994
|
+
// registration time). A workspace with no board file gets a structured
|
|
995
|
+
// board-unavailable result per call, so the surface stays reversible (§5):
|
|
996
|
+
// no board, nothing happens.
|
|
996
997
|
const registered = [];
|
|
997
998
|
if (typeof pi?.registerTool === "function") {
|
|
998
999
|
pi.registerTool({
|
|
@@ -1000,25 +1001,25 @@ export function registerKanbanBoardTools(pi, { boardPath } = {}) {
|
|
|
1000
1001
|
label: "Kanban Board",
|
|
1001
1002
|
description: "Read-only view of the validated task board: lanes, flags, priorities, dependencies, and dispatchability. The board is additive and grants no authority; agents read it and act within card states.",
|
|
1002
1003
|
parameters: { type: "object", additionalProperties: false, properties: {} },
|
|
1003
|
-
async execute() {
|
|
1004
|
-
//
|
|
1005
|
-
//
|
|
1006
|
-
//
|
|
1007
|
-
|
|
1008
|
-
if (!existsSync(
|
|
1004
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
1005
|
+
// The board is resolved per call from the calling session's working
|
|
1006
|
+
// directory. No board in this workspace: structured board-unavailable,
|
|
1007
|
+
// nothing happens.
|
|
1008
|
+
const resolvedBoardPath = (typeof resolveBoardPath === "function" ? resolveBoardPath(ctx?.cwd) : null) ?? boardPath ?? null;
|
|
1009
|
+
if (resolvedBoardPath === null || !existsSync(resolvedBoardPath)) {
|
|
1009
1010
|
const value = {
|
|
1010
1011
|
ok: false,
|
|
1011
1012
|
nonAuthorizing: true,
|
|
1012
1013
|
persisted: false,
|
|
1013
1014
|
boardUnavailable: true,
|
|
1014
1015
|
cards: [],
|
|
1015
|
-
errors: ["board
|
|
1016
|
+
errors: ["no board.md or TASKS.md in this workspace (board-unavailable)"],
|
|
1016
1017
|
};
|
|
1017
1018
|
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }], details: value };
|
|
1018
1019
|
}
|
|
1019
1020
|
let value;
|
|
1020
1021
|
try {
|
|
1021
|
-
const markdown = readFileSync(
|
|
1022
|
+
const markdown = readFileSync(resolvedBoardPath, "utf8");
|
|
1022
1023
|
const validated = validateBoard(markdown);
|
|
1023
1024
|
value = validated.ok
|
|
1024
1025
|
? { ok: true, nonAuthorizing: true, persisted: false, cards: validated.cards, errors: [] }
|
|
@@ -1075,18 +1076,25 @@ export function registerKanbanBoardTools(pi, { boardPath } = {}) {
|
|
|
1075
1076
|
},
|
|
1076
1077
|
required: ["title", "specification", "definitionOfDone", "stoppingPoint", "scopePaths", "authority"],
|
|
1077
1078
|
},
|
|
1078
|
-
async execute(_toolContext, input) {
|
|
1079
|
-
//
|
|
1080
|
-
//
|
|
1081
|
-
|
|
1082
|
-
|
|
1079
|
+
async execute(_toolContext, input, _signal, _onUpdate, ctx) {
|
|
1080
|
+
// Resolve the board per call from the calling session's working
|
|
1081
|
+
// directory. No board here: structured board-unavailable, no write.
|
|
1082
|
+
let resolvedBoardPath = (typeof resolveBoardPath === "function" ? resolveBoardPath(ctx?.cwd) : null) ?? boardPath ?? null;
|
|
1083
|
+
// The write tool bootstraps: a MISSING board file is fine (the writer
|
|
1084
|
+
// creates it fresh under the lock). When the resolver finds no board,
|
|
1085
|
+
// fall back to the canonical board.md in the workspace so the writer
|
|
1086
|
+
// can create it.
|
|
1087
|
+
if (resolvedBoardPath === null && typeof ctx?.cwd === "string" && ctx.cwd !== "") {
|
|
1088
|
+
resolvedBoardPath = join(ctx.cwd, "TASKS.md");
|
|
1089
|
+
}
|
|
1090
|
+
if (resolvedBoardPath === null) {
|
|
1083
1091
|
const value = {
|
|
1084
1092
|
ok: false,
|
|
1085
1093
|
persisted: false,
|
|
1086
1094
|
boardUnavailable: true,
|
|
1087
1095
|
code: "board-unavailable",
|
|
1088
|
-
reason: "board
|
|
1089
|
-
errors: ["board
|
|
1096
|
+
reason: "no board.md or TASKS.md in this workspace (board-unavailable)",
|
|
1097
|
+
errors: ["no board.md or TASKS.md in this workspace (board-unavailable)"],
|
|
1090
1098
|
};
|
|
1091
1099
|
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }], details: value };
|
|
1092
1100
|
}
|
|
@@ -1132,12 +1140,15 @@ export function registerKanbanBoardTools(pi, { boardPath } = {}) {
|
|
|
1132
1140
|
let result;
|
|
1133
1141
|
try {
|
|
1134
1142
|
result = writeCard({
|
|
1135
|
-
boardPath:
|
|
1143
|
+
boardPath: resolvedBoardPath,
|
|
1136
1144
|
input: writerInput,
|
|
1137
1145
|
authority: input?.authority,
|
|
1138
1146
|
registries: {},
|
|
1139
1147
|
surface: "tasks",
|
|
1140
|
-
requireExistingBoard:
|
|
1148
|
+
// No requireExistingBoard here: the write tool bootstraps a
|
|
1149
|
+
// fresh board when none exists (§3.4/§5). Recreation is safe —
|
|
1150
|
+
// the writer builds fresh content only, and every card still
|
|
1151
|
+
// requires a genuine authority record.
|
|
1141
1152
|
});
|
|
1142
1153
|
} catch (error) {
|
|
1143
1154
|
const code = typeof error?.code === "string" ? error.code : "writer-error";
|
|
@@ -1179,5 +1190,5 @@ export function registerKanbanBoardTools(pi, { boardPath } = {}) {
|
|
|
1179
1190
|
});
|
|
1180
1191
|
registered.push("agentic_kanban_board_write");
|
|
1181
1192
|
}
|
|
1182
|
-
return { registered
|
|
1193
|
+
return { registered };
|
|
1183
1194
|
}
|