devflow-kit 3.7.0 → 3.8.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 +359 -1
- package/README.md +11 -11
- package/dist/agents/code.md +2 -4
- package/dist/agents/design.md +1 -1
- package/dist/agents/fix.md +6 -7
- package/dist/agents/git.md +1 -1
- 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 +2 -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/{learning.js → curation.js} +302 -99
- package/dist/cli/commands/gate-level-prompts.js +426 -0
- package/dist/cli/commands/gates.js +34 -11
- package/dist/cli/commands/init-seed.js +11 -8
- package/dist/cli/commands/init.js +240 -54
- package/dist/cli/commands/memory.js +2 -2
- package/dist/cli/commands/prompt-io.js +37 -0
- package/dist/cli/commands/uninstall.js +81 -9
- package/dist/cli.js +2 -2
- package/dist/commands/bug-analysis.md +24 -35
- package/dist/commands/code-review.md +32 -36
- package/dist/commands/debug.md +4 -4
- package/dist/commands/dynamic-build.md +25 -24
- package/dist/commands/dynamic-plan.md +4 -4
- package/dist/commands/dynamic-profile.md +4 -4
- package/dist/commands/dynamic-tickets.md +4 -4
- package/dist/commands/explore.md +5 -5
- package/dist/commands/implement.md +52 -81
- package/dist/commands/plan.md +5 -5
- package/dist/commands/release.md +4 -4
- package/dist/commands/research.md +5 -5
- package/dist/commands/resolve.md +28 -30
- package/dist/commands/self-review.md +4 -12
- 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 +36 -0
- package/dist/core/curation-move.js +35 -0
- package/dist/core/curation-queue-cleanup.js +134 -0
- package/dist/core/curation-store.js +98 -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 +1 -1
- 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 +15 -15
- 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 +35 -0
- package/dist/core/tracker.js +1 -1
- package/dist/{learning-off → curation-off}/agents/code.md +2 -4
- package/dist/{learning-off → curation-off}/agents/fix.md +6 -7
- 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 +1 -1
- package/dist/{learning-off → curation-off}/commands/bug-analysis.md +20 -31
- package/dist/{learning-off → curation-off}/commands/code-review.md +29 -33
- package/dist/{learning-off → curation-off}/commands/debug.md +1 -1
- package/dist/{learning-off → curation-off}/commands/dynamic-build.md +22 -21
- package/dist/{learning-off → curation-off}/commands/explore.md +1 -1
- package/dist/{learning-off → curation-off}/commands/implement.md +49 -78
- package/dist/{learning-off → curation-off}/commands/plan.md +1 -1
- package/dist/{learning-off → curation-off}/commands/resolve.md +25 -27
- package/dist/{learning-off → curation-off}/commands/self-review.md +1 -9
- package/dist/hud/components/{learning-counts.js → curation-counts.js} +36 -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/validate-branch.md +8 -5
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +13 -5
- 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 +2 -2
- 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/fix.mds +6 -5
- package/src/assets/agents/git.mds +1 -1
- 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 +22 -22
- package/src/assets/agents/validate.md +13 -26
- package/src/assets/commands/_partials/_decisions.mds +13 -13
- 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 +2 -2
- 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 +46 -50
- 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 +56 -50
- package/src/assets/commands/self-review.mds +20 -20
- package/src/assets/mds/git/_ops.mds +3 -1
- package/src/assets/mds/git/_ops_pr.mds +1 -1
- package/src/assets/mds/git/_ops_steps.mds +10 -4
- package/src/assets/mds/git/_pr.mds +7 -4
- package/src/assets/rules/quality.md +1 -0
- package/src/assets/scripts/hooks/background-memory-update +2 -2
- 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 +134 -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} +242 -118
- 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 +34 -22
- package/src/assets/scripts/hooks/lib/tracked-areas.cjs +259 -0
- package/src/assets/scripts/hooks/memory-worker +1 -1
- package/src/assets/scripts/hooks/pre-compact-memory +6 -16
- package/src/assets/scripts/hooks/queue-append +228 -103
- package/src/assets/scripts/hooks/resolve-project-root +2 -2
- package/src/assets/scripts/hooks/session-start-context +143 -69
- package/src/assets/scripts/hooks/session-start-memory +3 -43
- package/src/assets/scripts/lib/project-config.cjs +12 -5
- package/src/assets/scripts/resolve-base.cjs +1043 -0
- package/src/assets/scripts/resolve-settings.cjs +42 -18
- package/src/assets/skills/agent-references/SKILL.md +5 -2
- package/src/assets/skills/agent-references/references/commands/implement-levels.md +27 -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 +24 -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/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
|
@@ -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
|
+
}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
# ignores .devflow/. Called from capture-prompt, capture-turn, capture-question,
|
|
4
4
|
# memory-worker, and pre-compact-memory. Idempotent.
|
|
5
5
|
# Returns non-zero, having written nothing, outside a git project, at HOME, or when
|
|
6
|
-
# .devflow is a symbolic link.
|
|
6
|
+
# .devflow is a symbolic link. Also non-zero when it is given no root, when the ledger
|
|
7
|
+
# folder check fails (df_ledger_folder), and when the scaffold folders cannot be made.
|
|
8
|
+
# A missing curation-move shim is no failure: the ledger folder step is skipped.
|
|
7
9
|
# Usage: source ensure-devflow-init "$CWD"
|
|
8
10
|
|
|
9
11
|
[ -z "$1" ] && return 1
|
|
@@ -24,12 +26,14 @@ _EDI_LEDGER="${DF_LEDGER_ROOT:-$_EDI_ROOT}"
|
|
|
24
26
|
# `.git` of its own, so it is refused rather than scaffolded. This is the one gate every capture and
|
|
25
27
|
# memory caller (capture-prompt, capture-turn, capture-question, memory-worker,
|
|
26
28
|
# pre-compact-memory) passes through before it writes, and each of them treats a
|
|
27
|
-
# non-zero return as "stop" (`|| exit 0`), so memory and
|
|
29
|
+
# non-zero return as "stop" (`|| exit 0`), so memory and curation stop together
|
|
28
30
|
# in a non-git directory. ensure-root-gitignore itself stays ungated: it is a
|
|
29
31
|
# pure writer its parity suite runs in plain directories, and both of
|
|
30
|
-
# its callers now decide before reaching it. A helper that
|
|
31
|
-
# command not found — the refusing branch, so the failure mode writes nothing.
|
|
32
|
-
source
|
|
32
|
+
# its callers now decide before reaching it. A helper that is missing is a
|
|
33
|
+
# command not found — the refusing branch, so the failure mode writes nothing. Each
|
|
34
|
+
# source is guarded by a builtin readability test: bash 3.2 under `set -e` ends the
|
|
35
|
+
# shell at a `source` of a missing file, even inside `|| true`.
|
|
36
|
+
[ -r "$_EDI_DIR/git-marker" ] && source "$_EDI_DIR/git-marker" 2>/dev/null || true
|
|
33
37
|
df_is_project_root "$_EDI_ROOT" 2>/dev/null || return 1
|
|
34
38
|
|
|
35
39
|
_DEVFLOW_DIR="$_EDI_ROOT/.devflow"
|
|
@@ -45,32 +49,60 @@ if ! df_no_symlink_below "$_EDI_ROOT" "$_DEVFLOW_DIR"; then
|
|
|
45
49
|
return 1
|
|
46
50
|
fi
|
|
47
51
|
|
|
48
|
-
#
|
|
52
|
+
# The one-time move of the old learning/ ledger folder to curation/ (curation-move):
|
|
53
|
+
# before anything below reads or creates the ledger folder, on the LEDGER root (the
|
|
54
|
+
# main worktree's, in a linked worktree), and whether or not the curation switch is
|
|
55
|
+
# on. A move another process holds, or a refused one, leaves the old folder where it
|
|
56
|
+
# is; the creator rule below then takes the old folder as the ledger folder. A shim that
|
|
57
|
+
# is missing skips the move, and the ledger folder step below.
|
|
58
|
+
[ -r "$_EDI_DIR/curation-move" ] && source "$_EDI_DIR/curation-move" 2>/dev/null || true
|
|
59
|
+
df_curation_move "$_EDI_LEDGER" 2>/dev/null || true
|
|
60
|
+
|
|
61
|
+
# curation/ is scaffolded only where the ledger lives. In a linked worktree whose
|
|
49
62
|
# main checkout holds the ledger (D-LEDGER-MAIN-WORKTREE, resolve-project-root) a
|
|
50
|
-
#
|
|
51
|
-
# create the ledger's own
|
|
52
|
-
|
|
53
|
-
|
|
63
|
+
# curation/ here would be an empty directory nothing writes to; the capture hooks
|
|
64
|
+
# create the ledger's own curation/ at the ledger root when they append to it.
|
|
65
|
+
# The creator rule (git-marker, df_make_ledger_folder): the folder made is the new one
|
|
66
|
+
# only when the old name is absent, so a new folder never hides an old ledger, and a
|
|
67
|
+
# creator never makes the old folder. The names come from the move shim
|
|
68
|
+
# (df_curation_move_names), the one shell place that holds them. Either name satisfies
|
|
69
|
+
# the fast path below, so a root whose old folder is still there (held, refused or not
|
|
70
|
+
# yet tried) forks no mkdir.
|
|
71
|
+
# Without the shim there are no names, and this step has no ledger folder to check or
|
|
72
|
+
# make: the rest of the scaffold goes on.
|
|
73
|
+
_EDI_CURATION=""
|
|
74
|
+
if df_curation_move_names 2>/dev/null; then
|
|
75
|
+
df_ledger_folder "$_DEVFLOW_DIR" "$DF_CM_OLD_FOLDER" "$DF_CM_NEW_FOLDER" || return 1
|
|
76
|
+
_EDI_CURATION="$DF_LEDGER_FOLDER"
|
|
77
|
+
[ "$_EDI_LEDGER" = "$_EDI_ROOT" ] || _EDI_CURATION=""
|
|
78
|
+
fi
|
|
54
79
|
|
|
55
80
|
# Fast-path: if all subdirectories already exist, skip mkdir and gitignore setup.
|
|
56
81
|
# The marker is the CURRENT carve-out version (D-GITIGNORE-V6), bumped with both
|
|
57
82
|
# writers' stamps, so a v5-stamped project misses this once and gains the
|
|
58
83
|
# `!.devflow/project.json` line.
|
|
59
84
|
if [ -d "$_DEVFLOW_DIR/memory" ] && [ -d "$_DEVFLOW_DIR/docs" ] && \
|
|
60
|
-
{ [ -z "$
|
|
85
|
+
{ [ -z "$_EDI_CURATION" ] || [ -d "$_EDI_CURATION" ]; } && \
|
|
61
86
|
[ -d "$_DEVFLOW_DIR/features" ] && \
|
|
62
87
|
[ -f "$_DEVFLOW_DIR/.root-gitignore-configured-v6" ]; then
|
|
63
88
|
return 0
|
|
64
89
|
fi
|
|
65
90
|
|
|
66
|
-
# Create all subdirectories
|
|
91
|
+
# Create all subdirectories. The ledger folder is made by its own guarded creator, never
|
|
92
|
+
# by this mkdir: a probe that saw the old folder must not bring it back after a move.
|
|
67
93
|
mkdir -p \
|
|
68
94
|
"$_DEVFLOW_DIR/memory" \
|
|
69
|
-
${_EDI_LEARNING:+"$_EDI_LEARNING"} \
|
|
70
95
|
"$_DEVFLOW_DIR/features" \
|
|
71
96
|
"$_DEVFLOW_DIR/docs" \
|
|
72
97
|
2>/dev/null || return 1
|
|
73
98
|
|
|
99
|
+
# A ledger folder that could not be made leaves the memory scaffold as it is, and the
|
|
100
|
+
# next event looks again. A link on its path makes the capture hooks refuse its queue;
|
|
101
|
+
# after a failed mkdir, the append makes the folder again, as its creator.
|
|
102
|
+
if [ -n "$_EDI_CURATION" ]; then
|
|
103
|
+
df_make_ledger_folder "$_EDI_ROOT" "$_DEVFLOW_DIR" "$DF_CM_OLD_FOLDER" "$DF_CM_NEW_FOLDER" || true
|
|
104
|
+
fi
|
|
105
|
+
|
|
74
106
|
# One-time root .gitignore setup — delegated to the sibling ensure-root-gitignore
|
|
75
107
|
# helper (single source of truth) so the always-on, memory-independent
|
|
76
108
|
# session-start-context hook applies the identical rule. _EDI_DIR and _EDI_ROOT
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
dbg() { :; }
|
|
23
23
|
|
|
24
24
|
# Re-entrancy guard — must come before hook-bootstrap to minimize overhead
|
|
25
|
-
# in background worker sessions (claude -p for memory /
|
|
25
|
+
# in background worker sessions (claude -p for memory / curation workers fire hooks too)
|
|
26
26
|
if [ "${DEVFLOW_BG_UPDATER:-}" = "1" ]; then exit 0; fi
|
|
27
27
|
|
|
28
28
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
@@ -266,11 +266,11 @@ fi
|
|
|
266
266
|
# Acquire spawn lock — prevents concurrent sessions from double-spawning the relay
|
|
267
267
|
# Stale break: 30s (lock abandoned by crashed hook)
|
|
268
268
|
source "$SCRIPT_DIR/get-mtime" 2>/dev/null || true
|
|
269
|
-
source "$SCRIPT_DIR/
|
|
269
|
+
source "$SCRIPT_DIR/curation-lock" 2>/dev/null || true
|
|
270
270
|
|
|
271
271
|
SPAWN_LOCK="$MACHINE_DEVFLOW_DIR/.proxy-spawn.lock"
|
|
272
272
|
|
|
273
|
-
if !
|
|
273
|
+
if ! curation_lock_acquire "$SPAWN_LOCK" 2 2>/dev/null; then
|
|
274
274
|
# Another session is racing to start the proxy — give it a moment then re-probe
|
|
275
275
|
log "spawn lock busy — another session may be starting proxy"
|
|
276
276
|
sleep 1
|
|
@@ -339,7 +339,7 @@ while [ "$_i" -lt 80 ]; do
|
|
|
339
339
|
_i=$(( _i + 1 ))
|
|
340
340
|
done
|
|
341
341
|
|
|
342
|
-
|
|
342
|
+
curation_lock_release "$SPAWN_LOCK"
|
|
343
343
|
|
|
344
344
|
if [ "$_RELAY_UP" = "true" ]; then
|
|
345
345
|
log "relay ready after $(( _i + 1 )) probe(s)"
|