@pennixrv/trellis 0.6.40 → 0.6.41

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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": "0.6.41",
3
+ "description": "Restores active-task visibility when a host context key has no persisted session file.",
4
+ "breaking": false,
5
+ "recommendMigrate": false,
6
+ "changelog": "**Fix:**\n- Projects one unique developer-owned resumable task as read-only workflow context when the host provides a context key whose session file is missing, while still refusing to guess when another session pointer exists.",
7
+ "migrations": [],
8
+ "notes": "No project migration is required. Existing projects can run the normal trellis update flow."
9
+ }
@@ -373,7 +373,8 @@ export class TrellisContext {
373
373
  *
374
374
  * Resolution order (mirrors Python `active_task.resolve_active_task`):
375
375
  * 1. Lookup the runtime file for the input-derived context key. A known
376
- * key is authoritative, so a missing or empty context returns no task.
376
+ * key is authoritative when it contains a task; a missing or empty
377
+ * context may use the guarded unbound-task projection.
377
378
  * 2. If no context key is available and exactly one session runtime file
378
379
  * exists locally, use it (`_resolveSingleSessionFallback`). Refuses to
379
380
  * guess when 0 or ≥2 files exist so multi-window isolation holds.
@@ -393,6 +394,10 @@ export class TrellisContext {
393
394
  stale: !taskDir || !existsSync(taskDir),
394
395
  }
395
396
  }
397
+ const unbound = this._resolveUnboundTask()
398
+ if (unbound) {
399
+ return unbound
400
+ }
396
401
  return { taskPath: null, source: "none", stale: false }
397
402
  }
398
403
 
@@ -456,7 +461,14 @@ export class TrellisContext {
456
461
  } catch {
457
462
  return null
458
463
  }
459
- if (sessionFiles.length > 0) return null
464
+ if (sessionFiles.some(file => {
465
+ try {
466
+ const context = JSON.parse(readFileSync(join(sessionsDir, file), "utf-8"))
467
+ return Boolean(this.normalizeTaskRef(context?.current_task || ""))
468
+ } catch {
469
+ return false
470
+ }
471
+ })) return null
460
472
  }
461
473
 
462
474
  const developer = (process.env.TRELLIS_DEVELOPER || "").trim() || (() => {
@@ -2,8 +2,8 @@
2
2
  """Session-scoped active task resolution.
3
3
 
4
4
  The user-facing concept is a single "active task". Trellis stores that pointer
5
- per AI session/window under `.trellis/.runtime/sessions/`; without a stable
6
- session key there is no active task.
5
+ per AI session/window under `.trellis/.runtime/sessions/`; when the pointer is
6
+ missing, a unique developer-owned resumable task may be exposed read-only.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
@@ -654,9 +654,10 @@ def resolve_active_task(
654
654
  """Resolve the active task from session runtime state only.
655
655
 
656
656
  A stale session task is returned as stale. A known context identity is
657
- authoritative: a missing or empty context for that identity returns no
658
- task. When context identity is unavailable, single-session inference may
659
- cover pull-based platform sub-agents (copilot, gemini, qoder) that don't
657
+ authoritative when it contains a task reference; a missing or empty
658
+ context may use the guarded unbound-task projection. When context identity
659
+ is unavailable, single-session inference may cover pull-based platform
660
+ sub-agents (copilot, gemini, qoder) that don't
660
661
  inherit the parent's session id. ≥2 files or 0 files yield
661
662
  ActiveTask(None) — refuses to guess across windows.
662
663
  """
@@ -671,6 +672,10 @@ def resolve_active_task(
671
672
  active = _active_from_ref(task_ref, repo_root, "session", context_key)
672
673
  if active:
673
674
  return active
675
+ if allow_single_session_fallback:
676
+ unbound = _resolve_unbound_task(repo_root)
677
+ if unbound is not None:
678
+ return unbound
674
679
  return ActiveTask(None, "none", context_key)
675
680
 
676
681
  if allow_single_session_fallback:
@@ -713,8 +718,10 @@ def _resolve_single_session_fallback(repo_root: Path) -> ActiveTask | None:
713
718
  def _resolve_unbound_task(repo_root: Path) -> ActiveTask | None:
714
719
  """Expose one developer-owned task when no session pointer exists."""
715
720
  sessions_dir = _runtime_sessions_dir(repo_root)
716
- if sessions_dir.is_dir() and any(sessions_dir.glob("*.json")):
717
- return None
721
+ if sessions_dir.is_dir():
722
+ session_files = sorted(sessions_dir.glob("*.json"))
723
+ if any(_string_value((_read_json(session) or {}).get("current_task")) for session in session_files):
724
+ return None
718
725
 
719
726
  from .paths import get_developer, get_tasks_dir
720
727
  from .tasks import iter_active_tasks
@@ -76,7 +76,7 @@ python3 ./.trellis/scripts/task.py create-pr [name] [--dry-run]
76
76
 
77
77
  > Run `python3 ./.trellis/scripts/task.py --help` to see the authoritative, up-to-date list.
78
78
 
79
- **Current-task mechanism**: `task.py create` creates the task directory and (when session identity is available) auto-sets the per-session active-task pointer so the planning breadcrumb fires immediately. `task.py start` writes the same pointer (idempotent if already set) and flips `task.json.status` from `planning` to `in_progress`. State is stored under `.trellis/.runtime/sessions/`. If no context key is available from hook input, `TRELLIS_CONTEXT_ID`, or a platform-native session environment variable, there is no active task and `task.py start` fails with a session identity hint. `task.py finish` deletes the current session file (status unchanged). `task.py archive <task>` writes `status=completed`, moves the directory to `archive/`, and deletes any runtime session files that still point at the archived task.
79
+ **Current-task mechanism**: `task.py create` creates the task directory and (when session identity is available) auto-sets the per-session active-task pointer so the planning breadcrumb fires immediately. `task.py start` writes the same pointer (idempotent if already set) and flips `task.json.status` from `planning` to `in_progress`. State is stored under `.trellis/.runtime/sessions/`. If no context key is available from hook input, `TRELLIS_CONTEXT_ID`, or a platform-native session environment variable, `task.py start` fails with a session identity hint because it cannot persist a session pointer; read-only context may still project one unique developer-owned resumable task. `task.py finish` deletes the current session file (status unchanged). `task.py archive <task>` writes `status=completed`, moves the directory to `archive/`, and deletes any runtime session files that still point at the archived task.
80
80
 
81
81
  ### Workspace System
82
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pennixrv/trellis",
3
- "version": "0.6.40",
3
+ "version": "0.6.41",
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.6.40"
37
+ "@pennixrv/trellis-core": "0.6.41"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@eslint/js": "^9.18.0",