@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.
@@ -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 reversibility): the board surface registers
5
- // only when a real board file is observed in the workspace. No board file,
6
- // no behavior change and no new tool.
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
- const boardPath = resolveBoardPath(pi?.ctx?.cwd);
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.0",
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
- const observation = observeBoardProvider({ boardPath });
993
- if (!observation.present) return { registered: [], observation };
994
- // Additive, fail-closed: registered only behind the observation,
995
- // so removing the board file removes the surface (§5).
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
- // F7(b): every tool call re-observes the board. If the board file was
1005
- // removed after registration, return an observed board-unavailable
1006
- // result instead of silently serving a stale board. Registration
1007
- // stays; the surface is gated per call.
1008
- if (!existsSync(observation.boardPath)) {
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 file is no longer present (board-unavailable)"],
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(observation.boardPath, "utf8");
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
- // F7(b): re-observe the board on every call. If the board file was
1080
- // removed after registration, return board-unavailable instead of
1081
- // writing to a stale path.
1082
- if (!existsSync(observation.boardPath)) {
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 file is no longer present (board-unavailable)",
1089
- errors: ["board file is no longer present (board-unavailable)"],
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: observation.boardPath,
1143
+ boardPath: resolvedBoardPath,
1136
1144
  input: writerInput,
1137
1145
  authority: input?.authority,
1138
1146
  registries: {},
1139
1147
  surface: "tasks",
1140
- requireExistingBoard: true,
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, observation };
1193
+ return { registered };
1183
1194
  }