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
|
@@ -6,35 +6,73 @@
|
|
|
6
6
|
# under a lock. Used by capture-prompt, capture-turn, and capture-question so the
|
|
7
7
|
# per-feature dual-queue-append logic exists in exactly one place.
|
|
8
8
|
#
|
|
9
|
-
# Source-order requirement: source json-parse (for _HAS_JQ) and
|
|
10
|
-
#
|
|
9
|
+
# Source-order requirement: source json-parse (for _HAS_JQ) and curation-lock (for
|
|
10
|
+
# curation_lock_acquire/curation_lock_release, which itself requires get-mtime) BEFORE
|
|
11
11
|
# calling queue_append_row or queue_append_both, and define log/dbg. This file
|
|
12
|
-
# sources its
|
|
13
|
-
#
|
|
12
|
+
# sources its siblings git-marker (for df_no_symlink_below) and curation-move (for the
|
|
13
|
+
# ledger folder's names, df_curation_move_names) itself. Sourcing it only defines
|
|
14
|
+
# functions -- no side effects until they are called.
|
|
14
15
|
#
|
|
15
16
|
# queue_append_row <root> <queue_file> <role> <content> <ts>
|
|
16
17
|
# Appends one JSONL row {role, content, ts} to <queue_file>, which lies below
|
|
17
|
-
# <root>: the project root for the memory queue, the ledger root for the
|
|
18
|
+
# <root>: the project root for the memory queue, the ledger root for the curation
|
|
18
19
|
# queue. When the queue, or a folder between <root> and it, is a symbolic link,
|
|
19
20
|
# nothing is written: the skip is logged once and the call returns 0, as a
|
|
20
|
-
# skipped capture does (D-HOOKS-NO-SYMLINK, git-marker). Otherwise it
|
|
21
|
-
#
|
|
21
|
+
# skipped capture does (D-HOOKS-NO-SYMLINK, git-marker). Otherwise it builds the
|
|
22
|
+
# row first (a row that fails to build writes nothing), then makes one builtin
|
|
23
|
+
# append under umask 077, so a queue it creates has mode 0600. The umask is the
|
|
24
|
+
# caller's again when the append returns, so a folder made later in the same hook
|
|
25
|
+
# has the mode its parent's umask gives. A ledger queue follows the creator rule
|
|
26
|
+
# (git-marker, df_ledger_queue): it is appended to in the
|
|
27
|
+
# old learning/ folder while that exists, which is never created here; the new
|
|
28
|
+
# curation/ folder is created only when the old name is absent. An append that
|
|
29
|
+
# finds the old folder gone (the one-time move ran meanwhile) looks again and goes
|
|
30
|
+
# on in the new folder, so a capture never drops a row and never waits. Any other
|
|
31
|
+
# queue's folder is created if absent.
|
|
32
|
+
# A queue in an area that git tracks, or that git did not answer for, is paused: before
|
|
33
|
+
# the row is built, queue_tracked_area asks for the area the queue lies in, and a queue
|
|
34
|
+
# that is not untracked gets no row, no folder and no file. The skip is logged once. The
|
|
35
|
+
# check runs on every call, whether or not the queue's folder exists.
|
|
22
36
|
# After appending, truncates from 200 to the newest 100 lines under a lock
|
|
23
|
-
# (
|
|
37
|
+
# (curation_lock_acquire on "<queue_file>.lock", 2s timeout), keeping mode 0600.
|
|
24
38
|
# The append itself is intentionally lock-free (accepted-class race shared with
|
|
25
39
|
# the pre-existing memory design -- see the design doc's "Append-vs-claim race"
|
|
26
40
|
# note).
|
|
27
41
|
#
|
|
28
|
-
# queue_append_both <memory_root> <memory_queue> <
|
|
29
|
-
#
|
|
30
|
-
# root that queue lies below. Each queue is gated
|
|
31
|
-
# compute memory_enabled/
|
|
32
|
-
# for the at-most-one-fork read of both).
|
|
42
|
+
# queue_append_both <memory_root> <memory_queue> <curation_root> <curation_queue> <memory_enabled> <curation_enabled> <role> <content> <ts>
|
|
43
|
+
# Builds the row once, then appends it as queue_append_row does to each queue whose
|
|
44
|
+
# *_enabled flag is "true", with the root that queue lies below. Each queue is gated
|
|
45
|
+
# independently -- callers compute memory_enabled/curation_enabled themselves (see
|
|
46
|
+
# queue_read_gates below for the at-most-one-fork read of both). With neither queue
|
|
47
|
+
# enabled it builds nothing. A queue whose area is paused (see queue_tracked_area) reads
|
|
48
|
+
# as not enabled for the call, and nothing is built for it.
|
|
49
|
+
#
|
|
50
|
+
# queue_tracked_area <root> <curation|memory|both> [git|node]
|
|
51
|
+
# Does git track files in the data folders of the area at <root>? Sets
|
|
52
|
+
# DF_TRACKED_AREA_CURATION or DF_TRACKED_AREA_MEMORY (one variable per area) to
|
|
53
|
+
# `tracked`, `untracked` or `unknown`, and always returns 0. The area `both` is one
|
|
54
|
+
# call over the union of the two: it sets both variables, and a listing in it is
|
|
55
|
+
# followed by one call per area, so each variable names its own area. A root is asked
|
|
56
|
+
# once per area in a process. The shell twin of lib/tracked-areas.cjs, held to its
|
|
57
|
+
# answers by tests/decisions/tracked-area-parity.test.ts:
|
|
58
|
+
# - no `.git` entry (a file, a folder or a link) at the root: `untracked`, no call;
|
|
59
|
+
# - a root that is not an absolute path, whose physical parent holds a colon, or that
|
|
60
|
+
# can not be entered: `unknown`;
|
|
61
|
+
# - git mode (the default): one `git ls-files -z` over the area's pathspecs, with the
|
|
62
|
+
# leaf's environment scrub, a ceiling at the root's parent and the fsmonitor command
|
|
63
|
+
# off. Only the first byte of the listing is read. A byte read means `tracked`,
|
|
64
|
+
# whatever git's status (git ends on a closed pipe). With no byte, git status 0
|
|
65
|
+
# means `untracked`, and any other status `unknown`;
|
|
66
|
+
# - node mode: the json-helper `tracked-area` op, which bounds the call in time. Without
|
|
67
|
+
# node it runs as git mode; an answer that is not one of the three words is `unknown`.
|
|
68
|
+
# The capture hooks ask in git mode, with no process but git. The session start asks in
|
|
69
|
+
# node mode, so a hung git is cut.
|
|
33
70
|
#
|
|
34
71
|
# queue_read_gates <manifest_path> [<root>]
|
|
35
|
-
# Reads BOTH switches, `memory` and `
|
|
72
|
+
# Reads BOTH switches, `memory` and `curation` (curation is read under both of
|
|
73
|
+
# its keys, the current one and the pre-rename copy), in at most ONE subprocess fork
|
|
36
74
|
# (AC-P1 -- at most one gate-read fork per capture hook, not two; none when
|
|
37
|
-
# the fast paths settle it). Sets _QG_MEMORY and
|
|
75
|
+
# the fast paths settle it). Sets _QG_MEMORY and _QG_CURATION ("true"/"false")
|
|
38
76
|
# in the caller's scope. The two values are newline-separated
|
|
39
77
|
# rather than using a control-character delimiter: they are always the literal
|
|
40
78
|
# strings "true"/"false", never arbitrary content, so a plain newline split is
|
|
@@ -44,35 +82,43 @@
|
|
|
44
82
|
# machine switch is on AND neither repository file sets `features.<name>` to
|
|
45
83
|
# the literal `false` -- the rule resolve-settings.cjs folds, held to the same
|
|
46
84
|
# answers by the shared table tests/fixtures/settings-switch-table.ts (TP-49).
|
|
47
|
-
# A repository can switch memory or
|
|
85
|
+
# A repository can switch memory or curation off for itself, never back on.
|
|
48
86
|
# machine <manifest_path>, the ~/.devflow/manifest.json `devflow init` and
|
|
49
|
-
# `devflow memory|
|
|
87
|
+
# `devflow memory|curation --enable/--disable` write. Only an
|
|
50
88
|
# explicit boolean `false` switches a feature off -- an absent,
|
|
51
89
|
# unreadable or malformed manifest, a missing key, or a non-boolean
|
|
52
90
|
# value leaves it ON (fail-open), the rule
|
|
53
91
|
# isMachineFeatureOn() applies in the CLI.
|
|
54
92
|
# project <root>/.devflow/project.json (team-committed) and
|
|
55
93
|
# personal <root>/.devflow/config.json (this worktree's own), read ONLY under
|
|
56
|
-
# `features.memory`
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
94
|
+
# `features.memory` and, for curation, `features.curation` or
|
|
95
|
+
# `features.learning` (a narrow-only union: a literal false in
|
|
96
|
+
# either key turns curation off, and each key is parsed on its
|
|
97
|
+
# own). The retired top-level keys (`memory`, `curation`,
|
|
98
|
+
# `learning`, `decisions`) and `features.decisions` narrow nothing
|
|
99
|
+
# (#378: reading them let a feature run in every repo but one). An
|
|
100
|
+
# invalid file -- unparseable, over 4096 bytes, a BOM, a symlink, a
|
|
101
|
+
# duplicated key -- narrows nothing.
|
|
61
102
|
# <root> is the checkout's toplevel (DF_ROOT, which every caller holds as
|
|
62
|
-
# $PROJECT_ROOT), for
|
|
103
|
+
# $PROJECT_ROOT), for curation too: project.json is committed per branch and
|
|
63
104
|
# config.json lives per worktree, so both are read where the session runs, as
|
|
64
105
|
# resolve-settings.cjs reads them -- never at DF_LEDGER_ROOT, which only says
|
|
65
106
|
# where the queue lands. Without <root> (or with an empty one) no repository
|
|
66
107
|
# file is read.
|
|
67
108
|
#
|
|
68
|
-
#
|
|
69
|
-
# `features.
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
109
|
+
# The machine rule for curation (#529), the one isMachineFeatureOn() applies:
|
|
110
|
+
# off when `features.curation` or `features.learning` is a boolean false; else
|
|
111
|
+
# on when either is a boolean true; else, when neither is a boolean, the
|
|
112
|
+
# pre-rename `features.decisions` decides if it is a boolean; else on. A
|
|
113
|
+
# non-boolean value is ignored for that key only, so a disagreeing pair
|
|
114
|
+
# resolves to off.
|
|
115
|
+
#
|
|
116
|
+
# D-LEARNING-LEGACY-DECISIONS: in the MANIFEST only, the pre-rename
|
|
117
|
+
# `features.decisions` is read when neither current key is a boolean --
|
|
118
|
+
# readManifest's migration precedence exactly, so a legacy `decisions: false`
|
|
119
|
+
# switches curation off before any command has healed the file.
|
|
74
120
|
#
|
|
75
|
-
# Every hook that gates on memory or
|
|
121
|
+
# Every hook that gates on memory or curation reads it through here: the
|
|
76
122
|
# capture hooks, session-start-context (Sections 1-2), memory-worker,
|
|
77
123
|
# session-start-memory, pre-compact-memory and background-memory-update. The
|
|
78
124
|
# callers pass <manifest_path> as $HOME/.devflow/manifest.json, the machine
|
|
@@ -81,80 +127,370 @@
|
|
|
81
127
|
|
|
82
128
|
# queue_append_row checks its queue with df_no_symlink_below, so this helper sources
|
|
83
129
|
# git-marker itself rather than trusting every caller to have done so first. One that
|
|
84
|
-
#
|
|
85
|
-
|
|
130
|
+
# is missing is a command not found: the refusing branch, so nothing is written.
|
|
131
|
+
# The ledger folder's names come from the move shim (df_curation_move_names), sourced
|
|
132
|
+
# the same way and refused the same way.
|
|
133
|
+
#
|
|
134
|
+
# Each source is guarded by a builtin readability test, because bash 3.2 under `set -e`
|
|
135
|
+
# ends the shell at a `source` of a missing file, even inside `|| true`. Without the test
|
|
136
|
+
# the refusing branch would never run, and the capture hooks would stop here.
|
|
137
|
+
[ -r "${BASH_SOURCE[0]%/*}/git-marker" ] && source "${BASH_SOURCE[0]%/*}/git-marker" 2>/dev/null || true
|
|
138
|
+
[ -r "${BASH_SOURCE[0]%/*}/curation-move" ] && source "${BASH_SOURCE[0]%/*}/curation-move" 2>/dev/null || true
|
|
86
139
|
|
|
87
|
-
|
|
88
|
-
|
|
140
|
+
# The caller's umask, read once per process. A queue is created 0600, so the append
|
|
141
|
+
# runs under umask 077 -- and the rest of the hook must run under the umask its parent
|
|
142
|
+
# gave it, or a folder the next append makes would be 0700. `$(umask)` is one subshell
|
|
143
|
+
# with no exec, and it runs on the first append only; each append gives this value
|
|
144
|
+
# back, so the umask the process holds when it first appends is the one it keeps.
|
|
145
|
+
_QAR_UMASK=""
|
|
146
|
+
|
|
147
|
+
# _qar_build_row <role> <content> <ts> -- sets _QAR_ROW to the JSONL row, built by jq
|
|
148
|
+
# (--arg, --argjson) or node (JSON.stringify), never by string concatenation. A
|
|
149
|
+
# predicate: it succeeds only for a row that built, and a row that fails to build is
|
|
150
|
+
# empty, so nothing partial can reach a queue. Call it in an `if` or an `||` list.
|
|
151
|
+
_qar_build_row() {
|
|
152
|
+
_QAR_ROW=""
|
|
153
|
+
if [ "$_HAS_JQ" = "true" ]; then
|
|
154
|
+
_QAR_ROW=$(jq -n -c --arg role "$1" --arg content "$2" --argjson ts "$3" \
|
|
155
|
+
'{role: $role, content: $content, ts: $ts}') || _QAR_ROW=""
|
|
156
|
+
else
|
|
157
|
+
_QAR_ROW=$(node -e "process.stdout.write(JSON.stringify({role:process.argv[1],content:process.argv[2],ts:parseInt(process.argv[3])}))" \
|
|
158
|
+
-- "$1" "$2" "$3") || _QAR_ROW=""
|
|
159
|
+
fi
|
|
160
|
+
if [ -z "$_QAR_ROW" ]; then
|
|
161
|
+
log "Queue skipped: the row could not be built; nothing written"
|
|
162
|
+
return 1
|
|
163
|
+
fi
|
|
164
|
+
return 0
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# _qar_write <queue_file> <row> -- one builtin append, under umask 077 so a queue it
|
|
168
|
+
# creates has mode 0600 (SEC-2). The umask is the caller's again before it returns,
|
|
169
|
+
# whether the append worked or not. The redirection error is silenced by 2>/dev/null
|
|
170
|
+
# written BEFORE it. A predicate: it fails when the row was not written.
|
|
171
|
+
_qar_write() {
|
|
172
|
+
local _qar_status=0
|
|
173
|
+
umask 077
|
|
174
|
+
printf '%s\n' "$2" 2>/dev/null >> "$1" || _qar_status=1
|
|
175
|
+
umask "$_QAR_UMASK" 2>/dev/null || true
|
|
176
|
+
return "$_qar_status"
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# _qar_append <root> <queue_file> <row> -- appends <row> to <queue_file>, which lies
|
|
180
|
+
# below <root>, and sets _QAR_FILE to the queue it reached (the old ledger folder, or
|
|
181
|
+
# the new one after a move). A predicate: it fails when nothing was written -- a link on
|
|
182
|
+
# the path (D-HOOKS-NO-SYMLINK: the skip is logged once) or a queue that could not be
|
|
183
|
+
# written -- and a skipped capture is not an error for the hook.
|
|
184
|
+
_qar_append() {
|
|
185
|
+
local _qar_root="$1" _qar_file="$2" _qar_row="$3"
|
|
186
|
+
local _qar_given="$2" _qar_old="" _qar_new=""
|
|
187
|
+
_QAR_FILE=""
|
|
188
|
+
|
|
189
|
+
# The names the creator rule below needs. A shim that failed to source is a command
|
|
190
|
+
# not found, so nothing is appended: no queue is written without them.
|
|
191
|
+
if ! df_curation_move_names 2>/dev/null; then
|
|
192
|
+
log "Queue skipped: the ledger folder names are unavailable; nothing written"
|
|
193
|
+
return 1
|
|
194
|
+
fi
|
|
195
|
+
_qar_old="$DF_CM_OLD_FOLDER"
|
|
196
|
+
_qar_new="$DF_CM_NEW_FOLDER"
|
|
197
|
+
|
|
198
|
+
# The creator rule (git-marker, df_ledger_queue): a ledger queue is appended to in
|
|
199
|
+
# the old folder while that folder is present, so a new name never hides an old
|
|
200
|
+
# ledger. Any other queue (the memory queue) comes back unchanged. A helper that
|
|
201
|
+
# failed to source is a command not found, so the queue stays as given and the
|
|
202
|
+
# check below, from the same library, refuses it.
|
|
203
|
+
if df_ledger_queue "$_qar_file" "$_qar_old" "$_qar_new"; then _qar_file="$DF_LEDGER_QUEUE"; fi
|
|
89
204
|
|
|
90
205
|
# D-HOOKS-NO-SYMLINK (git-marker): a skipped append is a skipped capture.
|
|
91
206
|
if ! df_no_symlink_below "$_qar_root" "$_qar_file"; then
|
|
92
207
|
log "Queue skipped: a symbolic link sits on the path to $_qar_file; nothing written"
|
|
93
|
-
return
|
|
208
|
+
return 1
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# A folder is made only where this append is its creator: never the old ledger
|
|
212
|
+
# folder, which a move may rename away at any moment, and which a make-again would
|
|
213
|
+
# bring back beside the new one. An old folder that is gone leaves the append to
|
|
214
|
+
# fail, and the re-probe below decides again. A folder is made under the caller's
|
|
215
|
+
# umask: only the write below runs under 077.
|
|
216
|
+
case "${_qar_file%/*}" in
|
|
217
|
+
*/"$_qar_old") ;;
|
|
218
|
+
*) mkdir -p "${_qar_file%/*}" 2>/dev/null || true ;;
|
|
219
|
+
esac
|
|
220
|
+
|
|
221
|
+
if [ -z "$_QAR_UMASK" ]; then _QAR_UMASK=$(umask) || _QAR_UMASK=""; fi
|
|
222
|
+
if ! _qar_write "$_qar_file" "$_qar_row"; then
|
|
223
|
+
# The old folder may have been moved after the probe. Look again: when the old
|
|
224
|
+
# name is gone, the creator makes the new folder once and the row goes there.
|
|
225
|
+
df_ledger_queue "$_qar_given" "$_qar_old" "$_qar_new" || true
|
|
226
|
+
if [ "$DF_LEDGER_QUEUE" != "$_qar_file" ] \
|
|
227
|
+
&& df_no_symlink_below "$_qar_root" "$DF_LEDGER_QUEUE" \
|
|
228
|
+
&& mkdir -p "${DF_LEDGER_QUEUE%/*}" 2>/dev/null \
|
|
229
|
+
&& _qar_write "$DF_LEDGER_QUEUE" "$_qar_row"; then
|
|
230
|
+
_qar_file="$DF_LEDGER_QUEUE"
|
|
231
|
+
else
|
|
232
|
+
log "Queue append failed: $_qar_file could not be written"
|
|
233
|
+
return 1
|
|
234
|
+
fi
|
|
94
235
|
fi
|
|
95
|
-
|
|
236
|
+
_QAR_FILE="$_qar_file"
|
|
237
|
+
return 0
|
|
238
|
+
}
|
|
96
239
|
|
|
97
|
-
|
|
98
|
-
|
|
240
|
+
# _qar_trim_overflow <queue_file> -- overflow safety: past 200 lines, keep the newest
|
|
241
|
+
# 100, locked to prevent a multi-session race.
|
|
242
|
+
_qar_trim_overflow() {
|
|
243
|
+
local _qar_file="$1" _qar_lines="" _qar_lock="" _qar_tmp=""
|
|
244
|
+
[ -f "$_qar_file" ] || return 0
|
|
245
|
+
_qar_lines=$(wc -l < "$_qar_file" | tr -d ' ')
|
|
246
|
+
if [ "$_qar_lines" -gt 200 ]; then
|
|
247
|
+
_qar_lock="${_qar_file}.lock"
|
|
248
|
+
if curation_lock_acquire "$_qar_lock" 2; then
|
|
249
|
+
_qar_lines=$(wc -l < "$_qar_file" | tr -d ' ')
|
|
250
|
+
if [ "$_qar_lines" -gt 200 ]; then
|
|
251
|
+
_qar_tmp="${_qar_file}.tmp.$$"
|
|
252
|
+
# SEC-2: the trimmed copy is created under umask 077, so the renamed
|
|
253
|
+
# queue keeps mode 0600. mv replaces the inode, so the queue takes the
|
|
254
|
+
# copy's mode, and a chmod after the mv would leave a window. The copy is
|
|
255
|
+
# created only where nothing stands at its name: builtin tests come first,
|
|
256
|
+
# so an entry already there, a link to a FIFO or a device included, is
|
|
257
|
+
# never opened (noclobber alone would open one, as no regular file stands
|
|
258
|
+
# there), and noclobber then makes the create itself exclusive. Such an
|
|
259
|
+
# entry is removed instead, and the trim waits for a later append.
|
|
260
|
+
if [ ! -e "$_qar_tmp" ] && [ ! -L "$_qar_tmp" ] && (umask 077 && set -o noclobber && tail -100 "$_qar_file" > "$_qar_tmp") && mv "$_qar_tmp" "$_qar_file"; then
|
|
261
|
+
log "Queue overflow: truncated from $_qar_lines to 100 lines ($(basename "$_qar_file"))"
|
|
262
|
+
dbg "Queue overflow: truncated from $_qar_lines to 100 lines ($_qar_file)"
|
|
263
|
+
else
|
|
264
|
+
rm -f "$_qar_tmp" 2>/dev/null || true
|
|
265
|
+
log "Queue overflow: not truncated, its copy could not be written ($(basename "$_qar_file"))"
|
|
266
|
+
fi
|
|
267
|
+
fi
|
|
268
|
+
curation_lock_release "$_qar_lock"
|
|
269
|
+
fi
|
|
99
270
|
fi
|
|
271
|
+
return 0
|
|
272
|
+
}
|
|
100
273
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
274
|
+
# --- The tracked-area gate: no capture into a folder that git tracks ---
|
|
275
|
+
#
|
|
276
|
+
# A folder that git tracks holds the repository's content, not devflow's data, so a
|
|
277
|
+
# capture writes nothing into it, and also when git did not answer (queue_tracked_area,
|
|
278
|
+
# documented above). The check runs before the row is built and before any folder is
|
|
279
|
+
# made, on every call, whether or not the queue's folder exists.
|
|
280
|
+
|
|
281
|
+
# _qta_specs <curation|memory|both> -- sets the array _QTA_SPECS to the pathspecs of the
|
|
282
|
+
# area: the shell twin of AREA_PATHSPECS in lib/tracked-areas.cjs, in the leaf's order and
|
|
283
|
+
# spelling (tests/decisions/tracked-area-parity.test.ts holds the two equal, element by
|
|
284
|
+
# element). `.devflow` is spelled twice, the second time with the fl ligature (U+FB02,
|
|
285
|
+
# bytes ef ac 82), which a case-folding file system opens as the same name. The `.devflow`
|
|
286
|
+
# entry itself (a link, a gitlink or a plain file) stands over every folder: the character
|
|
287
|
+
# class keeps `glob` from matching it as a leading directory. Fails for any other word.
|
|
288
|
+
_qta_specs() {
|
|
289
|
+
local _qs_fl=$'\xef\xac\x82' _qs_dir="" _qs_curation=() _qs_memory=() _qs_entry=()
|
|
290
|
+
for _qs_dir in ".devflow" ".dev${_qs_fl}ow"; do
|
|
291
|
+
_qs_curation+=(
|
|
292
|
+
":(icase,literal)${_qs_dir}/learning"
|
|
293
|
+
":(icase,literal)${_qs_dir}/curation"
|
|
294
|
+
":(icase,glob)${_qs_dir}/learning.backup-*"
|
|
295
|
+
":(icase,glob)${_qs_dir}/learning.backup-*/**"
|
|
296
|
+
)
|
|
297
|
+
_qs_memory+=(":(icase,literal)${_qs_dir}/memory")
|
|
298
|
+
_qs_entry+=(":(icase,glob)[.]${_qs_dir#.}")
|
|
299
|
+
done
|
|
300
|
+
case "${1:-}" in
|
|
301
|
+
curation) _QTA_SPECS=("${_qs_curation[@]}" "${_qs_entry[@]}") ;;
|
|
302
|
+
memory) _QTA_SPECS=("${_qs_memory[@]}" "${_qs_entry[@]}") ;;
|
|
303
|
+
both) _QTA_SPECS=("${_qs_curation[@]}" "${_qs_entry[@]}" "${_qs_memory[@]}") ;;
|
|
304
|
+
*) _QTA_SPECS=(); return 1 ;;
|
|
305
|
+
esac
|
|
306
|
+
return 0
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# _qta_probe <root> <curation|memory|both> <git|node> -- sets _QTA_ANSWER to `tracked`,
|
|
310
|
+
# `untracked` or `unknown`, as queue_tracked_area documents. Always returns 0.
|
|
311
|
+
#
|
|
312
|
+
# The git mode reads one byte of the listing and ends. Under bash 3.2 with errexit set the
|
|
313
|
+
# pipeline sits in an `if` condition (an empty listing makes `read` fail, and a failing
|
|
314
|
+
# condition does not end the shell), and PIPESTATUS is copied in both arms before any other
|
|
315
|
+
# command resets it: the second element is the reader's status, the first git's. Nothing
|
|
316
|
+
# follows the pipeline in an `||` list. The `cd -P` runs inside the subshell, so the
|
|
317
|
+
# caller's directory never changes, and git runs there with the variables that redirect it
|
|
318
|
+
# removed (the list is GIT_REDIRECT_VARIABLES of the leaf), a fixed locale, no optional
|
|
319
|
+
# lock, and the root's physical parent as its ceiling.
|
|
320
|
+
_qta_probe() {
|
|
321
|
+
local _qp_root="${1:-}" _qp_area="${2:-}" _qp_mode="${3:-git}" _qp_out="" _qp_ceiling="" _qp_c="" _qp_st=()
|
|
322
|
+
_QTA_ANSWER="unknown"
|
|
323
|
+
case "$_qp_root" in /*) ;; *) return 0 ;; esac
|
|
324
|
+
if [ ! -e "$_qp_root/.git" ] && [ ! -L "$_qp_root/.git" ]; then
|
|
325
|
+
_QTA_ANSWER="untracked"
|
|
326
|
+
return 0
|
|
327
|
+
fi
|
|
328
|
+
if [ "$_qp_mode" = "node" ] && [ "${_HAS_NODE:-}" = "true" ] && [ -n "${_JSON_HELPER:-}" ]; then
|
|
329
|
+
case "$_qp_area" in curation|memory|both) ;; *) return 0 ;; esac
|
|
330
|
+
_qp_out=$(node "$_JSON_HELPER" tracked-area "$_qp_area" "$_qp_root" 2>/dev/null) || _qp_out=""
|
|
331
|
+
_qp_out="${_qp_out%%$'\n'*}"
|
|
332
|
+
case "$_qp_out" in tracked|untracked|unknown) _QTA_ANSWER="$_qp_out" ;; esac
|
|
333
|
+
return 0
|
|
334
|
+
fi
|
|
335
|
+
_qta_specs "$_qp_area" || return 0
|
|
336
|
+
if (
|
|
337
|
+
cd -P "$_qp_root" >/dev/null 2>&1 || exit 1
|
|
338
|
+
_qp_ceiling="${PWD%/*}"
|
|
339
|
+
[ -n "$_qp_ceiling" ] || _qp_ceiling="/"
|
|
340
|
+
case "$_qp_ceiling" in *:*) exit 1 ;; esac
|
|
341
|
+
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_NAMESPACE GIT_LITERAL_PATHSPECS GIT_GLOB_PATHSPECS GIT_NOGLOB_PATHSPECS GIT_ICASE_PATHSPECS
|
|
342
|
+
export LC_ALL=C GIT_OPTIONAL_LOCKS=0 GIT_CEILING_DIRECTORIES="$_qp_ceiling"
|
|
343
|
+
exec git --no-literal-pathspecs -c core.fsmonitor=false ls-files -z -- "${_QTA_SPECS[@]}"
|
|
344
|
+
) </dev/null 2>/dev/null | { IFS= read -r -n 1 _qp_c; }; then
|
|
345
|
+
_qp_st=("${PIPESTATUS[@]}")
|
|
104
346
|
else
|
|
105
|
-
|
|
106
|
-
|
|
347
|
+
_qp_st=("${PIPESTATUS[@]}")
|
|
348
|
+
fi
|
|
349
|
+
if [ "${#_qp_st[@]}" -eq 2 ]; then
|
|
350
|
+
if [ "${_qp_st[1]}" -eq 0 ]; then
|
|
351
|
+
_QTA_ANSWER="tracked"
|
|
352
|
+
elif [ "${_qp_st[0]}" -eq 0 ]; then
|
|
353
|
+
_QTA_ANSWER="untracked"
|
|
354
|
+
fi
|
|
107
355
|
fi
|
|
356
|
+
return 0
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
# _qta_ask <root> <curation|memory> <git|node> -- sets the area's variable, unless this
|
|
360
|
+
# process already asked for that root and area. Always returns 0.
|
|
361
|
+
_qta_ask() {
|
|
362
|
+
case "$2" in
|
|
363
|
+
curation)
|
|
364
|
+
if [ "${_QTA_ASKED_CURATION:-}" != "r$1" ]; then
|
|
365
|
+
_qta_probe "$1" curation "$3"
|
|
366
|
+
DF_TRACKED_AREA_CURATION="$_QTA_ANSWER"
|
|
367
|
+
_QTA_ASKED_CURATION="r$1"
|
|
368
|
+
fi
|
|
369
|
+
;;
|
|
370
|
+
memory)
|
|
371
|
+
if [ "${_QTA_ASKED_MEMORY:-}" != "r$1" ]; then
|
|
372
|
+
_qta_probe "$1" memory "$3"
|
|
373
|
+
DF_TRACKED_AREA_MEMORY="$_QTA_ANSWER"
|
|
374
|
+
_QTA_ASKED_MEMORY="r$1"
|
|
375
|
+
fi
|
|
376
|
+
;;
|
|
377
|
+
esac
|
|
378
|
+
return 0
|
|
379
|
+
}
|
|
108
380
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
#
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
dbg "Queue overflow: truncated from $_qar_lines to 100 lines ($_qar_file)"
|
|
130
|
-
else
|
|
131
|
-
rm -f "$_qar_tmp" 2>/dev/null || true
|
|
132
|
-
log "Queue overflow: not truncated, its copy could not be written ($(basename "$_qar_file"))"
|
|
133
|
-
fi
|
|
381
|
+
queue_tracked_area() {
|
|
382
|
+
local _qa_root="${1:-}" _qa_area="${2:-}" _qa_mode="${3:-git}"
|
|
383
|
+
case "$_qa_area" in
|
|
384
|
+
curation|memory)
|
|
385
|
+
_qta_ask "$_qa_root" "$_qa_area" "$_qa_mode"
|
|
386
|
+
;;
|
|
387
|
+
both)
|
|
388
|
+
if [ "${_QTA_ASKED_CURATION:-}" != "r$_qa_root" ] || [ "${_QTA_ASKED_MEMORY:-}" != "r$_qa_root" ]; then
|
|
389
|
+
_qta_probe "$_qa_root" both "$_qa_mode"
|
|
390
|
+
if [ "$_QTA_ANSWER" = "tracked" ]; then
|
|
391
|
+
# The union listed something: one call per area says which.
|
|
392
|
+
_QTA_ASKED_CURATION=""
|
|
393
|
+
_QTA_ASKED_MEMORY=""
|
|
394
|
+
_qta_ask "$_qa_root" curation "$_qa_mode"
|
|
395
|
+
_qta_ask "$_qa_root" memory "$_qa_mode"
|
|
396
|
+
else
|
|
397
|
+
DF_TRACKED_AREA_CURATION="$_QTA_ANSWER"
|
|
398
|
+
DF_TRACKED_AREA_MEMORY="$_QTA_ANSWER"
|
|
399
|
+
_QTA_ASKED_CURATION="r$_qa_root"
|
|
400
|
+
_QTA_ASKED_MEMORY="r$_qa_root"
|
|
134
401
|
fi
|
|
135
|
-
learning_lock_release "$_qar_lock"
|
|
136
402
|
fi
|
|
137
|
-
|
|
403
|
+
;;
|
|
404
|
+
esac
|
|
405
|
+
return 0
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
# _qar_gate <memory_root> <memory_on> <curation_root> <curation_on> -- the one gate both
|
|
409
|
+
# append functions use. Sets _QAR_MEMORY_ON and _QAR_CURATION_ON to the flags as given,
|
|
410
|
+
# except that a queue whose area is not `untracked` reads "false" (paused), with one fixed
|
|
411
|
+
# log line. Only an enabled queue is asked about. When both are enabled at one root, one
|
|
412
|
+
# call over the union answers for both (queue_tracked_area `both`). Always returns 0.
|
|
413
|
+
_qar_gate() {
|
|
414
|
+
local _qg_mroot="$1" _qg_mon="$2" _qg_croot="$3" _qg_con="$4"
|
|
415
|
+
if [ "$_qg_mon" = "true" ] && [ "$_qg_con" = "true" ] && [ "$_qg_mroot" = "$_qg_croot" ]; then
|
|
416
|
+
queue_tracked_area "$_qg_mroot" both git
|
|
417
|
+
else
|
|
418
|
+
if [ "$_qg_mon" = "true" ]; then queue_tracked_area "$_qg_mroot" memory git; fi
|
|
419
|
+
if [ "$_qg_con" = "true" ]; then queue_tracked_area "$_qg_croot" curation git; fi
|
|
420
|
+
fi
|
|
421
|
+
if [ "$_qg_mon" = "true" ] && [ "${DF_TRACKED_AREA_MEMORY:-unknown}" != "untracked" ]; then
|
|
422
|
+
_qg_mon="false"
|
|
423
|
+
log "Queue skipped: the memory area is paused (git tracks files in its data folders, or did not answer); nothing written"
|
|
424
|
+
fi
|
|
425
|
+
if [ "$_qg_con" = "true" ] && [ "${DF_TRACKED_AREA_CURATION:-unknown}" != "untracked" ]; then
|
|
426
|
+
_qg_con="false"
|
|
427
|
+
log "Queue skipped: the curation area is paused (git tracks files in its data folders, or did not answer); nothing written"
|
|
138
428
|
fi
|
|
429
|
+
_QAR_MEMORY_ON="$_qg_mon"
|
|
430
|
+
_QAR_CURATION_ON="$_qg_con"
|
|
431
|
+
return 0
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
# _qar_area_of <root> <queue_file> -- sets _QAR_AREA to the area the queue lies in:
|
|
435
|
+
# `memory` below <root>/.devflow/memory, `curation` below the ledger folder (either name),
|
|
436
|
+
# and `both` for a queue in neither, which is paused when either area is.
|
|
437
|
+
_qar_area_of() {
|
|
438
|
+
case "$2" in
|
|
439
|
+
"$1"/.devflow/memory/*) _QAR_AREA="memory" ;;
|
|
440
|
+
"$1"/.devflow/learning/*|"$1"/.devflow/curation/*) _QAR_AREA="curation" ;;
|
|
441
|
+
*) _QAR_AREA="both" ;;
|
|
442
|
+
esac
|
|
443
|
+
return 0
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
queue_append_row() {
|
|
447
|
+
_qar_area_of "$1" "$2"
|
|
448
|
+
case "$_QAR_AREA" in
|
|
449
|
+
memory)
|
|
450
|
+
_qar_gate "$1" true "" false
|
|
451
|
+
if [ "$_QAR_MEMORY_ON" != "true" ]; then return 0; fi
|
|
452
|
+
;;
|
|
453
|
+
curation)
|
|
454
|
+
_qar_gate "" false "$1" true
|
|
455
|
+
if [ "$_QAR_CURATION_ON" != "true" ]; then return 0; fi
|
|
456
|
+
;;
|
|
457
|
+
*)
|
|
458
|
+
_qar_gate "$1" true "$1" true
|
|
459
|
+
if [ "$_QAR_MEMORY_ON" != "true" ] || [ "$_QAR_CURATION_ON" != "true" ]; then return 0; fi
|
|
460
|
+
;;
|
|
461
|
+
esac
|
|
462
|
+
_qar_build_row "$3" "$4" "$5" || return 0
|
|
463
|
+
if _qar_append "$1" "$2" "$_QAR_ROW"; then _qar_trim_overflow "$_QAR_FILE"; fi
|
|
464
|
+
return 0
|
|
139
465
|
}
|
|
140
466
|
|
|
141
467
|
queue_append_both() {
|
|
142
468
|
local _qab_memory_root="$1" _qab_memory_queue="$2"
|
|
143
|
-
local
|
|
144
|
-
local _qab_memory_enabled="$5"
|
|
145
|
-
local _qab_role="$7" _qab_content="$8" _qab_ts="$9"
|
|
469
|
+
local _qab_curation_root="$3" _qab_curation_queue="$4"
|
|
470
|
+
local _qab_memory_enabled="$5" _qab_curation_enabled="$6"
|
|
471
|
+
local _qab_role="$7" _qab_content="$8" _qab_ts="$9" _qab_row=""
|
|
472
|
+
|
|
473
|
+
# A paused queue reads as not enabled: the gate comes before the row is built.
|
|
474
|
+
_qar_gate "$_qab_memory_root" "$_qab_memory_enabled" "$_qab_curation_root" "$_qab_curation_enabled"
|
|
475
|
+
_qab_memory_enabled="$_QAR_MEMORY_ON"
|
|
476
|
+
_qab_curation_enabled="$_QAR_CURATION_ON"
|
|
477
|
+
if [ "$_qab_memory_enabled" != "true" ] && [ "$_qab_curation_enabled" != "true" ]; then return 0; fi
|
|
478
|
+
# One row for both queues: it is built once, and each enabled queue takes the same line.
|
|
479
|
+
_qar_build_row "$_qab_role" "$_qab_content" "$_qab_ts" || return 0
|
|
480
|
+
_qab_row="$_QAR_ROW"
|
|
146
481
|
|
|
147
482
|
if [ "$_qab_memory_enabled" = "true" ]; then
|
|
148
|
-
|
|
483
|
+
if _qar_append "$_qab_memory_root" "$_qab_memory_queue" "$_qab_row"; then _qar_trim_overflow "$_QAR_FILE"; fi
|
|
149
484
|
fi
|
|
150
|
-
if [ "$
|
|
151
|
-
|
|
485
|
+
if [ "$_qab_curation_enabled" = "true" ]; then
|
|
486
|
+
if _qar_append "$_qab_curation_root" "$_qab_curation_queue" "$_qab_row"; then _qar_trim_overflow "$_QAR_FILE"; fi
|
|
152
487
|
fi
|
|
488
|
+
return 0
|
|
153
489
|
}
|
|
154
490
|
|
|
155
491
|
# _qg_repo_file_can_narrow <file> -- succeeds when <file> COULD set
|
|
156
|
-
# features.memory or features.learning to false, so only the
|
|
157
|
-
# it; fails when it certainly cannot. Builtins only: no fork.
|
|
492
|
+
# features.memory, features.curation or features.learning to false, so only the
|
|
493
|
+
# parser can decide it; fails when it certainly cannot. Builtins only: no fork.
|
|
158
494
|
#
|
|
159
495
|
# D-GATES-FAST-PATH (repository files): the read is bounded -- `-n 4097`, which
|
|
160
496
|
# bash 3.2 supports -- and `read -d ''` succeeds only when it stops short of the
|
|
@@ -164,14 +500,14 @@ queue_append_both() {
|
|
|
164
500
|
# symlink and refuses a directory or FIFO, so nothing here can block; the parser
|
|
165
501
|
# itself refuses the symlink. A `\u` escape is the only JSON spelling of a
|
|
166
502
|
# letter other than the letter itself, so without one a narrowing file shows
|
|
167
|
-
# `"features"`, then `{`, then `"memory"` or `"learning"`, then
|
|
168
|
-
# literally and in that order. `.` spans newlines (POSIX ERE without
|
|
503
|
+
# `"features"`, then `{`, then `"memory"`, `"curation"` or `"learning"`, then
|
|
504
|
+
# `false`, literally and in that order. `.` spans newlines (POSIX ERE without
|
|
169
505
|
# REG_NEWLINE), so the match can only over-report -- an over-report costs one
|
|
170
506
|
# fork and the parser's answer, never a wrong one. No `"decisions"` and no
|
|
171
507
|
# top-level key before `features` is ever matched.
|
|
172
508
|
_qg_repo_file_can_narrow() {
|
|
173
509
|
local _qgf_text=""
|
|
174
|
-
local _qgf_re='"features"[[:space:]]*:[[:space:]]*[{].*"(memory|learning)"[[:space:]]*:[[:space:]]*false'
|
|
510
|
+
local _qgf_re='"features"[[:space:]]*:[[:space:]]*[{].*"(memory|curation|learning)"[[:space:]]*:[[:space:]]*false'
|
|
175
511
|
[ -f "$1" ] || return 1
|
|
176
512
|
if IFS= read -r -d '' -n 4097 _qgf_text 2>/dev/null < "$1"; then return 1; fi
|
|
177
513
|
case "$_qgf_text" in *'\u'*) return 0 ;; esac
|
|
@@ -180,22 +516,24 @@ _qg_repo_file_can_narrow() {
|
|
|
180
516
|
|
|
181
517
|
queue_read_gates() {
|
|
182
518
|
local _qg_manifest="${1:-}" _qg_root="${2:-}" _qg_text="" _qg_parse="false" _qg_repo="false" _qg_fields=""
|
|
519
|
+
local _qg_scripts="${BASH_SOURCE[0]%/*}/.."
|
|
183
520
|
_QG_MEMORY="true"
|
|
184
|
-
|
|
521
|
+
_QG_CURATION="true"
|
|
185
522
|
|
|
186
523
|
if [ -n "$_qg_manifest" ] && [ -f "$_qg_manifest" ]; then
|
|
187
524
|
# D-GATES-FAST-PATH: every capture hook and every session start pays this
|
|
188
525
|
# read, and on most machines both switches are on. Only a JSON boolean
|
|
189
|
-
# `false` under a "memory", "learning" or "decisions" key can
|
|
190
|
-
# off, and a `\u` escape is the only JSON spelling of a
|
|
191
|
-
# letter itself, so without one such a key appears
|
|
192
|
-
# no `\u` and no key-then-`false` sequence
|
|
193
|
-
# off -- valid or not, it reads both-on
|
|
194
|
-
# builtins (read, case, [[ =~ ]]) with
|
|
195
|
-
# the full parse below. `read -d ''`
|
|
196
|
-
# i.e. before the end of the file, so a
|
|
197
|
-
# too. An unreadable file reads empty
|
|
198
|
-
|
|
526
|
+
# `false` under a "memory", "curation", "learning" or "decisions" key can
|
|
527
|
+
# switch anything off, and a `\u` escape is the only JSON spelling of a
|
|
528
|
+
# letter other than the letter itself, so without one such a key appears
|
|
529
|
+
# literally. A text holding no `\u` and no key-then-`false` sequence
|
|
530
|
+
# therefore cannot switch anything off -- valid or not, it reads both-on
|
|
531
|
+
# either way -- and is settled by shell builtins (read, case, [[ =~ ]]) with
|
|
532
|
+
# no jq/node fork; everything else takes the full parse below. `read -d ''`
|
|
533
|
+
# succeeds only on reaching a NUL byte, i.e. before the end of the file, so a
|
|
534
|
+
# text it cut short is parsed in full too. An unreadable file reads empty
|
|
535
|
+
# and fails open, as the parser would.
|
|
536
|
+
local _qg_re='"(memory|curation|learning|decisions)"[[:space:]]*:[[:space:]]*false'
|
|
199
537
|
IFS= read -r -d '' _qg_text 2>/dev/null < "$_qg_manifest" && _qg_parse="true"
|
|
200
538
|
case "$_qg_text" in *'\u'*) _qg_parse="true" ;; esac
|
|
201
539
|
if [[ $_qg_text =~ $_qg_re ]]; then _qg_parse="true"; fi
|
|
@@ -216,7 +554,7 @@ queue_read_gates() {
|
|
|
216
554
|
# manifest too, read only when the fast path above flagged it (an unflagged
|
|
217
555
|
# manifest is both-on), so a false in all three layers still costs one fork.
|
|
218
556
|
# jq cannot see duplicate keys, so this path is node whatever _HAS_JQ says.
|
|
219
|
-
local
|
|
557
|
+
local _qg_manifest_arg=""
|
|
220
558
|
[ "$_qg_parse" = "true" ] && _qg_manifest_arg="$_qg_manifest"
|
|
221
559
|
_qg_fields=$(node -e "
|
|
222
560
|
const S = require(require('path').resolve(process.argv[1], 'resolve-settings.cjs'));
|
|
@@ -225,7 +563,7 @@ queue_read_gates() {
|
|
|
225
563
|
try { m = JSON.parse(require('fs').readFileSync(process.argv[2], 'utf8')); } catch (_) { m = undefined; }
|
|
226
564
|
}
|
|
227
565
|
const w = S.foldSettings(Object.assign({ manifest: m }, S.readRepoLayers(process.argv[3]))).switches;
|
|
228
|
-
process.stdout.write(String(w.memory.on) + String.fromCharCode(10) + String(w.
|
|
566
|
+
process.stdout.write(String(w.memory.on) + String.fromCharCode(10) + String(w.curation.on));
|
|
229
567
|
" -- "$_qg_scripts" "$_qg_manifest_arg" "$_qg_root" 2>/dev/null) || _qg_fields=""
|
|
230
568
|
fi
|
|
231
569
|
|
|
@@ -237,20 +575,24 @@ queue_read_gates() {
|
|
|
237
575
|
# `try` covers a manifest whose top level or `features` is not an object; a
|
|
238
576
|
# parse failure empties the output, which reads as "not switched off". The
|
|
239
577
|
# comma produces two raw-output lines (newline-separated) in one jq process.
|
|
578
|
+
# Curation (#529): off when a boolean curation or learning is false; else,
|
|
579
|
+
# when neither is a boolean, off when decisions is false (isMachineFeatureOn).
|
|
240
580
|
_qg_fields=$(jq -r '
|
|
241
581
|
(if (try .features.memory catch null) == false then "false" else "true" end),
|
|
242
|
-
(if (try (.features
|
|
582
|
+
(if (try (.features
|
|
583
|
+
| ([.curation, .learning] | map(select(type == "boolean"))) as $c
|
|
584
|
+
| if ($c | length) > 0 then any($c[]; . == false) else .decisions == false end
|
|
585
|
+
) catch false)
|
|
243
586
|
then "false" else "true" end)
|
|
244
587
|
' "$_qg_manifest" 2>/dev/null) || _qg_fields=""
|
|
245
588
|
else
|
|
589
|
+
# No jq: the fold is the resolver's own machineSwitchOn (resolve-settings.cjs),
|
|
590
|
+
# so the machine rule is not copied here. One node fork, as for jq's one process.
|
|
246
591
|
_qg_fields=$(node -e "
|
|
592
|
+
const S = require(require('path').resolve(process.argv[2], 'resolve-settings.cjs'));
|
|
247
593
|
const m = JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'));
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const on = (k) => !(isObj && f[k] === false);
|
|
251
|
-
const learnKey = isObj && typeof f.learning !== 'boolean' ? 'decisions' : 'learning';
|
|
252
|
-
process.stdout.write(String(on('memory')) + String.fromCharCode(10) + String(on(learnKey)));
|
|
253
|
-
" -- "$_qg_manifest" 2>/dev/null) || _qg_fields=""
|
|
594
|
+
process.stdout.write(String(S.machineSwitchOn(m, 'memory')) + String.fromCharCode(10) + String(S.machineSwitchOn(m, 'curation')));
|
|
595
|
+
" -- "$_qg_manifest" "$_qg_scripts" 2>/dev/null) || _qg_fields=""
|
|
254
596
|
fi
|
|
255
597
|
fi
|
|
256
598
|
|
|
@@ -258,7 +600,7 @@ queue_read_gates() {
|
|
|
258
600
|
case "$_qg_fields" in
|
|
259
601
|
true$'\n'true|true$'\n'false|false$'\n'true|false$'\n'false)
|
|
260
602
|
_QG_MEMORY="${_qg_fields%%$'\n'*}"
|
|
261
|
-
|
|
603
|
+
_QG_CURATION="${_qg_fields#*$'\n'}"
|
|
262
604
|
;;
|
|
263
605
|
esac
|
|
264
606
|
|