devflow-kit 3.7.0 → 3.9.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 +479 -1
- package/README.md +15 -15
- package/dist/agents/code.md +6 -8
- package/dist/agents/design.md +1 -1
- package/dist/agents/fix.md +7 -8
- package/dist/agents/git.md +3 -3
- package/dist/agents/harden.md +1 -1
- package/dist/agents/refactor.md +2 -4
- package/dist/agents/review.md +4 -4
- package/dist/agents/skim.md +2 -2
- package/dist/agents/triage.md +9 -2
- package/dist/cli/commands/capture.js +1 -1
- package/dist/cli/commands/curation-move-io.js +102 -0
- package/dist/cli/commands/curation-move-step.js +374 -0
- package/dist/cli/commands/curation.js +808 -0
- package/dist/cli/commands/gate-level-prompts.js +428 -0
- package/dist/cli/commands/gates.js +35 -11
- package/dist/cli/commands/init-seed.js +11 -8
- package/dist/cli/commands/init.js +244 -57
- package/dist/cli/commands/memory.js +60 -13
- package/dist/cli/commands/prompt-io.js +37 -0
- package/dist/cli/commands/uninstall.js +157 -17
- package/dist/cli.js +2 -2
- package/dist/commands/bug-analysis.md +25 -36
- package/dist/commands/code-review.md +40 -44
- package/dist/commands/debug.md +5 -5
- package/dist/commands/dynamic-build.md +26 -25
- package/dist/commands/dynamic-plan.md +5 -5
- package/dist/commands/dynamic-profile.md +5 -5
- package/dist/commands/dynamic-tickets.md +5 -5
- package/dist/commands/explore.md +6 -6
- package/dist/commands/implement.md +53 -82
- package/dist/commands/plan.md +6 -6
- package/dist/commands/release.md +5 -5
- package/dist/commands/research.md +6 -6
- package/dist/commands/resolve.md +39 -41
- package/dist/commands/self-review.md +5 -13
- package/dist/core/area-pause-notice.js +44 -0
- package/dist/core/assets.js +20 -20
- package/dist/core/curate-tuning-config.js +5 -6
- package/dist/core/curation-move-notice.js +84 -0
- package/dist/core/curation-move-skip.js +35 -0
- package/dist/core/curation-move.js +35 -0
- package/dist/core/curation-queue-cleanup.js +135 -0
- package/dist/core/curation-store.js +99 -0
- package/dist/core/{learning-variants.js → curation-variants.js} +65 -41
- package/dist/core/echo-value.js +11 -5
- package/dist/core/evidence-policy.js +11 -9
- package/dist/core/feature-switch.js +95 -19
- package/dist/core/fs-atomic.js +1 -1
- package/dist/core/gate-level.js +10 -2
- package/dist/core/git.js +16 -14
- package/dist/core/ledger-root.js +82 -22
- package/dist/core/linked-path.js +4 -4
- package/dist/core/manifest-lock.js +238 -0
- package/dist/core/manifest.js +58 -14
- package/dist/core/migrations.js +1 -13
- package/dist/core/observations.js +2 -2
- package/dist/core/plugins.js +19 -19
- package/dist/core/pool.js +26 -0
- package/dist/core/project-paths.js +103 -34
- package/dist/core/queue-drain.js +2 -2
- package/dist/core/tracked-areas.js +159 -0
- package/dist/core/tracker.js +1 -1
- package/dist/{learning-off → curation-off}/agents/code.md +6 -8
- package/dist/{learning-off → curation-off}/agents/fix.md +7 -8
- package/dist/{learning-off → curation-off}/agents/refactor.md +2 -4
- package/dist/{learning-off → curation-off}/agents/review.md +3 -3
- package/dist/{learning-off → curation-off}/agents/triage.md +8 -1
- package/dist/{learning-off → curation-off}/commands/bug-analysis.md +20 -31
- package/dist/{learning-off → curation-off}/commands/code-review.md +37 -41
- package/dist/{learning-off → curation-off}/commands/debug.md +2 -2
- package/dist/{learning-off → curation-off}/commands/dynamic-build.md +23 -22
- package/dist/{learning-off → curation-off}/commands/explore.md +2 -2
- package/dist/{learning-off → curation-off}/commands/implement.md +50 -79
- package/dist/{learning-off → curation-off}/commands/plan.md +2 -2
- package/dist/{learning-off → curation-off}/commands/resolve.md +36 -38
- package/dist/{learning-off → curation-off}/commands/self-review.md +2 -10
- package/dist/hud/components/{learning-counts.js → curation-counts.js} +63 -17
- package/dist/hud/config.js +1 -1
- package/dist/hud/index.js +6 -6
- package/dist/hud/render.js +3 -3
- package/dist/skills/git/references/pr/post-resolution-summary.md +12 -11
- package/dist/skills/git/references/pr/post-review-summary.md +12 -11
- package/dist/skills/git/references/pr/update-pr-evidence.md +2 -2
- package/dist/skills/git/references/pr/validate-branch.md +8 -5
- package/dist/skills/git/references/publication-gate.md +6 -7
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/github/manage-debt.md +6 -2
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/jira/manage-debt.md +6 -2
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/linear/manage-debt.md +6 -2
- package/dist/targets/claude-code/{learning-install.js → curation-install.js} +85 -65
- package/dist/targets/claude-code/installer.js +15 -15
- package/dist/targets/claude-code/language-stamp.js +5 -5
- package/dist/targets/claude-code/post-install.js +2 -2
- package/dist/targets/claude-code/tracker-servers.js +1 -1
- package/package.json +3 -2
- package/src/assets/agents/_partials/_code.mds +11 -11
- package/src/assets/agents/code.mds +6 -6
- package/src/assets/agents/curate.md +8 -8
- package/src/assets/agents/debug.mds +6 -6
- package/src/assets/agents/design.mds +18 -18
- package/src/assets/agents/evaluate.md +6 -12
- package/src/assets/agents/fix.mds +7 -6
- package/src/assets/agents/git.mds +3 -3
- package/src/assets/agents/harden.mds +7 -7
- package/src/assets/agents/knowledge.mds +23 -23
- package/src/assets/agents/refactor.mds +2 -2
- package/src/assets/agents/research.mds +15 -15
- package/src/assets/agents/review.mds +43 -43
- package/src/assets/agents/skim.mds +11 -11
- package/src/assets/agents/triage.mds +29 -22
- package/src/assets/agents/validate.md +30 -50
- package/src/assets/commands/_partials/_decisions.mds +22 -20
- package/src/assets/commands/_partials/_engine.mds +8 -8
- package/src/assets/commands/_partials/_knowledge.mds +29 -15
- package/src/assets/commands/_partials/_preamble.mds +4 -4
- package/src/assets/commands/_partials/_settings.mds +3 -3
- package/src/assets/commands/_partials/_wave.mds +7 -7
- package/src/assets/commands/bug-analysis.mds +37 -48
- package/src/assets/commands/code-review.mds +53 -57
- package/src/assets/commands/debug.mds +14 -14
- package/src/assets/commands/dynamic-build.mds +38 -37
- package/src/assets/commands/dynamic-plan.mds +22 -22
- package/src/assets/commands/dynamic-profile.mds +2 -2
- package/src/assets/commands/dynamic-tickets.mds +8 -8
- package/src/assets/commands/explore.mds +20 -20
- package/src/assets/commands/implement.mds +77 -98
- package/src/assets/commands/plan.mds +27 -27
- package/src/assets/commands/release.mds +12 -12
- package/src/assets/commands/research.mds +19 -19
- package/src/assets/commands/resolve.mds +66 -60
- package/src/assets/commands/self-review.mds +20 -20
- package/src/assets/mds/git/_ops.mds +9 -2
- package/src/assets/mds/git/_ops_pr.mds +21 -11
- package/src/assets/mds/git/_ops_steps.mds +20 -4
- package/src/assets/mds/git/_pr.mds +21 -18
- package/src/assets/mds/git/_references.mds +6 -7
- package/src/assets/mds/tracker/_github.mds +1 -4
- package/src/assets/mds/tracker/_jira.mds +1 -4
- package/src/assets/mds/tracker/_linear.mds +1 -4
- package/src/assets/rules/quality.md +1 -0
- package/src/assets/scripts/ci-wait.cjs +389 -92
- package/src/assets/scripts/hooks/background-memory-update +81 -3
- package/src/assets/scripts/hooks/capture-prompt +12 -12
- package/src/assets/scripts/hooks/capture-question +11 -11
- package/src/assets/scripts/hooks/capture-turn +12 -12
- package/src/assets/scripts/hooks/{learning-lock → curation-lock} +2 -2
- package/src/assets/scripts/hooks/curation-move +293 -0
- package/src/assets/scripts/hooks/ensure-devflow-init +45 -13
- package/src/assets/scripts/hooks/ensure-proxy +4 -4
- package/src/assets/scripts/hooks/ensure-root-gitignore +4 -4
- package/src/assets/scripts/hooks/git-marker +81 -0
- package/src/assets/scripts/hooks/json-helper.cjs +176 -51
- package/src/assets/scripts/hooks/json-parse +1 -1
- package/src/assets/scripts/hooks/lib/curation-move.cjs +1582 -0
- package/src/assets/scripts/hooks/lib/{learning-store.cjs → curation-store.cjs} +308 -122
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +3 -3
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +24 -7
- package/src/assets/scripts/hooks/lib/project-paths.cjs +132 -39
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +42 -22
- package/src/assets/scripts/hooks/lib/tracked-areas.cjs +452 -0
- package/src/assets/scripts/hooks/memory-permission +10 -2
- package/src/assets/scripts/hooks/memory-worker +12 -1
- package/src/assets/scripts/hooks/pre-compact-memory +17 -16
- package/src/assets/scripts/hooks/queue-append +444 -102
- package/src/assets/scripts/hooks/resolve-project-root +2 -2
- package/src/assets/scripts/hooks/session-start-context +198 -72
- package/src/assets/scripts/hooks/session-start-memory +42 -43
- package/src/assets/scripts/lib/project-config.cjs +12 -5
- package/src/assets/scripts/pr-evidence.cjs +5 -2
- package/src/assets/scripts/resolve-base.cjs +1043 -0
- package/src/assets/scripts/resolve-settings.cjs +283 -45
- package/src/assets/scripts/verify-evidence.cjs +307 -75
- package/src/assets/skills/agent-references/SKILL.md +11 -2
- package/src/assets/skills/agent-references/references/commands/implement-levels.md +29 -0
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +6 -4
- package/src/assets/skills/agent-references/references/commands/resolve-levels.md +25 -0
- package/src/assets/skills/agent-references/references/synthesize/review.md +4 -4
- package/src/assets/skills/agent-references/references/triage/severity-tiers.md +43 -0
- package/src/assets/skills/agent-references/references/validate/passed-alone.md +14 -0
- package/src/assets/skills/apply-decisions/SKILL.md +1 -1
- package/src/assets/skills/docs-framework/SKILL.md +7 -7
- package/src/assets/skills/docs-framework/references/patterns.md +3 -4
- package/src/assets/skills/quality-gates/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/references/roots.md +2 -2
- package/templates/lint-pack/.lint-pack.conf +86 -0
- package/templates/lint-pack/README.md +190 -0
- package/templates/lint-pack/lint-pack.sh +905 -0
- package/dist/cli/commands/learning.js +0 -554
- package/dist/core/learning-queue-cleanup.js +0 -27
- package/dist/core/learning-store.js +0 -61
- /package/dist/{learning-off → curation-off}/agents/debug.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/design.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/harden.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/knowledge.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/research.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/skim.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-plan.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-profile.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-tickets.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/release.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/research.md +0 -0
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
# for the next run (D-QUEUE-NO-ORPHAN-DELETE)
|
|
30
30
|
# No parser: neither jq nor node on PATH → exits with no LLM run; the queue and any .processing batch are left in place
|
|
31
31
|
# Parser fails: jq or node exits non-zero while it reads the queue or the batch → exits with no LLM run; the turns are kept
|
|
32
|
+
# Paused area: git tracks files in the memory folder, or a `.git` entry stands and git does not answer → exits with
|
|
33
|
+
# result=skip and no LLM run, and reads, claims, writes and notices nothing; asked at the start, and again
|
|
34
|
+
# after the run, where a late answer leaves the staged file unswapped and the batch in .processing
|
|
32
35
|
|
|
33
36
|
set -e
|
|
34
37
|
|
|
@@ -128,6 +131,59 @@ source "$SCRIPT_DIR/resolve-project-root" 2>/dev/null || true
|
|
|
128
131
|
# The staged-path check, the settings JSON with the worker's Edit allow and deny rules, the
|
|
129
132
|
# result line and the one-time notice (needs df_no_symlink_below from resolve-project-root).
|
|
130
133
|
source "$SCRIPT_DIR/memory-permission" || { echo "background-memory-update: failed to source memory-permission" >&2; exit 1; }
|
|
134
|
+
source "$SCRIPT_DIR/queue-append" || { echo "background-memory-update: failed to source queue-append" >&2; exit 1; }
|
|
135
|
+
|
|
136
|
+
# --- The memory area check: git tracks files in the memory folder, or does not answer ---
|
|
137
|
+
# A folder that git tracks holds the repository's content, not devflow's data. While git
|
|
138
|
+
# tracks files in the memory folder of this checkout, or a `.git` entry stands and git does
|
|
139
|
+
# not answer, the memory area is paused, and the worker reads and writes nothing there.
|
|
140
|
+
# Sets DF_TRACKED_AREA_MEMORY to `tracked`, `untracked` or `unknown` for the project root in
|
|
141
|
+
# $1: queue_tracked_area in git mode, run in a background subshell that answers by its exit
|
|
142
|
+
# status (10 untracked, 11 tracked), so that a git that hangs can be cut and no file is made.
|
|
143
|
+
# Every call asks git afresh: the second ask of a run must see what changed during the run.
|
|
144
|
+
# The bound is DEVFLOW_BG_AREA_PROBE_SECS (default 5, at most 60; tests override it as they
|
|
145
|
+
# do DEVFLOW_BG_WATCHDOG_SECS), kept by the watchdog's background-and-kill idiom: a SIGKILL
|
|
146
|
+
# to the probe's process group (set -m) after a sleep, and a SIGKILL cancel of the watchdog,
|
|
147
|
+
# so no exit trap runs in either subshell (the one `Run:` line, above). A probe that is
|
|
148
|
+
# cut, or ends in any other status, reads `unknown`: the area stays paused. The notices of
|
|
149
|
+
# memory-permission read this variable, so the worker leaves one only after this check has
|
|
150
|
+
# asked and found the area free. Always returns 0.
|
|
151
|
+
probe_memory_area() {
|
|
152
|
+
local _root="${1:-}" _secs _pid _wd_pid _status=1
|
|
153
|
+
DF_TRACKED_AREA_MEMORY="unknown"
|
|
154
|
+
_secs="${DEVFLOW_BG_AREA_PROBE_SECS:-5}"
|
|
155
|
+
case "$_secs" in
|
|
156
|
+
''|*[!0123456789]*|????*) _secs=5 ;;
|
|
157
|
+
esac
|
|
158
|
+
if [ "$_secs" -lt 1 ] || [ "$_secs" -gt 60 ]; then _secs=5; fi
|
|
159
|
+
set -m
|
|
160
|
+
(
|
|
161
|
+
_QTA_ASKED_MEMORY=""
|
|
162
|
+
queue_tracked_area "$_root" memory git
|
|
163
|
+
case "$DF_TRACKED_AREA_MEMORY" in
|
|
164
|
+
untracked) exit 10 ;;
|
|
165
|
+
tracked) exit 11 ;;
|
|
166
|
+
esac
|
|
167
|
+
exit 12
|
|
168
|
+
) </dev/null >/dev/null 2>&1 &
|
|
169
|
+
_pid=$!
|
|
170
|
+
set +m
|
|
171
|
+
(
|
|
172
|
+
sleep "$_secs"
|
|
173
|
+
kill -KILL -"$_pid" 2>/dev/null || true
|
|
174
|
+
) </dev/null >/dev/null 2>&1 &
|
|
175
|
+
_wd_pid=$!
|
|
176
|
+
if wait "$_pid" 2>/dev/null; then _status=0; else _status=$?; fi
|
|
177
|
+
# SIGKILL, not SIGTERM, as for the other watchdogs: no trap can run in the subshell.
|
|
178
|
+
kill -KILL "$_wd_pid" 2>/dev/null || true
|
|
179
|
+
wait "$_wd_pid" 2>/dev/null || true
|
|
180
|
+
case "$_status" in
|
|
181
|
+
10) DF_TRACKED_AREA_MEMORY="untracked" ;;
|
|
182
|
+
11) DF_TRACKED_AREA_MEMORY="tracked" ;;
|
|
183
|
+
esac
|
|
184
|
+
return 0
|
|
185
|
+
}
|
|
186
|
+
|
|
131
187
|
PROJECT_ROOT="$(df_resolve_root "$CWD" 2>/dev/null || true)"
|
|
132
188
|
[ -n "$PROJECT_ROOT" ] || PROJECT_ROOT="$CWD"
|
|
133
189
|
# One canonical root feeds every path below, the staged path in the prompt and in the
|
|
@@ -136,6 +192,7 @@ PROJECT_ROOT="$(df_resolve_root "$CWD" 2>/dev/null || true)"
|
|
|
136
192
|
PROJECT_ROOT_CANONICAL=$(cd -P -- "$PROJECT_ROOT" 2>/dev/null && pwd -P) || PROJECT_ROOT_CANONICAL=""
|
|
137
193
|
if [ -z "$PROJECT_ROOT_CANONICAL" ]; then
|
|
138
194
|
log "SKIP: project root could not be resolved; no LLM run"
|
|
195
|
+
probe_memory_area "$PROJECT_ROOT"
|
|
139
196
|
memory_notice_write "$PROJECT_ROOT/.devflow/memory" "$PROJECT_ROOT" path
|
|
140
197
|
exit 0
|
|
141
198
|
fi
|
|
@@ -152,15 +209,25 @@ OK_FILE="$MEMORY_DIR/.last-refresh-ok"
|
|
|
152
209
|
|
|
153
210
|
# --- Re-check the memory switch at runtime (defense-in-depth: the feature may
|
|
154
211
|
# have been disabled since spawn, or narrowed by a checkout that now carries a
|
|
155
|
-
# `features.memory: false`). Same helper as every other memory/
|
|
212
|
+
# `features.memory: false`). Same helper as every other memory/curation gate
|
|
156
213
|
# (D-FEATURES-NARROW-ONLY, see queue-append).
|
|
157
|
-
source "$SCRIPT_DIR/queue-append" || { echo "background-memory-update: failed to source queue-append" >&2; exit 1; }
|
|
158
214
|
queue_read_gates "$DEVFLOW_MANIFEST" "$PROJECT_ROOT"
|
|
159
215
|
if [ "$_QG_MEMORY" != "true" ]; then
|
|
160
216
|
log "ABORT: memory disabled (disabled after spawn)"
|
|
161
217
|
exit 0
|
|
162
218
|
fi
|
|
163
219
|
|
|
220
|
+
# --- Ask whether git tracks files in the memory folder ---
|
|
221
|
+
# The area may have become tracked since the Stop hook spawned this worker. A paused area
|
|
222
|
+
# ends the run here, before the binary is resolved, a link is checked, the lock is taken or
|
|
223
|
+
# a batch is claimed: nothing is read, moved or written, and no notice is left. The run's
|
|
224
|
+
# `Run:` line says skip.
|
|
225
|
+
probe_memory_area "$PROJECT_ROOT"
|
|
226
|
+
if [ "$DF_TRACKED_AREA_MEMORY" != "untracked" ]; then
|
|
227
|
+
log "SKIP: the memory area is paused (git tracks files in the memory folder, or did not answer); nothing read or written; no LLM run"
|
|
228
|
+
exit 0
|
|
229
|
+
fi
|
|
230
|
+
|
|
164
231
|
# --- Resolve claude binary ---
|
|
165
232
|
CLAUDE_BIN=$(command -v claude 2>/dev/null || true)
|
|
166
233
|
if [ -z "$CLAUDE_BIN" ]; then
|
|
@@ -340,7 +407,7 @@ else
|
|
|
340
407
|
fi
|
|
341
408
|
fi
|
|
342
409
|
|
|
343
|
-
# --- Worker-level lock (300s stale-break — much longer than
|
|
410
|
+
# --- Worker-level lock (300s stale-break — much longer than curation-lock's 30s) ---
|
|
344
411
|
# This prevents a second worker (spawned 121s later) from double-writing WORKING-MEMORY.md
|
|
345
412
|
# while the first worker's claude -p call (up to 120s) is still in flight.
|
|
346
413
|
#
|
|
@@ -1126,6 +1193,17 @@ case "$MEMORY_RESULT_DENIALS" in
|
|
|
1126
1193
|
*) RESULT_DENIED="yes" ;;
|
|
1127
1194
|
esac
|
|
1128
1195
|
|
|
1196
|
+
# The run took minutes, so the memory area is asked again before anything lands in the
|
|
1197
|
+
# folder. If git tracks files there now, or does not answer, nothing below runs: the staged
|
|
1198
|
+
# file is not checked, moved or removed, no stamp is touched, no denied run is counted and
|
|
1199
|
+
# no notice is left. The batch stays in .processing.
|
|
1200
|
+
probe_memory_area "$PROJECT_ROOT"
|
|
1201
|
+
if [ "$DF_TRACKED_AREA_MEMORY" != "untracked" ]; then
|
|
1202
|
+
RUN_RESULT="skip"
|
|
1203
|
+
log "SKIP: the memory area is paused (git tracks files in the memory folder, or did not answer); nothing swapped or stamped; the batch stays in .processing"
|
|
1204
|
+
exit 0
|
|
1205
|
+
fi
|
|
1206
|
+
|
|
1129
1207
|
# A folder or a link at the staged path now was put there during the run: the path was
|
|
1130
1208
|
# empty at the spawn. It is removed the way it is before a run (memory_staged_remove), and
|
|
1131
1209
|
# the run ends as failed: the checks below find no staged file, and the batch stays for
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
# Dual-append: writes the user turn to BOTH the memory queue and the
|
|
3
|
+
# Curation System: capture-prompt (UserPromptSubmit Hook)
|
|
4
|
+
# Dual-append: writes the user turn to BOTH the memory queue and the curation
|
|
5
5
|
# queue, each independently gated by its own feature flag (AC-F4). Delegates
|
|
6
6
|
# the actual JSONL-write logic to the shared queue-append helper rather than
|
|
7
7
|
# duplicating it inline.
|
|
@@ -41,18 +41,18 @@ devflow_debug_set_cwd "$CWD"
|
|
|
41
41
|
source "$SCRIPT_DIR/resolve-project-root" 2>/dev/null || true
|
|
42
42
|
df_resolve_roots "$CWD" 2>/dev/null || true
|
|
43
43
|
PROJECT_ROOT="${DF_ROOT:-$CWD}"
|
|
44
|
-
# The
|
|
44
|
+
# The curation queue is the repository's, not the checkout's: in a linked worktree
|
|
45
45
|
# it is the main worktree's queue (D-LEDGER-MAIN-WORKTREE, resolve-project-root),
|
|
46
46
|
# the one the Curate agent drains into the shared ledger. Memory stays per checkout.
|
|
47
47
|
LEDGER_ROOT="${DF_LEDGER_ROOT:-$PROJECT_ROOT}"
|
|
48
48
|
|
|
49
|
-
# The machine-wide manifest (the memory/
|
|
49
|
+
# The machine-wide manifest (the memory/curation switches, D-FEATURES-NARROW-ONLY
|
|
50
50
|
# in queue-append) lives at the machine root,
|
|
51
51
|
# $HOME/.devflow (D-ONE-HOME).
|
|
52
52
|
DEVFLOW_MANIFEST="$HOME/.devflow/manifest.json"
|
|
53
53
|
PROJECT_DEVFLOW_DIR="$PROJECT_ROOT/.devflow"
|
|
54
54
|
MEMORY_DIR="$PROJECT_DEVFLOW_DIR/memory"
|
|
55
|
-
|
|
55
|
+
CURATION_DIR="$LEDGER_ROOT/.devflow/curation"
|
|
56
56
|
|
|
57
57
|
if [ -z "$PROMPT" ]; then
|
|
58
58
|
dbg "EXIT: empty PROMPT"
|
|
@@ -61,16 +61,16 @@ fi
|
|
|
61
61
|
|
|
62
62
|
source "$SCRIPT_DIR/queue-append" || { echo "capture-prompt: failed to source queue-append" >&2; exit 1; }
|
|
63
63
|
|
|
64
|
-
# --- AC-P1: at most ONE gate-read fork for memory +
|
|
64
|
+
# --- AC-P1: at most ONE gate-read fork for memory + curation ---
|
|
65
65
|
# The machine switch, narrowed by this checkout's project.json/config.json
|
|
66
66
|
# `features` (D-FEATURES-NARROW-ONLY, see queue-append).
|
|
67
67
|
queue_read_gates "$DEVFLOW_MANIFEST" "$PROJECT_ROOT"
|
|
68
68
|
MEMORY_ENABLED="$_QG_MEMORY"
|
|
69
|
-
|
|
69
|
+
CURATION_ENABLED="$_QG_CURATION"
|
|
70
70
|
|
|
71
|
-
dbg "MEMORY_ENABLED=$MEMORY_ENABLED
|
|
71
|
+
dbg "MEMORY_ENABLED=$MEMORY_ENABLED CURATION_ENABLED=$CURATION_ENABLED"
|
|
72
72
|
|
|
73
|
-
if [ "$MEMORY_ENABLED" != "true" ] && [ "$
|
|
73
|
+
if [ "$MEMORY_ENABLED" != "true" ] && [ "$CURATION_ENABLED" != "true" ]; then
|
|
74
74
|
dbg "EXIT: both features disabled"
|
|
75
75
|
exit 0
|
|
76
76
|
fi
|
|
@@ -82,15 +82,15 @@ source "$SCRIPT_DIR/hook-log-init" "capture-prompt"
|
|
|
82
82
|
source "$SCRIPT_DIR/ensure-devflow-init" "$CWD" || exit 0
|
|
83
83
|
|
|
84
84
|
source "$SCRIPT_DIR/get-mtime" || { echo "capture-prompt: failed to source get-mtime" >&2; exit 1; }
|
|
85
|
-
source "$SCRIPT_DIR/
|
|
85
|
+
source "$SCRIPT_DIR/curation-lock" || { echo "capture-prompt: failed to source curation-lock" >&2; exit 1; }
|
|
86
86
|
|
|
87
87
|
# Each queue is checked from the root it lies below, and its folder created there,
|
|
88
88
|
# by queue_append_row (D-HOOKS-NO-SYMLINK, git-marker).
|
|
89
89
|
TS=$(date +%s)
|
|
90
90
|
queue_append_both \
|
|
91
91
|
"$PROJECT_ROOT" "$MEMORY_DIR/.pending-turns.jsonl" \
|
|
92
|
-
"$LEDGER_ROOT" "$
|
|
93
|
-
"$MEMORY_ENABLED" "$
|
|
92
|
+
"$LEDGER_ROOT" "$CURATION_DIR/.pending-turns.jsonl" \
|
|
93
|
+
"$MEMORY_ENABLED" "$CURATION_ENABLED" \
|
|
94
94
|
"user" "$PROMPT" "$TS"
|
|
95
95
|
|
|
96
96
|
log "Captured user turn (${#PROMPT} chars)"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Curation System: capture-question (PostToolUse Hook, matcher: AskUserQuestion)
|
|
4
4
|
# Captures each answered question as a {role:"qa"} row into BOTH the memory and
|
|
5
|
-
#
|
|
5
|
+
# curation queues -- high decision-signal content that free-running Stop-hook
|
|
6
6
|
# turns miss (the model's own paraphrase of an answer is lossy; the raw Q&A
|
|
7
7
|
# pair is not).
|
|
8
8
|
#
|
|
@@ -60,18 +60,18 @@ devflow_debug_set_cwd "$CWD"
|
|
|
60
60
|
source "$SCRIPT_DIR/resolve-project-root" 2>/dev/null || true
|
|
61
61
|
df_resolve_roots "$CWD" 2>/dev/null || true
|
|
62
62
|
PROJECT_ROOT="${DF_ROOT:-$CWD}"
|
|
63
|
-
# The
|
|
63
|
+
# The curation queue is the repository's, not the checkout's: in a linked worktree
|
|
64
64
|
# it is the main worktree's queue (D-LEDGER-MAIN-WORKTREE, resolve-project-root),
|
|
65
65
|
# the one the Curate agent drains into the shared ledger. Memory stays per checkout.
|
|
66
66
|
LEDGER_ROOT="${DF_LEDGER_ROOT:-$PROJECT_ROOT}"
|
|
67
67
|
|
|
68
|
-
# The machine-wide manifest (the memory/
|
|
68
|
+
# The machine-wide manifest (the memory/curation switches, D-FEATURES-NARROW-ONLY
|
|
69
69
|
# in queue-append) lives at the machine root,
|
|
70
70
|
# $HOME/.devflow (D-ONE-HOME).
|
|
71
71
|
DEVFLOW_MANIFEST="$HOME/.devflow/manifest.json"
|
|
72
72
|
PROJECT_DEVFLOW_DIR="$PROJECT_ROOT/.devflow"
|
|
73
73
|
MEMORY_DIR="$PROJECT_DEVFLOW_DIR/memory"
|
|
74
|
-
|
|
74
|
+
CURATION_DIR="$LEDGER_ROOT/.devflow/curation"
|
|
75
75
|
|
|
76
76
|
# --- Parse questions + answers into "question<TAB>answer" rows (one subprocess) ---
|
|
77
77
|
# Defensive against: tool_response absent or a plain string (error case), missing
|
|
@@ -123,14 +123,14 @@ fi
|
|
|
123
123
|
|
|
124
124
|
source "$SCRIPT_DIR/queue-append" || { echo "capture-question: failed to source queue-append" >&2; exit 1; }
|
|
125
125
|
|
|
126
|
-
# --- AC-P1: at most ONE gate-read fork for memory +
|
|
126
|
+
# --- AC-P1: at most ONE gate-read fork for memory + curation ---
|
|
127
127
|
# The machine switch, narrowed by this checkout's project.json/config.json
|
|
128
128
|
# `features` (D-FEATURES-NARROW-ONLY, see queue-append).
|
|
129
129
|
queue_read_gates "$DEVFLOW_MANIFEST" "$PROJECT_ROOT"
|
|
130
130
|
MEMORY_ENABLED="$_QG_MEMORY"
|
|
131
|
-
|
|
131
|
+
CURATION_ENABLED="$_QG_CURATION"
|
|
132
132
|
|
|
133
|
-
if [ "$MEMORY_ENABLED" != "true" ] && [ "$
|
|
133
|
+
if [ "$MEMORY_ENABLED" != "true" ] && [ "$CURATION_ENABLED" != "true" ]; then
|
|
134
134
|
dbg "EXIT: both features disabled"
|
|
135
135
|
exit 0
|
|
136
136
|
fi
|
|
@@ -138,7 +138,7 @@ fi
|
|
|
138
138
|
source "$SCRIPT_DIR/hook-log-init" "capture-question"
|
|
139
139
|
source "$SCRIPT_DIR/ensure-devflow-init" "$CWD" || exit 0
|
|
140
140
|
source "$SCRIPT_DIR/get-mtime" || { echo "capture-question: failed to source get-mtime" >&2; exit 1; }
|
|
141
|
-
source "$SCRIPT_DIR/
|
|
141
|
+
source "$SCRIPT_DIR/curation-lock" || { echo "capture-question: failed to source curation-lock" >&2; exit 1; }
|
|
142
142
|
|
|
143
143
|
# Each queue is checked from the root it lies below, and its folder created there,
|
|
144
144
|
# by queue_append_row (D-HOOKS-NO-SYMLINK, git-marker).
|
|
@@ -151,8 +151,8 @@ A: ${A}"
|
|
|
151
151
|
TS=$(date +%s)
|
|
152
152
|
queue_append_both \
|
|
153
153
|
"$PROJECT_ROOT" "$MEMORY_DIR/.pending-turns.jsonl" \
|
|
154
|
-
"$LEDGER_ROOT" "$
|
|
155
|
-
"$MEMORY_ENABLED" "$
|
|
154
|
+
"$LEDGER_ROOT" "$CURATION_DIR/.pending-turns.jsonl" \
|
|
155
|
+
"$MEMORY_ENABLED" "$CURATION_ENABLED" \
|
|
156
156
|
"qa" "$CONTENT" "$TS"
|
|
157
157
|
_QUESTION_COUNT=$(( _QUESTION_COUNT + 1 ))
|
|
158
158
|
done <<EOF
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Curation System: capture-turn (Stop Hook)
|
|
4
4
|
# Dual-append: writes the assistant turn to BOTH the memory queue and the
|
|
5
|
-
#
|
|
5
|
+
# curation queue, each independently gated by its own feature flag (AC-F4). The
|
|
6
6
|
# 120s-throttle/nohup-spawn block lives in memory-worker, a separate Stop
|
|
7
7
|
# hook that Claude Code runs in parallel with this one; the worker tolerates a
|
|
8
8
|
# queue this hook has not appended to yet (D-QUEUE-NO-ORPHAN-DELETE).
|
|
@@ -45,18 +45,18 @@ dbg "ASSISTANT_MSG length=${#ASSISTANT_MSG}"
|
|
|
45
45
|
source "$SCRIPT_DIR/resolve-project-root" 2>/dev/null || true
|
|
46
46
|
df_resolve_roots "$CWD" 2>/dev/null || true
|
|
47
47
|
PROJECT_ROOT="${DF_ROOT:-$CWD}"
|
|
48
|
-
# The
|
|
48
|
+
# The curation queue is the repository's, not the checkout's: in a linked worktree
|
|
49
49
|
# it is the main worktree's queue (D-LEDGER-MAIN-WORKTREE, resolve-project-root),
|
|
50
50
|
# the one the Curate agent drains into the shared ledger. Memory stays per checkout.
|
|
51
51
|
LEDGER_ROOT="${DF_LEDGER_ROOT:-$PROJECT_ROOT}"
|
|
52
52
|
|
|
53
|
-
# The machine-wide manifest (the memory/
|
|
53
|
+
# The machine-wide manifest (the memory/curation switches, D-FEATURES-NARROW-ONLY
|
|
54
54
|
# in queue-append) lives at the machine root,
|
|
55
55
|
# $HOME/.devflow (D-ONE-HOME).
|
|
56
56
|
DEVFLOW_MANIFEST="$HOME/.devflow/manifest.json"
|
|
57
57
|
PROJECT_DEVFLOW_DIR="$PROJECT_ROOT/.devflow"
|
|
58
58
|
MEMORY_DIR="$PROJECT_DEVFLOW_DIR/memory"
|
|
59
|
-
|
|
59
|
+
CURATION_DIR="$LEDGER_ROOT/.devflow/curation"
|
|
60
60
|
|
|
61
61
|
# Skip if empty response
|
|
62
62
|
if [ -z "$ASSISTANT_MSG" ]; then
|
|
@@ -66,16 +66,16 @@ fi
|
|
|
66
66
|
|
|
67
67
|
source "$SCRIPT_DIR/queue-append" || { echo "capture-turn: failed to source queue-append" >&2; exit 1; }
|
|
68
68
|
|
|
69
|
-
# --- AC-P1: at most ONE gate-read fork for memory +
|
|
69
|
+
# --- AC-P1: at most ONE gate-read fork for memory + curation ---
|
|
70
70
|
# The machine switch, narrowed by this checkout's project.json/config.json
|
|
71
71
|
# `features` (D-FEATURES-NARROW-ONLY, see queue-append).
|
|
72
72
|
queue_read_gates "$DEVFLOW_MANIFEST" "$PROJECT_ROOT"
|
|
73
73
|
MEMORY_ENABLED="$_QG_MEMORY"
|
|
74
|
-
|
|
74
|
+
CURATION_ENABLED="$_QG_CURATION"
|
|
75
75
|
|
|
76
|
-
dbg "MEMORY_ENABLED=$MEMORY_ENABLED
|
|
76
|
+
dbg "MEMORY_ENABLED=$MEMORY_ENABLED CURATION_ENABLED=$CURATION_ENABLED"
|
|
77
77
|
|
|
78
|
-
if [ "$MEMORY_ENABLED" != "true" ] && [ "$
|
|
78
|
+
if [ "$MEMORY_ENABLED" != "true" ] && [ "$CURATION_ENABLED" != "true" ]; then
|
|
79
79
|
dbg "EXIT: both features disabled"
|
|
80
80
|
exit 0
|
|
81
81
|
fi
|
|
@@ -88,15 +88,15 @@ source "$SCRIPT_DIR/hook-log-init" "capture-turn"
|
|
|
88
88
|
source "$SCRIPT_DIR/ensure-devflow-init" "$CWD" || exit 0
|
|
89
89
|
|
|
90
90
|
source "$SCRIPT_DIR/get-mtime" || { echo "capture-turn: failed to source get-mtime" >&2; exit 1; }
|
|
91
|
-
source "$SCRIPT_DIR/
|
|
91
|
+
source "$SCRIPT_DIR/curation-lock" || { echo "capture-turn: failed to source curation-lock" >&2; exit 1; }
|
|
92
92
|
|
|
93
93
|
# Each queue is checked from the root it lies below, and its folder created there,
|
|
94
94
|
# by queue_append_row (D-HOOKS-NO-SYMLINK, git-marker).
|
|
95
95
|
TS=$(date +%s)
|
|
96
96
|
queue_append_both \
|
|
97
97
|
"$PROJECT_ROOT" "$MEMORY_DIR/.pending-turns.jsonl" \
|
|
98
|
-
"$LEDGER_ROOT" "$
|
|
99
|
-
"$MEMORY_ENABLED" "$
|
|
98
|
+
"$LEDGER_ROOT" "$CURATION_DIR/.pending-turns.jsonl" \
|
|
99
|
+
"$MEMORY_ENABLED" "$CURATION_ENABLED" \
|
|
100
100
|
"assistant" "$ASSISTANT_MSG" "$TS"
|
|
101
101
|
|
|
102
102
|
log "Captured assistant turn (${#ASSISTANT_MSG} chars)"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# break) rather than crashing on a BSD-only stat flag — safe-by-construction,
|
|
9
9
|
# never a hard failure, but source get-mtime first for accurate staleness.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
curation_lock_acquire() {
|
|
12
12
|
local lock_dir="$1"
|
|
13
13
|
local timeout="${2:-3}"
|
|
14
14
|
local stale_threshold=30
|
|
@@ -39,6 +39,6 @@ learning_lock_acquire() {
|
|
|
39
39
|
return 1
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
curation_lock_release() {
|
|
43
43
|
rmdir "$1" 2>/dev/null || true
|
|
44
44
|
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# curation-move -- the one-time ledger folder move for hooks (sourced, not executed).
|
|
3
|
+
#
|
|
4
|
+
# Sourcing it only defines functions -- no side effects until they are called. It is
|
|
5
|
+
# installed with the other hooks and is NOT gated on the curation switch: the old
|
|
6
|
+
# folder holds the project's decisions whether the feature is on or off, and a
|
|
7
|
+
# creator that ran before the move would hide it.
|
|
8
|
+
#
|
|
9
|
+
# df_curation_move <ledger_root> [retry]
|
|
10
|
+
# Moves <ledger_root>/.devflow/learning to .devflow/curation, once, by running the
|
|
11
|
+
# move op of json-helper.cjs (moveLedger in lib/curation-move.cjs). <ledger_root>
|
|
12
|
+
# is the repository's ledger root (DF_LEDGER_ROOT), never the checkout's own.
|
|
13
|
+
# Always returns 0, and never waits: a move another process holds is skipped and
|
|
14
|
+
# tried again at the next event.
|
|
15
|
+
#
|
|
16
|
+
# 1. Probe the old folder with a builtin test. Absent is the steady state: nothing
|
|
17
|
+
# is forked, no key is computed, nothing is logged.
|
|
18
|
+
# 2. Builtin refusals, which do nothing else: the root is not a directory, is a
|
|
19
|
+
# link, is the home directory or the machine directory (~/.devflow), or a link
|
|
20
|
+
# sits on the way down to its .devflow. The op refuses the same set.
|
|
21
|
+
# 3. Compute the marker key (df_curation_move_key) with builtins only, and test the
|
|
22
|
+
# four markers, files at the logs root, never inside the project: curation-move.<key>.skip
|
|
23
|
+
# (an earlier attempt was refused or failed; the refused marker), curation-move.<key>.busy
|
|
24
|
+
# (a fresh queue claim held the move, and a claim can last 15 minutes), and
|
|
25
|
+
# curation-move.<key>.tracked and curation-move.<key>.unknown (the move was skipped: git
|
|
26
|
+
# tracks files in a curation data folder, or git did not answer). A marker means
|
|
27
|
+
# return, with no fork and no log line. A second argument `retry` (session start) removes all four first, so a
|
|
28
|
+
# refusal, a skip and a claim are looked at again once per session; that removal
|
|
29
|
+
# forks only when a marker exists. The HUD and status read every marker but the busy
|
|
30
|
+
# one: a busy move is not a refusal.
|
|
31
|
+
# 4. Fork node once, with the root as one argv word (no path is ever part of the
|
|
32
|
+
# script text); the op knows the two folder names itself. The op answers on its first line:
|
|
33
|
+
# moved <mode> one log line (the mode, then the backup folder and the notes
|
|
34
|
+
# the op reports, when any);
|
|
35
|
+
# busy nothing: no log line and no marker, the next event retries (a
|
|
36
|
+
# lock is held, which does not last);
|
|
37
|
+
# busy claim nothing is logged, and the busy marker is written, so no event
|
|
38
|
+
# forks node again until the next session start;
|
|
39
|
+
# skipped <why> <why> is `tracked` or `unknown`. Nothing in the project changed.
|
|
40
|
+
# One log line (with `unknown`, the fixed cause word from the
|
|
41
|
+
# op's `cause <word>` line), the marker of that reason is
|
|
42
|
+
# written, and DF_CURATION_MOVE_SKIPPED is set to the reason, so
|
|
43
|
+
# the hook that called the shim can tell the user
|
|
44
|
+
# (session-start-context);
|
|
45
|
+
# none nothing (the folder went away after the probe);
|
|
46
|
+
# anything else, or a non-zero exit (a refusal or an I/O error): one log line,
|
|
47
|
+
# and the refused marker is written.
|
|
48
|
+
# Hooks that run under `set -e` call it as a plain statement: every path ends in
|
|
49
|
+
# `return 0`, the marker write and the node call are guarded.
|
|
50
|
+
#
|
|
51
|
+
# DF_CURATION_MOVE_SKIPPED is empty at every call, and holds the reason only after the
|
|
52
|
+
# call that ran node and got a skip.
|
|
53
|
+
#
|
|
54
|
+
# df_curation_move_key <root>
|
|
55
|
+
# Sets DF_CURATION_MOVE_KEY to the marker key of <root>: a 32-bit hash over every
|
|
56
|
+
# byte of the root under LC_ALL=C (each step: hash = 33 * hash + (byte & 255),
|
|
57
|
+
# masked to 32 bits, starting from 5381) as eight lowercase hex characters, an
|
|
58
|
+
# underscore, then the last 60 bytes of the root (all of them when it is shorter)
|
|
59
|
+
# with every byte outside letters and digits replaced by an underscore. The
|
|
60
|
+
# TypeScript twin is curationMoveMarkerKey in src/core/project-paths.ts; status and
|
|
61
|
+
# the HUD read the marker through it (tests/shell-hooks.test.ts holds both to the
|
|
62
|
+
# same answers). Returns 1, setting nothing, for a root of more than 4096 bytes.
|
|
63
|
+
#
|
|
64
|
+
# df_curation_move_plain <text>
|
|
65
|
+
# Sets DF_CURATION_MOVE_PLAIN to <text> with every control character replaced by a
|
|
66
|
+
# space: C0 and DEL, and the C1 controls U+0080-U+009F (the bytes C2 80 to C2 9F in
|
|
67
|
+
# UTF-8). Builtin expansion only, so it forks nothing. node's answer can carry a name
|
|
68
|
+
# the repository holds, and no log line may carry an escape sequence or break in two.
|
|
69
|
+
# It works bytewise under LC_ALL=C, which it puts back as it was, so other UTF-8
|
|
70
|
+
# text, whatever the locale, is kept.
|
|
71
|
+
#
|
|
72
|
+
# df_curation_move_names
|
|
73
|
+
# Sets the names the shell reads: DF_CM_OLD_FOLDER and DF_CM_NEW_FOLDER (the ledger
|
|
74
|
+
# folder before and after the rename) and DF_CM_NEW_TUNING_FILE. They mirror
|
|
75
|
+
# LEGACY_NAMES.folder, CURATION_DIR_NAME and CURATION_TUNING_FILE_NAME in
|
|
76
|
+
# src/core/project-paths.ts and hooks/lib/project-paths.cjs (tests/project-paths.test.ts
|
|
77
|
+
# holds all three to the same values). The old backup prefix and the old tuning file
|
|
78
|
+
# are read only by the node move, so the shell holds no copy of them. Every hook that
|
|
79
|
+
# names a ledger folder (queue-append, ensure-devflow-init, session-start-context)
|
|
80
|
+
# takes the name from here.
|
|
81
|
+
#
|
|
82
|
+
# Source-order requirement: none. This file sources its sibling git-marker itself,
|
|
83
|
+
# for df_no_symlink_below; one that failed to source is a command not found, which
|
|
84
|
+
# the refusal below reads as "a link may sit on the path". The caller defines log()
|
|
85
|
+
# to have the move reported; without it the move is silent.
|
|
86
|
+
|
|
87
|
+
source "${BASH_SOURCE[0]%/*}/git-marker" 2>/dev/null || true
|
|
88
|
+
|
|
89
|
+
df_curation_move_names() {
|
|
90
|
+
DF_CM_OLD_FOLDER="learning"
|
|
91
|
+
DF_CM_NEW_FOLDER="curation"
|
|
92
|
+
DF_CM_NEW_TUNING_FILE="curation.json"
|
|
93
|
+
return 0
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
df_curation_move_key() {
|
|
97
|
+
local _root="$1" _hash=5381 _i=0 _len=0 _char="" _byte=0 _start=0 _hex="" _tail="" _lc_set=0 _lc_old=""
|
|
98
|
+
if [ "${LC_ALL+set}" = "set" ]; then _lc_set=1; _lc_old="$LC_ALL"; fi
|
|
99
|
+
LC_ALL=C
|
|
100
|
+
_len=${#_root}
|
|
101
|
+
if [ "$_len" -gt 4096 ]; then
|
|
102
|
+
if [ "$_lc_set" = 1 ]; then LC_ALL="$_lc_old"; else unset LC_ALL; fi
|
|
103
|
+
return 1
|
|
104
|
+
fi
|
|
105
|
+
while [ "$_i" -lt "$_len" ]; do
|
|
106
|
+
_char="${_root:$_i:1}"
|
|
107
|
+
printf -v _byte '%d' "'$_char"
|
|
108
|
+
_hash=$(( (_hash * 33 + (_byte & 255)) & 4294967295 ))
|
|
109
|
+
_i=$((_i + 1))
|
|
110
|
+
done
|
|
111
|
+
printf -v _hex '%08x' "$_hash"
|
|
112
|
+
if [ "$_len" -gt 60 ]; then _start=$((_len - 60)); fi
|
|
113
|
+
_tail="${_root:$_start}"
|
|
114
|
+
_tail="${_tail//[^A-Za-z0-9]/_}"
|
|
115
|
+
if [ "$_lc_set" = 1 ]; then LC_ALL="$_lc_old"; else unset LC_ALL; fi
|
|
116
|
+
DF_CURATION_MOVE_KEY="${_hex}_${_tail}"
|
|
117
|
+
return 0
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
df_curation_move_plain() {
|
|
121
|
+
local _text="$1" _lc_set=0 _lc_old=""
|
|
122
|
+
if [ "${LC_ALL+set}" = "set" ]; then _lc_set=1; _lc_old="$LC_ALL"; fi
|
|
123
|
+
LC_ALL=C
|
|
124
|
+
_text="${_text//$'\xc2'[$'\x80'-$'\x9f']/ }"
|
|
125
|
+
_text="${_text//[[:cntrl:]]/ }"
|
|
126
|
+
if [ "$_lc_set" = 1 ]; then LC_ALL="$_lc_old"; else unset LC_ALL; fi
|
|
127
|
+
DF_CURATION_MOVE_PLAIN="$_text"
|
|
128
|
+
return 0
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# The marker paths of the key in DF_CURATION_MOVE_KEY, built here and nowhere else. The
|
|
132
|
+
# refused marker keeps the file suffix `.skip` (getCurationMoveMarkerPath in
|
|
133
|
+
# src/core/curation-move-notice.ts).
|
|
134
|
+
_df_cm_markers() {
|
|
135
|
+
local _stem="$HOME/.devflow/logs/curation-move.${DF_CURATION_MOVE_KEY}"
|
|
136
|
+
DF_CM_REFUSED_MARKER="${_stem}.skip"
|
|
137
|
+
DF_CM_BUSY_MARKER="${_stem}.busy"
|
|
138
|
+
DF_CM_TRACKED_MARKER="${_stem}.tracked"
|
|
139
|
+
DF_CM_UNKNOWN_MARKER="${_stem}.unknown"
|
|
140
|
+
return 0
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# True when anything stands at $1. A link counts as a marker and is never followed.
|
|
144
|
+
_df_cm_present() {
|
|
145
|
+
[ -e "$1" ] || [ -L "$1" ]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Remove the marker at $1. It forks only when one stands there.
|
|
149
|
+
_df_cm_clear() {
|
|
150
|
+
if _df_cm_present "$1"; then rm -f "$1" 2>/dev/null || true; fi
|
|
151
|
+
return 0
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Create the marker at $1 under noclobber, which is put back as it was. A marker that
|
|
155
|
+
# cannot be written is ignored: the next event looks again.
|
|
156
|
+
_df_cm_mark() {
|
|
157
|
+
local _noclobber=0
|
|
158
|
+
case $- in *C*) _noclobber=1 ;; esac
|
|
159
|
+
if ! _df_cm_present "$1"; then
|
|
160
|
+
set -o noclobber
|
|
161
|
+
{ : > "$1"; } 2>/dev/null || true
|
|
162
|
+
if [ "$_noclobber" = 0 ]; then set +o noclobber; fi
|
|
163
|
+
fi
|
|
164
|
+
return 0
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# Log the one line for a move: $1 is the op's whole answer, $2 its first line (`moved <mode>`).
|
|
168
|
+
# The lines after it are `backup <name>` and `note <text>`; at most 32 are read.
|
|
169
|
+
_df_cm_report_moved() {
|
|
170
|
+
local _out="$1" _first="$2" _rest="" _line="" _backup="" _notes="" _n=0
|
|
171
|
+
_rest="${_out#*$'\n'}"
|
|
172
|
+
if [ "$_rest" = "$_out" ]; then _rest=""; fi
|
|
173
|
+
while [ -n "$_rest" ] && [ "$_n" -lt 32 ]; do
|
|
174
|
+
_line="${_rest%%$'\n'*}"
|
|
175
|
+
case "$_line" in
|
|
176
|
+
"backup "*) df_curation_move_plain "${_line#backup }"; _backup=", backup folder $DF_CURATION_MOVE_PLAIN" ;;
|
|
177
|
+
"note "*) df_curation_move_plain "${_line#note }"; _notes="${_notes:+$_notes; }$DF_CURATION_MOVE_PLAIN" ;;
|
|
178
|
+
esac
|
|
179
|
+
if [ "$_line" = "$_rest" ]; then _rest=""; else _rest="${_rest#*$'\n'}"; fi
|
|
180
|
+
_n=$((_n + 1))
|
|
181
|
+
done
|
|
182
|
+
if declare -F log >/dev/null 2>&1; then
|
|
183
|
+
log "Curation move (${_first#moved })${_backup}${_notes:+: $_notes}"
|
|
184
|
+
fi
|
|
185
|
+
return 0
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Log the refusal in $1 (the op's first line) and write the refused marker.
|
|
189
|
+
_df_cm_mark_refused() {
|
|
190
|
+
if declare -F log >/dev/null 2>&1; then
|
|
191
|
+
log "Curation move refused: ${1:0:200}; not tried again until the next session start"
|
|
192
|
+
fi
|
|
193
|
+
_df_cm_mark "$DF_CM_REFUSED_MARKER"
|
|
194
|
+
return 0
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# Set DF_CM_CAUSE to the fixed word of the `cause <word>` line in the op's whole answer $1
|
|
198
|
+
# (lowercase letters and hyphens, at most 32 bytes), or to "" when there is none.
|
|
199
|
+
_df_cm_cause() {
|
|
200
|
+
local _rest="${1#*$'\n'}" _word=""
|
|
201
|
+
DF_CM_CAUSE=""
|
|
202
|
+
if [ "$_rest" = "$1" ]; then return 0; fi
|
|
203
|
+
case "${_rest%%$'\n'*}" in
|
|
204
|
+
"cause "*) _word="${_rest%%$'\n'*}"; _word="${_word#cause }" ;;
|
|
205
|
+
esac
|
|
206
|
+
case "$_word" in
|
|
207
|
+
""|*[!a-z-]*) ;;
|
|
208
|
+
*) if [ "${#_word}" -le 32 ]; then DF_CM_CAUSE="$_word"; fi ;;
|
|
209
|
+
esac
|
|
210
|
+
return 0
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# Log the skip for reason $1 (`tracked` or `unknown`), write its marker and record the
|
|
214
|
+
# reason in DF_CURATION_MOVE_SKIPPED. $2 is the op's whole answer, whose cause word the
|
|
215
|
+
# `unknown` line carries. The log words are fixed: they hold nothing from the project.
|
|
216
|
+
_df_cm_mark_skipped() {
|
|
217
|
+
local _why="$1" _words="" _marker=""
|
|
218
|
+
DF_CURATION_MOVE_SKIPPED="$_why"
|
|
219
|
+
case "$_why" in
|
|
220
|
+
tracked)
|
|
221
|
+
_words="git tracks files in the .devflow data folders"
|
|
222
|
+
_marker="$DF_CM_TRACKED_MARKER" ;;
|
|
223
|
+
*)
|
|
224
|
+
_df_cm_cause "${2:-}"
|
|
225
|
+
_words="git did not answer${DF_CM_CAUSE:+ ($DF_CM_CAUSE)}"
|
|
226
|
+
_marker="$DF_CM_UNKNOWN_MARKER" ;;
|
|
227
|
+
esac
|
|
228
|
+
if declare -F log >/dev/null 2>&1; then
|
|
229
|
+
log "Curation move skipped: $_words; not tried again until the next session start"
|
|
230
|
+
fi
|
|
231
|
+
_df_cm_mark "$_marker"
|
|
232
|
+
return 0
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
df_curation_move() {
|
|
236
|
+
local _root="$1" _retry="${2:-}"
|
|
237
|
+
local _devflow="" _back="$PWD" _real="" _home_real="" _machine_real=""
|
|
238
|
+
local _helper="" _out="" _rc=0 _first="" _marker=""
|
|
239
|
+
|
|
240
|
+
DF_CURATION_MOVE_SKIPPED=""
|
|
241
|
+
[ -n "$_root" ] || return 0
|
|
242
|
+
[ -n "${HOME:-}" ] || return 0
|
|
243
|
+
df_curation_move_names
|
|
244
|
+
_devflow="$_root/.devflow"
|
|
245
|
+
|
|
246
|
+
# 1. The steady state: no old folder (a link or a file at its name counts as
|
|
247
|
+
# present, so the op can refuse it).
|
|
248
|
+
if [ ! -e "$_devflow/$DF_CM_OLD_FOLDER" ] && [ ! -L "$_devflow/$DF_CM_OLD_FOLDER" ]; then
|
|
249
|
+
return 0
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
# 2. The refusals that need no fork. The physical paths are compared, because the
|
|
253
|
+
# root, HOME and a repository's toplevel reach here by different routes (on macOS
|
|
254
|
+
# /var is /private/var): cd -P publishes the resolved path in $PWD.
|
|
255
|
+
[ -d "$_root" ] || return 0
|
|
256
|
+
if [ -L "$_root" ]; then return 0; fi
|
|
257
|
+
if cd -P "$_root" 2>/dev/null; then _real="$PWD"; fi
|
|
258
|
+
if cd -P "$HOME" 2>/dev/null; then _home_real="$PWD"; fi
|
|
259
|
+
if cd -P "$HOME/.devflow" 2>/dev/null; then _machine_real="$PWD"; fi
|
|
260
|
+
cd "$_back" 2>/dev/null || true
|
|
261
|
+
[ -n "$_real" ] || return 0
|
|
262
|
+
if [ "$_real" = "$_home_real" ] || [ "$_real" = "$_machine_real" ]; then return 0; fi
|
|
263
|
+
if ! df_no_symlink_below "$_root" "$_devflow"; then return 0; fi
|
|
264
|
+
|
|
265
|
+
# 3. The markers.
|
|
266
|
+
df_curation_move_key "$_root" || return 0
|
|
267
|
+
_df_cm_markers
|
|
268
|
+
set -- "$DF_CM_REFUSED_MARKER" "$DF_CM_BUSY_MARKER" "$DF_CM_TRACKED_MARKER" "$DF_CM_UNKNOWN_MARKER"
|
|
269
|
+
if [ "$_retry" = "retry" ]; then
|
|
270
|
+
for _marker in "$@"; do _df_cm_clear "$_marker"; done
|
|
271
|
+
fi
|
|
272
|
+
for _marker in "$@"; do
|
|
273
|
+
if _df_cm_present "$_marker"; then return 0; fi
|
|
274
|
+
done
|
|
275
|
+
|
|
276
|
+
# 4. The one fork. 2>&1 so a refusal's message reaches the log line.
|
|
277
|
+
_helper="${BASH_SOURCE[0]%/*}/json-helper.cjs"
|
|
278
|
+
_out=$(node "$_helper" move-ledger "$_root" 2>&1) || _rc=$?
|
|
279
|
+
df_curation_move_plain "${_out%%$'\n'*}"
|
|
280
|
+
_first="$DF_CURATION_MOVE_PLAIN"
|
|
281
|
+
|
|
282
|
+
if [ "$_rc" -eq 0 ]; then
|
|
283
|
+
case "$_first" in
|
|
284
|
+
busy|none) return 0 ;;
|
|
285
|
+
"busy claim") _df_cm_mark "$DF_CM_BUSY_MARKER"; return 0 ;;
|
|
286
|
+
"skipped tracked") _df_cm_mark_skipped tracked; return 0 ;;
|
|
287
|
+
"skipped unknown") _df_cm_mark_skipped unknown "$_out"; return 0 ;;
|
|
288
|
+
"moved "*) _df_cm_report_moved "$_out" "$_first"; return 0 ;;
|
|
289
|
+
esac
|
|
290
|
+
fi
|
|
291
|
+
_df_cm_mark_refused "$_first"
|
|
292
|
+
return 0
|
|
293
|
+
}
|