devflow-kit 3.0.1 → 3.2.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 +49 -0
- package/README.md +1 -1
- package/dist/agents/git.md +2 -2
- package/dist/cli/agents-view/index.js +1 -1
- package/dist/cli/agents-view/render.js +71 -17
- package/dist/cli/agents-view/state.js +42 -16
- package/dist/cli/agents-view/terminal.js +5 -5
- package/dist/cli/commands/agents.js +142 -51
- package/dist/cli/commands/ambient.js +1 -1
- package/dist/cli/commands/attribution-prompts.js +8 -8
- package/dist/cli/commands/capture.js +1 -1
- package/dist/cli/commands/compliance-prompts.js +8 -8
- package/dist/cli/commands/compliance.js +8 -7
- package/dist/cli/commands/flags.js +33 -31
- package/dist/cli/commands/hud.js +1 -1
- package/dist/cli/commands/init-seed.js +9 -9
- package/dist/cli/commands/init.js +162 -85
- package/dist/cli/commands/install-report.js +10 -10
- package/dist/cli/commands/learning.js +302 -136
- package/dist/cli/commands/memory.js +36 -15
- package/dist/cli/commands/proxy.js +23 -23
- package/dist/cli/commands/rules.js +6 -5
- package/dist/cli/commands/tracker-prompts.js +6 -6
- package/dist/cli/commands/tracker.js +9 -9
- package/dist/cli/commands/uninstall.js +183 -59
- package/dist/cli/flags-view/render.js +5 -5
- package/dist/cli/flags-view/state.js +9 -9
- package/dist/cli/flags-view/terminal.js +4 -4
- package/dist/cli/tui/cells.js +1 -1
- package/dist/cli/tui/terminal.js +6 -6
- package/dist/commands/code-review.md +0 -2
- package/dist/commands/debug.md +14 -11
- package/dist/commands/dynamic-build.md +51 -47
- package/dist/commands/dynamic-plan.md +27 -7
- package/dist/commands/dynamic-profile.md +17 -3
- package/dist/commands/dynamic-tickets.md +18 -4
- package/dist/commands/explore.md +9 -3
- package/dist/commands/implement.md +20 -16
- package/dist/commands/plan.md +13 -9
- package/dist/commands/release.md +23 -3
- package/dist/commands/research.md +9 -3
- package/dist/commands/resolve.md +9 -12
- package/dist/commands/self-review.md +0 -2
- package/dist/core/agent-frontmatter.js +28 -3
- package/dist/core/agent-models.js +204 -42
- package/dist/core/agent-state.js +28 -6
- package/dist/core/ansi.js +2 -2
- package/dist/core/assets.js +1 -1
- package/dist/core/cache.js +7 -8
- package/dist/core/codex-auth-inspect.js +4 -4
- package/dist/core/compliance-compose.js +3 -3
- package/dist/core/compliance.js +3 -4
- package/dist/core/evidence-policy.js +14 -13
- package/dist/core/external-models.js +1 -1
- package/dist/core/feature-config.js +71 -13
- package/dist/core/feature-switch.js +3 -3
- package/dist/core/flags.js +49 -25
- package/dist/core/fs-atomic.js +6 -7
- package/dist/core/learning-queue-cleanup.js +16 -81
- package/dist/core/learning-store.js +61 -0
- package/dist/core/linked-path.js +46 -0
- package/dist/core/manifest.js +5 -5
- package/dist/core/mds-variants.js +13 -13
- package/dist/core/model-discovery.js +8 -8
- package/dist/core/observations.js +17 -101
- package/dist/core/orphan-sweep.js +4 -4
- package/dist/core/plugins.js +13 -8
- package/dist/core/project-paths.js +9 -13
- package/dist/core/proxy-log.js +8 -8
- package/dist/core/proxy-state.js +3 -3
- package/dist/core/queue-drain.js +31 -0
- package/dist/core/reference-sweep.js +6 -6
- package/dist/core/teammate-mode-cleanup.js +1 -1
- package/dist/core/tracker.js +14 -14
- package/dist/hud/colors.js +2 -2
- package/dist/hud/components/learning-counts.js +54 -22
- package/dist/hud/components/version-badge.js +1 -1
- package/dist/skills/git/references/pr/resolve-review-threads.md +2 -2
- package/dist/skills/git/references/tracker/github/create-release.md +2 -2
- package/dist/skills/git/references/tracker/jira/create-release.md +2 -2
- package/dist/skills/git/references/tracker/linear/create-release.md +2 -2
- package/dist/targets/claude-code/compliance-install.js +17 -15
- package/dist/targets/claude-code/hooks.js +2 -2
- package/dist/targets/claude-code/installer.js +59 -32
- package/dist/targets/claude-code/legacy.js +1 -1
- package/dist/targets/claude-code/post-install.js +135 -45
- package/dist/targets/claude-code/tracker-install.js +2 -2
- package/package.json +1 -1
- package/src/assets/agents/code.md +15 -21
- package/src/assets/agents/design.md +4 -2
- package/src/assets/agents/diagnose.md +3 -1
- package/src/assets/agents/evaluate.md +4 -0
- package/src/assets/agents/git.mds +2 -2
- package/src/assets/agents/knowledge.md +5 -3
- package/src/assets/agents/learning.md +281 -196
- package/src/assets/agents/research.md +3 -1
- package/src/assets/agents/review.md +5 -3
- package/src/assets/agents/scrutinize.md +5 -1
- package/src/assets/agents/simplify.md +4 -0
- package/src/assets/agents/skim.md +4 -2
- package/src/assets/agents/synthesize.md +6 -0
- package/src/assets/agents/test.md +18 -10
- package/src/assets/agents/triage.md +11 -9
- package/src/assets/agents/validate.md +14 -10
- package/src/assets/commands/_partials/_decisions.mds +8 -3
- package/src/assets/commands/_partials/_docs_root.mds +3 -3
- package/src/assets/commands/_partials/_engine.mds +16 -32
- package/src/assets/commands/_partials/_knowledge.mds +0 -2
- package/src/assets/commands/_partials/_preamble.mds +6 -2
- package/src/assets/commands/_partials/_settings.mds +2 -2
- package/src/assets/commands/_partials/_tracker.mds +1 -1
- package/src/assets/commands/code-review.mds +0 -2
- package/src/assets/commands/debug.mds +13 -8
- package/src/assets/commands/dynamic-build.mds +18 -12
- package/src/assets/commands/dynamic-plan.mds +10 -4
- package/src/assets/commands/dynamic-profile.mds +1 -1
- package/src/assets/commands/dynamic-tickets.mds +2 -2
- package/src/assets/commands/explore.mds +9 -1
- package/src/assets/commands/implement.mds +19 -13
- package/src/assets/commands/plan.mds +12 -8
- package/src/assets/commands/release.md +23 -3
- package/src/assets/commands/research.mds +9 -3
- package/src/assets/commands/resolve.mds +9 -10
- package/src/assets/mds/git/_pr.mds +3 -3
- package/src/assets/mds/tracker/_common.mds +1 -1
- package/src/assets/mds/tracker/_github.mds +3 -3
- package/src/assets/mds/tracker/_jira.mds +3 -3
- package/src/assets/mds/tracker/_linear.mds +3 -3
- package/src/assets/mds/tracker/_mcp.mds +6 -5
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -2
- package/src/assets/scripts/hooks/background-memory-update +97 -33
- package/src/assets/scripts/hooks/capture-prompt +4 -3
- package/src/assets/scripts/hooks/capture-question +4 -3
- package/src/assets/scripts/hooks/capture-turn +5 -20
- package/src/assets/scripts/hooks/ensure-devflow-init +14 -2
- package/src/assets/scripts/hooks/ensure-proxy +5 -6
- package/src/assets/scripts/hooks/ensure-root-gitignore +123 -11
- package/src/assets/scripts/hooks/git-marker +71 -0
- package/src/assets/scripts/hooks/is-hex-sha +1 -1
- package/src/assets/scripts/hooks/json-helper.cjs +345 -944
- package/src/assets/scripts/hooks/json-parse +25 -129
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +205 -156
- package/src/assets/scripts/hooks/lib/learning-store.cjs +3207 -0
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +7 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +13 -19
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +253 -226
- package/src/assets/scripts/hooks/memory-worker +10 -0
- package/src/assets/scripts/hooks/pre-compact-memory +66 -14
- package/src/assets/scripts/hooks/preamble +9 -1
- package/src/assets/scripts/hooks/queue-append +55 -23
- package/src/assets/scripts/hooks/resolve-project-root +3 -4
- package/src/assets/scripts/hooks/session-start-context +146 -45
- package/src/assets/scripts/hooks/session-start-memory +33 -11
- package/src/assets/scripts/lib/project-config.cjs +2 -2
- package/src/assets/scripts/pr-evidence.cjs +3 -3
- package/src/assets/scripts/redact-secrets.cjs +20 -20
- package/src/assets/scripts/release-trace.cjs +1 -1
- package/src/assets/scripts/resolve-evidence-policy.cjs +3 -3
- package/src/assets/scripts/resolve-settings.cjs +3 -3
- package/src/assets/scripts/verify-evidence.cjs +2 -2
- package/src/assets/skills/apply-decisions/SKILL.md +37 -17
- package/src/assets/skills/docs-framework/SKILL.md +2 -2
- package/src/assets/skills/feature-knowledge/SKILL.md +6 -5
- package/src/assets/skills/test-driven-development/SKILL.md +6 -4
- package/dist/core/observation-io.js +0 -50
- package/src/assets/scripts/hooks/decisions-usage-scan.cjs +0 -131
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
# - session-start-context (always-on, memory-independent — covers memory-off
|
|
27
27
|
# projects that still use learning/knowledge)
|
|
28
28
|
# Both reach this one writer so the rule is applied identically everywhere; this
|
|
29
|
-
# decouples git-tracking of .devflow/ from any single feature toggle
|
|
29
|
+
# decouples git-tracking of .devflow/ from any single feature toggle.
|
|
30
30
|
#
|
|
31
31
|
# Idempotent and O(1) after the first run via the .root-gitignore-configured-v6
|
|
32
32
|
# marker under .devflow/ (project-local). The marker is a claim, not proof, so the
|
|
@@ -58,6 +58,22 @@
|
|
|
58
58
|
# `!.claudeignore` entry means "the user owns that line" — emit the block without it.
|
|
59
59
|
# All matching is whole-line and whitespace-tolerant; never substring.
|
|
60
60
|
#
|
|
61
|
+
# D-GITIGNORE-LINK-INSIDE: a root .gitignore that is a symbolic link is written only
|
|
62
|
+
# when the file it resolves to lies inside the project root and outside any .git
|
|
63
|
+
# folder in the project, the project's own or a nested repository's: no part of its
|
|
64
|
+
# path below the root may be named .git, in any letter case, as git itself refuses
|
|
65
|
+
# such a path. Then that file is read and written directly, never through the link.
|
|
66
|
+
# Otherwise nothing is read or written anywhere, no marker is stamped, the skip is
|
|
67
|
+
# logged once and the caller goes on. Reason: a repository can commit .gitignore as a
|
|
68
|
+
# link to any file on the machine, and the carve-out would be appended to it; a file
|
|
69
|
+
# in a .git is no file of the repository's either, but git's own hooks and config,
|
|
70
|
+
# and a line appended to a hook runs as a command the next time git runs it. The case
|
|
71
|
+
# matters because a case-insensitive file system (macOS) opens .git for .GIT. The TS
|
|
72
|
+
# twin, ensureDevflowGitignore in src/targets/claude-code/post-install.ts, applies the
|
|
73
|
+
# same rule the same way: the link is followed one hop at a time, at most 40 hops, and
|
|
74
|
+
# only its last folder is resolved physically, so a missing file inside the project
|
|
75
|
+
# is created there as before.
|
|
76
|
+
#
|
|
61
77
|
# Usage: source ensure-root-gitignore "$PROJECT_ROOT"
|
|
62
78
|
# Sourced helper: uses `return` (never exit), _ERG_-prefixed locals (never clobbers
|
|
63
79
|
# caller vars).
|
|
@@ -68,6 +84,76 @@ _ERG_DEVFLOW_DIR="$1/.devflow"
|
|
|
68
84
|
_ERG_MARKER="$_ERG_DEVFLOW_DIR/.root-gitignore-configured-v6"
|
|
69
85
|
_ERG_GITIGNORE="$1/.gitignore"
|
|
70
86
|
|
|
87
|
+
# _erg_resolve_inside <root> <link> — follow the symbolic link <link> one hop at a
|
|
88
|
+
# time, at most 40, and set _ERG_RESOLVED to the file it leads to, its folder
|
|
89
|
+
# resolved physically. Returns 0 when that file lies inside <root> and no part of its
|
|
90
|
+
# path below <root> is named .git in any letter case (D-GITIGNORE-LINK-INSIDE);
|
|
91
|
+
# returns 1, with _ERG_RESOLVED empty, when it does not and when the link cannot be
|
|
92
|
+
# followed: a read that fails, an empty or folder-like target, a chain longer than
|
|
93
|
+
# 40, a folder that does not resolve. A predicate: call it in an `if`, never as a
|
|
94
|
+
# plain statement under `set -e`. Relative targets are joined to the folder the link
|
|
95
|
+
# sits in as spelled, never normalised, so `..` is resolved by the file system as the
|
|
96
|
+
# write itself would resolve it. Each hop forks one readlink; `cd -P` and $PWD are
|
|
97
|
+
# builtins, and the caller's folder is restored before returning.
|
|
98
|
+
_erg_resolve_inside() {
|
|
99
|
+
local _root="$1" _cur="$2" _target="" _dir="" _base="" _root_real="" _hops=0 _back="$PWD"
|
|
100
|
+
_ERG_RESOLVED=''
|
|
101
|
+
# Absolute from the start, so no path is read as an option, searched for along
|
|
102
|
+
# CDPATH, or taken relative to a folder this function moved into.
|
|
103
|
+
case "$_root" in /*) ;; *) _root="$_back/$_root" ;; esac
|
|
104
|
+
case "$_cur" in /*) ;; *) _cur="$_back/$_cur" ;; esac
|
|
105
|
+
while [ -L "$_cur" ]; do
|
|
106
|
+
[ "$_hops" -lt 40 ] || return 1
|
|
107
|
+
# The `x` keeps a target's own trailing newlines from command substitution; the
|
|
108
|
+
# one newline readlink adds is then removed.
|
|
109
|
+
_target="$(readlink "$_cur" 2>/dev/null && printf x)"
|
|
110
|
+
case "$_target" in *x) _target="${_target%x}" ;; *) return 1 ;; esac
|
|
111
|
+
_target="${_target%$'\n'}"
|
|
112
|
+
case "$_target" in
|
|
113
|
+
'') return 1 ;;
|
|
114
|
+
/*) _cur="$_target" ;;
|
|
115
|
+
*) _cur="${_cur%/*}/$_target" ;;
|
|
116
|
+
esac
|
|
117
|
+
_hops=$((_hops + 1))
|
|
118
|
+
done
|
|
119
|
+
_base="${_cur##*/}"
|
|
120
|
+
_dir="${_cur%/*}"
|
|
121
|
+
case "$_base" in ''|.|..) return 1 ;; esac
|
|
122
|
+
[ -n "$_dir" ] || _dir=/
|
|
123
|
+
if cd -P "$_root" 2>/dev/null; then
|
|
124
|
+
_root_real="${PWD%/}"
|
|
125
|
+
if cd -P "$_dir" 2>/dev/null; then _ERG_RESOLVED="${PWD%/}/$_base"; fi
|
|
126
|
+
fi
|
|
127
|
+
cd "$_back" 2>/dev/null || true
|
|
128
|
+
case "$_ERG_RESOLVED" in
|
|
129
|
+
'') ;;
|
|
130
|
+
"$_root_real"/?*)
|
|
131
|
+
# Each part below the root is tested whole, in any letter case: `cd -P` keeps
|
|
132
|
+
# the spelling the link gave, and a case-insensitive file system opens `.git`
|
|
133
|
+
# for `.GIT`.
|
|
134
|
+
case "/${_ERG_RESOLVED#"$_root_real"/}/" in
|
|
135
|
+
*/.[Gg][Ii][Tt]/*) ;;
|
|
136
|
+
*) return 0 ;;
|
|
137
|
+
esac
|
|
138
|
+
;;
|
|
139
|
+
esac
|
|
140
|
+
_ERG_RESOLVED=''
|
|
141
|
+
return 1
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# D-GITIGNORE-LINK-INSIDE (above): checked before the fast path, so a link that leads
|
|
145
|
+
# elsewhere is neither read nor written. A regular .gitignore costs one builtin test.
|
|
146
|
+
if [ -L "$_ERG_GITIGNORE" ]; then
|
|
147
|
+
if _erg_resolve_inside "$1" "$_ERG_GITIGNORE"; then
|
|
148
|
+
_ERG_GITIGNORE="$_ERG_RESOLVED"
|
|
149
|
+
else
|
|
150
|
+
if declare -F log >/dev/null 2>&1; then
|
|
151
|
+
log "Skipped: $_ERG_GITIGNORE is a symbolic link that leads outside the project, into a .git, or nowhere; nothing was written through it"
|
|
152
|
+
fi
|
|
153
|
+
return 0
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
|
|
71
157
|
# Whole-line, whitespace-tolerant matchers. `*` and `.` are escaped so the ERE
|
|
72
158
|
# matches the literal gitignore patterns.
|
|
73
159
|
_ERG_RE_OPTOUT='^[[:space:]]*/\.devflow/[[:space:]]*$'
|
|
@@ -208,18 +294,30 @@ else
|
|
|
208
294
|
# Upgrade our legacy wholesale entry: strip the bare `.devflow/` line and our old
|
|
209
295
|
# comment first, then fall through to the shared append below. Portable (grep
|
|
210
296
|
# filter + mv; no sed -i, which differs on BSD/GNU).
|
|
211
|
-
# The
|
|
297
|
+
# The filter's own status is deliberately ignored: grep -v exits 1 when it
|
|
212
298
|
# emits no lines, which is the correct outcome for a .gitignore whose only
|
|
213
|
-
# content was the legacy entry
|
|
299
|
+
# content was the legacy entry, so `true` ends the group and the subshell fails
|
|
300
|
+
# only when the copy cannot be created. The copy is created only where nothing
|
|
301
|
+
# stands at its name: builtin tests come first, so an entry already there, a
|
|
302
|
+
# link to a FIFO or a device included, is never opened (noclobber alone would
|
|
303
|
+
# open one, as no regular file stands there), and noclobber then makes the
|
|
304
|
+
# create itself exclusive (D-HOOKS-NO-SYMLINK, git-marker). Such an entry is
|
|
305
|
+
# removed instead; the upgrade then waits for a later run, and the skip is
|
|
306
|
+
# logged once.
|
|
214
307
|
_ERG_TMP="$_ERG_GITIGNORE.devflow-tmp.$$"
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
308
|
+
if [ ! -e "$_ERG_TMP" ] && [ ! -L "$_ERG_TMP" ] && (set -o noclobber; {
|
|
309
|
+
grep -vE "$_ERG_RE_LEGACY" "$_ERG_GITIGNORE" \
|
|
310
|
+
| grep -vF '# Devflow runtime data (local by default; remove to share via git)'
|
|
311
|
+
true
|
|
312
|
+
} > "$_ERG_TMP") 2>/dev/null \
|
|
313
|
+
&& [ -f "$_ERG_TMP" ] && mv "$_ERG_TMP" "$_ERG_GITIGNORE"; then
|
|
219
314
|
:
|
|
220
315
|
else
|
|
221
|
-
rm -f "$_ERG_TMP" 2>/dev/null
|
|
316
|
+
rm -f "$_ERG_TMP" 2>/dev/null || true
|
|
222
317
|
_ERG_MODE=fail
|
|
318
|
+
if declare -F log >/dev/null 2>&1; then
|
|
319
|
+
log "Legacy .devflow/ entry not upgraded: its copy could not be written ($_ERG_TMP); a later run retries"
|
|
320
|
+
fi
|
|
223
321
|
fi
|
|
224
322
|
fi
|
|
225
323
|
fi
|
|
@@ -231,7 +329,8 @@ fi
|
|
|
231
329
|
# unchanged, and when the run ends on a last line with no newline, one is added
|
|
232
330
|
# before <text>. Each read ends in a trailing `x`, so command substitution cannot
|
|
233
331
|
# strip the file's own trailing newlines. The file is rewritten in place with one
|
|
234
|
-
# `>`, as the TS twin's writeFile does
|
|
332
|
+
# `>`, as the TS twin's writeFile does; a .gitignore linked to a file inside the
|
|
333
|
+
# project is rewritten at that file, so the link stays (D-GITIGNORE-LINK-INSIDE).
|
|
235
334
|
# Returns non-zero, having written nothing, when the anchor line cannot be found.
|
|
236
335
|
_erg_insert_in_block() {
|
|
237
336
|
local _anchor="$1" _run="$2" _text="$3" _hit="" _n="" _next="" _line="" _i=0 _head="" _tail=""
|
|
@@ -284,9 +383,22 @@ case "$_ERG_MODE" in
|
|
|
284
383
|
;;
|
|
285
384
|
esac
|
|
286
385
|
|
|
287
|
-
# On success, stamp the current-format v6 marker and drop legacy markers.
|
|
386
|
+
# On success, stamp the current-format v6 marker and drop legacy markers. The marker
|
|
387
|
+
# is created only where nothing stands, never `touch`ed: `touch` follows a symbolic
|
|
388
|
+
# link committed at its path (D-HOOKS-NO-SYMLINK, git-marker), and so does
|
|
389
|
+
# noclobber's create when the link leads to a FIFO, where it would wait at every
|
|
390
|
+
# session start and prompt, or to a device. So builtin tests come first: a link
|
|
391
|
+
# there is left as it was and the skip logged once, a marker already there is all
|
|
392
|
+
# the fast path needs, and noclobber makes the create itself exclusive. A `.devflow`
|
|
393
|
+
# that is itself a link is refused by both callers before they reach this file.
|
|
288
394
|
[ "$_ERG_OK" = 1 ] && {
|
|
289
|
-
|
|
395
|
+
if [ -L "$_ERG_MARKER" ]; then
|
|
396
|
+
if declare -F log >/dev/null 2>&1; then
|
|
397
|
+
log "Marker not stamped: $_ERG_MARKER is a symbolic link"
|
|
398
|
+
fi
|
|
399
|
+
elif [ ! -e "$_ERG_MARKER" ]; then
|
|
400
|
+
(set -o noclobber; : > "$_ERG_MARKER") 2>/dev/null || true
|
|
401
|
+
fi
|
|
290
402
|
_erg_drop_legacy_markers
|
|
291
403
|
}
|
|
292
404
|
return 0
|
|
@@ -62,3 +62,74 @@ df_is_project_root() {
|
|
|
62
62
|
[ -n "$_root_real" ] || return 1
|
|
63
63
|
[ "$_root_real" != "$_home_real" ]
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
# df_no_symlink_below <root> <path>... — returns 0 when no <path>, and no folder
|
|
67
|
+
# between <root> and it, is a symbolic link. Returns 1 when one is, and for any
|
|
68
|
+
# call it cannot vouch for: no <path>, an empty <root>, a <path> not below <root>,
|
|
69
|
+
# or a <path> with an empty, `.` or `..` part or more than 64 parts below <root>.
|
|
70
|
+
# A predicate: call it in an `if` or an `||` list, never as a plain statement
|
|
71
|
+
# under `set -e`.
|
|
72
|
+
#
|
|
73
|
+
# D-HOOKS-NO-SYMLINK: a hook writes under a project's `.devflow/`, and reads a file
|
|
74
|
+
# there into the session context, a backup, a prompt or an agent directive, only
|
|
75
|
+
# where nothing on the way down from the root is a symbolic link. A repository can
|
|
76
|
+
# commit a link anywhere in its own `.devflow/`, and the hooks' shell commands follow
|
|
77
|
+
# one. `>>` and `touch` write through a linked file and `mkdir -p` creates folders
|
|
78
|
+
# inside a linked folder, so a linked queue or folder would put captured
|
|
79
|
+
# conversation text into whatever file the link names. `head`, `sed`, `read` and
|
|
80
|
+
# `jq` read through one, so a linked working memory, backup or decisions file would
|
|
81
|
+
# put whatever file the link names into the session, the memory refresh's prompt or
|
|
82
|
+
# the backup the next session restores, with no one choosing to read it. Each hook
|
|
83
|
+
# checks every path this rule covers: a refused write is skipped and a refused
|
|
84
|
+
# read is treated as an absent file (df_file_below below), each logged once. A
|
|
85
|
+
# rename's target is one of those paths: `mv` moves a file into the folder a link
|
|
86
|
+
# at its target names, so a link there, to anything, refuses the rename.
|
|
87
|
+
#
|
|
88
|
+
# Only the part of each path below <root> is checked, never the root or a folder
|
|
89
|
+
# above it: the root is where the session runs or where git keeps the ledger,
|
|
90
|
+
# chosen by the user and not by what the repository holds, and a folder above it
|
|
91
|
+
# may legitimately be a link (on macOS /tmp and /var lead into /private). The check
|
|
92
|
+
# runs just before the write or the read, so a process that swaps a link in between
|
|
93
|
+
# is not stopped; such a process already runs as the user.
|
|
94
|
+
#
|
|
95
|
+
# Zero forks: builtin `[ -L ]` tests and parameter expansion only.
|
|
96
|
+
df_no_symlink_below() {
|
|
97
|
+
local _root="$1" _path="" _rest="" _seg="" _at="" _n=0
|
|
98
|
+
[ -n "$_root" ] || return 1
|
|
99
|
+
shift
|
|
100
|
+
[ "$#" -gt 0 ] || return 1
|
|
101
|
+
for _path in "$@"; do
|
|
102
|
+
case "$_path" in
|
|
103
|
+
"$_root"/?*) _rest="${_path#"$_root"/}" ;;
|
|
104
|
+
*) return 1 ;;
|
|
105
|
+
esac
|
|
106
|
+
_at="$_root"
|
|
107
|
+
_n=0
|
|
108
|
+
while [ -n "$_rest" ]; do
|
|
109
|
+
[ "$_n" -lt 64 ] || return 1
|
|
110
|
+
_seg="${_rest%%/*}"
|
|
111
|
+
if [ "$_seg" = "$_rest" ]; then _rest=""; else _rest="${_rest#*/}"; fi
|
|
112
|
+
case "$_seg" in ''|.|..) return 1 ;; esac
|
|
113
|
+
_at="$_at/$_seg"
|
|
114
|
+
if [ -L "$_at" ]; then return 1; fi
|
|
115
|
+
_n=$((_n + 1))
|
|
116
|
+
done
|
|
117
|
+
done
|
|
118
|
+
return 0
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# df_file_below <root> <path> — returns 0 when <path> is a regular file that
|
|
122
|
+
# df_no_symlink_below admits, so a hook may read it. Returns 1 when no regular file
|
|
123
|
+
# stands there, and when a symbolic link sits on its path: the file is then treated
|
|
124
|
+
# as absent (D-HOOKS-NO-SYMLINK, above), and that refusal is logged once through the
|
|
125
|
+
# caller's log(), where it defines one. `[ -f ]` follows a link, so a dangling link
|
|
126
|
+
# or a link to a folder reads as absent with nothing logged: nothing would be read
|
|
127
|
+
# through it. A predicate, like df_no_symlink_below; it forks nothing unless it logs.
|
|
128
|
+
df_file_below() {
|
|
129
|
+
[ -f "$2" ] || return 1
|
|
130
|
+
if df_no_symlink_below "$1" "$2"; then return 0; fi
|
|
131
|
+
if declare -F log >/dev/null 2>&1; then
|
|
132
|
+
log "Skipped: a symbolic link sits on the path to $2; treated as absent"
|
|
133
|
+
fi
|
|
134
|
+
return 1
|
|
135
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Usage: source "$SCRIPT_DIR/is-hex-sha" then is_hex_sha <value> [min_len] [max_len]
|
|
4
4
|
# Returns 0 (true) if value consists entirely of lowercase hex chars and has
|
|
5
5
|
# length within [min_len, max_len]. Defaults: min_len=7, max_len=40.
|
|
6
|
-
# Pure shell — no forks
|
|
6
|
+
# Pure shell — no forks.
|
|
7
7
|
|
|
8
8
|
is_hex_sha() {
|
|
9
9
|
local _v="$1" _min="${2:-7}" _max="${3:-40}" _len
|