devflow-kit 3.2.0 → 3.4.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 (188) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/dist/agents/code.md +330 -0
  4. package/{src/assets → dist}/agents/design.md +11 -1
  5. package/{src/assets → dist}/agents/diagnose.md +17 -13
  6. package/dist/agents/git.md +29 -56
  7. package/{src/assets → dist}/agents/knowledge.md +8 -5
  8. package/{src/assets → dist}/agents/research.md +21 -2
  9. package/{src/assets → dist}/agents/review.md +18 -7
  10. package/{src/assets → dist}/agents/scrutinize.md +35 -11
  11. package/dist/agents/skim.md +148 -0
  12. package/{src/assets → dist}/agents/triage.md +8 -2
  13. package/dist/cli/commands/init.js +62 -0
  14. package/dist/cli/commands/learning.js +63 -8
  15. package/dist/cli/commands/uninstall.js +42 -1
  16. package/dist/commands/bug-analysis.md +30 -8
  17. package/dist/commands/code-review.md +142 -61
  18. package/dist/commands/debug.md +15 -13
  19. package/dist/commands/dynamic-build.md +176 -130
  20. package/dist/commands/dynamic-plan.md +31 -19
  21. package/dist/commands/dynamic-profile.md +27 -13
  22. package/dist/commands/dynamic-tickets.md +28 -14
  23. package/dist/commands/explore.md +16 -14
  24. package/dist/commands/implement.md +163 -158
  25. package/dist/commands/plan.md +37 -24
  26. package/dist/commands/release.md +69 -4
  27. package/dist/commands/research.md +33 -11
  28. package/dist/commands/resolve.md +61 -47
  29. package/dist/commands/self-review.md +50 -32
  30. package/dist/core/agent-models.js +53 -7
  31. package/dist/core/assets.js +55 -10
  32. package/dist/core/claude-md-audit.js +190 -0
  33. package/dist/core/feature-switch.js +20 -1
  34. package/dist/core/flags.js +28 -0
  35. package/dist/core/fs-atomic.js +8 -3
  36. package/dist/core/learning-tuning-config.js +8 -0
  37. package/dist/core/learning-variants.js +213 -0
  38. package/dist/core/manifest.js +62 -0
  39. package/dist/core/mds-variants.js +38 -1
  40. package/dist/core/plugins.js +78 -11
  41. package/{src/assets → dist/learning-off}/agents/code.md +77 -28
  42. package/dist/learning-off/agents/design.md +119 -0
  43. package/dist/learning-off/agents/diagnose.md +210 -0
  44. package/dist/learning-off/agents/knowledge.md +90 -0
  45. package/dist/learning-off/agents/research.md +149 -0
  46. package/dist/learning-off/agents/review.md +228 -0
  47. package/dist/learning-off/agents/scrutinize.md +117 -0
  48. package/{src/assets → dist/learning-off}/agents/skim.md +5 -10
  49. package/dist/learning-off/agents/triage.md +163 -0
  50. package/dist/learning-off/commands/bug-analysis.md +420 -0
  51. package/dist/learning-off/commands/code-review.md +525 -0
  52. package/dist/learning-off/commands/debug.md +294 -0
  53. package/dist/learning-off/commands/dynamic-build.md +1255 -0
  54. package/dist/learning-off/commands/dynamic-plan.md +424 -0
  55. package/dist/learning-off/commands/dynamic-profile.md +214 -0
  56. package/dist/learning-off/commands/dynamic-tickets.md +632 -0
  57. package/dist/learning-off/commands/explore.md +210 -0
  58. package/dist/learning-off/commands/implement.md +808 -0
  59. package/dist/learning-off/commands/plan.md +664 -0
  60. package/dist/learning-off/commands/release.md +310 -0
  61. package/dist/learning-off/commands/research.md +222 -0
  62. package/dist/learning-off/commands/resolve.md +837 -0
  63. package/dist/learning-off/commands/self-review.md +266 -0
  64. package/dist/skills/git/references/tracker/_contract.md +33 -0
  65. package/dist/skills/git/references/tracker/github/fetch-issue.md +2 -0
  66. package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +2 -0
  67. package/dist/skills/git/references/tracker/github/gather-release-evidence.md +5 -1
  68. package/dist/skills/git/references/tracker/github/post-wave-report.md +2 -0
  69. package/dist/skills/git/references/tracker/github/setup-task.md +12 -0
  70. package/dist/skills/git/references/tracker/jira/associate-release.md +1 -1
  71. package/dist/skills/git/references/tracker/jira/fetch-issue.md +2 -0
  72. package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +2 -0
  73. package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +5 -1
  74. package/dist/skills/git/references/tracker/jira/post-wave-report.md +2 -0
  75. package/dist/skills/git/references/tracker/jira/setup-task.md +14 -2
  76. package/dist/skills/git/references/tracker/linear/associate-release.md +1 -1
  77. package/dist/skills/git/references/tracker/linear/fetch-issue.md +2 -0
  78. package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +2 -0
  79. package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +5 -1
  80. package/dist/skills/git/references/tracker/linear/post-wave-report.md +2 -0
  81. package/dist/skills/git/references/tracker/linear/setup-task.md +14 -2
  82. package/dist/targets/claude-code/installer.js +72 -36
  83. package/dist/targets/claude-code/language-stamp.js +185 -0
  84. package/dist/targets/claude-code/learning-install.js +489 -0
  85. package/package.json +1 -1
  86. package/src/assets/agents/code.mds +339 -0
  87. package/src/assets/agents/design.mds +149 -0
  88. package/src/assets/agents/diagnose.mds +225 -0
  89. package/src/assets/agents/evaluate.md +12 -25
  90. package/src/assets/agents/git.mds +29 -56
  91. package/src/assets/agents/knowledge.mds +125 -0
  92. package/src/assets/agents/learning.md +4 -6
  93. package/src/assets/agents/research.mds +176 -0
  94. package/src/assets/agents/review.mds +286 -0
  95. package/src/assets/agents/scrutinize.mds +132 -0
  96. package/src/assets/agents/simplify.md +20 -0
  97. package/src/assets/agents/skim.mds +161 -0
  98. package/src/assets/agents/synthesize.md +12 -0
  99. package/src/assets/agents/test.md +2 -2
  100. package/src/assets/agents/triage.mds +194 -0
  101. package/src/assets/agents/validate.md +15 -8
  102. package/src/assets/commands/_partials/_compliance.mds +5 -4
  103. package/src/assets/commands/_partials/_decisions.mds +31 -0
  104. package/src/assets/commands/_partials/_engine.mds +29 -24
  105. package/src/assets/commands/_partials/_knowledge.mds +26 -13
  106. package/src/assets/commands/_partials/_plan_contract.mds +1 -1
  107. package/src/assets/commands/_partials/_preamble.mds +33 -9
  108. package/src/assets/commands/_partials/_publication.mds +5 -4
  109. package/src/assets/commands/_partials/_settings.mds +13 -5
  110. package/src/assets/commands/_partials/_wave.mds +16 -6
  111. package/src/assets/commands/bug-analysis.mds +24 -2
  112. package/src/assets/commands/code-review.mds +148 -45
  113. package/src/assets/commands/debug.mds +17 -1
  114. package/src/assets/commands/dynamic-build.mds +144 -65
  115. package/src/assets/commands/dynamic-plan.mds +36 -6
  116. package/src/assets/commands/dynamic-profile.mds +9 -1
  117. package/src/assets/commands/dynamic-tickets.mds +16 -2
  118. package/src/assets/commands/explore.mds +27 -1
  119. package/src/assets/commands/implement.mds +171 -138
  120. package/src/assets/commands/plan.mds +47 -8
  121. package/src/assets/commands/{release.md → release.mds} +27 -24
  122. package/src/assets/commands/research.mds +28 -4
  123. package/src/assets/commands/resolve.mds +69 -17
  124. package/src/assets/commands/self-review.mds +43 -13
  125. package/src/assets/mds/tracker/_common.mds +1 -1
  126. package/src/assets/mds/tracker/_contract.mds +72 -0
  127. package/src/assets/mds/tracker/_github.mds +13 -2
  128. package/src/assets/mds/tracker/_jira.mds +17 -5
  129. package/src/assets/mds/tracker/_linear.mds +17 -5
  130. package/src/assets/mds/tracker/_mcp.mds +2 -2
  131. package/src/assets/mds/tracker/_steps.mds +97 -0
  132. package/src/assets/rules/context-economy.md +10 -0
  133. package/src/assets/rules/go.md +1 -0
  134. package/src/assets/rules/java.md +1 -0
  135. package/src/assets/rules/python.md +1 -0
  136. package/src/assets/rules/rust.md +1 -0
  137. package/src/assets/rules/typescript.md +1 -0
  138. package/src/assets/scripts/ci-wait.cjs +636 -0
  139. package/src/assets/scripts/claude-md-audit.cjs +611 -0
  140. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +2 -3
  141. package/src/assets/scripts/hooks/background-memory-update +290 -9
  142. package/src/assets/scripts/hooks/json-helper.cjs +13 -5
  143. package/src/assets/scripts/hooks/json-parse +34 -10
  144. package/src/assets/scripts/hooks/session-start-context +315 -7
  145. package/src/assets/scripts/release-trace.cjs +27 -10
  146. package/src/assets/skills/accessibility/SKILL.md +1 -1
  147. package/src/assets/skills/apply-decisions/SKILL.md +12 -82
  148. package/src/assets/skills/apply-feature-knowledge/SKILL.md +9 -43
  149. package/src/assets/skills/architecture/SKILL.md +1 -1
  150. package/src/assets/skills/boundary-validation/SKILL.md +1 -1
  151. package/src/assets/skills/complexity/SKILL.md +1 -1
  152. package/src/assets/skills/compliance/SKILL.md +1 -1
  153. package/src/assets/skills/consistency/SKILL.md +1 -1
  154. package/src/assets/skills/database/SKILL.md +1 -1
  155. package/src/assets/skills/dependencies/SKILL.md +1 -1
  156. package/src/assets/skills/dependency-research/SKILL.md +3 -6
  157. package/src/assets/skills/design-review/SKILL.md +1 -1
  158. package/src/assets/skills/docs-framework/SKILL.md +1 -1
  159. package/src/assets/skills/documentation/SKILL.md +1 -1
  160. package/src/assets/skills/feature-knowledge/SKILL.md +43 -12
  161. package/src/assets/skills/gap-analysis/SKILL.md +1 -1
  162. package/src/assets/skills/git/SKILL.md +1 -1
  163. package/src/assets/skills/go/SKILL.md +1 -1
  164. package/src/assets/skills/java/SKILL.md +1 -1
  165. package/src/assets/skills/patterns/SKILL.md +1 -1
  166. package/src/assets/skills/performance/SKILL.md +1 -1
  167. package/src/assets/skills/python/SKILL.md +1 -1
  168. package/src/assets/skills/qa/SKILL.md +1 -3
  169. package/src/assets/skills/quality-gates/SKILL.md +10 -13
  170. package/src/assets/skills/quality-gates/references/report-template.md +20 -20
  171. package/src/assets/skills/react/SKILL.md +1 -1
  172. package/src/assets/skills/regression/SKILL.md +1 -1
  173. package/src/assets/skills/reliability/SKILL.md +1 -1
  174. package/src/assets/skills/research-codebase/SKILL.md +1 -1
  175. package/src/assets/skills/research-competitor/SKILL.md +1 -1
  176. package/src/assets/skills/research-external/SKILL.md +1 -1
  177. package/src/assets/skills/research-technology/SKILL.md +1 -1
  178. package/src/assets/skills/review-methodology/SKILL.md +1 -1
  179. package/src/assets/skills/rust/SKILL.md +1 -1
  180. package/src/assets/skills/security/SKILL.md +1 -1
  181. package/src/assets/skills/software-design/SKILL.md +1 -1
  182. package/src/assets/skills/test-driven-development/SKILL.md +9 -29
  183. package/src/assets/skills/testing/SKILL.md +1 -1
  184. package/src/assets/skills/typescript/SKILL.md +1 -1
  185. package/src/assets/skills/ui-design/SKILL.md +1 -1
  186. package/src/assets/skills/worktree-support/SKILL.md +3 -55
  187. package/src/assets/skills/worktree-support/references/discovery.md +48 -0
  188. package/src/assets/skills/worktree-support/references/roots.md +2 -2
@@ -2,7 +2,9 @@
2
2
 
3
3
  # background-memory-update — Eager working-memory refresh worker
4
4
  # Spawned as a detached background process by memory-worker (Stop hook) after the
5
- # 120s throttle expires. Drains .pending-turns.jsonl → calls `claude -p` (sonnet 5.5) →
5
+ # 120s throttle expires. Drains .pending-turns.jsonl → calls `claude -p` on the model
6
+ # and effort the `agents.memory` entry of ~/.devflow/agent-models.json names, else the
7
+ # shipped worker default, in a lean session (D-MEMORY-WORKER-LEAN) →
6
8
  # rewrites WORKING-MEMORY.md with a git-reconciliation stamp on line 1.
7
9
  #
8
10
  # Applies the LLM-vs-plumbing principle: artifact content (WORKING-MEMORY.md) is
@@ -118,6 +120,92 @@ if ! df_no_symlink_below "$PROJECT_ROOT" "$LOCK_DIR" "$QUEUE_FILE" "$PROCESSING_
118
120
  exit 0
119
121
  fi
120
122
 
123
+ # --- CLI version probe: choose the argv form (D-MEMORY-WORKER-LEAN) ---
124
+ # The lean argv (--effort, --safe-mode, --max-turns, ...) is verified from
125
+ # MEMORY_MIN_CLAUDE_VERSION up. A CLI older than that, or one whose version cannot be
126
+ # read, gets the legacy argv instead, because one flag a CLI does not know fails every
127
+ # run. The probe runs here, once $CLAUDE_BIN has resolved and the link check has
128
+ # passed, and before the lock is taken, so a slow or hung `--version` can never extend
129
+ # the lock's hold. It is bounded by DEVFLOW_BG_VERSION_PROBE_SECS (default 10, at most
130
+ # 60; tests override it as they do DEVFLOW_BG_WATCHDOG_SECS) with the watchdog's
131
+ # background-and-kill idiom. Its output is parsed, never executed.
132
+ #
133
+ # Errexit safety: the worker runs under `set -e`, and a helper's last non-zero status
134
+ # aborts the caller, so each helper below ends in an explicit return status and is
135
+ # called inside an `if` condition.
136
+ MEMORY_MIN_CLAUDE_VERSION="2.1.286"
137
+
138
+ # Sets CLAUDE_VERSION_TEXT to the first 512 bytes `claude --version` printed ("" on any
139
+ # failure). Returns 0 when it exited 0 inside the bound, else 1. A hung probe gets
140
+ # SIGTERM to its own process group (set -m, as for the spawn below) after the bound
141
+ # and SIGKILL 2s later; the watchdog subshell has its descriptors closed so a sleep it
142
+ # leaves behind holds no pipe open.
143
+ probe_claude_version() {
144
+ local _secs _out _pid _wd_pid _status=1
145
+ CLAUDE_VERSION_TEXT=""
146
+ _secs="${DEVFLOW_BG_VERSION_PROBE_SECS:-10}"
147
+ case "$_secs" in
148
+ ''|*[!0123456789]*|????*) _secs=10 ;;
149
+ esac
150
+ if [ "$_secs" -lt 1 ] || [ "$_secs" -gt 60 ]; then _secs=10; fi
151
+ _out=$(mktemp "${TMPDIR:-/tmp}/devflow-claude-version.XXXXXX" 2>/dev/null) || return 1
152
+ set -m
153
+ "$CLAUDE_BIN" --version </dev/null >"$_out" 2>/dev/null &
154
+ _pid=$!
155
+ set +m
156
+ (
157
+ sleep "$_secs"
158
+ kill -TERM -"$_pid" 2>/dev/null || true
159
+ sleep 2
160
+ kill -KILL -"$_pid" 2>/dev/null || true
161
+ ) </dev/null >/dev/null 2>&1 &
162
+ _wd_pid=$!
163
+ if wait "$_pid" 2>/dev/null; then _status=0; fi
164
+ kill "$_wd_pid" 2>/dev/null || true
165
+ wait "$_wd_pid" 2>/dev/null || true
166
+ if [ "$_status" -eq 0 ]; then
167
+ CLAUDE_VERSION_TEXT=$(head -c 512 "$_out" 2>/dev/null || true)
168
+ fi
169
+ rm -f "$_out" 2>/dev/null || true
170
+ return "$_status"
171
+ }
172
+
173
+ # Returns 0 when the first X.Y.Z in $1 is at or above the first X.Y.Z in $2, comparing
174
+ # the three components as base-10 numbers left to right (2.10.0 is above 2.1.286, and
175
+ # a leading zero is not octal). Returns 1 when it is below, or when either text holds
176
+ # no X.Y.Z or a component longer than six digits. The digit set is spelled out: a
177
+ # range such as 0-9 collates by locale on bash 3.2.
178
+ claude_version_at_least() {
179
+ local _text="$1" _floor="$2" _re='([0123456789]+)\.([0123456789]+)\.([0123456789]+)'
180
+ local _f1 _f2 _f3 _v1 _v2 _v3
181
+ [[ "$_floor" =~ $_re ]] || return 1
182
+ _f1=${BASH_REMATCH[1]}; _f2=${BASH_REMATCH[2]}; _f3=${BASH_REMATCH[3]}
183
+ [[ "$_text" =~ $_re ]] || return 1
184
+ _v1=${BASH_REMATCH[1]}; _v2=${BASH_REMATCH[2]}; _v3=${BASH_REMATCH[3]}
185
+ if [ "${#_v1}" -gt 6 ] || [ "${#_v2}" -gt 6 ] || [ "${#_v3}" -gt 6 ]; then return 1; fi
186
+ _v1=$((10#$_v1)); _v2=$((10#$_v2)); _v3=$((10#$_v3))
187
+ _f1=$((10#$_f1)); _f2=$((10#$_f2)); _f3=$((10#$_f3))
188
+ if [ "$_v1" -ne "$_f1" ]; then
189
+ if [ "$_v1" -gt "$_f1" ]; then return 0; fi
190
+ return 1
191
+ fi
192
+ if [ "$_v2" -ne "$_f2" ]; then
193
+ if [ "$_v2" -gt "$_f2" ]; then return 0; fi
194
+ return 1
195
+ fi
196
+ if [ "$_v3" -ge "$_f3" ]; then return 0; fi
197
+ return 1
198
+ }
199
+
200
+ CLAUDE_ARGV_FORM="legacy"
201
+ if ! probe_claude_version; then
202
+ log "claude --version probe failed or timed out — using the legacy argv"
203
+ elif claude_version_at_least "$CLAUDE_VERSION_TEXT" "$MEMORY_MIN_CLAUDE_VERSION"; then
204
+ CLAUDE_ARGV_FORM="lean"
205
+ else
206
+ log "claude --version is below ${MEMORY_MIN_CLAUDE_VERSION} or unparseable — using the legacy argv"
207
+ fi
208
+
121
209
  # --- Worker-level lock (300s stale-break — much longer than learning-lock's 30s) ---
122
210
  # This prevents a second worker (spawned 121s later) from double-writing WORKING-MEMORY.md
123
211
  # while the first worker's claude -p call (up to 120s) is still in flight.
@@ -171,7 +259,20 @@ fi
171
259
  # Clean up any staged file left by a watchdog-killed prior run.
172
260
  # Without this, a stale staged file with a valid stamp would be mistakenly
173
261
  # mv-ed to the real path on the NEXT run's CAS check (D-MEMORY-STAGED-CAS).
262
+ #
263
+ # D-MEMORY-WORKER-LEAN: the removal is also a CHECKED precondition. The model gets the
264
+ # Write tool alone, and Write refuses a file that exists and has not been read, which
265
+ # is how an earlier Write-only worker failed silently: every Write errored while the
266
+ # run exited 0. Once nothing stands at the staged path, the one Write is always a
267
+ # create, so Write-only is safe. A path `rm -f` cannot clear (a directory, say) would
268
+ # make every Write fail, so the run stops here instead: no LLM call, the batch and
269
+ # the queue untouched (nothing has been claimed yet), the lock released by the EXIT
270
+ # trap. `-e` or `-L`, so a link there counts as present.
174
271
  rm -f "$STAGED_FILE" 2>/dev/null || true
272
+ if [ -e "$STAGED_FILE" ] || [ -L "$STAGED_FILE" ]; then
273
+ log "SKIP: $STAGED_FILE could not be removed — a Write to it would fail, so no LLM run; the batch and the queue are left as they are"
274
+ exit 0
275
+ fi
175
276
 
176
277
  # --- Orphan-only skip: if queue has no assistant/qa turn, exit and leave the queue ---
177
278
  # This prevents fabrication-prone LLM runs with only user turns in the queue.
@@ -500,7 +601,107 @@ Instructions:
500
601
  EOF
501
602
  )
502
603
 
503
- log "Spawning claude -p (model claude-sonnet-5-5, ${TURN_COUNT} turns)"
604
+ # --- Model and effort (D-MEMORY-WORKER-LEAN) ---
605
+ # One authority: WORKER_AGENTS.memory in src/core/agent-models.ts, which `devflow
606
+ # agents` shows as the worker's default. The two defaults below are that row spelled
607
+ # out for bash, held equal to it by tests/agent-models-worker.test.ts, as are the
608
+ # alias and effort lists and the name rules. A personal `agents.memory` entry in
609
+ # $HOME/.devflow/agent-models.json (the machine root; nothing relocates it) overrides
610
+ # them. Each field is validated on its own against the worker domain, so a bad model
611
+ # keeps a good effort and the reverse:
612
+ # model = `default` (the shipped one), one of the aliases, or a `claude-` name;
613
+ # a name starts with a letter or digit, continues with those and . _ -,
614
+ # and is 64 characters at most (MODEL_NAME_RE in src/core/agent-frontmatter.ts)
615
+ # effort = `default` (the shipped one) or one of the levels
616
+ # `inherit` is outside both, since a background worker has no session to inherit from.
617
+ # Only a value that passed its match reaches argv. A value that is absent, empty or
618
+ # not a JSON string reads as absent and takes the shipped one silently; a string that
619
+ # fails its match takes it with one log line naming the field, never the value.
620
+ # The character sets are spelled out because a range such as A-Za-z matches accented
621
+ # letters in a UTF-8 locale on bash 3.2.
622
+ MEMORY_MODEL_DEFAULT="claude-sonnet-5-5"
623
+ MEMORY_EFFORT_DEFAULT="high"
624
+ MEMORY_MODEL_ALIASES="haiku sonnet opus fable"
625
+ MEMORY_MODEL_PREFIX="claude-"
626
+ MEMORY_MODEL_MAX_LEN=64
627
+ MEMORY_EFFORT_LEVELS="low medium high xhigh max"
628
+ MEMORY_NAME_HEAD="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
629
+ MEMORY_EFFORT_CHARS="abcdefghijklmnopqrstuvwxyz"
630
+
631
+ # Prints the string value of agents.memory.<field> followed by one `x` (the caller strips
632
+ # it, so a value's own trailing newline survives the command substitution). Prints just
633
+ # the `x` when the file, the field or a string there is missing. Always returns 0.
634
+ memory_agent_field() {
635
+ local _file="$HOME/.devflow/agent-models.json"
636
+ if [ "$_JSON_AVAILABLE" = "true" ] && [ -f "$_file" ]; then
637
+ json_string_field_file "$_file" "agents.memory.$1" 2>/dev/null || true
638
+ fi
639
+ printf x
640
+ return 0
641
+ }
642
+
643
+ # Sets MEMORY_MODEL. Always returns 0.
644
+ resolve_memory_model() {
645
+ local _raw _tail="${MEMORY_NAME_HEAD}._-" _ok=""
646
+ MEMORY_MODEL="$MEMORY_MODEL_DEFAULT"
647
+ _raw=$(memory_agent_field model)
648
+ _raw=${_raw%x}
649
+ case "$_raw" in
650
+ ''|default) return 0 ;;
651
+ [!$MEMORY_NAME_HEAD]*|*[!$_tail]*) ;;
652
+ *)
653
+ if [ "${#_raw}" -le "$MEMORY_MODEL_MAX_LEN" ]; then
654
+ case " $MEMORY_MODEL_ALIASES " in
655
+ *" $_raw "*) _ok="yes" ;;
656
+ esac
657
+ case "$_raw" in
658
+ "$MEMORY_MODEL_PREFIX"?*) _ok="yes" ;;
659
+ esac
660
+ fi
661
+ ;;
662
+ esac
663
+ if [ -n "$_ok" ]; then
664
+ MEMORY_MODEL="$_raw"
665
+ else
666
+ log "agents.memory.model: not a model the worker accepts (default, an alias or a ${MEMORY_MODEL_PREFIX} name) — using the shipped model"
667
+ fi
668
+ return 0
669
+ }
670
+
671
+ # Sets MEMORY_EFFORT. Always returns 0.
672
+ resolve_memory_effort() {
673
+ local _raw _ok=""
674
+ MEMORY_EFFORT="$MEMORY_EFFORT_DEFAULT"
675
+ _raw=$(memory_agent_field effort)
676
+ _raw=${_raw%x}
677
+ case "$_raw" in
678
+ ''|default) return 0 ;;
679
+ *[!$MEMORY_EFFORT_CHARS]*) ;;
680
+ *)
681
+ case " $MEMORY_EFFORT_LEVELS " in
682
+ *" $_raw "*) _ok="yes" ;;
683
+ esac
684
+ ;;
685
+ esac
686
+ if [ -n "$_ok" ]; then
687
+ MEMORY_EFFORT="$_raw"
688
+ else
689
+ log "agents.memory.effort: not an effort level the worker accepts (default or a level) — using the shipped effort"
690
+ fi
691
+ return 0
692
+ }
693
+
694
+ resolve_memory_model
695
+ resolve_memory_effort
696
+
697
+ if [ "$CLAUDE_ARGV_FORM" = "lean" ]; then
698
+ EFFORT_LOG="effort ${MEMORY_EFFORT}"
699
+ ARGV_LOG="lean argv"
700
+ else
701
+ EFFORT_LOG="no effort passed"
702
+ ARGV_LOG="legacy argv"
703
+ fi
704
+ log "Spawning claude -p (model ${MEMORY_MODEL}, ${EFFORT_LOG}, ${ARGV_LOG}, ${TURN_COUNT} turns)"
504
705
  # SECURITY: never log PROMPT — it contains turn content which may include secrets
505
706
 
506
707
  # --- Watchdog timeout (overridable for tests: DEVFLOW_BG_WATCHDOG_SECS=2) ---
@@ -537,14 +738,94 @@ fi
537
738
  # Restore set +m immediately after capturing CLAUDE_PID so the watchdog subshell and
538
739
  # other subsequent background operations stay in the worker's group.
539
740
  # Job-control notifications go to stderr which is redirected to the log — suppress them.
741
+ #
742
+ # D-MEMORY-WORKER-LEAN — the spawn. The worker opens a headless session to make one
743
+ # Write, so the session is cut to what that Write needs. Prompt on stdin, output and
744
+ # errors to the log, as before. Two argv forms, chosen by the version probe above:
745
+ #
746
+ # lean (CLI at or above MEMORY_MIN_CLAUDE_VERSION):
747
+ # -p --model M --effort E --safe-mode --tools Write --disable-slash-commands \
748
+ # --strict-mcp-config --mcp-config '{"mcpServers":{}}' --max-turns 3 \
749
+ # --dangerously-skip-permissions --output-format text
750
+ # legacy (older, unreadable or timed-out probe):
751
+ # -p --model M --tools Write --dangerously-skip-permissions --output-format text
752
+ #
753
+ # What each flag is for:
754
+ # --model, --effort the resolved agents.memory values above. --effort is the only
755
+ # effort source: an inherited CLAUDE_CODE_EFFORT_LEVEL beats the
756
+ # flag (measured on 2026-10-09), so it is unset below, for both
757
+ # forms, and the log's "no effort passed" is then true.
758
+ # --safe-mode starts with the user's customisations off: instruction files,
759
+ # skills, plugins, hooks, MCP servers, custom commands and agents.
760
+ # Auth, model selection and built-in tools work as usual, and
761
+ # settings `env` (the provider URL and key) still applies under
762
+ # it (measured on 2026-10-09), so a routed or keyed install works.
763
+ # --tools Write Write is the only tool. `--allowedTools` only pre-approves, it
764
+ # leaves every tool, listing and MCP server loaded.
765
+ # --disable-slash-commands, --strict-mcp-config with an empty --mcp-config
766
+ # no slash-command or skill listing, no MCP server and none of the
767
+ # instructions they add to the context.
768
+ # --max-turns 3 the worker's own pattern is one Write and then the end of the
769
+ # turn, two requests; the third is margin. A run that reaches the
770
+ # cap exits non-zero, so its batch stays for the retry; the Write
771
+ # on the capped turn still runs. 3 was verified on claude 2.1.294
772
+ # on 2026-10-09 (cap 1 exits 1, caps 2 and 3 exit 0 on that
773
+ # pattern). `--max-turns` is not listed in `claude --help` there
774
+ # but is registered (`--max-turns abc` errors "must be a number"),
775
+ # which is why it was exercised rather than read off the help.
776
+ # --dangerously-skip-permissions headless: nothing could answer a prompt.
777
+ # --output-format text
778
+ # DEVFLOW_BG_UPDATER=1 the recursion guard stays on both forms, though --safe-mode
779
+ # also disables hooks. CLAUDE_CODE_DISABLE_CLAUDE_MDS=1 and
780
+ # CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 keep the instruction files and
781
+ # auto-memory out on both forms, including the legacy one.
782
+ #
783
+ # Why Write-only is safe: the staged path is empty before every run (the removal and
784
+ # the checked precondition after the lock above), so the one Write is always a create.
785
+ # Write refuses an existing file that has not been read, and that is the whole history
786
+ # of this flag set:
787
+ # a472a490 passed `--tools "Write"` with `--allowedTools` for two memory files;
788
+ # e701cecc widened the tools to `Read,Write,Bash` after counting the silent
789
+ # read-before-write failures (the run exited 0 while every Write errored);
790
+ # baf02265 removed both flags, with no reason in its message;
791
+ # 6186645f raised the worker's model and kept `--allowedTools 'Read,Write'`.
792
+ #
793
+ # The version floor is 2.1.286, the lowest CLI on which the whole lean argv was run
794
+ # and its flags, the settings `env` under --safe-mode and the effort override were
795
+ # checked (2.1.286 and 2.1.292 to 2.1.294 behave identically; 2026-10-09, recorded in
796
+ # docs/reference/platform-assumptions.md). The legacy form carries no --effort,
797
+ # --safe-mode, --max-turns, --disable-slash-commands, --strict-mcp-config or
798
+ # --mcp-config, none of which is verified on older CLIs, so on it effort is not
799
+ # applied, MCP servers stay loaded and there is no turn cap.
800
+ #
801
+ # The turns input has no byte cap: the queue holds every turn whole by design, and the
802
+ # prompt is bounded by the newest-20-rows window above and the 64 KB existing-memory
803
+ # cap, not by cutting a turn.
804
+ unset CLAUDE_CODE_EFFORT_LEVEL
540
805
  set -m
541
- DEVFLOW_BG_UPDATER=1 "$CLAUDE_BIN" -p \
542
- --model claude-sonnet-5-5 \
543
- --dangerously-skip-permissions \
544
- --allowedTools 'Read,Write' \
545
- --output-format text \
546
- <<< "$PROMPT" \
547
- >> "$LOG_FILE" 2>&1 &
806
+ if [ "$CLAUDE_ARGV_FORM" = "lean" ]; then
807
+ DEVFLOW_BG_UPDATER=1 CLAUDE_CODE_DISABLE_CLAUDE_MDS=1 CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 "$CLAUDE_BIN" -p \
808
+ --model "$MEMORY_MODEL" \
809
+ --effort "$MEMORY_EFFORT" \
810
+ --safe-mode \
811
+ --tools Write \
812
+ --disable-slash-commands \
813
+ --strict-mcp-config \
814
+ --mcp-config '{"mcpServers":{}}' \
815
+ --max-turns 3 \
816
+ --dangerously-skip-permissions \
817
+ --output-format text \
818
+ <<< "$PROMPT" \
819
+ >> "$LOG_FILE" 2>&1 &
820
+ else
821
+ DEVFLOW_BG_UPDATER=1 CLAUDE_CODE_DISABLE_CLAUDE_MDS=1 CLAUDE_CODE_DISABLE_AUTO_MEMORY=1 "$CLAUDE_BIN" -p \
822
+ --model "$MEMORY_MODEL" \
823
+ --tools Write \
824
+ --dangerously-skip-permissions \
825
+ --output-format text \
826
+ <<< "$PROMPT" \
827
+ >> "$LOG_FILE" 2>&1 &
828
+ fi
548
829
  CLAUDE_PID=$!
549
830
  set +m
550
831
 
@@ -13,7 +13,7 @@
13
13
  // get-field <field> [default] Read field from stdin JSON
14
14
  // get-string-field <field> Read field from stdin JSON only when it is a string
15
15
  // extract-cwd-field <field> Extract cwd + arbitrary field, SOH-byte delimited
16
- // session-output <context> Build SessionStart output envelope
16
+ // session-output <context> [message] Build SessionStart output envelope (+ systemMessage)
17
17
  // prompt-output <context> Build UserPromptSubmit output envelope
18
18
  // backup-construct Build pre-compact backup JSON from --arg pairs
19
19
  // assign-anchor <decision|pitfall> <obs_id>
@@ -293,13 +293,21 @@ try {
293
293
  }
294
294
 
295
295
  case 'session-output': {
296
+ // D-SYSTEMMESSAGE-ENVELOPE (json-parse json_session_output): an optional second
297
+ // argument is a user-facing message, carried in the top-level `systemMessage` key.
298
+ // One argument, or an empty message, gives today's envelope; a message with an
299
+ // empty context gives `systemMessage` alone, with no hookSpecificOutput key.
296
300
  const ctx = args[0];
297
- console.log(JSON.stringify({
298
- hookSpecificOutput: {
301
+ const message = args[1] || '';
302
+ const envelope = {};
303
+ if (message === '' || ctx !== '') {
304
+ envelope.hookSpecificOutput = {
299
305
  hookEventName: 'SessionStart',
300
306
  additionalContext: ctx,
301
- },
302
- }));
307
+ };
308
+ }
309
+ if (message !== '') envelope.systemMessage = message;
310
+ console.log(JSON.stringify(envelope));
303
311
  break;
304
312
  }
305
313
 
@@ -98,18 +98,42 @@ json_extract_cwd_prompt() {
98
98
 
99
99
  # --- Hook output envelopes ---
100
100
 
101
- # Build SessionStart output. Usage: json_session_output "$CONTEXT"
101
+ # Build SessionStart output. Usage: json_session_output "$CONTEXT" ["$SYSTEM_MESSAGE"]
102
+ #
103
+ # D-SYSTEMMESSAGE-ENVELOPE: the optional second argument is a message shown to the
104
+ # user, carried in the top-level `systemMessage` key beside (never inside)
105
+ # `hookSpecificOutput`; it does not enter the model's context. With ONE argument the
106
+ # envelope is exactly what every existing caller has always received, byte for byte
107
+ # (ensure-proxy, session-start-orchestrator, session-start-memory and Sections 1-5 of
108
+ # session-start-context pass one). With two:
109
+ # - a message and a context -> both keys, hookSpecificOutput first;
110
+ # - a message and no context -> `systemMessage` alone, with NO hookSpecificOutput key;
111
+ # - an empty message -> the one-argument envelope for that context.
112
+ # The node fallback (json-helper.cjs session-output) builds the same content, compact
113
+ # where jq pretty-prints.
102
114
  json_session_output() {
103
- local ctx="$1"
104
- if [ "$_HAS_JQ" = "true" ]; then
105
- jq -n --arg ctx "$ctx" '{
106
- "hookSpecificOutput": {
107
- "hookEventName": "SessionStart",
108
- "additionalContext": $ctx
109
- }
110
- }'
115
+ local ctx="$1" msg="${2:-}"
116
+ if [ "$#" -lt 2 ] || [ -z "$msg" ]; then
117
+ if [ "$_HAS_JQ" = "true" ]; then
118
+ jq -n --arg ctx "$ctx" '{
119
+ "hookSpecificOutput": {
120
+ "hookEventName": "SessionStart",
121
+ "additionalContext": $ctx
122
+ }
123
+ }'
124
+ else
125
+ node "$_JSON_HELPER" session-output "$ctx"
126
+ fi
127
+ elif [ "$_HAS_JQ" = "true" ]; then
128
+ jq -n --arg ctx "$ctx" --arg msg "$msg" '
129
+ (if $ctx != "" then {
130
+ "hookSpecificOutput": {
131
+ "hookEventName": "SessionStart",
132
+ "additionalContext": $ctx
133
+ }
134
+ } else {} end) + {"systemMessage": $msg}'
111
135
  else
112
- node "$_JSON_HELPER" session-output "$ctx"
136
+ node "$_JSON_HELPER" session-output "$ctx" "$msg"
113
137
  fi
114
138
  }
115
139