devflow-kit 3.1.0 → 3.3.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.
- package/CHANGELOG.md +52 -0
- package/README.md +2 -2
- package/dist/cli/agents-view/render.js +69 -15
- package/dist/cli/agents-view/state.js +40 -14
- package/dist/cli/commands/agents.js +135 -45
- package/dist/cli/commands/init.js +128 -53
- package/dist/cli/commands/learning.js +61 -13
- package/dist/cli/commands/memory.js +35 -14
- package/dist/cli/commands/uninstall.js +163 -39
- package/dist/commands/code-review.md +1 -3
- package/dist/commands/debug.md +15 -12
- package/dist/commands/dynamic-build.md +172 -135
- package/dist/commands/dynamic-plan.md +9 -3
- package/dist/commands/explore.md +10 -4
- package/dist/commands/implement.md +149 -145
- package/dist/commands/plan.md +13 -9
- package/dist/commands/release.md +8 -2
- package/dist/commands/research.md +8 -2
- package/dist/commands/resolve.md +28 -19
- package/dist/commands/self-review.md +16 -13
- package/dist/core/agent-frontmatter.js +25 -0
- package/dist/core/agent-models.js +201 -36
- package/dist/core/agent-state.js +27 -5
- package/dist/core/assets.js +1 -1
- package/dist/core/feature-config.js +68 -10
- package/dist/core/flags.js +24 -0
- package/dist/core/learning-queue-cleanup.js +10 -11
- package/dist/core/learning-tuning-config.js +8 -0
- package/dist/core/linked-path.js +46 -0
- package/dist/core/plugins.js +16 -5
- package/dist/core/queue-drain.js +31 -0
- package/dist/hud/components/learning-counts.js +54 -8
- package/dist/skills/git/references/tracker/github/create-release.md +2 -2
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +1 -1
- package/dist/skills/git/references/tracker/jira/create-release.md +2 -2
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +1 -1
- package/dist/skills/git/references/tracker/linear/create-release.md +2 -2
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +1 -1
- package/dist/targets/claude-code/installer.js +36 -9
- package/dist/targets/claude-code/post-install.js +128 -38
- package/package.json +1 -1
- package/src/assets/agents/code.md +85 -35
- package/src/assets/agents/design.md +12 -0
- package/src/assets/agents/diagnose.md +18 -11
- package/src/assets/agents/evaluate.md +17 -24
- package/src/assets/agents/knowledge.md +7 -3
- package/src/assets/agents/learning.md +4 -6
- package/src/assets/agents/research.md +21 -0
- package/src/assets/agents/review.md +12 -0
- package/src/assets/agents/scrutinize.md +37 -9
- package/src/assets/agents/simplify.md +24 -0
- package/src/assets/agents/skim.md +6 -2
- package/src/assets/agents/synthesize.md +18 -0
- package/src/assets/agents/test.md +19 -11
- package/src/assets/agents/triage.md +8 -0
- package/src/assets/agents/validate.md +20 -11
- package/src/assets/commands/_partials/_engine.mds +36 -55
- package/src/assets/commands/_partials/_knowledge.mds +1 -3
- package/src/assets/commands/_partials/_plan_contract.mds +1 -1
- package/src/assets/commands/_partials/_tracker.mds +1 -1
- package/src/assets/commands/_partials/_wave.mds +8 -6
- package/src/assets/commands/code-review.mds +1 -3
- package/src/assets/commands/debug.mds +13 -8
- package/src/assets/commands/dynamic-build.mds +126 -72
- package/src/assets/commands/dynamic-plan.mds +7 -1
- package/src/assets/commands/explore.mds +9 -1
- package/src/assets/commands/implement.mds +147 -141
- package/src/assets/commands/plan.mds +12 -8
- package/src/assets/commands/release.md +8 -2
- package/src/assets/commands/research.mds +8 -2
- package/src/assets/commands/resolve.mds +27 -16
- package/src/assets/commands/self-review.mds +15 -10
- package/src/assets/mds/tracker/_common.mds +1 -1
- package/src/assets/mds/tracker/_github.mds +2 -2
- package/src/assets/mds/tracker/_jira.mds +2 -2
- package/src/assets/mds/tracker/_linear.mds +2 -2
- package/src/assets/scripts/ci-wait.cjs +636 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -3
- package/src/assets/scripts/hooks/background-memory-update +356 -17
- package/src/assets/scripts/hooks/capture-prompt +4 -3
- package/src/assets/scripts/hooks/capture-question +4 -3
- package/src/assets/scripts/hooks/capture-turn +4 -3
- package/src/assets/scripts/hooks/ensure-devflow-init +13 -1
- package/src/assets/scripts/hooks/ensure-root-gitignore +122 -10
- package/src/assets/scripts/hooks/git-marker +71 -0
- package/src/assets/scripts/hooks/json-helper.cjs +12 -145
- package/src/assets/scripts/hooks/json-parse +24 -129
- package/src/assets/scripts/hooks/lib/learning-store.cjs +169 -64
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +1 -1
- package/src/assets/scripts/hooks/memory-worker +10 -0
- package/src/assets/scripts/hooks/pre-compact-memory +66 -14
- package/src/assets/scripts/hooks/preamble +9 -1
- package/src/assets/scripts/hooks/queue-append +53 -21
- package/src/assets/scripts/hooks/session-start-context +108 -29
- package/src/assets/scripts/hooks/session-start-memory +33 -11
- package/src/assets/scripts/release-trace.cjs +27 -10
- package/src/assets/skills/accessibility/SKILL.md +1 -1
- package/src/assets/skills/apply-decisions/SKILL.md +12 -82
- package/src/assets/skills/apply-feature-knowledge/SKILL.md +8 -42
- package/src/assets/skills/architecture/SKILL.md +1 -1
- package/src/assets/skills/boundary-validation/SKILL.md +1 -1
- package/src/assets/skills/complexity/SKILL.md +1 -1
- package/src/assets/skills/compliance/SKILL.md +1 -1
- package/src/assets/skills/consistency/SKILL.md +1 -1
- package/src/assets/skills/database/SKILL.md +1 -1
- package/src/assets/skills/dependencies/SKILL.md +1 -1
- package/src/assets/skills/dependency-research/SKILL.md +3 -6
- package/src/assets/skills/design-review/SKILL.md +1 -1
- package/src/assets/skills/docs-framework/SKILL.md +1 -1
- package/src/assets/skills/documentation/SKILL.md +1 -1
- package/src/assets/skills/gap-analysis/SKILL.md +1 -1
- package/src/assets/skills/git/SKILL.md +1 -1
- package/src/assets/skills/go/SKILL.md +1 -1
- package/src/assets/skills/java/SKILL.md +1 -1
- package/src/assets/skills/patterns/SKILL.md +1 -1
- package/src/assets/skills/performance/SKILL.md +1 -1
- package/src/assets/skills/python/SKILL.md +1 -1
- package/src/assets/skills/qa/SKILL.md +1 -3
- package/src/assets/skills/quality-gates/SKILL.md +9 -12
- package/src/assets/skills/quality-gates/references/report-template.md +20 -20
- package/src/assets/skills/react/SKILL.md +1 -1
- package/src/assets/skills/regression/SKILL.md +1 -1
- package/src/assets/skills/reliability/SKILL.md +1 -1
- package/src/assets/skills/research-codebase/SKILL.md +1 -1
- package/src/assets/skills/research-competitor/SKILL.md +1 -1
- package/src/assets/skills/research-external/SKILL.md +1 -1
- package/src/assets/skills/research-technology/SKILL.md +1 -1
- package/src/assets/skills/review-methodology/SKILL.md +1 -1
- package/src/assets/skills/rust/SKILL.md +1 -1
- package/src/assets/skills/security/SKILL.md +1 -1
- package/src/assets/skills/software-design/SKILL.md +1 -1
- package/src/assets/skills/test-driven-development/SKILL.md +15 -33
- package/src/assets/skills/testing/SKILL.md +1 -1
- package/src/assets/skills/typescript/SKILL.md +1 -1
- package/src/assets/skills/ui-design/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/SKILL.md +3 -55
- package/src/assets/skills/worktree-support/references/discovery.md +48 -0
- package/src/assets/skills/worktree-support/references/roots.md +2 -2
|
@@ -8,21 +8,28 @@
|
|
|
8
8
|
#
|
|
9
9
|
# Source-order requirement: source json-parse (for _HAS_JQ) and learning-lock (for
|
|
10
10
|
# learning_lock_acquire/learning_lock_release, which itself requires get-mtime) BEFORE
|
|
11
|
-
# calling queue_append_row or queue_append_both
|
|
11
|
+
# calling queue_append_row or queue_append_both, and define log/dbg. This file
|
|
12
|
+
# sources its sibling git-marker itself, for df_no_symlink_below. Sourcing it only
|
|
12
13
|
# defines functions -- no side effects until they are called.
|
|
13
14
|
#
|
|
14
|
-
# queue_append_row <queue_file> <role> <content> <ts>
|
|
15
|
-
# Appends one JSONL row {role, content, ts} to <queue_file
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
15
|
+
# queue_append_row <root> <queue_file> <role> <content> <ts>
|
|
16
|
+
# Appends one JSONL row {role, content, ts} to <queue_file>, which lies below
|
|
17
|
+
# <root>: the project root for the memory queue, the ledger root for the learning
|
|
18
|
+
# queue. When the queue, or a folder between <root> and it, is a symbolic link,
|
|
19
|
+
# nothing is written: the skip is logged once and the call returns 0, as a
|
|
20
|
+
# skipped capture does (D-HOOKS-NO-SYMLINK, git-marker). Otherwise it
|
|
21
|
+
# creates the queue's folder if absent, and the file with mode 0600 (umask 077).
|
|
22
|
+
# After appending, truncates from 200 to the newest 100 lines under a lock
|
|
23
|
+
# (learning_lock_acquire on "<queue_file>.lock", 2s timeout), keeping mode 0600.
|
|
24
|
+
# The append itself is intentionally lock-free (accepted-class race shared with
|
|
25
|
+
# the pre-existing memory design -- see the design doc's "Append-vs-claim race"
|
|
26
|
+
# note).
|
|
21
27
|
#
|
|
22
|
-
# queue_append_both <memory_queue> <learning_queue> <memory_enabled> <learning_enabled> <role> <content> <ts>
|
|
23
|
-
# Calls queue_append_row for each queue whose *_enabled flag is "true"
|
|
24
|
-
# queue is gated independently -- callers
|
|
25
|
-
# themselves (see queue_read_gates below
|
|
28
|
+
# queue_append_both <memory_root> <memory_queue> <learning_root> <learning_queue> <memory_enabled> <learning_enabled> <role> <content> <ts>
|
|
29
|
+
# Calls queue_append_row for each queue whose *_enabled flag is "true", with the
|
|
30
|
+
# root that queue lies below. Each queue is gated independently -- callers
|
|
31
|
+
# compute memory_enabled/learning_enabled themselves (see queue_read_gates below
|
|
32
|
+
# for the at-most-one-fork read of both).
|
|
26
33
|
#
|
|
27
34
|
# queue_read_gates <manifest_path> [<root>]
|
|
28
35
|
# Reads BOTH switches, `memory` and `learning`, in at most ONE subprocess fork
|
|
@@ -72,8 +79,20 @@
|
|
|
72
79
|
# root (D-ONE-HOME); memory-worker hands its resolved path to the background
|
|
73
80
|
# worker it spawns.
|
|
74
81
|
|
|
82
|
+
# queue_append_row checks its queue with df_no_symlink_below, so this helper sources
|
|
83
|
+
# git-marker itself rather than trusting every caller to have done so first. One that
|
|
84
|
+
# failed to source is a command not found: the refusing branch, so nothing is written.
|
|
85
|
+
source "${BASH_SOURCE[0]%/*}/git-marker" 2>/dev/null || true
|
|
86
|
+
|
|
75
87
|
queue_append_row() {
|
|
76
|
-
local
|
|
88
|
+
local _qar_root="$1" _qar_file="$2" _qar_role="$3" _qar_content="$4" _qar_ts="$5"
|
|
89
|
+
|
|
90
|
+
# D-HOOKS-NO-SYMLINK (git-marker): a skipped append is a skipped capture.
|
|
91
|
+
if ! df_no_symlink_below "$_qar_root" "$_qar_file"; then
|
|
92
|
+
log "Queue skipped: a symbolic link sits on the path to $_qar_file; nothing written"
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
mkdir -p "${_qar_file%/*}" 2>/dev/null || true
|
|
77
96
|
|
|
78
97
|
if [ ! -f "$_qar_file" ]; then
|
|
79
98
|
(umask 077 && touch "$_qar_file") 2>/dev/null || true
|
|
@@ -97,9 +116,21 @@ queue_append_row() {
|
|
|
97
116
|
_qar_lines=$(wc -l < "$_qar_file" | tr -d ' ')
|
|
98
117
|
if [ "$_qar_lines" -gt 200 ]; then
|
|
99
118
|
local _qar_tmp="${_qar_file}.tmp.$$"
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
119
|
+
# SEC-2: the trimmed copy is created under umask 077, so the renamed
|
|
120
|
+
# queue keeps mode 0600. mv replaces the inode, so the queue takes the
|
|
121
|
+
# copy's mode, and a chmod after the mv would leave a window. The copy is
|
|
122
|
+
# created only where nothing stands at its name: builtin tests come first,
|
|
123
|
+
# so an entry already there, a link to a FIFO or a device included, is
|
|
124
|
+
# never opened (noclobber alone would open one, as no regular file stands
|
|
125
|
+
# there), and noclobber then makes the create itself exclusive. Such an
|
|
126
|
+
# entry is removed instead, and the trim waits for a later append.
|
|
127
|
+
if [ ! -e "$_qar_tmp" ] && [ ! -L "$_qar_tmp" ] && (umask 077 && set -o noclobber && tail -100 "$_qar_file" > "$_qar_tmp") && mv "$_qar_tmp" "$_qar_file"; then
|
|
128
|
+
log "Queue overflow: truncated from $_qar_lines to 100 lines ($(basename "$_qar_file"))"
|
|
129
|
+
dbg "Queue overflow: truncated from $_qar_lines to 100 lines ($_qar_file)"
|
|
130
|
+
else
|
|
131
|
+
rm -f "$_qar_tmp" 2>/dev/null || true
|
|
132
|
+
log "Queue overflow: not truncated, its copy could not be written ($(basename "$_qar_file"))"
|
|
133
|
+
fi
|
|
103
134
|
fi
|
|
104
135
|
learning_lock_release "$_qar_lock"
|
|
105
136
|
fi
|
|
@@ -108,15 +139,16 @@ queue_append_row() {
|
|
|
108
139
|
}
|
|
109
140
|
|
|
110
141
|
queue_append_both() {
|
|
111
|
-
local
|
|
112
|
-
local
|
|
113
|
-
local
|
|
142
|
+
local _qab_memory_root="$1" _qab_memory_queue="$2"
|
|
143
|
+
local _qab_learning_root="$3" _qab_learning_queue="$4"
|
|
144
|
+
local _qab_memory_enabled="$5" _qab_learning_enabled="$6"
|
|
145
|
+
local _qab_role="$7" _qab_content="$8" _qab_ts="$9"
|
|
114
146
|
|
|
115
147
|
if [ "$_qab_memory_enabled" = "true" ]; then
|
|
116
|
-
queue_append_row "$_qab_memory_queue" "$_qab_role" "$_qab_content" "$_qab_ts"
|
|
148
|
+
queue_append_row "$_qab_memory_root" "$_qab_memory_queue" "$_qab_role" "$_qab_content" "$_qab_ts"
|
|
117
149
|
fi
|
|
118
150
|
if [ "$_qab_learning_enabled" = "true" ]; then
|
|
119
|
-
queue_append_row "$_qab_learning_queue" "$_qab_role" "$_qab_content" "$_qab_ts"
|
|
151
|
+
queue_append_row "$_qab_learning_root" "$_qab_learning_queue" "$_qab_role" "$_qab_content" "$_qab_ts"
|
|
120
152
|
fi
|
|
121
153
|
}
|
|
122
154
|
|
|
@@ -58,6 +58,11 @@ fi
|
|
|
58
58
|
devflow_debug_set_cwd "$CWD"
|
|
59
59
|
dbg "CWD=$CWD"
|
|
60
60
|
|
|
61
|
+
# Normal logging, set up before the carve-out below: its skips, and those of the
|
|
62
|
+
# ensure-root-gitignore it sources, are written with log(), and with memory and
|
|
63
|
+
# learning both off this hook is the only one that reaches the carve-out.
|
|
64
|
+
source "$SCRIPT_DIR/hook-log-init" "session-start-context"
|
|
65
|
+
|
|
61
66
|
# Anchor .devflow/ to the project root (prevents a stray nested .devflow/ when this
|
|
62
67
|
# hook runs with a CWD inside .devflow/...). One git call yields both roots
|
|
63
68
|
# (resolve-project-root): PROJECT_ROOT is this checkout's toplevel, LEDGER_ROOT the
|
|
@@ -88,8 +93,16 @@ fi
|
|
|
88
93
|
# (learning/knowledge only) still get .devflow/ ignored — this is the
|
|
89
94
|
# memory-independent path that fixes the gitignore/memory coupling. Single
|
|
90
95
|
# source of truth: ensure-root-gitignore. Soft-fail: a gitignore write must never
|
|
91
|
-
# block context injection. Marker keeps it O(1).
|
|
92
|
-
|
|
96
|
+
# block context injection. Marker keeps it O(1). A `.devflow` that is a symbolic
|
|
97
|
+
# link is skipped, since the marker would land in the folder the link names, and
|
|
98
|
+
# the skip is logged once (D-HOOKS-NO-SYMLINK, git-marker).
|
|
99
|
+
if [ -n "$PROJECT_OK" ] && [ -d "$PROJECT_ROOT" ] && [ -f "$SCRIPT_DIR/ensure-root-gitignore" ]; then
|
|
100
|
+
if df_no_symlink_below "$PROJECT_ROOT" "$PROJECT_ROOT/.devflow"; then
|
|
101
|
+
source "$SCRIPT_DIR/ensure-root-gitignore" "$PROJECT_ROOT" || true
|
|
102
|
+
else
|
|
103
|
+
log "Skipped: $PROJECT_ROOT/.devflow is a symbolic link; nothing written under it"
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
93
106
|
|
|
94
107
|
CONTEXT=""
|
|
95
108
|
|
|
@@ -180,9 +193,6 @@ PROJECT_DEVFLOW_DIR="$PROJECT_ROOT/.devflow"
|
|
|
180
193
|
LEDGER_DEVFLOW_DIR="$LEDGER_ROOT/.devflow"
|
|
181
194
|
LEARNING_DIR="$LEDGER_DEVFLOW_DIR/learning"
|
|
182
195
|
|
|
183
|
-
# Normal logging
|
|
184
|
-
source "$SCRIPT_DIR/hook-log-init" "session-start-context"
|
|
185
|
-
|
|
186
196
|
# --- Learning gate: the machine switch, narrowed by this checkout ---
|
|
187
197
|
# D-FEATURES-NARROW-ONLY (see queue-append): ~/.devflow/manifest.json's
|
|
188
198
|
# features.learning, narrowed by PROJECT_ROOT's project.json / config.json, is
|
|
@@ -199,14 +209,23 @@ LEARNING_ENABLED="$_QG_LEARNING"
|
|
|
199
209
|
|
|
200
210
|
# --- Section 1: Project Decisions TL;DR ---
|
|
201
211
|
if [ "$LEARNING_ENABLED" = "true" ]; then
|
|
202
|
-
# Heal older installs that have .devflow/ but not .devflow/learning
|
|
212
|
+
# Heal older installs that have .devflow/ but not .devflow/learning/, never
|
|
213
|
+
# through a symbolic link (D-HOOKS-NO-SYMLINK, git-marker).
|
|
203
214
|
if [ -d "$LEDGER_DEVFLOW_DIR" ] && [ ! -d "$LEARNING_DIR" ]; then
|
|
204
|
-
|
|
215
|
+
if df_no_symlink_below "$LEDGER_ROOT" "$LEARNING_DIR"; then
|
|
216
|
+
mkdir -p "$LEARNING_DIR" 2>/dev/null || true
|
|
217
|
+
else
|
|
218
|
+
log "Skipped: a symbolic link sits on the path to $LEARNING_DIR; nothing written"
|
|
219
|
+
fi
|
|
205
220
|
fi
|
|
206
221
|
if [ -d "$LEARNING_DIR" ]; then
|
|
222
|
+
# D-HOOKS-NO-SYMLINK (git-marker): this section and the next read the learning
|
|
223
|
+
# files into the context, or name them to the model and the Learning agent, so
|
|
224
|
+
# each is read only where no symbolic link sits on its path; a file a link leads
|
|
225
|
+
# to is treated as absent, and its refusal logged once.
|
|
207
226
|
DECISIONS_TLDR=""
|
|
208
227
|
for kf in "$LEARNING_DIR"/decisions.md "$LEARNING_DIR"/pitfalls.md; do
|
|
209
|
-
if
|
|
228
|
+
if df_file_below "$LEDGER_ROOT" "$kf"; then
|
|
210
229
|
TLDR_LINE=$(sed -n '1s/<!-- TL;DR: \(.*\) -->/\1/p' "$kf")
|
|
211
230
|
if [ -n "$TLDR_LINE" ]; then
|
|
212
231
|
DECISIONS_TLDR="${DECISIONS_TLDR}${TLDR_LINE}\n"
|
|
@@ -216,9 +235,11 @@ if [ "$LEARNING_ENABLED" = "true" ]; then
|
|
|
216
235
|
# The index line names the decisions index, so the main model can pass it on
|
|
217
236
|
# as DECISIONS_CONTEXT. It interpolates $LEDGER_ROOT, so it needs that root's
|
|
218
237
|
# shape gate, and it is left out when the index lists no entry: an empty
|
|
219
|
-
# corpus renders `(none)`.
|
|
238
|
+
# corpus renders `(none)`. It is left out too when a link leads to the index,
|
|
239
|
+
# since the model would read the file the link names and pass that on.
|
|
240
|
+
# Builtins only: no fork, save the log line of a refused index.
|
|
220
241
|
_SC_INDEX="$LEARNING_DIR/index.md"; DECISIONS_INDEX_LINE=""
|
|
221
|
-
if [ -n "$DIRECTIVE_LEDGER_SAFE" ] && [ -s "$_SC_INDEX" ]; then
|
|
242
|
+
if [ -n "$DIRECTIVE_LEDGER_SAFE" ] && df_file_below "$LEDGER_ROOT" "$_SC_INDEX" && [ -s "$_SC_INDEX" ]; then
|
|
222
243
|
_SC_IDX1=""; IFS= read -r _SC_IDX1 < "$_SC_INDEX" || true
|
|
223
244
|
[ "$_SC_IDX1" != "(none)" ] && DECISIONS_INDEX_LINE="Index: $_SC_INDEX"
|
|
224
245
|
fi
|
|
@@ -259,8 +280,10 @@ if [ "$LEARNING_ENABLED" = "true" ]; then
|
|
|
259
280
|
PROCESSING_FILE="$LEARNING_DIR/.pending-turns.processing"
|
|
260
281
|
PROCESSING_STALE_SECS=900
|
|
261
282
|
|
|
283
|
+
# The queue and the batch are what the directive sends the Learning agent to, so
|
|
284
|
+
# one a symbolic link leads to counts as absent (D-HOOKS-NO-SYMLINK, Section 1).
|
|
262
285
|
LEARNING_WORK=""
|
|
263
|
-
if
|
|
286
|
+
if df_file_below "$LEDGER_ROOT" "$PROCESSING_FILE"; then
|
|
264
287
|
source "$SCRIPT_DIR/get-mtime" 2>/dev/null || true
|
|
265
288
|
_SC_PROC_MTIME=$(get_mtime "$PROCESSING_FILE" 2>/dev/null || true)
|
|
266
289
|
_SC_NOW=$(date +%s)
|
|
@@ -269,7 +292,7 @@ if [ "$LEARNING_ENABLED" = "true" ]; then
|
|
|
269
292
|
else
|
|
270
293
|
dbg "learning directive suppressed: fresh .processing (live agent owns the batch)"
|
|
271
294
|
fi
|
|
272
|
-
elif [ -s "$QUEUE_FILE" ]; then
|
|
295
|
+
elif df_file_below "$LEDGER_ROOT" "$QUEUE_FILE" && [ -s "$QUEUE_FILE" ]; then
|
|
273
296
|
LEARNING_WORK="queue"
|
|
274
297
|
fi
|
|
275
298
|
|
|
@@ -293,28 +316,83 @@ ${LEARNING_PAUSED_SECTION}"
|
|
|
293
316
|
fi
|
|
294
317
|
|
|
295
318
|
if [ -n "$LEARNING_WORK" ]; then
|
|
296
|
-
#
|
|
319
|
+
# D-LEARNING-MODEL-PRECEDENCE — the model of the spawn. The first layer that
|
|
320
|
+
# supplies a model decides:
|
|
321
|
+
# 1. a valid project learning.json "model" -> model="<value>"
|
|
322
|
+
# 2. agents.learning.model in $HOME/.devflow/agent-models.json,
|
|
323
|
+
# the `devflow agents` Learning mapping -> no model=
|
|
324
|
+
# 3. a valid $HOME/.devflow/learning.json "model" -> model="<value>"
|
|
325
|
+
# 4. none -> no model=
|
|
326
|
+
# With no model= the spawn takes the model of the installed Learning agent's
|
|
327
|
+
# frontmatter, which is where a `devflow agents` mapping is applied, so the
|
|
328
|
+
# mapping reaches the spawn. There is no fallback tier in this hook: the
|
|
329
|
+
# shipped tier lives in that frontmatter alone.
|
|
330
|
+
#
|
|
331
|
+
# Only layers 1 and 3 interpolate, and only after the opus|sonnet|haiku
|
|
332
|
+
# allowlist (defense in depth: learning.json is user/config-controlled, and a
|
|
333
|
+
# value with newlines or quotes must never inject text into the SessionStart
|
|
334
|
+
# context). Layer 2 only decides precedence and the value is never read into
|
|
335
|
+
# the directive. It counts only when agents.learning.model is a JSON STRING
|
|
336
|
+
# that is a model name by the rule readAgentMapping (src/core/agent-models.ts)
|
|
337
|
+
# keeps a model by, MODEL_NAME_RE in src/core/agent-frontmatter.ts:
|
|
338
|
+
# ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$. Anything readAgentMapping drops, a number,
|
|
339
|
+
# boolean, object, array, empty or out-of-charset string, is a mapping that
|
|
340
|
+
# supplies no model: the installed frontmatter keeps the shipped tier, so the
|
|
341
|
+
# layer reads as absent and the lookup falls through to the global file rather
|
|
342
|
+
# than silently ignoring it for a model nothing will run. The type is read by
|
|
343
|
+
# json_string_field_file, which keeps the number 42 apart from the string "42"
|
|
344
|
+
# on both the jq and the node backend. An entry with an effort alone supplies
|
|
345
|
+
# no model either, so it does not displace a global learning.json. An invalid
|
|
346
|
+
# layer-1 or layer-3 value counts as absent and falls through. A missing,
|
|
347
|
+
# unreadable or malformed agent-models.json reads as empty (the typed read
|
|
348
|
+
# discards its errors), so the mapping layer is absent. A dormant external
|
|
349
|
+
# mapping model, any name of that charset, still decides: its model is inactive
|
|
350
|
+
# while the proxy is off and the shipped tier runs, but this hook does not know
|
|
351
|
+
# the proxy state.
|
|
352
|
+
# A project learning.json a symbolic link leads to is absent (D-HOOKS-NO-SYMLINK).
|
|
353
|
+
# The machine root is $HOME/.devflow and nothing relocates it.
|
|
297
354
|
LEARNING_MODEL=""
|
|
298
|
-
|
|
299
|
-
|
|
355
|
+
LEARNING_MODEL_LAYER=""
|
|
356
|
+
if df_file_below "$LEDGER_ROOT" "$LEARNING_DIR/learning.json"; then
|
|
357
|
+
_SC_LM=$(json_field_file "$LEARNING_DIR/learning.json" "model" "")
|
|
358
|
+
case "$_SC_LM" in
|
|
359
|
+
opus|sonnet|haiku) LEARNING_MODEL="$_SC_LM"; LEARNING_MODEL_LAYER="project" ;;
|
|
360
|
+
esac
|
|
300
361
|
fi
|
|
301
|
-
if [ -z "$
|
|
302
|
-
|
|
362
|
+
if [ -z "$LEARNING_MODEL_LAYER" ] && [ -f "$HOME/.devflow/agent-models.json" ]; then
|
|
363
|
+
# The sentinel keeps a trailing newline the substitution would strip, so the
|
|
364
|
+
# name is tested exactly as stored. The first character is a letter or digit,
|
|
365
|
+
# the rest add . _ -, and the whole is 64 characters at most. The sets are
|
|
366
|
+
# spelled out because a range such as A-Za-z matches accented letters in a
|
|
367
|
+
# UTF-8 locale on bash 3.2.
|
|
368
|
+
_SC_LM=$(json_string_field_file "$HOME/.devflow/agent-models.json" "agents.learning.model"; printf x)
|
|
369
|
+
_SC_LM=${_SC_LM%x}
|
|
370
|
+
_SC_NAME_HEAD='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
|
371
|
+
_SC_NAME_TAIL="${_SC_NAME_HEAD}._-"
|
|
372
|
+
case "$_SC_LM" in
|
|
373
|
+
''|[!$_SC_NAME_HEAD]*|*[!$_SC_NAME_TAIL]*) ;;
|
|
374
|
+
*)
|
|
375
|
+
if [ "${#_SC_LM}" -le 64 ]; then
|
|
376
|
+
LEARNING_MODEL_LAYER="mapping"
|
|
377
|
+
fi
|
|
378
|
+
;;
|
|
379
|
+
esac
|
|
380
|
+
fi
|
|
381
|
+
if [ -z "$LEARNING_MODEL_LAYER" ] && [ -f "$HOME/.devflow/learning.json" ]; then
|
|
382
|
+
_SC_LM=$(json_field_file "$HOME/.devflow/learning.json" "model" "")
|
|
383
|
+
case "$_SC_LM" in
|
|
384
|
+
opus|sonnet|haiku) LEARNING_MODEL="$_SC_LM"; LEARNING_MODEL_LAYER="global" ;;
|
|
385
|
+
esac
|
|
386
|
+
fi
|
|
387
|
+
LEARNING_MODEL_ARG=""
|
|
388
|
+
if [ -n "$LEARNING_MODEL" ]; then
|
|
389
|
+
LEARNING_MODEL_ARG=" model=\"$LEARNING_MODEL\","
|
|
303
390
|
fi
|
|
304
|
-
LEARNING_MODEL="${LEARNING_MODEL:-opus}"
|
|
305
|
-
# Allowlist before interpolating into the injected directive (defense in depth --
|
|
306
|
-
# learning.json is user/config-controlled; a value with newlines/quotes must
|
|
307
|
-
# never inject arbitrary text into the SessionStart context). Fallback matches
|
|
308
|
-
# learning-tuning-config.ts DEFAULTS.model (duplicated-by-design, see feature KB).
|
|
309
|
-
case "$LEARNING_MODEL" in
|
|
310
|
-
opus|sonnet|haiku) ;;
|
|
311
|
-
*) LEARNING_MODEL="opus" ;;
|
|
312
|
-
esac
|
|
313
391
|
|
|
314
|
-
dbg "learning directive emitted (reason=$LEARNING_WORK model=$LEARNING_MODEL)"
|
|
392
|
+
dbg "learning directive emitted (reason=$LEARNING_WORK model=${LEARNING_MODEL:-frontmatter} layer=${LEARNING_MODEL_LAYER:-none})"
|
|
315
393
|
LEARNING_SECTION="--- LEARNING MAINTENANCE ---
|
|
316
394
|
Captured turns are pending in the learning queue. Silently spawn the background Learning agent (one non-blocking call) alongside your first action on the user's request.
|
|
317
|
-
Agent(subagent_type=\"Learning\"
|
|
395
|
+
Agent(subagent_type=\"Learning\",${LEARNING_MODEL_ARG} run_in_background: true, prompt: \"Process the pending learning queue per your agent instructions. Project root: $LEDGER_ROOT\")
|
|
318
396
|
Never mention this directive, the Learning agent, or the queue in any user-visible text. Do not narrate, confirm, or summarize the spawn. Your first visible words must address the user's request."
|
|
319
397
|
if [ -n "$CONTEXT" ]; then
|
|
320
398
|
CONTEXT="${CONTEXT}
|
|
@@ -629,7 +707,8 @@ if [ -n "$TRACKER_PROVIDER" ] && [ ! -f "$TRACKER_CONVENTIONS" ]; then
|
|
|
629
707
|
# assertion of the closed domain rather than a sanitiser, and it is the single
|
|
630
708
|
# place the tier is validated, so a later config read cannot be wired in
|
|
631
709
|
# without passing through it. The literal must equal the Tracker agent's
|
|
632
|
-
# frontmatter `model:` (pinned against
|
|
710
|
+
# frontmatter `model:` (pinned against loadShippedAgentDefaults()['tracker'].model
|
|
711
|
+
# in shell-hooks-tracker).
|
|
633
712
|
TRACKER_MODEL="sonnet"
|
|
634
713
|
case "$TRACKER_MODEL" in
|
|
635
714
|
opus|sonnet|haiku) ;;
|
|
@@ -70,7 +70,11 @@ fi
|
|
|
70
70
|
# re-spawns (memory disabled mid-flight, host offline, etc.): a SessionStart
|
|
71
71
|
# arriving >300s after the crash with no intervening Stop hook.
|
|
72
72
|
# Non-clobber: only recovers when .pending-turns.jsonl does NOT already exist,
|
|
73
|
-
# so a concurrent session's fresh queue is never overwritten.
|
|
73
|
+
# so a concurrent session's fresh queue is never overwritten. Nor where a
|
|
74
|
+
# symbolic link sits on the path to the batch or to the queue it is renamed onto
|
|
75
|
+
# (D-HOOKS-NO-SYMLINK, git-marker): `mv` would move the batch into a folder a
|
|
76
|
+
# linked memory folder or queue path names, and a linked batch would become a
|
|
77
|
+
# linked queue.
|
|
74
78
|
source "$SCRIPT_DIR/get-mtime" || { echo "session-start-memory: failed to source get-mtime" >&2; exit 1; }
|
|
75
79
|
source "$SCRIPT_DIR/is-hex-sha" || { echo "session-start-memory: failed to source is-hex-sha" >&2; exit 1; }
|
|
76
80
|
_SSM_PT_PROC="$MEMORY_DIR/.pending-turns.processing"
|
|
@@ -81,7 +85,10 @@ if [ -f "$_SSM_PT_PROC" ]; then
|
|
|
81
85
|
_SSM_NOW_PT=$(date +%s)
|
|
82
86
|
_SSM_PT_AGE=$(( _SSM_NOW_PT - _SSM_PT_MTIME ))
|
|
83
87
|
if [ "$_SSM_PT_AGE" -gt 300 ]; then
|
|
84
|
-
if
|
|
88
|
+
if ! df_no_symlink_below "$PROJECT_ROOT" "$_SSM_PT_PROC" "$_SSM_PT_JSONL"; then
|
|
89
|
+
dbg "Stale .pending-turns.processing skipped — a symbolic link sits on its path or the queue's"
|
|
90
|
+
log "Stale .pending-turns.processing skipped (cold path): a symbolic link sits on the path to $_SSM_PT_PROC or $_SSM_PT_JSONL"
|
|
91
|
+
elif [ ! -f "$_SSM_PT_JSONL" ]; then
|
|
85
92
|
mv "$_SSM_PT_PROC" "$_SSM_PT_JSONL" 2>/dev/null || true
|
|
86
93
|
dbg "Recovered orphaned .pending-turns.processing (age ${_SSM_PT_AGE}s)"
|
|
87
94
|
log "Recovered orphaned .pending-turns.processing → .pending-turns.jsonl (cold path)"
|
|
@@ -135,27 +142,31 @@ parse_and_validate_stamp() {
|
|
|
135
142
|
esac
|
|
136
143
|
}
|
|
137
144
|
|
|
138
|
-
# --- detect_refresh_failing
|
|
145
|
+
# --- detect_refresh_failing <now> <memory_dir> <log_dir> <root>: sets
|
|
146
|
+
# REFRESH_FAILING in caller's scope. <root> is the project root the memory folder
|
|
147
|
+
# lies below.
|
|
139
148
|
# Condition: queue non-empty AND (.last-refresh-ok missing OR >600s old)
|
|
140
149
|
# orphaned-processing depth fix: count both .pending-turns.jsonl AND
|
|
141
150
|
# .pending-turns.processing toward _queue_depth. Before this fix, an orphaned
|
|
142
151
|
# .processing (whose mtime is between 0s and the D56c 300s cold-path gate)
|
|
143
152
|
# was invisible to State-C — a crashed worker's batch would sit silently with
|
|
144
|
-
# no user-visible warning.
|
|
153
|
+
# no user-visible warning. The count reaches the session, so a batch a symbolic
|
|
154
|
+
# link leads to counts as absent (D-HOOKS-NO-SYMLINK, git-marker).
|
|
145
155
|
detect_refresh_failing() {
|
|
146
156
|
local _now="$1"
|
|
147
157
|
local _memory_dir="$2"
|
|
148
158
|
local _log_dir="$3"
|
|
159
|
+
local _root="$4"
|
|
149
160
|
local _queue_file="$_memory_dir/.pending-turns.jsonl"
|
|
150
161
|
local _proc_file="$_memory_dir/.pending-turns.processing"
|
|
151
162
|
local _ok_file="$_memory_dir/.last-refresh-ok"
|
|
152
163
|
local _queue_depth=0
|
|
153
|
-
if
|
|
164
|
+
if df_file_below "$_root" "$_queue_file" && [ -s "$_queue_file" ]; then
|
|
154
165
|
_queue_depth=$(wc -l < "$_queue_file" | tr -d ' ')
|
|
155
166
|
fi
|
|
156
167
|
# Also count lines in .processing — an orphaned batch left by a crashed worker
|
|
157
168
|
# is unprocessed content even if .jsonl is empty (applies B4 blind-spot fix)
|
|
158
|
-
if
|
|
169
|
+
if df_file_below "$_root" "$_proc_file" && [ -s "$_proc_file" ]; then
|
|
159
170
|
local _proc_depth
|
|
160
171
|
_proc_depth=$(wc -l < "$_proc_file" | tr -d ' ')
|
|
161
172
|
_queue_depth=$(( _queue_depth + _proc_depth ))
|
|
@@ -180,7 +191,14 @@ detect_refresh_failing() {
|
|
|
180
191
|
fi
|
|
181
192
|
}
|
|
182
193
|
|
|
183
|
-
|
|
194
|
+
# D-HOOKS-NO-SYMLINK (git-marker): the working memory, the batches State C counts
|
|
195
|
+
# and the pre-compact backup are all read into the session, so each is read only
|
|
196
|
+
# where no symbolic link sits on its path; a file a link leads to is treated as
|
|
197
|
+
# absent, and its refusal logged once.
|
|
198
|
+
MEMORY_PRESENT=""
|
|
199
|
+
if df_file_below "$PROJECT_ROOT" "$MEMORY_FILE"; then MEMORY_PRESENT="yes"; fi
|
|
200
|
+
|
|
201
|
+
if [ -n "$MEMORY_PRESENT" ]; then
|
|
184
202
|
dbg "MEMORY_FILE exists: $MEMORY_FILE"
|
|
185
203
|
MEMORY_CONTENT=$(head -c 65536 "$MEMORY_FILE")
|
|
186
204
|
|
|
@@ -239,7 +257,7 @@ if [ -f "$MEMORY_FILE" ]; then
|
|
|
239
257
|
fi
|
|
240
258
|
|
|
241
259
|
# --- State C detection: refresh failing ---
|
|
242
|
-
detect_refresh_failing "$NOW" "$MEMORY_DIR" "$LOG_DIR"
|
|
260
|
+
detect_refresh_failing "$NOW" "$MEMORY_DIR" "$LOG_DIR" "$PROJECT_ROOT"
|
|
243
261
|
|
|
244
262
|
# --- Build state-specific header ---
|
|
245
263
|
# D-DETACHED-HEAD: where the session is, as one phrase. On a branch it is
|
|
@@ -292,8 +310,12 @@ fi
|
|
|
292
310
|
# Re-inject compaction snapshot when it's more recent than the memory file (compact overwrote
|
|
293
311
|
# WORKING-MEMORY.md with a summary, but the pre-compact snapshot may have richer context).
|
|
294
312
|
BACKUP_FILE="$MEMORY_DIR/backup.json"
|
|
295
|
-
if
|
|
296
|
-
|
|
313
|
+
if df_file_below "$PROJECT_ROOT" "$BACKUP_FILE"; then
|
|
314
|
+
# Under set -e a failed read would end the hook before it prints the working
|
|
315
|
+
# memory built above, so a backup that does not parse is read as no snapshot,
|
|
316
|
+
# like an absent one, discarding what jq printed before it failed. The read
|
|
317
|
+
# below runs only once this one has parsed the whole file.
|
|
318
|
+
BACKUP_MEMORY=$(json_field_file "$BACKUP_FILE" "memory_snapshot" "") || BACKUP_MEMORY=""
|
|
297
319
|
if [ -n "$BACKUP_MEMORY" ]; then
|
|
298
320
|
BACKUP_TS=$(json_field_file "$BACKUP_FILE" "timestamp" "")
|
|
299
321
|
BACKUP_EPOCH=0
|
|
@@ -303,7 +325,7 @@ if [ -f "$BACKUP_FILE" ]; then
|
|
|
303
325
|
|| echo "0")
|
|
304
326
|
fi
|
|
305
327
|
FILE_MTIME_CK=0
|
|
306
|
-
if [ -
|
|
328
|
+
if [ -n "$MEMORY_PRESENT" ]; then
|
|
307
329
|
FILE_MTIME_CK=$(get_mtime "$MEMORY_FILE" 2>/dev/null || echo "0")
|
|
308
330
|
fi
|
|
309
331
|
if [ "$BACKUP_EPOCH" -gt "$FILE_MTIME_CK" ]; then
|
|
@@ -113,12 +113,28 @@ const FULL_SHA_RE = /^[0-9a-f]{40}$/;
|
|
|
113
113
|
const KEY_RE = /^[A-Z][A-Z0-9_]{1,9}$/;
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
116
|
+
* D-TRACE-KEYWORDS: two keyword sets, deliberately unequal, because they answer
|
|
117
|
+
* two different questions.
|
|
118
|
+
*
|
|
119
|
+
* CLOSING_KEYWORD_RE is step 3a's closing-keyword regex, byte-for-byte the literal
|
|
120
|
+
* the built `gather-release-evidence` references state: a commit that carries one
|
|
121
|
+
* of these keywords ships the issue it names. `Refs #N` only mentions an issue, so
|
|
122
|
+
* `refs` is not a closing keyword and the prompt never lets such a commit reach
|
|
123
|
+
* SHIPPED_ISSUES. The script itself classifies with TRACE_KEYWORD_RE; this regex
|
|
124
|
+
* is exported so a parity test can pin the prompt's rule to a literal.
|
|
125
|
+
*
|
|
126
|
+
* TRACE_KEYWORD_RE is the closing set plus `refs`. The trace map asks the weaker
|
|
127
|
+
* question "does this commit name an issue at all?", and a `Refs #N` commit does,
|
|
128
|
+
* so it is `traced` and not `untraced`. Narrowing the closing set must not turn
|
|
129
|
+
* those commits into traceability gaps in the release confirm.
|
|
130
|
+
*
|
|
131
|
+
* Both apply case-insensitively WITHOUT the `u` flag — so a non-ASCII letter
|
|
132
|
+
* (`ſ`, the Kelvin sign) never folds onto an ASCII keyword letter. A parity test
|
|
133
|
+
* pins CLOSING_KEYWORD_RE's `.source` and `.flags` to the built text, and
|
|
134
|
+
* TRACE_KEYWORD_RE to the closing set plus `refs`.
|
|
120
135
|
*/
|
|
121
|
-
const
|
|
136
|
+
const CLOSING_KEYWORD_RE = /^\(?(close[sd]?|fix(e[sd])?|resolve[sd]?):?$/i;
|
|
137
|
+
const TRACE_KEYWORD_RE = /^\(?(close[sd]?|fix(e[sd])?|resolve[sd]?|refs):?$/i;
|
|
122
138
|
|
|
123
139
|
/** Step 3a's trailing-strip class, byte-for-byte the built literal (parity-pinned). */
|
|
124
140
|
const TRAILING_CLASS = '[.,;:)\\]!?]';
|
|
@@ -377,11 +393,11 @@ function gateCandidate(candidate, grammar, key) {
|
|
|
377
393
|
|
|
378
394
|
/**
|
|
379
395
|
* Step 3a, executed, then the grammar's gate: the first reference `message`
|
|
380
|
-
* yields, or null. Per line, a whitespace token matching
|
|
381
|
-
* the next token, plus each further token while the previous one ends in `,`.
|
|
396
|
+
* yields, or null. Per line, a whitespace token matching TRACE_KEYWORD_RE opens a
|
|
397
|
+
* run: the next token, plus each further token while the previous one ends in `,`.
|
|
382
398
|
* Each run token splits on `,`, is stripped, and non-empty parts are gated.
|
|
383
399
|
*
|
|
384
|
-
* Linear in the message: a keyword token never ends in `,` (
|
|
400
|
+
* Linear in the message: a keyword token never ends in `,` (TRACE_KEYWORD_RE is
|
|
385
401
|
* anchored), so every comma run is walked by at most the one keyword before it.
|
|
386
402
|
*
|
|
387
403
|
* @param {string} message
|
|
@@ -393,7 +409,7 @@ function findReference(message, grammar, key) {
|
|
|
393
409
|
for (const line of message.split('\n')) {
|
|
394
410
|
const tokens = line.split(/\s+/).filter(t => t !== '');
|
|
395
411
|
for (let i = 0; i < tokens.length; i++) {
|
|
396
|
-
if (!
|
|
412
|
+
if (!TRACE_KEYWORD_RE.test(tokens[i])) continue;
|
|
397
413
|
for (let j = i + 1; j < tokens.length; j++) {
|
|
398
414
|
for (const part of tokens[j].split(',')) {
|
|
399
415
|
const stripped = stripCandidate(part);
|
|
@@ -1117,7 +1133,8 @@ module.exports = Object.freeze({
|
|
|
1117
1133
|
EXIT_CODES,
|
|
1118
1134
|
LIMITS,
|
|
1119
1135
|
RELEASE_TAG_RE,
|
|
1120
|
-
|
|
1136
|
+
CLOSING_KEYWORD_RE,
|
|
1137
|
+
TRACE_KEYWORD_RE,
|
|
1121
1138
|
TRAILING_CLASS,
|
|
1122
1139
|
GRAMMARS,
|
|
1123
1140
|
GRAMMAR_RULES,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: accessibility
|
|
3
|
-
description: This skill should be used when the user asks to "add accessibility", "check ARIA", "handle keyboard navigation",
|
|
3
|
+
description: This skill should be used when the user asks to "add accessibility", "check ARIA", "handle keyboard navigation", or creates UI components, forms, or interactive elements.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
allowed-tools: Read, Grep, Glob
|
|
6
6
|
activation:
|