syntaur 0.69.0 → 0.71.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.
Files changed (136) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dashboard/dist/assets/{_basePickBy-DKk6tHtk.js → _basePickBy-BfpzwNC3.js} +1 -1
  3. package/dashboard/dist/assets/{_baseUniq-DM-f7DWz.js → _baseUniq-wFEHBTCT.js} +1 -1
  4. package/dashboard/dist/assets/{arc-ZBlA3YdV.js → arc-Bdcw4sCg.js} +1 -1
  5. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-BUmvtGTF.js → architectureDiagram-2XIMDMQ5-BessFAtJ.js} +1 -1
  6. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-B3qxWK6s.js → blockDiagram-WCTKOSBZ-CmjyyZPp.js} +1 -1
  7. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-BEq_UJO-.js → c4Diagram-IC4MRINW-dT6p8cwc.js} +1 -1
  8. package/dashboard/dist/assets/channel-BOguQ5S4.js +1 -0
  9. package/dashboard/dist/assets/{chunk-4BX2VUAB-C-Y9ryMm.js → chunk-4BX2VUAB-BlY-k8DY.js} +1 -1
  10. package/dashboard/dist/assets/{chunk-55IACEB6-CGdtbsjw.js → chunk-55IACEB6-CtQ0p0jb.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-FMBD7UC4-DllxJhUp.js → chunk-FMBD7UC4-DEevt_Yh.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-JSJVCQXG-jjMM8O5F.js → chunk-JSJVCQXG-CpjLL7Nh.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-KX2RTZJC-B_6BPltQ.js → chunk-KX2RTZJC-BWNRms_o.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-NQ4KR5QH-D0hJiXHp.js → chunk-NQ4KR5QH-qo2zbylv.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-QZHKN3VN-BCWo4hLS.js → chunk-QZHKN3VN-DRvojtzc.js} +1 -1
  16. package/dashboard/dist/assets/{chunk-WL4C6EOR-DH_jEAwg.js → chunk-WL4C6EOR-A40iPU5N.js} +1 -1
  17. package/dashboard/dist/assets/classDiagram-VBA2DB6C-BLSHooma.js +1 -0
  18. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BLSHooma.js +1 -0
  19. package/dashboard/dist/assets/clone-iSEXw5tA.js +1 -0
  20. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-5ld00TOH.js → cose-bilkent-S5V4N54A-CYaO7nsk.js} +1 -1
  21. package/dashboard/dist/assets/{dagre-KLK3FWXG-Cnu6eQWy.js → dagre-KLK3FWXG-NX3inwVZ.js} +1 -1
  22. package/dashboard/dist/assets/{diagram-E7M64L7V-_CBBKNP-.js → diagram-E7M64L7V-D9cJwhQW.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-IFDJBPK2-DE6WjIb1.js → diagram-IFDJBPK2-Bo5sFrMg.js} +1 -1
  24. package/dashboard/dist/assets/{diagram-P4PSJMXO-DpW7UzNK.js → diagram-P4PSJMXO-DfRH0XU7.js} +1 -1
  25. package/dashboard/dist/assets/{erDiagram-INFDFZHY-BD2409fE.js → erDiagram-INFDFZHY-BgP1bwgw.js} +1 -1
  26. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-1p0khhFI.js → flowDiagram-PKNHOUZH-C8Wws1ci.js} +1 -1
  27. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-B2zFyA4s.js → ganttDiagram-A5KZAMGK-BgJhUuj8.js} +1 -1
  28. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-bH-4YH7h.js → gitGraphDiagram-K3NZZRJ6-CI9FgCgo.js} +1 -1
  29. package/dashboard/dist/assets/{graph-BT24B6iQ.js → graph-B_7GyeiA.js} +1 -1
  30. package/dashboard/dist/assets/{index-Cxqr3rQB.js → index-CbndxFlO.js} +3 -3
  31. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-CMzP4Hcg.js → infoDiagram-LFFYTUFH-BEtZcRxI.js} +1 -1
  32. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-DMEmFC7M.js → ishikawaDiagram-PHBUUO56-0o9Bksuc.js} +1 -1
  33. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CAtzYQUm.js → journeyDiagram-4ABVD52K-RyRU3as7.js} +1 -1
  34. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-d1JVbtvX.js → kanban-definition-K7BYSVSG-hLrHHizB.js} +1 -1
  35. package/dashboard/dist/assets/{layout-BVuI38I_.js → layout-w458wydd.js} +1 -1
  36. package/dashboard/dist/assets/{linear-Bc8PGMbp.js → linear-CnRlR5_w.js} +1 -1
  37. package/dashboard/dist/assets/{mermaid.core-UtwFYLNj.js → mermaid.core-DJ_9ty0Y.js} +4 -4
  38. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-DHc5RCDj.js → mindmap-definition-YRQLILUH-C7TIzJVC.js} +1 -1
  39. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-9anIsdIA.js → pieDiagram-SKSYHLDU-BZ9ysteM.js} +1 -1
  40. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-FZ0D9HnU.js → quadrantDiagram-337W2JSQ-DpaMVSO1.js} +1 -1
  41. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-BkjCvH_u.js → requirementDiagram-Z7DCOOCP-EXFcskZZ.js} +1 -1
  42. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-DzqwYHDo.js → sankeyDiagram-WA2Y5GQK-BdImdXkY.js} +1 -1
  43. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-BW4g5Ao-.js → sequenceDiagram-2WXFIKYE-CWN3faYf.js} +1 -1
  44. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-D0tKU3Z0.js → stateDiagram-RAJIS63D-DIQdn-BF.js} +1 -1
  45. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-1u62oWzq.js +1 -0
  46. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-Bu269QDX.js → timeline-definition-YZTLITO2-DMTIpUOE.js} +1 -1
  47. package/dashboard/dist/assets/{treemap-KZPCXAKY-BGu_rrva.js → treemap-KZPCXAKY-Y2AH2GB7.js} +1 -1
  48. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-Cx_n5FRZ.js → vennDiagram-LZ73GAT5-BF4bJK-l.js} +1 -1
  49. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-BOJsKV_W.js → xychartDiagram-JWTSCODW-DH-tOBC5.js} +1 -1
  50. package/dashboard/dist/index.html +1 -1
  51. package/dist/dashboard/server.js +1427 -550
  52. package/dist/dashboard/server.js.map +1 -1
  53. package/dist/index.js +2816 -1559
  54. package/dist/index.js.map +1 -1
  55. package/dist/launch/index.js +913 -141
  56. package/dist/launch/index.js.map +1 -1
  57. package/package.json +1 -1
  58. package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
  59. package/platforms/claude-code/hooks/enforce-boundaries.sh +49 -16
  60. package/platforms/claude-code/hooks/session-cleanup.sh +4 -2
  61. package/platforms/claude-code/skills/add-memory/SKILL.md +3 -2
  62. package/platforms/claude-code/skills/add-resource/SKILL.md +3 -2
  63. package/platforms/claude-code/skills/bundle-worktree/SKILL.md +3 -3
  64. package/platforms/claude-code/skills/capture-artifacts/SKILL.md +2 -2
  65. package/platforms/claude-code/skills/claim-resource/SKILL.md +3 -3
  66. package/platforms/claude-code/skills/clear-assignment/SKILL.md +9 -27
  67. package/platforms/claude-code/skills/complete-assignment/SKILL.md +3 -3
  68. package/platforms/claude-code/skills/create-assignment/SKILL.md +2 -2
  69. package/platforms/claude-code/skills/extend-resource/SKILL.md +2 -2
  70. package/platforms/claude-code/skills/grab-assignment/SKILL.md +17 -12
  71. package/platforms/claude-code/skills/grab-bundle/SKILL.md +10 -6
  72. package/platforms/claude-code/skills/log-progress/SKILL.md +6 -4
  73. package/platforms/claude-code/skills/plan-assignment/SKILL.md +3 -3
  74. package/platforms/claude-code/skills/plan-bundle/SKILL.md +4 -3
  75. package/platforms/claude-code/skills/replan/SKILL.md +9 -6
  76. package/platforms/claude-code/skills/resume-session/SKILL.md +14 -8
  77. package/platforms/claude-code/skills/save-session-summary/SKILL.md +4 -4
  78. package/platforms/claude-code/skills/set-workspace/SKILL.md +9 -6
  79. package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +9 -7
  80. package/platforms/claude-code/skills/syntaur-protocol/references/file-ownership.md +1 -1
  81. package/platforms/claude-code/skills/syntaur-worktree/SKILL.md +11 -7
  82. package/platforms/codex/.codex-plugin/plugin.json +1 -1
  83. package/platforms/codex/scripts/enforce-boundaries.sh +74 -12
  84. package/platforms/codex/scripts/session-cleanup.sh +20 -13
  85. package/platforms/codex/skills/add-memory/SKILL.md +3 -2
  86. package/platforms/codex/skills/add-resource/SKILL.md +3 -2
  87. package/platforms/codex/skills/bundle-worktree/SKILL.md +3 -3
  88. package/platforms/codex/skills/capture-artifacts/SKILL.md +2 -2
  89. package/platforms/codex/skills/claim-resource/SKILL.md +3 -3
  90. package/platforms/codex/skills/clear-assignment/SKILL.md +9 -27
  91. package/platforms/codex/skills/complete-assignment/SKILL.md +3 -3
  92. package/platforms/codex/skills/create-assignment/SKILL.md +2 -2
  93. package/platforms/codex/skills/extend-resource/SKILL.md +2 -2
  94. package/platforms/codex/skills/grab-assignment/SKILL.md +17 -12
  95. package/platforms/codex/skills/grab-bundle/SKILL.md +10 -6
  96. package/platforms/codex/skills/log-progress/SKILL.md +6 -4
  97. package/platforms/codex/skills/plan-assignment/SKILL.md +3 -3
  98. package/platforms/codex/skills/plan-bundle/SKILL.md +4 -3
  99. package/platforms/codex/skills/replan/SKILL.md +9 -6
  100. package/platforms/codex/skills/resume-session/SKILL.md +14 -8
  101. package/platforms/codex/skills/save-session-summary/SKILL.md +4 -4
  102. package/platforms/codex/skills/set-workspace/SKILL.md +9 -6
  103. package/platforms/codex/skills/syntaur-protocol/SKILL.md +9 -7
  104. package/platforms/codex/skills/syntaur-protocol/references/file-ownership.md +1 -1
  105. package/platforms/codex/skills/syntaur-worktree/SKILL.md +11 -7
  106. package/platforms/hermes/plugins/syntaur/__init__.py +29 -3
  107. package/platforms/hermes/plugins/syntaur/__pycache__/__init__.cpython-312.pyc +0 -0
  108. package/platforms/hermes/plugins/syntaur/__pycache__/boundary.cpython-312.pyc +0 -0
  109. package/platforms/hermes/plugins/syntaur/boundary.py +18 -9
  110. package/platforms/pi/extensions/syntaur/index.ts +84 -29
  111. package/skills/add-memory/SKILL.md +3 -2
  112. package/skills/add-resource/SKILL.md +3 -2
  113. package/skills/bundle-worktree/SKILL.md +3 -3
  114. package/skills/capture-artifacts/SKILL.md +2 -2
  115. package/skills/claim-resource/SKILL.md +3 -3
  116. package/skills/clear-assignment/SKILL.md +9 -27
  117. package/skills/complete-assignment/SKILL.md +3 -3
  118. package/skills/create-assignment/SKILL.md +2 -2
  119. package/skills/extend-resource/SKILL.md +2 -2
  120. package/skills/grab-assignment/SKILL.md +17 -12
  121. package/skills/grab-bundle/SKILL.md +10 -6
  122. package/skills/log-progress/SKILL.md +6 -4
  123. package/skills/plan-assignment/SKILL.md +3 -3
  124. package/skills/plan-bundle/SKILL.md +4 -3
  125. package/skills/replan/SKILL.md +9 -6
  126. package/skills/resume-session/SKILL.md +14 -8
  127. package/skills/save-session-summary/SKILL.md +4 -4
  128. package/skills/set-workspace/SKILL.md +9 -6
  129. package/skills/syntaur-protocol/SKILL.md +9 -7
  130. package/skills/syntaur-protocol/references/file-ownership.md +1 -1
  131. package/skills/syntaur-worktree/SKILL.md +11 -7
  132. package/dashboard/dist/assets/channel-fypxffzQ.js +0 -1
  133. package/dashboard/dist/assets/classDiagram-VBA2DB6C-1KnjQvtL.js +0 -1
  134. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-1KnjQvtL.js +0 -1
  135. package/dashboard/dist/assets/clone-CKMabBhS.js +0 -1
  136. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DxWQhjNO.js +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "syntaur",
3
- "version": "0.69.0",
3
+ "version": "0.71.0",
4
4
  "description": "Project workflow CLI with dashboard, Claude Code plugin, and Codex plugin",
5
5
  "homepage": "https://github.com/prong-horn/syntaur#readme",
6
6
  "repository": {
@@ -5,7 +5,7 @@
5
5
  "name": "Brennen",
6
6
  "email": ""
7
7
  },
8
- "version": "0.69.0",
8
+ "version": "0.71.0",
9
9
  "skills": [
10
10
  "./skills/syntaur-protocol",
11
11
  "./skills/grab-assignment",
@@ -56,25 +56,50 @@ if [ -z "$FILE_PATH" ]; then
56
56
  fi
57
57
 
58
58
  # --- Step 7: Check for context file ---
59
+ # context.json is a WORKSPACE MARKER. Its mere presence means "this workspace is
60
+ # under Syntaur enforcement"; its absence means "not a Syntaur workspace → allow".
59
61
  CONTEXT_FILE=".syntaur/context.json"
60
62
  if [ ! -f "$CONTEXT_FILE" ]; then
61
- # No active assignment; allow all writes
63
+ # No Syntaur workspace; allow all writes (unchanged behavior).
62
64
  allow_and_exit
63
65
  fi
64
66
 
65
- # --- Step 8: Read context ---
66
- ASSIGNMENT_DIR=$(jq -r '.assignmentDir // empty' "$CONTEXT_FILE" 2>/dev/null)
67
- MISSION_DIR=$(jq -r '.projectDir // empty' "$CONTEXT_FILE" 2>/dev/null)
68
- WORKSPACE_ROOT=$(jq -r '.workspaceRoot // empty' "$CONTEXT_FILE" 2>/dev/null)
67
+ # --- Step 8: Resolve the write boundary from the session's OPEN engagement ---
68
+ # The assignment scalars were demoted out of context.json the active assignment
69
+ # now lives on the session's engagement. Ask the CLI to resolve it. Parse the
70
+ # PreToolUse stdin payload for the calling session id and pass it explicitly so
71
+ # co-tenant clobbering can't misattribute. If the CLI is unavailable or resolves
72
+ # nothing, ASSIGNMENT_DIR/MISSION_DIR stay empty → we enforce WORKSPACE-ONLY
73
+ # below (we do NOT fail open).
74
+ ASSIGNMENT_DIR=""
75
+ MISSION_DIR=""
76
+ WORKSPACE_ROOT=""
77
+
78
+ # The cwd holding this context file (its parent's parent: ".syntaur/context.json").
79
+ CONTEXT_DIR="$(cd "$(dirname "$CONTEXT_FILE")/.." 2>/dev/null && pwd)"
80
+ [ -z "$CONTEXT_DIR" ] && CONTEXT_DIR="$(pwd)"
81
+
82
+ SID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
83
+
84
+ if command -v syntaur >/dev/null 2>&1; then
85
+ BOUNDARY_JSON=$(cd "$CONTEXT_DIR" 2>/dev/null && \
86
+ syntaur session boundary --json ${SID:+--session-id "$SID"} 2>/dev/null)
87
+ if [ -n "$BOUNDARY_JSON" ]; then
88
+ ASSIGNMENT_DIR=$(echo "$BOUNDARY_JSON" | jq -r '.assignmentDir // empty' 2>/dev/null)
89
+ MISSION_DIR=$(echo "$BOUNDARY_JSON" | jq -r '.projectDir // empty' 2>/dev/null)
90
+ WORKSPACE_ROOT=$(echo "$BOUNDARY_JSON" | jq -r '.workspaceRoot // empty' 2>/dev/null)
91
+ fi
92
+ fi
69
93
 
70
- if [ -z "$ASSIGNMENT_DIR" ] || [ -z "$MISSION_DIR" ]; then
71
- # Malformed context file; allow (defensive)
72
- allow_and_exit
94
+ # Fall back to the context.json workspace marker if the CLI did not surface one
95
+ # (e.g. standalone session with a worktree but no engagement yet).
96
+ if [ -z "$WORKSPACE_ROOT" ]; then
97
+ WORKSPACE_ROOT=$(jq -r '.workspaceRoot // empty' "$CONTEXT_FILE" 2>/dev/null)
73
98
  fi
74
99
 
75
- # --- Step 9: Expand ~ in paths ---
76
- ASSIGNMENT_DIR="${ASSIGNMENT_DIR/#\~/$HOME}"
77
- MISSION_DIR="${MISSION_DIR/#\~/$HOME}"
100
+ # --- Step 9: Expand ~ in paths (guarding empties) ---
101
+ [ -n "$ASSIGNMENT_DIR" ] && ASSIGNMENT_DIR="${ASSIGNMENT_DIR/#\~/$HOME}"
102
+ [ -n "$MISSION_DIR" ] && MISSION_DIR="${MISSION_DIR/#\~/$HOME}"
78
103
  if [ -n "$WORKSPACE_ROOT" ] && [ "$WORKSPACE_ROOT" != "null" ]; then
79
104
  WORKSPACE_ROOT="${WORKSPACE_ROOT/#\~/$HOME}"
80
105
  else
@@ -82,14 +107,18 @@ else
82
107
  fi
83
108
 
84
109
  # --- Step 10: Check allowed paths ---
110
+ # NOTE: every prefix test is guarded by a non-empty check so an EMPTY $DIR never
111
+ # globs to "/*" and silently allows the whole filesystem. When no assignment
112
+ # resolves, only the workspace-root (and context file) checks can match → this is
113
+ # WORKSPACE-ONLY enforcement, NOT fail-open.
85
114
 
86
115
  # Allow: files inside the assignment directory
87
- if [[ "$FILE_PATH" == "$ASSIGNMENT_DIR"/* ]]; then
116
+ if [ -n "$ASSIGNMENT_DIR" ] && [[ "$FILE_PATH" == "$ASSIGNMENT_DIR"/* ]]; then
88
117
  allow_and_exit
89
118
  fi
90
119
 
91
120
  # Allow: files in project resources/ directory (but NOT derived _index.md)
92
- if [[ "$FILE_PATH" == "$MISSION_DIR/resources/"* ]]; then
121
+ if [ -n "$MISSION_DIR" ] && [[ "$FILE_PATH" == "$MISSION_DIR/resources/"* ]]; then
93
122
  BASENAME=$(basename "$FILE_PATH")
94
123
  if [[ "$BASENAME" == _* ]]; then
95
124
  # Derived file (e.g., _index.md) -- fall through to block
@@ -100,7 +129,7 @@ if [[ "$FILE_PATH" == "$MISSION_DIR/resources/"* ]]; then
100
129
  fi
101
130
 
102
131
  # Allow: files in project memories/ directory (but NOT derived _index.md)
103
- if [[ "$FILE_PATH" == "$MISSION_DIR/memories/"* ]]; then
132
+ if [ -n "$MISSION_DIR" ] && [[ "$FILE_PATH" == "$MISSION_DIR/memories/"* ]]; then
104
133
  BASENAME=$(basename "$FILE_PATH")
105
134
  if [[ "$BASENAME" == _* ]]; then
106
135
  # Derived file (e.g., _index.md) -- fall through to block
@@ -112,7 +141,7 @@ fi
112
141
 
113
142
  # Allow: the context file itself
114
143
  CONTEXT_ABS="$(cd "$(dirname "$CONTEXT_FILE")" 2>/dev/null && echo "$(pwd)/$(basename "$CONTEXT_FILE")")"
115
- if [ "$FILE_PATH" = "$CONTEXT_ABS" ]; then
144
+ if [ -n "$CONTEXT_ABS" ] && [ "$FILE_PATH" = "$CONTEXT_ABS" ]; then
116
145
  allow_and_exit
117
146
  fi
118
147
 
@@ -122,7 +151,11 @@ if [ -n "$WORKSPACE_ROOT" ] && [[ "$FILE_PATH" == "$WORKSPACE_ROOT"/* ]]; then
122
151
  fi
123
152
 
124
153
  # --- Step 11: Block the write ---
125
- REASON="Syntaur write boundary violation: Cannot write to '$FILE_PATH'. Allowed paths: assignment dir ($ASSIGNMENT_DIR), project resources/memories, workspace ($WORKSPACE_ROOT)."
154
+ if [ -n "$ASSIGNMENT_DIR" ]; then
155
+ REASON="Syntaur write boundary violation: Cannot write to '$FILE_PATH'. Allowed paths: assignment dir ($ASSIGNMENT_DIR), project resources/memories${MISSION_DIR:+ ($MISSION_DIR)}, workspace (${WORKSPACE_ROOT:-none})."
156
+ else
157
+ REASON="Syntaur write boundary violation: Cannot write to '$FILE_PATH'. No active assignment for this session — writes are restricted to the workspace (${WORKSPACE_ROOT:-none}). Run /grab-assignment to bind an assignment and widen the boundary."
158
+ fi
126
159
 
127
160
  # Escape for JSON
128
161
  REASON_ESCAPED=$(echo "$REASON" | jq -Rs '.' 2>/dev/null)
@@ -34,12 +34,14 @@ syntaur_bounded_stop || true
34
34
  # after the agent walks away. Best-effort, bounded (~3s, no `timeout` on macOS),
35
35
  # and migration-gated (`--if-migrated`) so it can't re-derive pre-migration
36
36
  # assignments during rollout. Resolves the assignment from the ending session's
37
- # cwd `.syntaur/context.json`; no-ops silently when none is present.
37
+ # latest engagement (passed via --session-id); no-ops silently when none.
38
38
  HOOK_CWD=$(printf '%s' "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
39
+ HOOK_SID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
39
40
  syntaur_bounded_recompute() {
40
41
  local cpid kpid rc
41
42
  [ -n "$HOOK_CWD" ] && [ -f "$HOOK_CWD/.syntaur/context.json" ] || return 0
42
- ( cd "$HOOK_CWD" 2>/dev/null && syntaur recompute --if-migrated >/dev/null 2>&1 ) &
43
+ ( cd "$HOOK_CWD" 2>/dev/null && \
44
+ syntaur recompute --if-migrated ${HOOK_SID:+--session-id "$HOOK_SID"} >/dev/null 2>&1 ) &
43
45
  cpid=$!
44
46
  ( sleep 3; kill -KILL "$cpid" 2>/dev/null ) >/dev/null 2>&1 &
45
47
  kpid=$!
@@ -32,8 +32,9 @@ file-ownership protocol.
32
32
 
33
33
  ## Step 1: Resolve project
34
34
 
35
- If `.syntaur/context.json` is present and has `projectSlug`, default to that.
36
- Otherwise ask the user which project to add the memory to.
35
+ If the session has an open engagement with an active assignment (`syntaur
36
+ session resume --json`), default to its `projectSlug`. Otherwise ask the user
37
+ which project to add the memory to.
37
38
 
38
39
  ## Step 2: Gather inputs
39
40
 
@@ -30,8 +30,9 @@ CLI-managed file per the file-ownership protocol).
30
30
 
31
31
  ## Step 1: Resolve project
32
32
 
33
- If `.syntaur/context.json` is present and has `projectSlug`, default to that.
34
- Otherwise ask the user which project to add the resource to.
33
+ If the session has an open engagement with an active assignment (`syntaur
34
+ session resume --json`), default to its `projectSlug`. Otherwise ask the user
35
+ which project to add the resource to.
35
36
 
36
37
  ## Step 2: Gather inputs
37
38
 
@@ -45,9 +45,9 @@ The computed worktree path is **always**
45
45
 
46
46
  ## Step 2: Pre-flight
47
47
 
48
- - Confirm `.syntaur/context.json` is a bundle context (has `bundleId`,
49
- no assignment fields). If it's an assignment context, stop and tell the
50
- user to `/grab-bundle <id>` first.
48
+ - Confirm `.syntaur/context.json` is a bundle context (has `bundleId`). If the
49
+ session is instead bound to an assignment (an open engagement) with no
50
+ `bundleId`, stop and tell the user to `/grab-bundle <id>` first.
51
51
  - Confirm `<repository>/.git` exists.
52
52
  - Confirm the branch does NOT already exist. If it does, the CLI will
53
53
  surface a `GitWorktreeError` — surface that verbatim.
@@ -17,7 +17,7 @@ Attach proof to your work. Reviewers should be able to scroll a single page (`pr
17
17
 
18
18
  ## Input
19
19
 
20
- No arguments. Reads `.syntaur/context.json` from the current working directory to identify the active assignment.
20
+ No arguments. The active assignment is resolved from the session's open engagement (`.syntaur/context.json` is only a workspace marker, not the assignment source).
21
21
 
22
22
  ## Step 1: Decide whether to capture
23
23
 
@@ -60,7 +60,7 @@ Rules:
60
60
  - All other kinds (`screenshot`, `video`, `asciinema`) require `--file`. The CLI rejects nonexistent / non-file paths.
61
61
  - `--criterion <index>` is optional — pass the **0-based** index into the `## Acceptance Criteria` checklist when you want to anchor the artifact to a specific criterion.
62
62
  - `--transcribe` is video-only and writes a sibling `<id>.transcript.md` (requires `ELEVENLABS_API_KEY` + `ffmpeg`). `proof build` renders the transcript next to the player; clicking a phrase seeks the video.
63
- - If you have a `.syntaur/context.json` in the cwd, the positional target argument is unnecessary; otherwise pass `--project <slug> <assignment-slug>` or a bare assignment UUID.
63
+ - If this session has an open engagement (active assignment), the positional target argument is unnecessary the CLI resolves the target from the engagement. Otherwise pass `--project <slug> <assignment-slug>` or a bare assignment UUID.
64
64
 
65
65
  The CLI copies the file (if any) under `<assignmentDir>/proof/<criterion|untagged>/<id>.<ext>` and inserts a row in `~/.syntaur/syntaur.db`. Output prints the artifact id and absolute path.
66
66
 
@@ -29,15 +29,15 @@ Examples:
29
29
 
30
30
  Extract `<inventory_slug>` (required, first positional), plus optional `--ttl <duration>` and `--for <tag>`.
31
31
 
32
- ### Step 2: Pre-check assignment context
32
+ ### Step 2: Pre-check workspace context
33
33
 
34
- Read `.syntaur/context.json` from the current working directory. If the file is missing, OR has neither `sessionId` nor any of `projectSlug`/`assignmentSlug`/`assignmentDir`, abort with:
34
+ Read `.syntaur/context.json` (a workspace marker) from the current working directory. If the file is missing, OR carries no workspace markers (`repository`/`branch`/`worktreePath`/`workspaceRoot`) and no `sessionId`, abort with:
35
35
 
36
36
  > "No active Syntaur context in this workspace. Grab an assignment first (`/grab-assignment <project> <slug>`) or restart the session so the SessionStart hook can populate `.syntaur/context.json`."
37
37
 
38
38
  This guard exists because writing a leases-only context file would trip `syntaur doctor`'s workspace check.
39
39
 
40
- If `--for` was not provided, derive its value from the context: prefer `assignmentSlug` if present, otherwise `sessionId`.
40
+ If `--for` was not provided, derive its value: prefer the active assignment's slug from the session's open engagement (`syntaur session resume --json`); otherwise fall back to the `sessionId` from the workspace marker.
41
41
 
42
42
  ### Step 3: Run the claim CLI
43
43
 
@@ -14,7 +14,7 @@ metadata:
14
14
 
15
15
  # Clear Assignment
16
16
 
17
- Drop the active assignment binding from the current workspace. The assignment itself is left untouched in `~/.syntaur/projects/.../assignments/` — only the local `.syntaur/context.json` pointer is cleared so the session is no longer scoped to it.
17
+ Drop the active assignment binding from the current session. The assignment itself is left untouched in `~/.syntaur/projects/.../assignments/` — only the session's open engagement (the binding that makes it the active assignment) is closed so the session is no longer scoped to it. `.syntaur/context.json` is a workspace marker and is not the binding — it does not hold the active assignment.
18
18
 
19
19
  This is the inverse of `grab-assignment`. Unlike `complete-assignment`, it does **not** transition lifecycle state, write a handoff, or close out the work. Use it when:
20
20
 
@@ -28,17 +28,15 @@ If the assignment is actually done, use `complete-assignment` instead so a hando
28
28
 
29
29
  Optional flags from the user:
30
30
 
31
- - `--keep-session` — preserve `sessionId` / `transcriptPath` fields in `context.json` (just strip the assignment fields). Default: full delete.
32
- - `--unassign` — also run `syntaur unassign <slug> --project <project>` so the assignment is no longer claimed by this agent. Default: leave the claim in place (only the local context is cleared).
31
+ - `--unassign` — also run `syntaur unassign <slug> --project <project>` so the assignment is no longer claimed by this agent. Default: leave the claim in place (only the session's engagement is closed).
33
32
 
34
33
  ## Step 1: Load Context
35
34
 
36
- Read `.syntaur/context.json` from the current working directory.
35
+ The active assignment is resolved from the session's open engagement. Run `syntaur session resume --json` to read it.
37
36
 
38
- - If the file does not exist, tell the user: "No active assignment context to clear." and stop.
39
- - If the file exists but contains only session fields (`sessionId`, `transcriptPath`) and no `projectSlug` / `assignmentSlug`, tell the user: "No active assignment is bound — only a platform session record exists. Nothing to clear." and stop (unless they explicitly ask to wipe the session record too).
37
+ - If there is no open engagement, tell the user: "No active assignment is bound to this session — nothing to clear." and stop. (`.syntaur/context.json` is only a workspace marker; its presence does not mean an assignment is bound.)
40
38
 
41
- Otherwise extract: `projectSlug`, `assignmentSlug`, `assignmentDir`, `title`.
39
+ From the resolved engagement, note: `projectSlug`, `assignmentSlug`, `assignmentDir`, `title`.
42
40
 
43
41
  ## Step 2: Confirm with the User
44
42
 
@@ -68,29 +66,15 @@ For standalone assignments use the UUID (the folder name) in place of the slug,
68
66
 
69
67
  `syntaur unassign` clears the assignee on the assignment frontmatter (the inverse of `assign`) and bumps `updated`.
70
68
 
71
- ## Step 4: Clear the Context File
69
+ ## Step 4: Close the Engagement
72
70
 
73
- Default behaviordelete the file entirely:
71
+ Closing the session's open engagement is what drops the active-assignment binding that is the operation that "clears" the assignment. The dashboard status PATCH in Step 5 (to `cleared`) closes the open engagement for a live session.
74
72
 
75
- ```bash
76
- rm .syntaur/context.json
77
- ```
78
-
79
- If the user passed `--keep-session`, preserve session fields and strip everything else instead:
80
-
81
- ```bash
82
- jq '{sessionId, transcriptPath} | with_entries(select(.value != null))' \
83
- .syntaur/context.json > .syntaur/context.json.tmp \
84
- && mv .syntaur/context.json.tmp .syntaur/context.json
85
- ```
86
-
87
- If the resulting file would be empty (`{}`), delete it instead of leaving an empty stub.
88
-
89
- Do not delete the `.syntaur/` directory itself — other tooling may use it.
73
+ Do NOT delete or rewrite `.syntaur/context.json` to clear the assignment — it is a workspace marker and no longer carries the active assignment. Leave its repository/branch/worktree, session, and lease fields intact so other tooling keeps recognizing the workspace. Do not delete the `.syntaur/` directory.
90
74
 
91
75
  ## Step 5: Close Session (optional)
92
76
 
93
- If the Syntaur dashboard is running, mark this session as cleared so the dashboard does not keep showing it as active. Resolve `<session-id>` from *your* running process — prefer `$CLAUDE_CODE_SESSION_ID` (or the peer `OPENCODE_SESSION_ID` / `PI_SESSION_ID`), otherwise run `syntaur session resolve-id`. Only if neither yields an id, fall back to the `sessionId` you captured from the original `context.json` in Step 1 (before deletion) — that scalar is a shared, legacy hint a co-tenant can clobber, so don't treat it as authoritative:
77
+ If the Syntaur dashboard is running, mark this session as cleared so the dashboard does not keep showing it as active (this also closes the session's open engagement). Resolve `<session-id>` from *your* running process — prefer `$CLAUDE_CODE_SESSION_ID` (or the peer `OPENCODE_SESSION_ID` / `PI_SESSION_ID`), otherwise run `syntaur session resolve-id`. Only if neither yields an id, fall back to the legacy `sessionId` scalar in `.syntaur/context.json` — that scalar is a shared, legacy hint a co-tenant can clobber, so don't treat it as authoritative:
94
78
 
95
79
  ```bash
96
80
  curl -s -X PATCH "http://localhost:$(cat ~/.syntaur/dashboard-port 2>/dev/null || echo 4800)/api/agent-sessions/<session-id>/status" \
@@ -100,8 +84,6 @@ curl -s -X PATCH "http://localhost:$(cat ~/.syntaur/dashboard-port 2>/dev/null |
100
84
 
101
85
  If this fails (e.g., dashboard not running, endpoint not present in the installed version), it is non-critical — silently continue.
102
86
 
103
- Skip this step entirely when `--keep-session` is set.
104
-
105
87
  ## Step 6: Report to User
106
88
 
107
89
  Summarize:
@@ -19,11 +19,11 @@ Optional: the user may pass `--complete` to transition directly to `completed` i
19
19
 
20
20
  ## Step 1: Load Context
21
21
 
22
- Read `.syntaur/context.json` from the current working directory.
22
+ The active assignment is resolved from the session's open engagement. Run `syntaur session resume --json` to read it.
23
23
 
24
- If the file does not exist, tell the user: "No active assignment found. Run `grab-assignment` first."
24
+ If there is no open engagement (no active assignment), tell the user: "No active assignment for this session — grab one first." `.syntaur/context.json` is only a workspace marker; do not read the assignment from it.
25
25
 
26
- Extract: `projectSlug`, `assignmentSlug`, `assignmentDir`, `projectDir`.
26
+ From the resolved engagement, note: `projectSlug`, `assignmentSlug`, `assignmentDir`, `projectDir`.
27
27
 
28
28
  ## Step 2: Load Playbooks
29
29
 
@@ -30,9 +30,9 @@ Expects arguments from the user:
30
30
 
31
31
  If no title was provided, ask the user what the assignment should be called.
32
32
 
33
- If neither `--project` nor `--one-off` was provided, check `.syntaur/context.json` for an active assignment. If present, default `--project` to that context's `projectSlug` and confirm with the user: "Add this assignment to project `<projectSlug>`?"
33
+ If neither `--project` nor `--one-off` was provided, check for an active assignment via the session's open engagement (`syntaur session resume --json`). If there is one, default `--project` to its `projectSlug` and confirm with the user: "Add this assignment to project `<projectSlug>`?"
34
34
 
35
- If no active context and no project flag, ask the user which project to add it to, or whether it should be a one-off.
35
+ If there is no open engagement and no project flag, ask the user which project to add it to, or whether it should be a one-off.
36
36
 
37
37
  ## Step 1: Run the CLI
38
38
 
@@ -25,9 +25,9 @@ Examples:
25
25
 
26
26
  Extract `<lease_id>` (first positional) and `--ttl <duration>` (required). If either is missing, abort with a usage message and a hint to call `/list-resources` to see active leases.
27
27
 
28
- ### Step 2: Pre-check assignment context
28
+ ### Step 2: Pre-check workspace context
29
29
 
30
- Read `.syntaur/context.json` from the current working directory. If the file is missing, OR has neither `sessionId` nor any of `projectSlug`/`assignmentSlug`/`assignmentDir`, abort with:
30
+ Read `.syntaur/context.json` (a workspace marker) from the current working directory. If the file is missing, OR carries no workspace markers (`repository`/`branch`/`worktreePath`/`workspaceRoot`) and no `sessionId`, abort with:
31
31
 
32
32
  > "No active Syntaur context in this workspace. Grab an assignment first (`/grab-assignment <project> <slug>`) or restart the session so the SessionStart hook can populate `.syntaur/context.json`."
33
33
 
@@ -23,10 +23,16 @@ Expects up to two arguments from the user:
23
23
 
24
24
  ## Pre-flight Check
25
25
 
26
- Check if `.syntaur/context.json` already exists in the current working directory.
26
+ `.syntaur/context.json` is a WORKSPACE MARKER (repository/branch/worktree/workspaceRoot plus legacy session and bundle fields) — it is NOT the active-assignment source of truth. The active assignment binds via the session's open engagement (established by `track-session` in Step 6).
27
27
 
28
- - If it exists AND contains BOTH `projectSlug` and `assignmentSlug`, it represents an active assignment. Warn the user: "You already have an active assignment: `<assignmentSlug>` in project `<projectSlug>`. Grabbing a new one will replace this context. Proceed?" — stop if the user says no.
29
- - If it exists but only has session fields (`sessionId`, `transcriptPath`) and no project/assignment, it was populated by the platform's SessionStart hook (Claude Code, etc.) and does NOT represent an active assignment. Proceed silently and merge assignment fields on top in Step 5.
28
+ Check whether this session already has an open engagement i.e., a different assignment is already active:
29
+
30
+ ```bash
31
+ syntaur session resume --json 2>/dev/null
32
+ ```
33
+
34
+ - If it reports an active assignment, warn the user: "You already have an active assignment: `<assignmentSlug>` in project `<projectSlug>`. Grabbing a new one will rebind this session. Proceed?" — stop if the user says no.
35
+ - If there is no open engagement (no active assignment), proceed. A `.syntaur/context.json` that holds only workspace-marker / session fields is expected — it does not represent an active assignment.
30
36
 
31
37
  ## Step 1: Discover the Project (project-nested path)
32
38
 
@@ -75,25 +81,24 @@ From the assignment frontmatter extract: `title`, `workspace.repository`, `works
75
81
 
76
82
  If `workspace.repository` and `workspace.worktreePath` are both null, set them to the current working directory. Write boundaries use this path, so it must never be null while an agent is writing code.
77
83
 
78
- ## Step 5: Create or Merge Context File
84
+ ## Step 5: Create or Merge the Workspace Marker
79
85
 
80
- Merge assignment context into `.syntaur/context.json`. Never overwriteif the file already exists (e.g., platform SessionStart hook populated `sessionId` / `transcriptPath`), preserve those fields.
86
+ `.syntaur/context.json` is a WORKSPACE MARKER it records the repository/branch/worktree so tooling can recognize this directory as a Syntaur workspace. It is NOT the active-assignment source of truth: the assignment binds via the session's open engagement (Step 6, `track-session`). Do NOT write `projectSlug` / `assignmentSlug` / `assignmentDir` / `projectDir` / `title` — those scalars are non-authoritative.
87
+
88
+ Merge workspace markers into `.syntaur/context.json`. Never overwrite — if the file already exists (e.g., platform SessionStart hook populated `sessionId` / `transcriptPath`, or a worktree skill wrote bundle/lease fields), preserve those fields.
81
89
 
82
90
  ```bash
83
91
  mkdir -p .syntaur
84
92
  ```
85
93
 
86
- Prepare the assignment payload:
94
+ Prepare the workspace-marker payload:
87
95
 
88
96
  ```json
89
97
  {
90
- "projectSlug": "<project-slug or null for standalone>",
91
- "assignmentSlug": "<assignment-slug>",
92
- "projectDir": "/absolute/path/to/project or null",
93
- "assignmentDir": "/absolute/path/to/assignment",
94
- "workspaceRoot": "<workspace path or current working directory>",
95
- "title": "<assignment title>",
98
+ "repository": "<workspace.repository or null>",
96
99
  "branch": "<workspace.branch or null>",
100
+ "worktreePath": "<workspace.worktreePath or null>",
101
+ "workspaceRoot": "<workspace path or current working directory>",
97
102
  "grabbedAt": "<ISO 8601 timestamp>"
98
103
  }
99
104
  ```
@@ -34,16 +34,20 @@ the user to pick.
34
34
 
35
35
  ## Pre-flight check
36
36
 
37
- Read `.syntaur/context.json` in the current working directory.
37
+ First check whether this session already has an active assignment via its open
38
+ engagement (`syntaur session resume --json`):
39
+
40
+ - If it reports an active assignment, warn the user: "You already have an active
41
+ assignment. Grabbing a bundle will rebind this session. Proceed?" — stop if no.
42
+
43
+ Then read `.syntaur/context.json` (a workspace marker) in the current working
44
+ directory for bundle bookkeeping:
38
45
 
39
- - If it already contains assignment fields (`projectSlug`/`assignmentSlug`/
40
- `assignmentDir`), warn the user: "You already have an active assignment.
41
- Grabbing a bundle will replace this context. Proceed?" — stop if no.
42
46
  - If it already contains different bundle fields (`bundleId` set to a
43
47
  different id), warn: "Context is bound to bundle b:<old>. Switch to b:<new>?"
44
48
  — stop if no.
45
- - If it has only `sessionId` (platform SessionStart hook seeded it), proceed
46
- and merge bundle fields on top.
49
+ - A context.json holding only workspace-marker / `sessionId` fields is expected;
50
+ proceed and merge bundle fields on top.
47
51
 
48
52
  ## Step 1: Load the bundle
49
53
 
@@ -39,9 +39,11 @@ records current in real-time, especially after every meaningful action.
39
39
 
40
40
  ## Step 1: Verify there is an active assignment
41
41
 
42
- Read `.syntaur/context.json` from the current working directory. Extract
43
- `assignmentDir`. If missing, abort with: "No active assignment. Run
44
- `grab-assignment` first."
42
+ The active assignment is resolved from the session's open engagement — `syntaur
43
+ progress log` (Step 3) targets it automatically. `.syntaur/context.json` is only
44
+ a workspace marker; do not read the assignment from it. If there is no open
45
+ engagement (no active assignment), the CLI aborts with "No active assignment for
46
+ this session — grab one first." Run `grab-assignment` first.
45
47
 
46
48
  ## Step 2: Compose the entry
47
49
 
@@ -69,7 +71,7 @@ Run:
69
71
  syntaur progress log "<your composed entry body>"
70
72
  ```
71
73
 
72
- The command resolves the active assignment from `.syntaur/context.json`
74
+ The command resolves the active assignment from the session's open engagement
73
75
  (or pass `--assignment <slug> [--project <slug>]` to target one explicitly),
74
76
  then atomically:
75
77
 
@@ -20,14 +20,14 @@ Optional: the user may provide focus areas or notes to guide the plan.
20
20
 
21
21
  ## Step 1: Load Context
22
22
 
23
- Read `.syntaur/context.json` from the current working directory. If the file does not exist, tell the user: "No active assignment found. Run `grab-assignment` first to claim an assignment."
23
+ The active assignment is resolved from the session's open engagement. Run `syntaur session resume --json` to read it. If there is no open engagement (no active assignment), tell the user: "No active assignment for this session — grab one first." and stop. `.syntaur/context.json` is only a workspace marker; do not read the assignment from it.
24
24
 
25
- Extract:
25
+ From the resolved engagement, note:
26
26
  - `projectSlug` — the project slug (`null` for standalone assignments)
27
27
  - `assignmentSlug` — the assignment slug
28
28
  - `assignmentDir` — absolute path to the assignment folder
29
29
  - `projectDir` — absolute path to the project folder (may be null for standalone)
30
- - `workspaceRoot` — absolute path to the workspace (may be null)
30
+ - `workspaceRoot` — absolute path to the workspace (a workspace marker; may be null)
31
31
 
32
32
  ## Step 2: Load Playbooks
33
33
 
@@ -19,9 +19,10 @@ the bundle's own to track, only the member todos.
19
19
 
20
20
  ## Step 1: Load context
21
21
 
22
- Read `.syntaur/context.json` from the current working directory. It must
23
- contain `bundleId` (a bundle context). If it instead has assignment fields,
24
- stop and tell the user: "Active context is an assignment, not a bundle.
22
+ Read `.syntaur/context.json` (a workspace marker) from the current working
23
+ directory. It must contain `bundleId` (a bundle context). If there is no
24
+ `bundleId` but the session has an open engagement (an active assignment), stop
25
+ and tell the user: "This session is bound to an assignment, not a bundle.
25
26
  Use `/plan-assignment` instead, or `/grab-bundle <id>` to switch."
26
27
 
27
28
  Extract `bundleId`, `bundleScope`, `bundleScopeId`, `todoIds`, `planDir`.
@@ -38,12 +38,15 @@ todos are never deleted.**
38
38
 
39
39
  ## Step 1: Verify there is an active assignment
40
40
 
41
- Read `.syntaur/context.json` from the current working directory. Extract
42
- `assignmentSlug`, `projectSlug` (may be null for standalone), and
43
- `assignmentDir`.
44
-
45
- If the file is missing or has no `assignmentSlug`, abort with: "No active
46
- Syntaur assignment. Run `grab-assignment` first." Do not invent values.
41
+ The active assignment is resolved from the session's open engagement. Run
42
+ `syntaur session resume --json` to read the bound `assignmentSlug`,
43
+ `projectSlug` (may be null for standalone), and `assignmentDir`.
44
+ `.syntaur/context.json` is only a workspace marker — do not read the assignment
45
+ from it.
46
+
47
+ If there is no open engagement (no active assignment), abort with: "No active
48
+ assignment for this session — grab one first." Run `grab-assignment` first. Do
49
+ not invent values.
47
50
 
48
51
  ## Step 2: Confirm the prior plan was implemented
49
52
 
@@ -2,8 +2,9 @@
2
2
  name: resume-session
3
3
  description: >-
4
4
  Re-orient a fresh Syntaur session on the active assignment without
5
- re-reading the full transcript. Loads the latest saved session summary,
6
- `.syntaur/context.json`, and any open handoff. Use when the user says
5
+ re-reading the full transcript. Resolves the active assignment from the
6
+ session's open engagement and loads the latest saved session summary and any
7
+ open handoff. Use when the user says
7
8
  "resume", "pick up where we left off", "continue this assignment", or after
8
9
  a compact / new session start. Symmetric counterpart to
9
10
  `/save-session-summary`.
@@ -28,15 +29,20 @@ freely; nothing on disk changes.
28
29
  - Saving the current session's progress — that's `/save-session-summary` (the
29
30
  symmetric write side).
30
31
 
31
- ## Step 1: Verify there is an active context
32
+ ## Step 1: Verify there is an active assignment
32
33
 
33
34
  Run `syntaur session resume`. The CLI:
34
35
 
35
- 1. Reads `.syntaur/context.json` from the current working directory.
36
- 2. Aborts (exit 1) with a clear message if missing or if no `assignmentDir`
37
- is set telling the user to run `grab-assignment` first.
38
- 3. Otherwise scans `<assignmentDir>/sessions/<sid>/summary.md` and picks the
39
- most recently modified.
36
+ 1. Resolves the active assignment from the session's OPEN engagement (the
37
+ assignment this session is currently bound to). `.syntaur/context.json` is
38
+ only a workspace marker it identifies the repository/branch/worktree, not
39
+ the active assignment.
40
+ 2. Aborts (exit 1) with a clear message when there is no open engagement —
41
+ "No active assignment for this session — grab one first" — telling the user
42
+ to run `grab-assignment`.
43
+ 3. Otherwise resolves the assignment dir from the engagement and scans
44
+ `<assignmentDir>/sessions/<sid>/summary.md`, picking the most recently
45
+ modified.
40
46
  4. Reads `<assignmentDir>/handoff.md` (the canonical single-file handoff per
41
47
  assignment, managed by `complete-assignment`) and reports it if its body
42
48
  has been written beyond the scaffolded placeholder.
@@ -26,11 +26,11 @@ mid-assignment continuity** — distinct from `handoff.md`, which is the
26
26
 
27
27
  ## Step 1: Load Context
28
28
 
29
- Read `.syntaur/context.json` from the current working directory.
29
+ The active assignment is resolved from the session's open engagement. Run `syntaur session resume --json` to read it.
30
30
 
31
- If the file does not exist, tell the user: "No active assignment found. Run `grab-assignment` first." and stop.
31
+ If there is no open engagement (no active assignment), tell the user: "No active assignment for this session — grab one first." and stop. `.syntaur/context.json` is only a workspace marker; do not read the assignment from it.
32
32
 
33
- Extract:
33
+ From the resolved engagement, note:
34
34
  - `assignmentDir` (absolute path) — required.
35
35
 
36
36
  **Do not read the session id from `context.json` for identity.** That scalar is
@@ -86,7 +86,7 @@ printf '%s' "$BODY" | syntaur session save
86
86
  # Pass --session-id <id> only to override (e.g. you already have $CLAUDE_CODE_SESSION_ID).
87
87
  ```
88
88
 
89
- Resolves the active assignment from `.syntaur/context.json` (or pass
89
+ Resolves the active assignment from the session's open engagement (or pass
90
90
  `--assignment <slug> [--project <slug>]`). `--session-id` now defaults to the
91
91
  **resolved** session (env → process tree → transcript), falling back to the
92
92
  `context.json` hint only as a last resort — so a co-tenant that clobbered the
@@ -35,11 +35,14 @@ implementation code until workspace fields are set.
35
35
 
36
36
  ## Step 1: Resolve the assignment file
37
37
 
38
- Read `.syntaur/context.json` from the current working directory. Extract
39
- `assignmentDir`. The target file is `<assignmentDir>/assignment.md`.
38
+ The active assignment is resolved from the session's open engagement — `syntaur
39
+ workspace set` (Step 3) targets it automatically. `.syntaur/context.json` is
40
+ only a workspace marker; do not read the assignment from it.
40
41
 
41
- If no context, abort with: "No active assignment. Run `grab-assignment`
42
- first."
42
+ If there is no open engagement (no active assignment), the CLI aborts with "No
43
+ active assignment for this session — grab one first." Run `grab-assignment`
44
+ first, or pass `--assignment <slug> [--project <slug>]` to target one
45
+ explicitly.
43
46
 
44
47
  ## Step 2: Gather inputs
45
48
 
@@ -68,8 +71,8 @@ syntaur workspace set \
68
71
  --parent-branch <parent>
69
72
  ```
70
73
 
71
- Targets the active assignment from `.syntaur/context.json` by default; pass
72
- `--assignment <slug> [--project <slug>]` to target one explicitly. The command
74
+ Targets the active assignment from the session's open engagement by default;
75
+ pass `--assignment <slug> [--project <slug>]` to target one explicitly. The command
73
76
  does the whole safe write in one atomic step:
74
77
 
75
78
  - **Pre-write validation** — runs the same checks as `syntaur doctor