@evoclock/pi-agentic-driver 0.10.0 → 0.10.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoclock/pi-agentic-driver",
3
- "version": "0.10.0",
3
+ "version": "0.10.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",
@@ -379,6 +379,75 @@ function safeJsonParse(text) {
379
379
  }
380
380
  }
381
381
 
382
+ const CAPABILITY_REGISTRY_PATH = ".vogelkop/capabilities.json";
383
+
384
+ function capabilityRegistryError(registryPath, reason) {
385
+ return Object.assign(new Error(`capability registry is invalid at ${registryPath}: ${reason}`), {
386
+ code: "capability-registry-invalid",
387
+ });
388
+ }
389
+
390
+ // The workspace registry is deliberately loaded at tool-call time. A missing
391
+ // registry is an empty declaration; an unreadable or malformed present file is
392
+ // not silently downgraded to empty. Capability-less boards are independent of
393
+ // the registry, so they retain the writer's existing behavior even while an
394
+ // unused registry file is malformed.
395
+ function loadWorkspaceCapabilityNames(boardPath) {
396
+ const registryPath = join(dirname(boardPath), CAPABILITY_REGISTRY_PATH);
397
+ let text;
398
+ try {
399
+ text = readFileSync(registryPath, "utf8");
400
+ } catch (error) {
401
+ if (error?.code === "ENOENT") return [];
402
+ throw capabilityRegistryError(registryPath, String(error?.message || error).slice(0, 256));
403
+ }
404
+ let entries;
405
+ try {
406
+ entries = JSON.parse(text);
407
+ } catch (error) {
408
+ throw capabilityRegistryError(registryPath, `invalid JSON: ${String(error?.message || error).slice(0, 256)}`);
409
+ }
410
+ if (!Array.isArray(entries)) {
411
+ throw capabilityRegistryError(registryPath, "expected a JSON array");
412
+ }
413
+ const names = [];
414
+ for (const [index, entry] of entries.entries()) {
415
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)
416
+ || typeof entry.name !== "string" || entry.name.trim() === ""
417
+ || (entry.description !== undefined && typeof entry.description !== "string")) {
418
+ throw capabilityRegistryError(registryPath, `entry ${index} must contain a non-empty string name and an optional string description`);
419
+ }
420
+ names.push(entry.name);
421
+ }
422
+ return names;
423
+ }
424
+
425
+ function boardHasCapabilities(boardPath) {
426
+ if (!existsSync(boardPath)) return false;
427
+ try {
428
+ const parsed = parseBoard(readFileSync(boardPath, "utf8"));
429
+ return parsed.cards.some((card) => Array.isArray(card.capabilities) && card.capabilities.length > 0);
430
+ } catch {
431
+ // Let the writer report the board error; do not use an unreadable board to
432
+ // justify ignoring a malformed capability registry.
433
+ return true;
434
+ }
435
+ }
436
+
437
+ function workspaceRegistriesFor(boardPath, requestedCapabilities) {
438
+ try {
439
+ return { capabilities: loadWorkspaceCapabilityNames(boardPath) };
440
+ } catch (error) {
441
+ const capabilityRequested = requestedCapabilities !== undefined
442
+ && requestedCapabilities !== null
443
+ && (!Array.isArray(requestedCapabilities) || requestedCapabilities.length > 0);
444
+ if (error?.code === "capability-registry-invalid" && !capabilityRequested && !boardHasCapabilities(boardPath)) {
445
+ return { capabilities: [] };
446
+ }
447
+ throw error;
448
+ }
449
+ }
450
+
382
451
  // ---------------------------------------------------------------------------
383
452
  // Closed validator (§1, §6 gate 2). Validate before persist; decline on
384
453
  // violation; duplicate cardId is a validation error; decorative use of the
@@ -2898,11 +2967,12 @@ export function registerKanbanBoardTools(pi, { boardPath = null, resolveBoardPat
2898
2967
  // reason), never raw throws — the model sees the governance reason.
2899
2968
  let result;
2900
2969
  try {
2970
+ const registries = workspaceRegistriesFor(activeBoardPath, input?.capabilities);
2901
2971
  result = writeCard({
2902
2972
  boardPath: activeBoardPath,
2903
2973
  input: writerInput,
2904
2974
  authority: input?.authority,
2905
- registries: {},
2975
+ registries,
2906
2976
  surface: "tasks",
2907
2977
  // No requireExistingBoard: the write tool bootstraps a fresh
2908
2978
  // board when none exists. Recreation is safe — fresh content only,
@@ -3028,9 +3098,10 @@ export function registerKanbanBoardTools(pi, { boardPath = null, resolveBoardPat
3028
3098
  }
3029
3099
  let result;
3030
3100
  try {
3101
+ const registries = workspaceRegistriesFor(activeBoardPath, operation === "update" ? input?.capabilities : undefined);
3031
3102
  result = operation === "update"
3032
- ? updateCard({ boardPath: activeBoardPath, cardId: input.cardId, changes, authority: input?.authority, registries: {}, surface: "tasks" })
3033
- : deleteCard({ boardPath: activeBoardPath, cardId: input.cardId, authority: input?.authority, registries: {}, surface: "tasks" });
3103
+ ? updateCard({ boardPath: activeBoardPath, cardId: input.cardId, changes, authority: input?.authority, registries, surface: "tasks" })
3104
+ : deleteCard({ boardPath: activeBoardPath, cardId: input.cardId, authority: input?.authority, registries, surface: "tasks" });
3034
3105
  } catch (error) {
3035
3106
  const code = typeof error?.code === "string" ? error.code : "writer-error";
3036
3107
  const value = { ok: false, persisted: false, code, reason: String(error?.message || error).slice(0, 512), errors: [String(error?.message || error).slice(0, 512)] };