@pennixrv/trellis 0.7.1-beta.10 → 0.7.1-beta.12
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/dist/commands/teamwork/kill.d.ts +2 -0
- package/dist/commands/teamwork/kill.d.ts.map +1 -1
- package/dist/commands/teamwork/kill.js +79 -38
- package/dist/commands/teamwork/kill.js.map +1 -1
- package/dist/commands/teamwork/messages.d.ts.map +1 -1
- package/dist/commands/teamwork/messages.js +3 -0
- package/dist/commands/teamwork/messages.js.map +1 -1
- package/dist/commands/teamwork/process-identity.d.ts +3 -0
- package/dist/commands/teamwork/process-identity.d.ts.map +1 -0
- package/dist/commands/teamwork/process-identity.js +24 -0
- package/dist/commands/teamwork/process-identity.js.map +1 -0
- package/dist/commands/teamwork/rm.d.ts.map +1 -1
- package/dist/commands/teamwork/rm.js +57 -64
- package/dist/commands/teamwork/rm.js.map +1 -1
- package/dist/commands/teamwork/run.d.ts.map +1 -1
- package/dist/commands/teamwork/run.js +6 -3
- package/dist/commands/teamwork/run.js.map +1 -1
- package/dist/commands/teamwork/supervisor/stdout.d.ts.map +1 -1
- package/dist/commands/teamwork/supervisor/stdout.js +1 -2
- package/dist/commands/teamwork/supervisor/stdout.js.map +1 -1
- package/dist/commands/teamwork/supervisor/warning.d.ts.map +1 -1
- package/dist/commands/teamwork/supervisor/warning.js +2 -3
- package/dist/commands/teamwork/supervisor/warning.js.map +1 -1
- package/dist/commands/teamwork/supervisor.d.ts.map +1 -1
- package/dist/commands/teamwork/supervisor.js +18 -12
- package/dist/commands/teamwork/supervisor.js.map +1 -1
- package/dist/migrations/manifests/0.7.1-beta.11.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.12.json +9 -0
- package/dist/migrations/manifests/0.7.1-beta.6.json +1 -1
- package/dist/templates/codex/hooks/session-start.py +14 -11
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +1 -1
- package/dist/templates/copilot/hooks/session-start.py +14 -11
- package/dist/templates/shared-hooks/session-start.py +15 -11
- package/dist/templates/trellis/scripts/common/task_continuity.py +27 -27
- package/package.json +2 -2
|
@@ -313,6 +313,10 @@ def transfer_task_ownership(args: argparse.Namespace) -> int:
|
|
|
313
313
|
root = get_repo_root()
|
|
314
314
|
operation = args.transfer_action
|
|
315
315
|
try:
|
|
316
|
+
package_bound = args.package_id is not None or args.package_digest is not None
|
|
317
|
+
if operation != "status" or package_bound:
|
|
318
|
+
if not isinstance(args.package_id, str) or not PACKAGE_ID_RE.fullmatch(args.package_id) or not isinstance(args.package_digest, str) or not PACKAGE_DIGEST_RE.fullmatch(args.package_digest):
|
|
319
|
+
raise ContinuityError("TASK_TRANSFER_INVALID_INPUT", "session package identity and SHA-256 digest are required")
|
|
316
320
|
task_dir = resolve_task_dir(args.task_path, root)
|
|
317
321
|
if task_dir is None or not is_within_tasks_dir(task_dir, root):
|
|
318
322
|
raise ContinuityError("TASK_TRANSFER_WITHHELD", "task path is unavailable or unsafe")
|
|
@@ -328,8 +332,10 @@ def transfer_task_ownership(args: argparse.Namespace) -> int:
|
|
|
328
332
|
path = _ownership_path(root, task_path)
|
|
329
333
|
with _record_lock(path):
|
|
330
334
|
record = _load_owner(path)
|
|
335
|
+
if record and (record["task_id"] != task_id or record["task_path"] != task_path):
|
|
336
|
+
raise ContinuityError("TASK_TRANSFER_WITHHELD", "ownership record does not match the requested task")
|
|
331
337
|
if operation == "status":
|
|
332
|
-
if record and (record["package_id"] != args.package_id or record["package_digest"] != args.package_digest):
|
|
338
|
+
if record and package_bound and (record["package_id"] != args.package_id or record["package_digest"] != args.package_digest):
|
|
333
339
|
raise ContinuityError("TASK_TRANSFER_STALE_PACKAGE", "session package identity or digest changed")
|
|
334
340
|
result = record or {"status": "absent", "generation": 0, "task_id": task_id}
|
|
335
341
|
if args.include_current:
|
|
@@ -342,8 +348,6 @@ def transfer_task_ownership(args: argparse.Namespace) -> int:
|
|
|
342
348
|
if args.expected_generation is None or args.expected_generation < 0:
|
|
343
349
|
raise ContinuityError("TASK_TRANSFER_INVALID_INPUT", "a non-negative expected generation is required")
|
|
344
350
|
if operation == "quiesce":
|
|
345
|
-
if not PACKAGE_ID_RE.fullmatch(args.package_id) or not PACKAGE_DIGEST_RE.fullmatch(args.package_digest):
|
|
346
|
-
raise ContinuityError("TASK_TRANSFER_INVALID_INPUT", "session package identity or SHA-256 digest is invalid")
|
|
347
351
|
context_key, current_path = _direct_pointer(root, task_path)
|
|
348
352
|
if current_path is None:
|
|
349
353
|
raise ContinuityError("TASK_TRANSFER_WITHHELD", "source session has no current task")
|
|
@@ -360,13 +364,18 @@ def transfer_task_ownership(args: argparse.Namespace) -> int:
|
|
|
360
364
|
result = record
|
|
361
365
|
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
|
|
362
366
|
return 0
|
|
363
|
-
|
|
364
|
-
|
|
367
|
+
if record["status"] != "consumed" or record["generation"] != args.expected_generation:
|
|
368
|
+
raise ContinuityError("TASK_TRANSFER_STALE_GENERATION", "task already has an ownership record at a different state or generation")
|
|
369
|
+
if record["target_session_id"] != context_key:
|
|
370
|
+
raise ContinuityError("TASK_TRANSFER_WITHHELD", "only the consumed target can start another handoff")
|
|
371
|
+
if record["package_id"] == args.package_id:
|
|
372
|
+
raise ContinuityError("TASK_TRANSFER_STALE_PACKAGE", "another handoff requires a new session package")
|
|
373
|
+
elif args.expected_generation != 0:
|
|
365
374
|
raise ContinuityError("TASK_TRANSFER_STALE_GENERATION", "initial ownership generation must be 0")
|
|
366
375
|
record = {
|
|
367
376
|
"schema_version": 1, "task_id": task_id, "task_path": task_path,
|
|
368
377
|
"package_id": args.package_id, "package_digest": args.package_digest,
|
|
369
|
-
"generation": 1, "status": "quiescing", "source_session_id": context_key,
|
|
378
|
+
"generation": args.expected_generation + 1, "status": "quiescing", "source_session_id": context_key,
|
|
370
379
|
"target_session_id": None, "continuity_digest": continuity_digest,
|
|
371
380
|
"route": _route_snapshot(data),
|
|
372
381
|
}
|
|
@@ -385,17 +394,13 @@ def transfer_task_ownership(args: argparse.Namespace) -> int:
|
|
|
385
394
|
if not PACKAGE_DIGEST_RE.fullmatch(args.expected_continuity_digest):
|
|
386
395
|
raise ContinuityError("TASK_TRANSFER_INVALID_INPUT", "expected continuity digest must be a SHA-256 digest")
|
|
387
396
|
if args.expected_continuity_digest != current_digest:
|
|
388
|
-
raise ContinuityError("TASK_TRANSFER_STALE_CONTINUITY", "current task projection differs from the explicitly
|
|
397
|
+
raise ContinuityError("TASK_TRANSFER_STALE_CONTINUITY", "current task projection differs from the explicitly checked snapshot")
|
|
389
398
|
# Recovery acknowledges a live target snapshot without rebasing the source package.
|
|
390
|
-
|
|
399
|
+
recovered_claim = (
|
|
391
400
|
operation == "claim" and record["status"] == "sealed"
|
|
392
401
|
and args.expected_continuity_digest == current_digest
|
|
393
402
|
)
|
|
394
|
-
|
|
395
|
-
operation == "reconcile" and record["status"] == "consumed"
|
|
396
|
-
and args.expected_continuity_digest == current_digest
|
|
397
|
-
)
|
|
398
|
-
if operation != "release" and current_digest != record.get("target_continuity_digest", record["continuity_digest"]) and not (reconciled_claim or reconciled_target):
|
|
403
|
+
if operation != "release" and current_digest != record.get("target_continuity_digest", record["continuity_digest"]) and not recovered_claim:
|
|
399
404
|
raise ContinuityError("TASK_TRANSFER_STALE_CONTINUITY", "task continuity changed during ownership transfer")
|
|
400
405
|
if operation != "release" and _route_snapshot(data) != record["route"]:
|
|
401
406
|
raise ContinuityError("TASK_TRANSFER_STALE_ROUTE", "task phase, route, input, or implementation authorization changed")
|
|
@@ -403,13 +408,10 @@ def transfer_task_ownership(args: argparse.Namespace) -> int:
|
|
|
403
408
|
if operation in {"seal", "retire"}:
|
|
404
409
|
if context_key != record["source_session_id"] or current_path not in {record["task_path"], None}:
|
|
405
410
|
raise ContinuityError("TASK_TRANSFER_WITHHELD", "source session no longer owns the current task")
|
|
406
|
-
elif operation in {"claim", "consume"
|
|
411
|
+
elif operation in {"claim", "consume"}:
|
|
407
412
|
if context_key == record["source_session_id"]:
|
|
408
413
|
raise ContinuityError("TASK_TRANSFER_WITHHELD", "target must be a different direct session")
|
|
409
|
-
if operation == "
|
|
410
|
-
if not reconciled_target or context_key != record["target_session_id"] or current_path != record["task_path"]:
|
|
411
|
-
raise ContinuityError("TASK_TRANSFER_WITHHELD", "only the consumed target may reconcile its explicitly pinned projection")
|
|
412
|
-
elif operation == "release":
|
|
414
|
+
if operation == "release":
|
|
413
415
|
if record["status"] not in {"quiescing", "sealed", "recovery_required"} or record["target_session_id"] is not None:
|
|
414
416
|
raise ContinuityError("TASK_TRANSFER_INVALID_TRANSITION", "ownership can be released only before target claim")
|
|
415
417
|
if context_key != record["source_session_id"] or current_path not in {None, record["task_path"]}:
|
|
@@ -422,9 +424,7 @@ def transfer_task_ownership(args: argparse.Namespace) -> int:
|
|
|
422
424
|
path.unlink(missing_ok=True)
|
|
423
425
|
print(json.dumps({"status": "released", "generation": record["generation"], "task_id": task_id}, ensure_ascii=False, sort_keys=True))
|
|
424
426
|
return 0
|
|
425
|
-
if operation == "
|
|
426
|
-
record.update({"target_continuity_digest": current_digest, "generation": record["generation"] + 1})
|
|
427
|
-
elif operation == "seal" and record["status"] == "quiescing":
|
|
427
|
+
if operation == "seal" and record["status"] == "quiescing":
|
|
428
428
|
record.update({"status": "sealed", "generation": record["generation"] + 1})
|
|
429
429
|
elif operation == "retire" and record["status"] in {"sealed", "retiring", "recovery_required"}:
|
|
430
430
|
if record["status"] == "sealed":
|
|
@@ -440,14 +440,14 @@ def transfer_task_ownership(args: argparse.Namespace) -> int:
|
|
|
440
440
|
write_json(path, record)
|
|
441
441
|
raise ContinuityError("TASK_TRANSFER_RECOVERY_REQUIRED", "source pointer remains after retirement")
|
|
442
442
|
record["status"] = "ready"
|
|
443
|
-
elif operation == "claim" and (record["status"] in {"ready", "claiming", "recovery_required", "claimed"} or
|
|
443
|
+
elif operation == "claim" and (record["status"] in {"ready", "claiming", "recovery_required", "claimed"} or recovered_claim):
|
|
444
444
|
if record["status"] in {"claiming", "recovery_required"} and record["target_session_id"] != context_key:
|
|
445
445
|
raise ContinuityError("TASK_TRANSFER_WITHHELD", "only the recorded target session can recover a claim")
|
|
446
446
|
if current_path not in {None, record["task_path"]}:
|
|
447
447
|
raise ContinuityError("TASK_TRANSFER_WITHHELD", "target session already has a current task")
|
|
448
448
|
if record["status"] in {"ready", "sealed"}:
|
|
449
449
|
record.update({"status": "claiming", "generation": record["generation"] + 1, "target_session_id": context_key})
|
|
450
|
-
if
|
|
450
|
+
if recovered_claim:
|
|
451
451
|
record["target_continuity_digest"] = current_digest
|
|
452
452
|
if not write_json(path, record):
|
|
453
453
|
raise ContinuityError("TASK_TRANSFER_WRITE_FAILED", "could not record target claim")
|
|
@@ -497,12 +497,12 @@ def add_continuity_commands(subparsers) -> None:
|
|
|
497
497
|
capture.add_argument("--expected-source-digest", required=True, help="Current captured digest, or absent")
|
|
498
498
|
|
|
499
499
|
transfer = subparsers.add_parser("transfer", help="Transfer task ownership between direct sessions")
|
|
500
|
-
transfer.add_argument("transfer_action", choices=("status", "quiesce", "seal", "retire", "claim", "consume", "release"
|
|
500
|
+
transfer.add_argument("transfer_action", choices=("status", "quiesce", "seal", "retire", "claim", "consume", "release"))
|
|
501
501
|
transfer.add_argument("--task-id", required=True)
|
|
502
502
|
transfer.add_argument("--task-path", required=True)
|
|
503
|
-
transfer.add_argument("--package-id",
|
|
504
|
-
transfer.add_argument("--package-digest",
|
|
505
|
-
transfer.add_argument("--expected-continuity-digest", help="Expected live projection
|
|
503
|
+
transfer.add_argument("--package-id", help="Required with --package-digest for mutations or package-bound status")
|
|
504
|
+
transfer.add_argument("--package-digest", help="Required with --package-id for mutations or package-bound status")
|
|
505
|
+
transfer.add_argument("--expected-continuity-digest", help="Expected live projection for sealed-source claim recovery")
|
|
506
506
|
transfer.add_argument("--expected-generation", type=int)
|
|
507
507
|
transfer.add_argument("--include-current", action="store_true", help="Include the live task projection and its digest in transfer status")
|
|
508
508
|
transfer.add_argument("--explicit-user-request", action="store_true")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pennixrv/trellis",
|
|
3
|
-
"version": "0.7.1-beta.
|
|
3
|
+
"version": "0.7.1-beta.12",
|
|
4
4
|
"description": "AI capabilities grow like ivy — Trellis provides the structure to guide them along a disciplined path",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"inquirer": "^9.3.7",
|
|
35
35
|
"undici": "^6.21.0",
|
|
36
36
|
"zod": "^4.4.2",
|
|
37
|
-
"@pennixrv/trellis-core": "0.7.1-beta.
|
|
37
|
+
"@pennixrv/trellis-core": "0.7.1-beta.12"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@eslint/js": "^9.18.0",
|