@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.
Files changed (35) hide show
  1. package/dist/commands/teamwork/kill.d.ts +2 -0
  2. package/dist/commands/teamwork/kill.d.ts.map +1 -1
  3. package/dist/commands/teamwork/kill.js +79 -38
  4. package/dist/commands/teamwork/kill.js.map +1 -1
  5. package/dist/commands/teamwork/messages.d.ts.map +1 -1
  6. package/dist/commands/teamwork/messages.js +3 -0
  7. package/dist/commands/teamwork/messages.js.map +1 -1
  8. package/dist/commands/teamwork/process-identity.d.ts +3 -0
  9. package/dist/commands/teamwork/process-identity.d.ts.map +1 -0
  10. package/dist/commands/teamwork/process-identity.js +24 -0
  11. package/dist/commands/teamwork/process-identity.js.map +1 -0
  12. package/dist/commands/teamwork/rm.d.ts.map +1 -1
  13. package/dist/commands/teamwork/rm.js +57 -64
  14. package/dist/commands/teamwork/rm.js.map +1 -1
  15. package/dist/commands/teamwork/run.d.ts.map +1 -1
  16. package/dist/commands/teamwork/run.js +6 -3
  17. package/dist/commands/teamwork/run.js.map +1 -1
  18. package/dist/commands/teamwork/supervisor/stdout.d.ts.map +1 -1
  19. package/dist/commands/teamwork/supervisor/stdout.js +1 -2
  20. package/dist/commands/teamwork/supervisor/stdout.js.map +1 -1
  21. package/dist/commands/teamwork/supervisor/warning.d.ts.map +1 -1
  22. package/dist/commands/teamwork/supervisor/warning.js +2 -3
  23. package/dist/commands/teamwork/supervisor/warning.js.map +1 -1
  24. package/dist/commands/teamwork/supervisor.d.ts.map +1 -1
  25. package/dist/commands/teamwork/supervisor.js +18 -12
  26. package/dist/commands/teamwork/supervisor.js.map +1 -1
  27. package/dist/migrations/manifests/0.7.1-beta.11.json +9 -0
  28. package/dist/migrations/manifests/0.7.1-beta.12.json +9 -0
  29. package/dist/migrations/manifests/0.7.1-beta.6.json +1 -1
  30. package/dist/templates/codex/hooks/session-start.py +14 -11
  31. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +1 -1
  32. package/dist/templates/copilot/hooks/session-start.py +14 -11
  33. package/dist/templates/shared-hooks/session-start.py +15 -11
  34. package/dist/templates/trellis/scripts/common/task_continuity.py +27 -27
  35. 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
- raise ContinuityError("TASK_TRANSFER_STALE_GENERATION", "task already has an ownership record")
364
- if args.expected_generation != 0:
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 reconciled snapshot")
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
- reconciled_claim = (
399
+ recovered_claim = (
391
400
  operation == "claim" and record["status"] == "sealed"
392
401
  and args.expected_continuity_digest == current_digest
393
402
  )
394
- reconciled_target = (
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", "reconcile"}:
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 == "reconcile":
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 == "reconcile":
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 reconciled_claim):
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 reconciled_claim:
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", "reconcile"))
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", required=True)
504
- transfer.add_argument("--package-digest", required=True)
505
- transfer.add_argument("--expected-continuity-digest", help="Expected live projection; required for sealed-source claim or consumed-target reconciliation")
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.10",
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.10"
37
+ "@pennixrv/trellis-core": "0.7.1-beta.12"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@eslint/js": "^9.18.0",