@evoclock/pi-agentic-driver 0.10.1 → 1.0.0
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/.agentic-driver/nudge.defaults.json +11 -0
- package/.githooks/pre-commit +4 -0
- package/README.md +43 -31
- package/config/codebase-inventory.schema.json +23 -0
- package/docs/codebase-inventory.md +15 -0
- package/docs/recovery-evidence-store.md +9 -0
- package/extensions/herdr-dispatch.ts +15 -2
- package/extensions/recovery-evidence.ts +14 -0
- package/extensions/task-store-adapter.ts +101 -0
- package/extensions/tasks.ts +34 -11
- package/package.json +19 -1
- package/scripts/codebase_inventory.py +105 -0
- package/scripts/codebase_inventory_gate.py +48 -0
- package/scripts/enforcement/board_claim_bridge_pi.js +204 -0
- package/scripts/enforcement/herdr_async_dispatch_pi.js +83 -30
- package/scripts/enforcement/herdr_async_seam_pi.js +347 -0
- package/scripts/enforcement/herdr_communication_pi.js +608 -95
- package/scripts/enforcement/herdr_lifecycle_pi.js +266 -13
- package/scripts/enforcement/linux_microvm_remote_fixture.sh +6 -6
- package/scripts/enforcement/native_tui_context.js +16 -0
- package/scripts/enforcement/nudge_classifier_pi.js +176 -0
- package/scripts/enforcement/nudge_config_pi.js +234 -0
- package/scripts/enforcement/nudge_ledger_pi.js +514 -0
- package/scripts/enforcement/nudge_reason_state_pi.js +135 -0
- package/scripts/enforcement/nudge_validate_pi.js +224 -0
- package/scripts/enforcement/pulse_scheduler_pi.js +15 -3
- package/scripts/enforcement/recovery_evidence_review_pi.js +269 -0
- package/scripts/enforcement/recovery_evidence_store_pi.js +170 -0
- package/scripts/enforcement/session_tasks_core_pi.js +14 -0
- package/scripts/enforcement/task_board_core_pi.js +23 -5
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<img src="https://img.shields.io/badge/Python-3776AB?style=flat&logo=python&logoColor=white" alt="Python"/>
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
|
-
An agent without guardrails will rewrite code an existing abstraction already
|
|
16
|
+
An agent without guardrails will sometimes rewrite code an existing abstraction already
|
|
17
17
|
covers. It will ship to the wrong remote. It will lose context and report
|
|
18
18
|
success without evidence. Pi is deliberately lean by design: the harness is yours to
|
|
19
19
|
shape. That philosophy is exactly what these extensions practice, they make them mine (and possibly yours too). They take
|
|
@@ -21,14 +21,14 @@ some concerns I consider worth addressing, and make them part of the
|
|
|
21
21
|
harness.
|
|
22
22
|
|
|
23
23
|
**pi-agentic-driver makes agent work verifiable and controllable. The agent does
|
|
24
|
-
the work and each extension makes sure the work can be checked.**
|
|
25
|
-
before code is written. Communication
|
|
26
|
-
Isolation proofs verify their own cleanup. Sessions survive compaction and
|
|
24
|
+
the work and each extension makes sure the work can be checked.** Reviewing happens
|
|
25
|
+
before code is written. Communication involves reports without asserting authority that isn't granted.
|
|
26
|
+
Isolation proofs exist to verify their own cleanup. Sessions survive compaction and
|
|
27
27
|
Git operations stay exact, confirmed, and protected.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
Features have different evidence levels: focused tests and independent review
|
|
30
|
+
support the shipped contracts, while live host-dependent proof is stated only
|
|
31
|
+
where it has actually run. A receipt or worker report is evidence, not approval.
|
|
32
32
|
|
|
33
33
|
Extensions for [Pi](https://github.com/earendil-works/pi-coding-agent):
|
|
34
34
|
advisory code review, controlled role communication, and governed isolation
|
|
@@ -43,24 +43,26 @@ proofs for agentic workflows.
|
|
|
43
43
|
| Tool | What it does | Status |
|
|
44
44
|
|------|--------------|--------|
|
|
45
45
|
| `code_phage` | Reviews a plan against a stated goal before code is written and advises your agent. | shipped |
|
|
46
|
-
| `tasks` | Keeps a session plan
|
|
47
|
-
| `agentic_herdr_communication` |
|
|
46
|
+
| `tasks` | Keeps a priority-ordered session plan, then lets the owner make selected items durable in `TASKS.md`. | shipped |
|
|
47
|
+
| `agentic_herdr_communication` | Submits one prompt and observes delivery and marked reports; never grants authority. | shipped |
|
|
48
48
|
| `agentic_herdr_spawn_worker` | Starts one Pi worker in a pane or tab, with native confirmation. | shipped |
|
|
49
49
|
| `agentic_aidr` | A remedy for AI;DR. Reviews writing for clarity, simplicity, brevity, and humanity. | shipped |
|
|
50
50
|
| `agentic_linux_microvm_cutover` | Runs one job in a throwaway QEMU/KVM virtual machine on a Linux host, with a severity-tiered killswitch that stops escape attempts. | user-enabled, native confirmation |
|
|
51
|
-
| `agentic_worker_dispatch` | Runs
|
|
51
|
+
| `agentic_worker_dispatch` | Runs bounded worker journeys and observes worker liveness; recovery is not activated. | shipped |
|
|
52
52
|
| `agentic_kanban_board` | Shows the workspace task board: lanes, flags, priorities, dependencies, and which cards can run. | shipped |
|
|
53
53
|
| `agentic_kanban_board_write` | Adds cards to the board through the trusted writer, which records who authorized the work. | shipped |
|
|
54
54
|
| `agentic_kanban_board_update` | Moves, closes, flags, edits, or removes cards on the board, always recording who authorized the change. | shipped |
|
|
55
55
|
| `agentic_kanban_board_dispatch` | Claims an eligible board card for automated contained work and creates its assignment envelope. | shipped |
|
|
56
56
|
| `agentic_kanban_pulse` | Checks for ready work and available capacity, then starts assignments under the board policy. | shipped |
|
|
57
|
+
| `/agentic-recovery-evidence` | Opens private recovery evidence in the local TUI; it is not a model-callable tool or recovery authorization. | shipped, local review only |
|
|
57
58
|
| Router | Capacity/policy routing for Board Pulse: seats with scope statements, tunable policy values, and a binding dispatch gate. | shipped |
|
|
58
59
|
| Janus (bundled service) | Local Jev evaluation service on loopback: semantic ranking, the dispatch-gate judgment, redaction before every external call. See `janus/README.md`. | shipped |
|
|
59
60
|
|
|
60
|
-
**Status: active development and testing.**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
**Status: active development and testing.** The disabled NUDGE classifier and
|
|
62
|
+
private evidence store are packaged foundations, not a running recovery
|
|
63
|
+
ladder. Herdr 0.9.1 has no proven modal-safe nudge channel. No terminal
|
|
64
|
+
`prompt` or `send-keys` fallback is enabled. Pending integrations below are
|
|
65
|
+
not presented as live features.
|
|
64
66
|
|
|
65
67
|
## Code review and planning
|
|
66
68
|
|
|
@@ -101,13 +103,13 @@ only, not a runtime dependency).
|
|
|
101
103
|
|
|
102
104
|
Plans stay close to the work. They do not become board work by accident. The tasks skill gives Pi a familiar session list. It adds an owner-approved way to keep selected tasks in the durable `TASKS.md` board.
|
|
103
105
|
|
|
104
|
-
- **Keep the working list simple.** Use `TaskCreate`, `TaskGet`, `TaskList`, and `TaskUpdate` as usual;
|
|
106
|
+
- **Keep the working list simple.** Use `TaskCreate`, `TaskGet`, `TaskList`, and `TaskUpdate` as usual. Set an optional P0–P3 session priority; ranked open work appears first, unranked work keeps ID order, and completed work appears last. Priority does not authorize board dispatch. The widget and footer stay live as tasks change.
|
|
105
107
|
- **Keep the Claude-like view.** `/tasks` provides descriptions, active forms, and internal-task markers without mutating the plan.
|
|
106
108
|
- **Make durable work deliberate.** `TaskPromote` never runs automatically. It previews the exact cards and destination before the owner confirms.
|
|
107
109
|
- **Keep authority clear.** The trusted board writer creates and updates `TASKS.md`. The skill does not bypass it.
|
|
108
110
|
- **Make migration gentle.** Existing task files remain readable. Moving to the driver-owned skill does not strand current plans.
|
|
109
111
|
|
|
110
|
-
The result is a clean handoff from a session plan to durable work. Selected tasks can survive the session, appear on the shared board, and become available for controlled dispatch when ready.
|
|
112
|
+
The result is a clean handoff from a session plan to durable work. Selected tasks can survive the session, appear on the shared board, and become available for controlled dispatch when ready. A session priority is not copied into a board card without an owner-approved board action.
|
|
111
113
|
|
|
112
114
|
</details>
|
|
113
115
|
|
|
@@ -276,11 +278,15 @@ across versions rather than pinning one).
|
|
|
276
278
|
- **List and observe.** Worker roles are filtered to trusted repositories: a
|
|
277
279
|
checked-in registry plus canonical-path validation. Unlisted or
|
|
278
280
|
symlink-escaped repositories are denied.
|
|
279
|
-
- **
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
281
|
+
- **Submit once and observe separately.** A prompt returns an immediate,
|
|
282
|
+
non-authorizing delivery receipt. The `delivery` action can report queued,
|
|
283
|
+
delivered, answered, failed, unknown, or unattributed. A marked answer is
|
|
284
|
+
attributed only to its own delivery boundary and exact correlation line;
|
|
285
|
+
missing or ambiguous evidence is never called success.
|
|
286
|
+
- **Wait and read.** The same trust checks apply to settlement and marked
|
|
287
|
+
reports. Unknown delivery never causes an automatic retry or resend.
|
|
288
|
+
Duplicate submissions within the bounded uncertainty window return the
|
|
289
|
+
existing delivery ID, not another handoff.
|
|
284
290
|
- **Grant nothing.** Fixed argv, `shell: false`, a pinned executable, the
|
|
285
291
|
coordinator role class denied. Results come back as untrusted evidence. The
|
|
286
292
|
tool cannot control panes, start agents, run shells, or create authority.
|
|
@@ -383,9 +389,8 @@ starting revision, assigned branch, allowed paths, stopping point, expiry, and
|
|
|
383
389
|
interaction profile.
|
|
384
390
|
|
|
385
391
|
Blocked, altered, expired, reused, or unauthorized assignments fail closed.
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
attempt ends.
|
|
392
|
+
Board Pulse handles board assignment with full route evidence. The session
|
|
393
|
+
worker journey does not claim board cards or mint board envelopes.
|
|
389
394
|
|
|
390
395
|
Consuming an envelope releases the claim. It does not mark the card done.
|
|
391
396
|
|
|
@@ -579,10 +584,13 @@ end" or "don't wait for me" and the journey runs in autonomous mode. Say
|
|
|
579
584
|
"do these two things" and it runs in the normal mode, pausing for you
|
|
580
585
|
between steps. The mode is recorded in the report either way.
|
|
581
586
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
587
|
+
`agentic_worker_dispatch` observes coordinator session tasks through a
|
|
588
|
+
read-only task store; dispatch no longer fails for lack of that store.
|
|
589
|
+
Interactive steps require native confirmation. Autonomous journeys follow
|
|
590
|
+
the session's existing autonomy semantics. Board cards in canonical
|
|
591
|
+
`TASKS.md` are assigned by Board Pulse and `agentic_kanban_board_dispatch`,
|
|
592
|
+
which own the route evidence needed for claims and envelopes. The session
|
|
593
|
+
journey never claims board cards.
|
|
586
594
|
|
|
587
595
|
### The cast
|
|
588
596
|
|
|
@@ -652,9 +660,13 @@ silently allowed.
|
|
|
652
660
|
notes, governed checkpoint mutation, watchdog handoff, and fresh-session
|
|
653
661
|
resumption that identifies goal, changed files, checks, and next step
|
|
654
662
|
without executing anything.
|
|
655
|
-
- **
|
|
656
|
-
|
|
657
|
-
|
|
663
|
+
- **recovery journey.** A private exact-evidence store and read-only TUI review
|
|
664
|
+
are packaged. Trusted journey authorization, safe handoff, durable unknown-
|
|
665
|
+
delivery lock, and a modal-safe nudge channel are still missing. No recovery
|
|
666
|
+
action is enabled by a stored hash, receipt, or display token.
|
|
667
|
+
- **broader evidence ledger.** Deterministic indexing, lossless projection,
|
|
668
|
+
checkpoint produce/store/recover, and tested multihost reconciliation remain
|
|
669
|
+
planned beyond the bounded private store.
|
|
658
670
|
- **offline multihost evidence.** Record run evidence on each host while
|
|
659
671
|
disconnected and reconcile it deterministically on reconnection, with no
|
|
660
672
|
host as sole authority.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/evoclock/pi-agentic-driver/blob/main/config/codebase-inventory.schema.json",
|
|
4
|
+
"title": "Codebase inventory file record v1",
|
|
5
|
+
"description": "One compact JSON object per tracked code file; JSONL lines sorted by repository-relative path. Static extraction is best-effort; this is a search aid, not an authorization source.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["path", "subdir", "filename", "blob_sha", "loc", "size_bytes", "language", "functions", "classes", "dependencies", "exported_symbols"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"path": {"type": "string", "minLength": 1, "description": "Repository-relative POSIX path"},
|
|
11
|
+
"subdir": {"type": "string"},
|
|
12
|
+
"filename": {"type": "string", "minLength": 1},
|
|
13
|
+
"blob_sha": {"type": "string", "pattern": "^[a-f0-9]{40}$", "description": "Git SHA-1 blob ID for the source bytes, even in SHA-256 repositories this v1 field denotes the SHA-1 blob computation"},
|
|
14
|
+
"loc": {"type": "integer", "minimum": 0},
|
|
15
|
+
"size_bytes": {"type": "integer", "minimum": 0},
|
|
16
|
+
"language": {"enum": ["python", "shell", "javascript", "typescript"]},
|
|
17
|
+
"functions": {"type": "array", "items": {"$ref": "#/$defs/named"}, "uniqueItems": true},
|
|
18
|
+
"classes": {"type": "array", "items": {"$ref": "#/$defs/named"}, "uniqueItems": true},
|
|
19
|
+
"dependencies": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
|
20
|
+
"exported_symbols": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}
|
|
21
|
+
},
|
|
22
|
+
"$defs": {"named": {"type": "object", "required": ["name"], "additionalProperties": false, "properties": {"name": {"type": "string", "minLength": 1}}}}
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Codebase inventory
|
|
2
|
+
|
|
3
|
+
`pipeline_output/codebase_inventory.jsonl` is a searchable list of tracked code files. Before adding a module or function, search it for a filename or `"name":"symbol"`, then read any match. It is a discovery aid, not evidence that code is correct or safe.
|
|
4
|
+
|
|
5
|
+
## Provenance and schema
|
|
6
|
+
|
|
7
|
+
The earlier Claude Code `cc_pre_commit_verify_hook.py` in `agent-infra` called `pre_commit_verify.py`, which called `codebase_inventory_gate.py` and refreshed the inventory on commit. The original Python/shell-only generator missed JavaScript repositories. A later `agentic-driver/scripts/codebase_inventory.py` added JS/TS recognition and richer per-file records. Among inspected existing inventories, `local-assist-model` has the most records (1,524 files), while `agentic-driver` has the most complete *fields* per record (path, filename, blob hash, lines, bytes, language, functions, classes, dependencies, exports). This repository's documented v1 subset follows the latter format and is defined in `config/codebase-inventory.schema.json`. The compact line layout intentionally supports exact `grep '"name":"symbol"'` lookups. Static JS/TS symbol detection is approximate and never executes source; Python uses `ast`, shell uses bounded patterns. No function bodies, secret-bearing values, or docstrings are copied into the inventory.
|
|
8
|
+
|
|
9
|
+
Every JSONL line is a file record sorted by repository-relative path. Its `blob_sha` is the SHA-1 Git blob ID of the working-tree file, allowing drift checks. Only Git-indexed Python, shell, JS, JSX, TS and TSX files (including `.mjs`, `.cjs`, `.mts` and Python/shell shebang scripts) are scanned. Generated/untracked files are not scanned. Empty symbol lists do not mean that a file is inert; dynamic exports can evade static patterns.
|
|
10
|
+
|
|
11
|
+
## Refresh and commit gate
|
|
12
|
+
|
|
13
|
+
Run `python3 scripts/codebase_inventory.py` to regenerate the inventory. Review and stage both the code and `pipeline_output/codebase_inventory.jsonl`. `python3 scripts/codebase_inventory.py --check` compares without writing. `python3 scripts/codebase_inventory_gate.py` refreshes and blocks if inventoried source has unstaged edits or if the regenerated bytes differ from the staged inventory. It does not stage changes for you.
|
|
14
|
+
|
|
15
|
+
The repository ships an **opt-in Git pre-commit hook** at `.githooks/pre-commit` (no `cc_` prefix). If this repo has no existing hooks path, enable it with `git config --local core.hooksPath .githooks` and make the hook executable. **Do not replace an existing hook chain:** call `python3 scripts/codebase_inventory_gate.py` from that chain instead. Hook configuration is local and is not changed automatically by this package. No hook can intercept commits made with Git hooks disabled; this is a workflow freshness check, not a security boundary. The gate does not install dependencies or run code from inventoried files.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Recovery evidence: store contract (Phase 2, first slice)
|
|
2
|
+
|
|
3
|
+
The driver owns the NUDGE ledger and its evidence format. A host supplies a canonical, owner-private local directory: Vogelkop will choose its app-data root and provide a review UI; the Pi host will supply its own trusted root. Neither host may replace the driver's evidence checks or treat a digest as dispatch authority. No default path is selected, and no current journey calls this module.
|
|
4
|
+
|
|
5
|
+
`scripts/enforcement/recovery_evidence_store_pi.js` stores exact, schema-validated NUDGE ledger entries in a dedicated SQLite file. The caller MUST persist a `requested` entry **before** attempting a nudge or replacement. The caller later records one terminal result; the store retains both immutable phases, links the terminal to the requested digest, and checks the SHA-256 hash, byte count, entry identity, phase, and predecessor when reading. A reference contains `{schema, journeyId, entryId, phase, sha256, bytes}`. It is a retrieval and integrity pointer, **not** proof of authorization, tamper-proof authentication, or evidence that an action was delivered. The owner-private file contains exact message text; it must not be sent unredacted to Jev or printed in a public report.
|
|
6
|
+
|
|
7
|
+
The owner-provided root must be absolute, canonical, non-symlinked, and private (mode 0700). The DB file must be regular, non-symlinked, and private (0600). Read-only open never creates storage. Writes serialize in SQLite transactions and fail on duplicates, invalid entries, per-record size, total evidence-byte quota, or record-count quota. No automatic eviction or deletion API exists; a full store blocks **new recovery actions** until the owner reviews capacity. A failed terminal write **after** an attempted action means the caller must retain its unknown-delivery identity lock and must not resend or replace blindly. This module does not implement that journey lock.
|
|
8
|
+
|
|
9
|
+
There is deliberately no retention timer yet. A host review/acknowledgment and retention policy must be specified before any deletion feature. A lost store cannot be reconstructed from its hash. An attacker able to rewrite both DB contents and hashes can forge non-authorizing evidence; filesystem permissions are the local privacy boundary, not a substitute for a signed audit service. The current 32 KiB journey report, its truncation behavior, Jev selection, Vogelkop UI, and N1 Herdr transport remain unchanged. Before journey wiring, add host-provided root and read-access control, receipt pointers with mandatory pinned fields, crash/ambiguous-delivery tests, and a verified owner review route.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import { createTaskStore } from "./task-store-adapter.ts";
|
|
5
|
+
|
|
6
|
+
async function herdrDispatchCore(pi) {
|
|
5
7
|
const dispatch = await import(new URL("../scripts/enforcement/herdr_async_dispatch_pi.js", import.meta.url).href);
|
|
6
8
|
const lifecycle = await import(new URL("../scripts/enforcement/herdr_lifecycle_pi.js", import.meta.url).href);
|
|
7
9
|
|
|
@@ -23,5 +25,16 @@ export default async function herdrDispatchPi(pi) {
|
|
|
23
25
|
signal,
|
|
24
26
|
);
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
// A fresh read-only session view per journey; registration remains keyed
|
|
29
|
+
// on the real Pi API identity for the enforcement module's dedupe guard.
|
|
30
|
+
return dispatch.registerWorkerDispatchInterface(pi, { spawnReplacement, taskStoreFactory: createTaskStore });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Async transport seam (vogelkop phase #47): submit returns a versioned,
|
|
34
|
+
// non-authorizing receipt immediately; poll/observe/read are separate
|
|
35
|
+
// single-attempt operations. No retries, no resends, no authority.
|
|
36
|
+
export default async function herdrDispatchPi(pi) {
|
|
37
|
+
await herdrDispatchCore(pi);
|
|
38
|
+
const asyncSeam = await import(new URL("../scripts/enforcement/herdr_async_seam_pi.js", import.meta.url).href);
|
|
39
|
+
return asyncSeam.registerAsyncDispatchInterface(pi);
|
|
27
40
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
|
|
4
|
+
// Thin extension wrapper for the owner-only recovery-evidence review command.
|
|
5
|
+
// Deliberately registers no model-callable tool: the full evidence surface is
|
|
6
|
+
// the interactive native TUI overlay only. The registration is async (the
|
|
7
|
+
// trusted root is resolved and snapshotted); Pi awaits an asynchronous
|
|
8
|
+
// default factory before startup continues (extensions.md), so no return
|
|
9
|
+
// value or extra await is needed here.
|
|
10
|
+
import { registerRecoveryEvidenceReview } from "../scripts/enforcement/recovery_evidence_review_pi.js";
|
|
11
|
+
|
|
12
|
+
export default async function registerRecoveryEvidenceReviewExtension(pi: any) {
|
|
13
|
+
await registerRecoveryEvidenceReview(pi);
|
|
14
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
TASK_STATE_ENTRY, resolveTaskListId, tasksMirrorPath, loadSnapshotFile, validateSnapshot,
|
|
6
|
+
sessionTaskDisplayId, PROVENANCE_CAPTURE_STATUSES,
|
|
7
|
+
} from "../scripts/enforcement/session_tasks_core_pi.js";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
|
|
10
|
+
// Match TaskList's branch-first/high-water-mark observation in tasks.ts.
|
|
11
|
+
// The session mirror is a fallback; neither reader changes either source.
|
|
12
|
+
function observeSessionTasks(ctx) {
|
|
13
|
+
const id = resolveTaskListId({ session: ctx?.sessionManager?.getSessionId?.() ?? null });
|
|
14
|
+
let branch = null;
|
|
15
|
+
try {
|
|
16
|
+
const entries = ctx?.sessionManager?.getBranch?.() ?? [];
|
|
17
|
+
const snapshots = entries.filter((entry) => entry?.type === "custom" && entry?.customType === TASK_STATE_ENTRY);
|
|
18
|
+
const candidate = snapshots.at(-1)?.data;
|
|
19
|
+
if (validateSnapshot(candidate)) branch = candidate;
|
|
20
|
+
} catch { /* disk mirror remains available */ }
|
|
21
|
+
const disk = id ? loadSnapshotFile(tasksMirrorPath(id)) : null;
|
|
22
|
+
const fromDisk = (Number(disk?.highWaterMark) || 0) > (Number(branch?.highWaterMark) || 0);
|
|
23
|
+
const snapshot = fromDisk ? disk : branch ?? disk;
|
|
24
|
+
return { tasks: snapshot?.tasks ?? [], taskListId: id, source: fromDisk || (!branch && disk) ? "disk" : branch ? "branch" : "none" };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function sessionTasks(ctx) {
|
|
28
|
+
return observeSessionTasks(ctx).tasks;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Audit evidence only. A task-list environment override may select a different
|
|
32
|
+
// mirror; never attribute that mirror to the host session identity.
|
|
33
|
+
export function captureSessionTaskProvenance(ctx) {
|
|
34
|
+
const hostSessionId = ctx?.sessionManager?.getSessionId?.();
|
|
35
|
+
const observation = observeSessionTasks(ctx);
|
|
36
|
+
const source = { hostSessionId: typeof hostSessionId === "string" && hostSessionId.trim() ? hostSessionId : null,
|
|
37
|
+
taskListId: observation.taskListId, kind: observation.source };
|
|
38
|
+
if (!source.hostSessionId) return { status: "host-session-unavailable", source, tasks: [] };
|
|
39
|
+
if (source.taskListId !== source.hostSessionId) return { status: "task-list-mismatch", source, tasks: [] };
|
|
40
|
+
if (source.kind === "none") return { status: "source-unavailable", source, tasks: [] };
|
|
41
|
+
const capturedAt = new Date().toISOString();
|
|
42
|
+
const tasks = observation.tasks.map(({ id, subject, description }) => {
|
|
43
|
+
const material = { id, title: subject ?? null, description: description ?? null, capturedAt };
|
|
44
|
+
const canonical = JSON.stringify({ capturedAt, description: material.description, id, title: material.title });
|
|
45
|
+
return Object.freeze({ material: Object.freeze(material), digest: createHash("sha256").update(canonical, "utf8").digest("hex") });
|
|
46
|
+
});
|
|
47
|
+
return Object.freeze({ status: "captured", source: Object.freeze(source), tasks: Object.freeze(tasks) });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Model-visible projection: task identity, deterministic digest, and source
|
|
51
|
+
// only. Raw titles and descriptions can carry private planning text or
|
|
52
|
+
// secrets, so they never leave the local capture used for revalidation.
|
|
53
|
+
export function projectSessionTaskProvenance(capture) {
|
|
54
|
+
const rawStatus = capture?.status;
|
|
55
|
+
const status = typeof rawStatus === "string" && PROVENANCE_CAPTURE_STATUSES.includes(rawStatus)
|
|
56
|
+
? rawStatus
|
|
57
|
+
: "source-unavailable";
|
|
58
|
+
const tasks = Array.isArray(capture?.tasks) && status === "captured"
|
|
59
|
+
? capture.tasks.map((entry) => ({ taskDisplayId: sessionTaskDisplayId(entry?.material?.id), digest: typeof entry?.digest === "string" ? entry.digest : null }))
|
|
60
|
+
: [];
|
|
61
|
+
return Object.freeze({
|
|
62
|
+
status,
|
|
63
|
+
source: capture?.source ? Object.freeze({
|
|
64
|
+
hostSessionId: capture.source.hostSessionId ?? null,
|
|
65
|
+
taskListId: capture.source.taskListId ?? null,
|
|
66
|
+
kind: capture.source.kind ?? "none",
|
|
67
|
+
}) : null,
|
|
68
|
+
tasks: Object.freeze(tasks),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function revalidateSessionTaskProvenance(ctx, capture, taskId) {
|
|
73
|
+
if (capture?.status !== "captured") return { status: "capture-unavailable" };
|
|
74
|
+
const current = captureSessionTaskProvenance(ctx);
|
|
75
|
+
if (current.status !== "captured") return { status: current.status };
|
|
76
|
+
if (current.source.hostSessionId !== capture.source.hostSessionId || current.source.taskListId !== capture.source.taskListId
|
|
77
|
+
|| current.source.kind !== capture.source.kind)
|
|
78
|
+
return { status: "identity-mismatch" };
|
|
79
|
+
const original = capture.tasks.find((entry) => entry.material.id === taskId);
|
|
80
|
+
const observed = current.tasks.find((entry) => entry.material.id === taskId);
|
|
81
|
+
if (!original || !observed) return { status: "task-missing" };
|
|
82
|
+
return { status: original.material.title === observed.material.title
|
|
83
|
+
&& original.material.description === observed.material.description ? "matched" : "content-drift" };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// One instance per journey: observeAdvance only skips an already-dispatched
|
|
87
|
+
// head in this journey. It never updates TaskList or persists a completion.
|
|
88
|
+
export function createTaskStore(ctx) {
|
|
89
|
+
const skipped = new Set();
|
|
90
|
+
return {
|
|
91
|
+
list() {
|
|
92
|
+
return sessionTasks(ctx).filter((task) => !skipped.has(task.id)).map((task) => ({
|
|
93
|
+
id: task.id, status: task.status, blockedBy: [...(task.blockedBy ?? [])], owner: task.owner ?? null,
|
|
94
|
+
}));
|
|
95
|
+
},
|
|
96
|
+
observeAdvance(id) { skipped.add(id); },
|
|
97
|
+
captureProvenance() { return captureSessionTaskProvenance(ctx); },
|
|
98
|
+
projectProvenance(capture) { return projectSessionTaskProvenance(capture); },
|
|
99
|
+
revalidateProvenance(capture, id) { return revalidateSessionTaskProvenance(ctx, capture, id); },
|
|
100
|
+
};
|
|
101
|
+
}
|
package/extensions/tasks.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
buildPromotionPreview, buildBatchPromotionPreview,
|
|
27
27
|
reconcileDependencies, applyDependencyEdges,
|
|
28
28
|
promotionIdempotencyKey, forwardLinkFor, findExistingPromotedCard,
|
|
29
|
-
resolveCallerOrigin, canPromote, hasTasksCapability,
|
|
29
|
+
resolveCallerOrigin, canPromote, hasTasksCapability, SESSION_TASK_PRIORITIES,
|
|
30
30
|
} from "../scripts/enforcement/session_tasks_core_pi.js";
|
|
31
31
|
import { writeCard, updateCard, parseBoard } from "../scripts/enforcement/task_board_core_pi.js";
|
|
32
32
|
|
|
@@ -69,6 +69,15 @@ export default async function tasksPi(pi) {
|
|
|
69
69
|
const TASK_ICONS = Object.freeze({ pending: "▫", in_progress: "▪", completed: "✓" });
|
|
70
70
|
|
|
71
71
|
const isVisible = (task) => task?.metadata?._internal !== true;
|
|
72
|
+
// Presentation only: priority never grants board claim or dispatch authority.
|
|
73
|
+
const orderedVisibleTasks = () => tasks.filter(isVisible).sort((a, b) => {
|
|
74
|
+
if (a.status === "completed" && b.status !== "completed") return 1;
|
|
75
|
+
if (b.status === "completed" && a.status !== "completed") return -1;
|
|
76
|
+
const rankA = SESSION_TASK_PRIORITIES.indexOf(a.priority);
|
|
77
|
+
const rankB = SESSION_TASK_PRIORITIES.indexOf(b.priority);
|
|
78
|
+
if (rankA !== rankB) return (rankA < 0 ? 4 : rankA) - (rankB < 0 ? 4 : rankB);
|
|
79
|
+
return Number(a.id) - Number(b.id) || a.id.localeCompare(b.id);
|
|
80
|
+
});
|
|
72
81
|
const unresolvedTaskIds = () => new Set(
|
|
73
82
|
tasks.filter((task) => task.status !== "completed").map((task) => task.id),
|
|
74
83
|
);
|
|
@@ -77,6 +86,7 @@ export default async function tasksPi(pi) {
|
|
|
77
86
|
const parts = [
|
|
78
87
|
`${TASK_ICONS[task.status] ?? "?"} #${task.id}`,
|
|
79
88
|
`[${task.status}]`,
|
|
89
|
+
...(task.priority ? [`[${task.priority}]`] : []),
|
|
80
90
|
task.subject,
|
|
81
91
|
];
|
|
82
92
|
if (task.owner) parts.push(`(${task.owner})`);
|
|
@@ -88,7 +98,7 @@ export default async function tasksPi(pi) {
|
|
|
88
98
|
};
|
|
89
99
|
|
|
90
100
|
const renderTaskListLineForLLM = (task) => {
|
|
91
|
-
const parts = [`#${task.id}`, `[${task.status}]`, task.subject];
|
|
101
|
+
const parts = [`#${task.id}`, `[${task.status}]`, ...(task.priority ? [`[${task.priority}]`] : []), task.subject];
|
|
92
102
|
if (task.owner) parts.push(`(${task.owner})`);
|
|
93
103
|
let line = parts.join(" ");
|
|
94
104
|
const unresolved = unresolvedTaskIds();
|
|
@@ -109,6 +119,7 @@ export default async function tasksPi(pi) {
|
|
|
109
119
|
const parts = [
|
|
110
120
|
TASK_ICONS[task.status] ?? "?",
|
|
111
121
|
`[${task.status}]`,
|
|
122
|
+
...(task.priority ? [`[${task.priority}]`] : []),
|
|
112
123
|
task.subject,
|
|
113
124
|
];
|
|
114
125
|
if (task.owner) parts.push(`(${task.owner})`);
|
|
@@ -136,7 +147,7 @@ export default async function tasksPi(pi) {
|
|
|
136
147
|
return;
|
|
137
148
|
}
|
|
138
149
|
|
|
139
|
-
const visible =
|
|
150
|
+
const visible = orderedVisibleTasks();
|
|
140
151
|
try {
|
|
141
152
|
if (typeof uiHost.ui.setWidget === "function") {
|
|
142
153
|
if (visible.length === 0) {
|
|
@@ -150,9 +161,7 @@ export default async function tasksPi(pi) {
|
|
|
150
161
|
const header = `Tasks ${counts.pending} pending · ${counts.in_progress} in progress · ${counts.completed} done`;
|
|
151
162
|
const unresolved = unresolvedTaskIds();
|
|
152
163
|
const theme = uiHost.ui.theme;
|
|
153
|
-
const rows =
|
|
154
|
-
.sort((a, b) => Number(a.id) - Number(b.id) || a.id.localeCompare(b.id))
|
|
155
|
-
.map((task) => renderWidgetTaskLine(theme, task, unresolved));
|
|
164
|
+
const rows = visible.map((task) => renderWidgetTaskLine(theme, task, unresolved));
|
|
156
165
|
uiHost.ui.setWidget(WIDGET_KEY, [themed(theme, "fg", ["dim", header], header), ...rows], { placement: "aboveEditor" });
|
|
157
166
|
}
|
|
158
167
|
}
|
|
@@ -239,10 +248,14 @@ export default async function tasksPi(pi) {
|
|
|
239
248
|
description: { type: "string", description: "What needs to be done" },
|
|
240
249
|
activeForm: { type: "string", description: "Present continuous form shown in spinner when in_progress (e.g., \"Running tests\")" },
|
|
241
250
|
metadata: { type: "object", description: "Arbitrary metadata to attach to the task", additionalProperties: true, properties: {} },
|
|
251
|
+
priority: { type: "string", enum: ["P0", "P1", "P2", "P3"], description: "Optional session-list display priority; does not affect board dispatch." },
|
|
242
252
|
},
|
|
243
253
|
required: ["subject", "description"],
|
|
244
254
|
},
|
|
245
255
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
256
|
+
if (params.priority !== undefined && !SESSION_TASK_PRIORITIES.includes(params.priority)) {
|
|
257
|
+
return failResult("invalid-priority", "priority must be P0, P1, P2, or P3");
|
|
258
|
+
}
|
|
246
259
|
highWaterMark += 1;
|
|
247
260
|
let task = {
|
|
248
261
|
id: String(highWaterMark),
|
|
@@ -253,6 +266,7 @@ export default async function tasksPi(pi) {
|
|
|
253
266
|
blockedBy: [],
|
|
254
267
|
...(params.activeForm !== undefined ? { activeForm: params.activeForm } : {}),
|
|
255
268
|
...(params.metadata !== undefined ? { metadata: params.metadata } : {}),
|
|
269
|
+
...(params.priority !== undefined ? { priority: params.priority } : {}),
|
|
256
270
|
};
|
|
257
271
|
task = stampOrigin(task);
|
|
258
272
|
tasks.push(task);
|
|
@@ -273,7 +287,7 @@ export default async function tasksPi(pi) {
|
|
|
273
287
|
async execute(_toolCallId, params) {
|
|
274
288
|
const task = findTask(params.taskId);
|
|
275
289
|
if (!task) return { content: [{ type: "text", text: "Task not found" }], details: { task: null } };
|
|
276
|
-
const lines = [`Task #${task.id}: ${task.subject}`, `Status: ${task.status}`, `Description: ${task.description}`];
|
|
290
|
+
const lines = [`Task #${task.id}: ${task.subject}`, `Status: ${task.status}`, ...(task.priority ? [`Priority: ${task.priority}`] : []), `Description: ${task.description}`];
|
|
277
291
|
if (task.blockedBy.length > 0) lines.push(`Blocked by: ${task.blockedBy.map((id) => `#${id}`).join(", ")}`);
|
|
278
292
|
if (task.blocks.length > 0) lines.push(`Blocks: ${task.blocks.map((id) => `#${id}`).join(", ")}`);
|
|
279
293
|
// picc-tasks v0.2.0 TaskGet details carry the restricted task shape
|
|
@@ -299,16 +313,17 @@ export default async function tasksPi(pi) {
|
|
|
299
313
|
pi.registerTool({
|
|
300
314
|
name: "TaskList",
|
|
301
315
|
label: "TaskList",
|
|
302
|
-
description: "Use this tool to list all tasks in the task list.\n\n## When to Use This Tool\n\n- To see what tasks are available to work on (status: 'pending', no owner, not blocked)\n- To check overall progress on the project\n- To find tasks that are blocked and need dependencies resolved\n- After completing a task, to check for newly unblocked work or claim the next available task\n-
|
|
316
|
+
description: "Use this tool to list all tasks in the task list.\n\n## When to Use This Tool\n\n- To see what tasks are available to work on (status: 'pending', no owner, not blocked)\n- To check overall progress on the project\n- To find tasks that are blocked and need dependencies resolved\n- After completing a task, to check for newly unblocked work or claim the next available task\n- Choose unblocked work by explicit P0–P3 priority, then stable ID order. Unranked tasks follow ranked tasks; completed tasks display last. Session priority never grants board or dispatch authority.\n\n## Output\n\nReturns a summary of each task:\n- **id**: Task identifier (use with TaskGet, TaskUpdate)\n- **subject**: Brief description of the task\n- **status**: 'pending', 'in_progress', or 'completed'\n- **priority**: P0–P3 when set; absent means unranked\n- **owner**: Agent ID if assigned, empty if available\n- **blockedBy**: List of open task IDs that must be resolved first (tasks with blockedBy cannot be claimed until dependencies resolve)\n\nUse TaskGet with a specific task ID to view full details including description and comments.",
|
|
303
317
|
parameters: { type: "object", additionalProperties: false, properties: {} },
|
|
304
318
|
async execute() {
|
|
305
|
-
const visible =
|
|
319
|
+
const visible = orderedVisibleTasks();
|
|
306
320
|
if (visible.length === 0) return { content: [{ type: "text", text: "No tasks found" }], details: { tasks: [] } };
|
|
307
321
|
const unresolved = unresolvedTaskIds();
|
|
308
322
|
const list = visible.map((t) => ({
|
|
309
323
|
id: t.id,
|
|
310
324
|
subject: t.subject,
|
|
311
325
|
status: t.status,
|
|
326
|
+
...(t.priority !== undefined ? { priority: t.priority } : {}),
|
|
312
327
|
...(t.owner !== undefined ? { owner: t.owner } : {}),
|
|
313
328
|
blockedBy: t.blockedBy.filter((id) => unresolved.has(id)),
|
|
314
329
|
}));
|
|
@@ -321,7 +336,7 @@ export default async function tasksPi(pi) {
|
|
|
321
336
|
pi.registerTool({
|
|
322
337
|
name: "TaskUpdate",
|
|
323
338
|
label: "TaskUpdate",
|
|
324
|
-
description: "Use this tool to update a task in the task list.\n\n## When to Use This Tool\n\n**Mark tasks as resolved:**\n- When you have completed the work described in a task\n- When a task is no longer needed or has been superseded\n- IMPORTANT: Always mark your assigned tasks as resolved when you finish them\n- After resolving, call TaskList to find your next task\n\n- ONLY mark a task as completed when you have FULLY accomplished it\n- If you encounter errors, blockers, or cannot finish, keep the task as in_progress\n- When blocked, create a new task describing what needs to be resolved\n- Never mark a task as completed if:\n - Tests are failing\n - Implementation is partial\n - You encountered unresolved errors\n - You couldn't find necessary files or dependencies\n\n**Delete tasks:**\n- When a task is no longer relevant or was created in error\n- Setting status to `deleted` permanently removes the task\n\n**Update task details:**\n- When requirements change or become clearer\n- When establishing dependencies between tasks\n\n## Fields You Can Update\n\n- **status**: The task status (see Status Workflow below)\n- **subject**: Change the task title (imperative form, e.g., \"Run tests\")\n- **description**: Change the task description\n- **activeForm**: Present continuous form shown in spinner when in_progress (e.g., \"Running tests\")\n- **owner**: Change the task owner (agent name)\n- **metadata**: Merge metadata keys into the task (set a key to null to delete it)\n- **addBlocks**: Mark tasks that cannot start until this one completes\n- **addBlockedBy**: Mark tasks that must complete before this one can start\n\n## Status Workflow\n\nStatus progresses: `pending` → `in_progress` → `completed`\n\nUse `deleted` to permanently remove a task.\n\n## Staleness\n\nMake sure to read a task's latest state using `TaskGet` before updating it.\n\n## Examples\n\nMark task as in progress when starting work:\n```json\n{\"taskId\": \"1\", \"status\": \"in_progress\"}\n```\n\nMark task as completed after finishing work:\n```json\n{\"taskId\": \"1\", \"status\": \"completed\"}\n```\n\nDelete a task:\n```json\n{\"taskId\": \"1\", \"status\": \"deleted\"}\n```\n\nClaim a task by setting owner:\n```json\n{\"taskId\": \"1\", \"owner\": \"my-name\"}\n```\n\nSet up task dependencies:\n```json\n{\"taskId\": \"2\", \"addBlockedBy\": [\"1\"]}\n```",
|
|
339
|
+
description: "Use this tool to update a task in the task list.\n\n## When to Use This Tool\n\n**Mark tasks as resolved:**\n- When you have completed the work described in a task\n- When a task is no longer needed or has been superseded\n- IMPORTANT: Always mark your assigned tasks as resolved when you finish them\n- After resolving, call TaskList to find your next task\n\n- ONLY mark a task as completed when you have FULLY accomplished it\n- If you encounter errors, blockers, or cannot finish, keep the task as in_progress\n- When blocked, create a new task describing what needs to be resolved\n- Never mark a task as completed if:\n - Tests are failing\n - Implementation is partial\n - You encountered unresolved errors\n - You couldn't find necessary files or dependencies\n\n**Delete tasks:**\n- When a task is no longer relevant or was created in error\n- Setting status to `deleted` permanently removes the task\n\n**Update task details:**\n- When requirements change or become clearer\n- When establishing dependencies between tasks\n\n## Fields You Can Update\n\n- **status**: The task status (see Status Workflow below)\n- **subject**: Change the task title (imperative form, e.g., \"Run tests\")\n- **description**: Change the task description\n- **activeForm**: Present continuous form shown in spinner when in_progress (e.g., \"Running tests\")\n- **owner**: Change the task owner (agent name)\n- **priority**: Set P0–P3, or null to clear session-list priority\n- **metadata**: Merge metadata keys into the task (set a key to null to delete it)\n- **addBlocks**: Mark tasks that cannot start until this one completes\n- **addBlockedBy**: Mark tasks that must complete before this one can start\n\n## Status Workflow\n\nStatus progresses: `pending` → `in_progress` → `completed`\n\nUse `deleted` to permanently remove a task.\n\n## Staleness\n\nMake sure to read a task's latest state using `TaskGet` before updating it.\n\n## Examples\n\nMark task as in progress when starting work:\n```json\n{\"taskId\": \"1\", \"status\": \"in_progress\"}\n```\n\nMark task as completed after finishing work:\n```json\n{\"taskId\": \"1\", \"status\": \"completed\"}\n```\n\nDelete a task:\n```json\n{\"taskId\": \"1\", \"status\": \"deleted\"}\n```\n\nClaim a task by setting owner:\n```json\n{\"taskId\": \"1\", \"owner\": \"my-name\"}\n```\n\nSet up task dependencies:\n```json\n{\"taskId\": \"2\", \"addBlockedBy\": [\"1\"]}\n```",
|
|
325
340
|
parameters: {
|
|
326
341
|
type: "object",
|
|
327
342
|
additionalProperties: false,
|
|
@@ -331,6 +346,7 @@ export default async function tasksPi(pi) {
|
|
|
331
346
|
description: { type: "string", description: "New description for the task" },
|
|
332
347
|
activeForm: { type: "string", description: "Present continuous form shown in spinner when in_progress (e.g., \"Running tests\")" },
|
|
333
348
|
owner: { type: "string", description: "New owner for the task" },
|
|
349
|
+
priority: { type: ["string", "null"], enum: ["P0", "P1", "P2", "P3", null], description: "Set P0–P3 display priority, or null to clear. No board authority." },
|
|
334
350
|
metadata: { type: "object", description: "Metadata keys to merge into the task. Set a key to null to delete it.", additionalProperties: true, properties: {} },
|
|
335
351
|
status: { type: "string", enum: ["pending", "in_progress", "completed", "deleted"], description: "New status for the task" },
|
|
336
352
|
addBlocks: { type: "array", items: { type: "string" }, description: "Task IDs that this task blocks" },
|
|
@@ -344,12 +360,19 @@ export default async function tasksPi(pi) {
|
|
|
344
360
|
return { content: [{ type: "text", text: "Task not found" }], details: { success: false, taskId: params.taskId, error: "Task not found", updatedFields: [] } };
|
|
345
361
|
}
|
|
346
362
|
const task = tasks[idx];
|
|
363
|
+
if (params.priority !== undefined && params.priority !== null && !SESSION_TASK_PRIORITIES.includes(params.priority)) {
|
|
364
|
+
return failResult("invalid-priority", "priority must be P0, P1, P2, P3, or null to clear");
|
|
365
|
+
}
|
|
347
366
|
const updatedFields = [];
|
|
348
367
|
let statusChange;
|
|
349
368
|
if (params.subject !== undefined && params.subject !== task.subject) { task.subject = params.subject; updatedFields.push("subject"); }
|
|
350
369
|
if (params.description !== undefined && params.description !== task.description) { task.description = params.description; updatedFields.push("description"); }
|
|
351
370
|
if (params.activeForm !== undefined && params.activeForm !== task.activeForm) { task.activeForm = params.activeForm; updatedFields.push("activeForm"); }
|
|
352
371
|
if (params.owner !== undefined && params.owner !== task.owner) { task.owner = params.owner; updatedFields.push("owner"); }
|
|
372
|
+
if (params.priority !== undefined && params.priority !== task.priority) {
|
|
373
|
+
if (params.priority === null) delete task.priority; else task.priority = params.priority;
|
|
374
|
+
updatedFields.push("priority");
|
|
375
|
+
}
|
|
353
376
|
if (params.metadata !== undefined) {
|
|
354
377
|
const merged = { ...(task.metadata ?? {}) };
|
|
355
378
|
for (const [k, v] of Object.entries(params.metadata)) {
|
|
@@ -692,7 +715,7 @@ export default async function tasksPi(pi) {
|
|
|
692
715
|
return;
|
|
693
716
|
}
|
|
694
717
|
const blocks = [];
|
|
695
|
-
for (const task of tasks) {
|
|
718
|
+
for (const task of [...orderedVisibleTasks(), ...tasks.filter((task) => !isVisible(task))]) {
|
|
696
719
|
const internal = task.metadata?._internal === true ? " [internal]" : "";
|
|
697
720
|
blocks.push(`${renderTaskListLine(task)}${internal}`);
|
|
698
721
|
blocks.push(` ${task.description}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evoclock/pi-agentic-driver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
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",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"extensions/code-phage.js",
|
|
43
43
|
"extensions/herdr-communication.ts",
|
|
44
44
|
"extensions/herdr-dispatch.ts",
|
|
45
|
+
"extensions/task-store-adapter.ts",
|
|
45
46
|
"extensions/task-board.ts",
|
|
46
47
|
"extensions/pulse.ts",
|
|
47
48
|
"lib/adapters/diff-scope.mjs",
|
|
@@ -54,6 +55,18 @@
|
|
|
54
55
|
"lib/typescript_ast_metrics.mjs",
|
|
55
56
|
"scripts/enforcement/herdr_communication_pi.js",
|
|
56
57
|
"scripts/enforcement/herdr_async_dispatch_pi.js",
|
|
58
|
+
"scripts/enforcement/herdr_async_seam_pi.js",
|
|
59
|
+
"scripts/enforcement/board_claim_bridge_pi.js",
|
|
60
|
+
"scripts/enforcement/nudge_classifier_pi.js",
|
|
61
|
+
"scripts/enforcement/nudge_config_pi.js",
|
|
62
|
+
"scripts/enforcement/nudge_ledger_pi.js",
|
|
63
|
+
"scripts/enforcement/recovery_evidence_store_pi.js",
|
|
64
|
+
"extensions/recovery-evidence.ts",
|
|
65
|
+
"scripts/enforcement/recovery_evidence_review_pi.js",
|
|
66
|
+
"docs/recovery-evidence-store.md",
|
|
67
|
+
"scripts/enforcement/nudge_reason_state_pi.js",
|
|
68
|
+
"scripts/enforcement/nudge_validate_pi.js",
|
|
69
|
+
".agentic-driver/nudge.defaults.json",
|
|
57
70
|
"README.md",
|
|
58
71
|
"LICENSE",
|
|
59
72
|
"extensions/herdr-lifecycle.ts",
|
|
@@ -69,6 +82,11 @@
|
|
|
69
82
|
"PROVENANCE.md",
|
|
70
83
|
"extensions/aidr.ts",
|
|
71
84
|
"scripts/aidr_writing_review.js",
|
|
85
|
+
"scripts/codebase_inventory.py",
|
|
86
|
+
"scripts/codebase_inventory_gate.py",
|
|
87
|
+
"config/codebase-inventory.schema.json",
|
|
88
|
+
"docs/codebase-inventory.md",
|
|
89
|
+
".githooks/pre-commit",
|
|
72
90
|
"templates/AGENTS.md",
|
|
73
91
|
"scripts/enforcement/task_board_core_pi.js",
|
|
74
92
|
"scripts/enforcement/pulse_core_pi.js",
|