devflow-kit 3.6.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 +438 -1
- package/README.md +15 -11
- package/dist/agents/code.md +9 -15
- package/dist/agents/design.md +1 -1
- package/dist/agents/fix.md +11 -13
- package/dist/agents/git.md +1 -1
- package/dist/agents/harden.md +1 -1
- package/dist/agents/refactor.md +7 -10
- package/dist/agents/review.md +6 -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 +299 -0
- package/dist/cli/commands/init-seed.js +16 -5
- package/dist/cli/commands/init.js +263 -54
- package/dist/cli/commands/memory.js +20 -2
- package/dist/cli/commands/prompt-io.js +37 -0
- package/dist/cli/commands/uninstall.js +81 -9
- package/dist/cli.js +4 -2
- package/dist/commands/bug-analysis.md +25 -36
- package/dist/commands/code-review.md +77 -73
- package/dist/commands/debug.md +5 -5
- package/dist/commands/dynamic-build.md +76 -83
- 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 +68 -102
- package/dist/commands/plan.md +19 -29
- package/dist/commands/release.md +5 -5
- package/dist/commands/research.md +6 -6
- package/dist/commands/resolve.md +29 -31
- package/dist/commands/self-review.md +5 -13
- 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 +48 -0
- package/dist/core/evidence-policy.js +18 -6
- package/dist/core/feature-switch.js +95 -19
- package/dist/core/fs-atomic.js +1 -1
- package/dist/core/gate-level.js +221 -0
- 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 +84 -24
- package/dist/core/migrations.js +1 -13
- package/dist/core/model-discovery.js +7 -1
- 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 +6 -34
- package/dist/{learning-off → curation-off}/agents/code.md +9 -15
- package/dist/{learning-off → curation-off}/agents/fix.md +11 -13
- package/dist/{learning-off → curation-off}/agents/refactor.md +7 -10
- package/dist/{learning-off → curation-off}/agents/review.md +5 -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 +74 -70
- package/dist/{learning-off → curation-off}/commands/debug.md +2 -2
- package/dist/{learning-off → curation-off}/commands/dynamic-build.md +73 -80
- package/dist/{learning-off → curation-off}/commands/explore.md +2 -2
- package/dist/{learning-off → curation-off}/commands/implement.md +65 -99
- package/dist/{learning-off → curation-off}/commands/plan.md +15 -25
- package/dist/{learning-off → curation-off}/commands/resolve.md +26 -28
- package/dist/{learning-off → curation-off}/commands/self-review.md +2 -10
- 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 +20 -14
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +20 -20
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +20 -20
- package/dist/targets/claude-code/{learning-install.js → curation-install.js} +99 -67
- 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 +16 -17
- package/src/assets/agents/code.mds +4 -7
- package/src/assets/agents/curate.md +12 -14
- 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 +45 -43
- package/src/assets/agents/skim.mds +11 -11
- package/src/assets/agents/synthesize.md +2 -2
- 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 +3 -3
- package/src/assets/commands/_partials/_wave.mds +17 -24
- package/src/assets/commands/bug-analysis.mds +37 -48
- package/src/assets/commands/code-review.mds +90 -86
- package/src/assets/commands/debug.mds +14 -14
- package/src/assets/commands/dynamic-build.mds +78 -78
- 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 +92 -118
- package/src/assets/commands/plan.mds +41 -51
- 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 +13 -7
- package/src/assets/mds/git/_pr.mds +7 -4
- package/src/assets/mds/tracker/_common.mds +23 -12
- package/src/assets/mds/tracker/_github.mds +2 -9
- package/src/assets/mds/tracker/_jira.mds +2 -15
- package/src/assets/mds/tracker/_linear.mds +2 -15
- package/src/assets/rules/quality.md +1 -0
- package/src/assets/scripts/ci-wait.cjs +22 -4
- package/src/assets/scripts/claude-md-audit.cjs +66 -51
- package/src/assets/scripts/hooks/background-memory-update +406 -97
- 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 +145 -51
- package/src/assets/scripts/hooks/json-parse +19 -4
- 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-permission +358 -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 +145 -71
- package/src/assets/scripts/hooks/session-start-memory +27 -46
- package/src/assets/scripts/lib/project-config.cjs +31 -9
- package/src/assets/scripts/resolve-base.cjs +1043 -0
- package/src/assets/scripts/resolve-settings.cjs +238 -46
- package/src/assets/skills/agent-references/SKILL.md +9 -2
- package/src/assets/skills/agent-references/references/commands/code-review-levels.md +19 -0
- 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/plan-levels.md +40 -0
- package/src/assets/skills/agent-references/references/commands/resolve-levels.md +24 -0
- package/src/assets/skills/agent-references/references/synthesize/planning.md +9 -22
- package/src/assets/skills/agent-references/references/synthesize/review.md +11 -11
- 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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# ensure-root-gitignore — single source of truth for the project root .gitignore
|
|
3
3
|
# rules that govern .devflow/.
|
|
4
4
|
#
|
|
5
|
-
# .devflow/ holds per-developer runtime state (memory,
|
|
5
|
+
# .devflow/ holds per-developer runtime state (memory, curation, docs, locks) —
|
|
6
6
|
# local by default. FOUR exceptions are shared via git:
|
|
7
7
|
# 1. Feature knowledge bases under .devflow/features/: index.md and every
|
|
8
8
|
# {slug}/KNOWLEDGE.md (tracked, committed by the Knowledge agent at workflow end).
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
# so git never descends and later negations are dead. Hence the multi-line block.
|
|
23
23
|
#
|
|
24
24
|
# Sourced by:
|
|
25
|
-
# - ensure-devflow-init (reached via the memory/
|
|
25
|
+
# - ensure-devflow-init (reached via the memory/curation hooks)
|
|
26
26
|
# - session-start-context (always-on, memory-independent — covers memory-off
|
|
27
|
-
# projects that still use
|
|
27
|
+
# projects that still use curation/knowledge)
|
|
28
28
|
# Both reach this one writer so the rule is applied identically everywhere; this
|
|
29
29
|
# decouples git-tracking of .devflow/ from any single feature toggle.
|
|
30
30
|
#
|
|
@@ -201,7 +201,7 @@ mkdir -p "$_ERG_DEVFLOW_DIR" 2>/dev/null || return 1
|
|
|
201
201
|
# DEVFLOW_GITIGNORE_BLOCK_WITHOUT_CLAUDEIGNORE in
|
|
202
202
|
# src/targets/claude-code/post-install.ts.
|
|
203
203
|
printf -v _ERG_BLOCK_NO_CI '%s\n' \
|
|
204
|
-
'# Devflow runtime data — local by default (memory,
|
|
204
|
+
'# Devflow runtime data — local by default (memory, curation, docs, locks).' \
|
|
205
205
|
'# Shared via git: feature knowledge bases under .devflow/features/ (index.md and' \
|
|
206
206
|
'# every {slug}/KNOWLEDGE.md), .devflow/conventions.md (naming authority),' \
|
|
207
207
|
'# .devflow/policy.json (retired; presence only) and .devflow/project.json (team settings).' \
|
|
@@ -133,3 +133,84 @@ df_file_below() {
|
|
|
133
133
|
fi
|
|
134
134
|
return 1
|
|
135
135
|
}
|
|
136
|
+
|
|
137
|
+
# df_ledger_folder <devflow_dir> <old> [<new>] — sets DF_LEDGER_FOLDER to the path of
|
|
138
|
+
# the ledger folder a creator may create or write into: <devflow_dir>/<old> while that
|
|
139
|
+
# name is present (a folder, a file or a link, dangling or not), else
|
|
140
|
+
# <devflow_dir>/<new>. <new> defaults to <old>, so a caller that names only the old
|
|
141
|
+
# folder gets the old folder's path in every state.
|
|
142
|
+
#
|
|
143
|
+
# The creator rule: a creator makes the new folder only when the old name is absent.
|
|
144
|
+
# A new folder made beside a present old one would hide the old ledger from every
|
|
145
|
+
# reader, so the session-start heal, the ensure-devflow-init scaffold and queue
|
|
146
|
+
# append all take their folder from here. It answers a path and creates nothing; the
|
|
147
|
+
# caller checks the path with df_no_symlink_below and creates it.
|
|
148
|
+
#
|
|
149
|
+
# Zero forks: builtin tests and parameter expansion only. It always returns 0; read
|
|
150
|
+
# the answer from DF_LEDGER_FOLDER, not from a command substitution.
|
|
151
|
+
df_ledger_folder() {
|
|
152
|
+
local _dir="$1" _old="$2" _new="${3:-$2}"
|
|
153
|
+
if [ -e "$_dir/$_old" ] || [ -L "$_dir/$_old" ]; then
|
|
154
|
+
DF_LEDGER_FOLDER="$_dir/$_old"
|
|
155
|
+
else
|
|
156
|
+
DF_LEDGER_FOLDER="$_dir/$_new"
|
|
157
|
+
fi
|
|
158
|
+
return 0
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# df_ledger_queue <queue_file> <old> [<new>] — sets DF_LEDGER_QUEUE to the queue file
|
|
162
|
+
# a creator may append to. A queue file whose folder is named <old> or <new> is
|
|
163
|
+
# answered under the folder df_ledger_folder picks for that folder's parent; a queue
|
|
164
|
+
# in any other folder (the memory queue) and a path with fewer than two parts come
|
|
165
|
+
# back unchanged. <new> defaults to <old>. Creates nothing; zero forks; returns 0.
|
|
166
|
+
df_ledger_queue() {
|
|
167
|
+
local _file="$1" _old="$2" _new="${3:-$2}" _folder="" _parent="" _name=""
|
|
168
|
+
DF_LEDGER_QUEUE="$_file"
|
|
169
|
+
case "$_file" in */*/*) ;; *) return 0 ;; esac
|
|
170
|
+
_folder="${_file%/*}"
|
|
171
|
+
_parent="${_folder%/*}"
|
|
172
|
+
_name="${_folder##*/}"
|
|
173
|
+
case "$_name" in
|
|
174
|
+
"$_old"|"$_new")
|
|
175
|
+
df_ledger_folder "$_parent" "$_old" "$_new"
|
|
176
|
+
DF_LEDGER_QUEUE="$DF_LEDGER_FOLDER/${_file##*/}"
|
|
177
|
+
;;
|
|
178
|
+
esac
|
|
179
|
+
return 0
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# df_make_ledger_folder <root> <devflow_dir> <old> <new> — the one creator of the
|
|
183
|
+
# ledger folder for a hook. It sets DF_LEDGER_FOLDER as df_ledger_folder does, then
|
|
184
|
+
# makes the NEW folder and only the new one, and only where the old name is absent at
|
|
185
|
+
# the probe and no link sits on the way down from <root> (df_no_symlink_below). Where
|
|
186
|
+
# the old name is present it makes nothing, so a creator never makes the old folder:
|
|
187
|
+
# a `mkdir -p` on it would bring back an empty old folder beside the new one after a
|
|
188
|
+
# move had renamed it away. It logs nothing, so each hook words its own skip once.
|
|
189
|
+
# A predicate: it returns 0 when nothing is left to make or the folder was made, 2 for
|
|
190
|
+
# a link on the path (nothing is made), and 1 for an empty argument or a failed mkdir.
|
|
191
|
+
# Call it in an `if` or an `||` list, never as a plain statement under `set -e`.
|
|
192
|
+
#
|
|
193
|
+
# The remaining window is the gap between the probe and the mkdir. A move that
|
|
194
|
+
# finishes inside it leaves no folder this time, and the next event makes the new one.
|
|
195
|
+
# An old folder that appears inside it, from an older install or by hand, leaves the
|
|
196
|
+
# new folder beside it, and the next move merges the two. No data is lost either way:
|
|
197
|
+
# the helper only ever makes an empty folder, and never writes into or removes one.
|
|
198
|
+
df_make_ledger_folder() {
|
|
199
|
+
local _root="$1" _dir="$2" _old="$3" _new="$4"
|
|
200
|
+
[ -n "$_root" ] && [ -n "$_dir" ] && [ -n "$_old" ] && [ -n "$_new" ] || return 1
|
|
201
|
+
df_ledger_folder "$_dir" "$_old" "$_new"
|
|
202
|
+
[ "$DF_LEDGER_FOLDER" = "$_dir/$_new" ] || return 0
|
|
203
|
+
if ! df_no_symlink_below "$_root" "$DF_LEDGER_FOLDER"; then return 2; fi
|
|
204
|
+
if [ -d "$DF_LEDGER_FOLDER" ]; then return 0; fi
|
|
205
|
+
mkdir -p "$DF_LEDGER_FOLDER" 2>/dev/null || return 1
|
|
206
|
+
return 0
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
# df_global_tuning_file <machine_dir> — sets DF_GLOBAL_TUNING_FILE to the global
|
|
210
|
+
# curate-agent tuning file inside the machine directory (~/.devflow). The shell
|
|
211
|
+
# twin of getGlobalCurateTuningConfigPath in src/core/project-paths.ts: the one
|
|
212
|
+
# place a hook names the file. Zero forks; returns 0.
|
|
213
|
+
df_global_tuning_file() {
|
|
214
|
+
DF_GLOBAL_TUNING_FILE="$1/curation.json"
|
|
215
|
+
return 0
|
|
216
|
+
}
|
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
// src/assets/scripts/hooks/json-helper.cjs
|
|
4
4
|
// Provides jq-equivalent operations for hooks when jq is not installed, and the
|
|
5
|
-
//
|
|
5
|
+
// curation ops the Curate agent runs from the project root.
|
|
6
6
|
// SECURITY: This is a local CLI helper invoked only by shell hooks and the
|
|
7
7
|
// Curate agent with controlled arguments. No operation takes a file path: a
|
|
8
|
-
// file's content arrives on stdin (json-parse redirects it), and the
|
|
8
|
+
// file's content arrives on stdin (json-parse redirects it), and the curation ops
|
|
9
9
|
// build every path from the current directory.
|
|
10
10
|
// Usage: node json-helper.cjs <operation> [args...]
|
|
11
11
|
//
|
|
12
12
|
// Operations:
|
|
13
13
|
// get-field <field> [default] Read field from stdin JSON
|
|
14
14
|
// get-string-field <field> Read field from stdin JSON only when it is a string
|
|
15
|
+
// array-length <field> Count of an array field in stdin JSON; `unknown` for any other type
|
|
15
16
|
// extract-cwd-field <field> Extract cwd + arbitrary field, SOH-byte delimited
|
|
16
17
|
// session-output <context> [message] Build SessionStart output envelope (+ systemMessage)
|
|
17
18
|
// prompt-output <context> Build UserPromptSubmit output envelope
|
|
@@ -36,13 +37,17 @@
|
|
|
36
37
|
// show <anchor|obs_id> Read-only: print one entry as pretty JSON
|
|
37
38
|
// claim-due Hand out the entries due for maintenance, leased
|
|
38
39
|
// for a day, after the ref their claims are checked at
|
|
39
|
-
// claim-queue Claim the
|
|
40
|
+
// claim-queue Claim the curation queue for this run; prints
|
|
40
41
|
// claimed <token>[ takeover] | busy | none
|
|
41
42
|
// release-claim <token> Release the claim the token owns; prints
|
|
42
43
|
// released | not-owner | gone
|
|
44
|
+
// move-ledger <root> Move a project's old ledger folder to the new name,
|
|
45
|
+
// once; prints none | busy | busy claim |
|
|
46
|
+
// skipped tracked | skipped unknown | moved <mode>
|
|
47
|
+
// and its backup, cause and notes
|
|
43
48
|
//
|
|
44
|
-
// Every
|
|
45
|
-
// the mtime of an existing queue claim — the heartbeat (D-OWNED-CLAIM).
|
|
49
|
+
// Every curation op above except claim-queue, release-claim and move-ledger first
|
|
50
|
+
// refreshes the mtime of an existing queue claim — the heartbeat (D-OWNED-CLAIM).
|
|
46
51
|
|
|
47
52
|
'use strict';
|
|
48
53
|
|
|
@@ -52,21 +57,35 @@ const { constants: { MAX_STRING_LENGTH } } = require('buffer');
|
|
|
52
57
|
const op = process.argv[2];
|
|
53
58
|
const args = process.argv.slice(3);
|
|
54
59
|
|
|
55
|
-
/** The
|
|
56
|
-
let
|
|
60
|
+
/** The curation modules, once loaded; see curation(). */
|
|
61
|
+
let curationModules = null;
|
|
57
62
|
|
|
58
63
|
/**
|
|
59
|
-
* The
|
|
64
|
+
* The curation store, loaded on first use and memoized. The generic ops never
|
|
60
65
|
* call it, so a hook that falls back from jq to node never pays for loading it,
|
|
61
66
|
* and the store loads the renderer only when an op renders.
|
|
62
67
|
*
|
|
63
68
|
* @returns {{ store: object }}
|
|
64
69
|
*/
|
|
65
|
-
function
|
|
66
|
-
if (
|
|
67
|
-
|
|
70
|
+
function curation() {
|
|
71
|
+
if (curationModules === null) {
|
|
72
|
+
curationModules = { store: require('./lib/curation-store.cjs') };
|
|
68
73
|
}
|
|
69
|
-
return
|
|
74
|
+
return curationModules;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The ledger move module, once loaded; see curationMove(). */
|
|
78
|
+
let curationMoveModule = null;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The ledger move module, loaded on first use and memoized. Only the move op calls
|
|
82
|
+
* it, so no other curation op loads the move code.
|
|
83
|
+
*
|
|
84
|
+
* @returns {object}
|
|
85
|
+
*/
|
|
86
|
+
function curationMove() {
|
|
87
|
+
if (curationMoveModule === null) curationMoveModule = require('./lib/curation-move.cjs');
|
|
88
|
+
return curationMoveModule;
|
|
70
89
|
}
|
|
71
90
|
|
|
72
91
|
/** The bytes each read of stdin asks for. */
|
|
@@ -83,7 +102,7 @@ const STDIN_WAIT_CELL = new Int32Array(new SharedArrayBuffer(4));
|
|
|
83
102
|
|
|
84
103
|
/**
|
|
85
104
|
* Read stdin to its end, keeping at most `maxBytes`. Every op that reads stdin
|
|
86
|
-
* reads it here, so the generic ops and the
|
|
105
|
+
* reads it here, so the generic ops and the curation ops cannot read it two ways.
|
|
87
106
|
* Never throws: a failure is a refusal.
|
|
88
107
|
*
|
|
89
108
|
* D-STDIN-FD0: stdin is read from file descriptor 0 itself, in reads repeated
|
|
@@ -167,14 +186,16 @@ function parseArgs(argList) {
|
|
|
167
186
|
}
|
|
168
187
|
|
|
169
188
|
// ---------------------------------------------------------------------------
|
|
170
|
-
//
|
|
189
|
+
// Curation-op adapter
|
|
171
190
|
// ---------------------------------------------------------------------------
|
|
172
191
|
|
|
173
192
|
/**
|
|
174
|
-
* Print a
|
|
193
|
+
* Print a curation op's Result: `format(value)` and a newline on stdout, or the
|
|
175
194
|
* error message and a newline on stderr. Returns the exit code for the op to set
|
|
176
195
|
* as process.exitCode, so the process exits once, after the op has returned and
|
|
177
|
-
* every lock it took is released.
|
|
196
|
+
* every lock it took is released. The error message keeps its line structure (a
|
|
197
|
+
* refusal lists one problem per line), and each line has no control character, as it
|
|
198
|
+
* may name a file the repository holds. At most REFUSAL_MAX_LINES lines print.
|
|
178
199
|
*
|
|
179
200
|
* @param {{ ok: true, value: unknown } | { ok: false, error: { message: string } }} result
|
|
180
201
|
* @param {(value: any) => string} format - the stdout text for the value
|
|
@@ -185,10 +206,43 @@ function emit(result, format) {
|
|
|
185
206
|
process.stdout.write(`${format(result.value)}\n`);
|
|
186
207
|
return 0;
|
|
187
208
|
}
|
|
188
|
-
process.stderr.write(`${result.error.message}\n`);
|
|
209
|
+
process.stderr.write(`${refusalText(result.error.message)}\n`);
|
|
189
210
|
return 1;
|
|
190
211
|
}
|
|
191
212
|
|
|
213
|
+
/** The most lines of one refusal message that print. */
|
|
214
|
+
const REFUSAL_MAX_LINES = 200;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* A refusal message with each line scrubbed (singleLine) and the line structure kept.
|
|
218
|
+
* Past REFUSAL_MAX_LINES lines, the rest become one `... N more lines` line.
|
|
219
|
+
*
|
|
220
|
+
* @param {string} message
|
|
221
|
+
* @returns {string}
|
|
222
|
+
*/
|
|
223
|
+
function refusalText(message) {
|
|
224
|
+
const { singleLine } = curation().store;
|
|
225
|
+
const lines = String(message).split('\n');
|
|
226
|
+
if (lines.length <= REFUSAL_MAX_LINES) return lines.map(line => singleLine(line)).join('\n');
|
|
227
|
+
const shown = lines.slice(0, REFUSAL_MAX_LINES).map(line => singleLine(line));
|
|
228
|
+
return [...shown, `... ${lines.length - REFUSAL_MAX_LINES} more lines`].join('\n');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* The first line of move-ledger's answer: the state, with `moved` followed by its mode,
|
|
233
|
+
* `busy` followed by `claim` when a fresh queue claim holds the move, and `skipped`
|
|
234
|
+
* followed by its reason (`tracked` or `unknown`).
|
|
235
|
+
*
|
|
236
|
+
* @param {{ state: string, mode?: string, reason?: string }} moved
|
|
237
|
+
* @returns {string}
|
|
238
|
+
*/
|
|
239
|
+
function moveAnswerWord(moved) {
|
|
240
|
+
if (moved.state === 'moved') return `moved ${moved.mode}`;
|
|
241
|
+
if (moved.state === 'busy' && moved.reason === 'claim') return 'busy claim';
|
|
242
|
+
if (moved.state === 'skipped') return `skipped ${moved.reason}`;
|
|
243
|
+
return moved.state;
|
|
244
|
+
}
|
|
245
|
+
|
|
192
246
|
/**
|
|
193
247
|
* Refuse a malformed command line: print `<op>: usage: <usage>` on stderr and exit 1,
|
|
194
248
|
* before the op takes any lock.
|
|
@@ -201,11 +255,11 @@ function exitWithUsage(usage) {
|
|
|
201
255
|
process.exit(1);
|
|
202
256
|
}
|
|
203
257
|
|
|
204
|
-
/** The most stdin a
|
|
258
|
+
/** The most stdin a curation op reads: far above any valid input, so a runaway writer is refused, not parsed. */
|
|
205
259
|
const STDIN_JSON_MAX_BYTES = 64 * 1024;
|
|
206
260
|
|
|
207
261
|
/**
|
|
208
|
-
* A
|
|
262
|
+
* A curation op's stdin as one JSON object: the one way text reaches a curation
|
|
209
263
|
* op, so no field of it ever passes through argv or a shell word. Never throws.
|
|
210
264
|
*
|
|
211
265
|
* @param {string} opName - for the message
|
|
@@ -234,30 +288,30 @@ const PUT_MODES = new Map([['--create', 'create'], ['--update', 'update'], ['--r
|
|
|
234
288
|
const ENTRY_TYPES = new Set(['decision', 'pitfall']);
|
|
235
289
|
|
|
236
290
|
/**
|
|
237
|
-
* The
|
|
291
|
+
* The curation ops whose run sends the claim heartbeat first (D-OWNED-CLAIM).
|
|
238
292
|
* claim-queue and release-claim manage the claim themselves, and the generic ops
|
|
239
|
-
* never touch it. A new
|
|
293
|
+
* never touch it. A new curation op joins this set.
|
|
240
294
|
*/
|
|
241
|
-
const
|
|
295
|
+
const CURATION_OPS = new Set([
|
|
242
296
|
'assign-anchor', 'retire-anchor', 'restore-anchor', 'refresh-anchor', 'rotate-observations',
|
|
243
297
|
'put-observation', 'list', 'show', 'claim-due',
|
|
244
298
|
]);
|
|
245
299
|
|
|
246
300
|
/** Send the claim heartbeat; a failure is reported on stderr and never stops the op. */
|
|
247
301
|
function heartbeat(root) {
|
|
248
|
-
const beat =
|
|
302
|
+
const beat = curation().store.touchClaim(root);
|
|
249
303
|
if (!beat.ok) process.stderr.write(`${beat.error.message}\n`);
|
|
250
304
|
}
|
|
251
305
|
|
|
252
|
-
// The
|
|
306
|
+
// The curation ops run from the project root and take no path to a curation file:
|
|
253
307
|
// each builds its paths from the current directory. Every op that writes takes the
|
|
254
|
-
// store's one
|
|
255
|
-
// refuses, creating nothing, when .devflow/
|
|
308
|
+
// store's one curation lock through withDecisionsLock (D-ONE-LEARNING-LOCK) and
|
|
309
|
+
// refuses, creating nothing, when .devflow/curation/ is absent (D-NO-STRAY-TREE)
|
|
256
310
|
// or when it, or .devflow, is a symbolic link (D-NO-LINKED-TREE).
|
|
257
311
|
// A locked body returns its Result; emit prints it once the lock is released.
|
|
258
312
|
if (require.main === module) {
|
|
259
313
|
try {
|
|
260
|
-
if (
|
|
314
|
+
if (CURATION_OPS.has(op)) heartbeat(process.cwd());
|
|
261
315
|
switch (op) {
|
|
262
316
|
case 'get-field': {
|
|
263
317
|
const input = JSON.parse(readStdin());
|
|
@@ -280,6 +334,16 @@ try {
|
|
|
280
334
|
break;
|
|
281
335
|
}
|
|
282
336
|
|
|
337
|
+
case 'array-length': {
|
|
338
|
+
// The count of an array field: digits for an array, the word `unknown` for an
|
|
339
|
+
// absent field or any other type, so a caller can tell none from not reported.
|
|
340
|
+
// jq's `length` on an array agrees (json-parse json_array_length_file).
|
|
341
|
+
const input = JSON.parse(readStdin());
|
|
342
|
+
const val = getNestedField(input, args[0]);
|
|
343
|
+
console.log(Array.isArray(val) ? String(val.length) : 'unknown');
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
|
|
283
347
|
case 'extract-cwd-field': {
|
|
284
348
|
// Extract cwd and an arbitrary top-level field from hook JSON in one pass.
|
|
285
349
|
// Outputs: cwd + ASCII SOH (0x01) + field value (no trailing newline).
|
|
@@ -327,7 +391,6 @@ try {
|
|
|
327
391
|
console.log(JSON.stringify({
|
|
328
392
|
timestamp: data.ts || '',
|
|
329
393
|
trigger: 'pre-compact',
|
|
330
|
-
memory_snapshot: data.memory || '',
|
|
331
394
|
git: {
|
|
332
395
|
branch: data.branch || '',
|
|
333
396
|
status: data.status || '',
|
|
@@ -342,12 +405,12 @@ try {
|
|
|
342
405
|
// assign-anchor <decision|pitfall> <obs_id>
|
|
343
406
|
// Promote a v2 observation no ledger row carries to the next entry of its
|
|
344
407
|
// type, skipping each number a tracked file cites (assignAnchor,
|
|
345
|
-
//
|
|
408
|
+
// curation-store.cjs: D-LEDGER-REGISTRY, D-E4-SKIP). It never writes the log.
|
|
346
409
|
// stdout: the anchor; stderr: `assign-anchor: skipped <anchor>, cited in
|
|
347
410
|
// <path>:<line>` for each number skipped
|
|
348
411
|
// -------------------------------------------------------------------------
|
|
349
412
|
case 'assign-anchor': {
|
|
350
|
-
const { store } =
|
|
413
|
+
const { store } = curation();
|
|
351
414
|
if (args.length !== 2 || !ENTRY_TYPES.has(args[0]) || !store.OBS_ID_RE.test(args[1])) {
|
|
352
415
|
exitWithUsage('assign-anchor <decision|pitfall> <obs_id> (run from the project root)');
|
|
353
416
|
}
|
|
@@ -366,12 +429,12 @@ try {
|
|
|
366
429
|
// Make an active entry inactive with the one JSON object on stdin its status
|
|
367
430
|
// takes: { reason } for Retired and Deprecated, { by } for Superseded, and
|
|
368
431
|
// { at, quote } for Encoded, the quote checked at the verify ref
|
|
369
|
-
// (retireAnchor,
|
|
432
|
+
// (retireAnchor, curation-store.cjs: D-ENCODED-QUOTE).
|
|
370
433
|
// stdout: the new status in lower case and the anchor, then `repointed
|
|
371
434
|
// <anchor>` for each entry re-pointed to the successor
|
|
372
435
|
// -------------------------------------------------------------------------
|
|
373
436
|
case 'retire-anchor': {
|
|
374
|
-
const { store } =
|
|
437
|
+
const { store } = curation();
|
|
375
438
|
if (args.length !== 2 || !store.ANCHOR_ID_RE.test(args[0]) || !store.INACTIVE_STATUSES.includes(args[1])) {
|
|
376
439
|
exitWithUsage('retire-anchor <anchor> <Encoded|Superseded|Retired|Deprecated> (one JSON object on stdin; run from the project root)');
|
|
377
440
|
}
|
|
@@ -389,11 +452,11 @@ try {
|
|
|
389
452
|
// Make an inactive entry active again, its notes, last_verified and
|
|
390
453
|
// last_attempt cleared so it is due for maintenance again, ordered after
|
|
391
454
|
// integrity problems and legacy entries, or among them when it is one
|
|
392
|
-
// (restoreAnchor,
|
|
455
|
+
// (restoreAnchor, curation-store.cjs: D-DUE-ORDER).
|
|
393
456
|
// stdout: restored <anchor>
|
|
394
457
|
// -------------------------------------------------------------------------
|
|
395
458
|
case 'restore-anchor': {
|
|
396
|
-
const { store } =
|
|
459
|
+
const { store } = curation();
|
|
397
460
|
if (args.length !== 1 || !store.ANCHOR_ID_RE.test(args[0])) {
|
|
398
461
|
exitWithUsage('restore-anchor <anchor> (run from the project root)');
|
|
399
462
|
}
|
|
@@ -404,12 +467,12 @@ try {
|
|
|
404
467
|
// -------------------------------------------------------------------------
|
|
405
468
|
// refresh-anchor <anchor> [<anchor>...] [--verified]
|
|
406
469
|
// Re-project active v2 entries from their log rows, or under --verified stamp
|
|
407
|
-
// them verified today; all or nothing (refreshAnchors,
|
|
470
|
+
// them verified today; all or nothing (refreshAnchors, curation-store.cjs).
|
|
408
471
|
// stdout: `reprojected <anchor>` or `unchanged <anchor>` per anchor, or
|
|
409
472
|
// `verified <anchor>` under --verified, in the order given
|
|
410
473
|
// -------------------------------------------------------------------------
|
|
411
474
|
case 'refresh-anchor': {
|
|
412
|
-
const { store } =
|
|
475
|
+
const { store } = curation();
|
|
413
476
|
const verifiedFlags = args.filter(arg => arg === '--verified').length;
|
|
414
477
|
const anchors = args.filter(arg => arg !== '--verified');
|
|
415
478
|
if (verifiedFlags > 1 || anchors.length === 0 || !anchors.every(arg => store.ANCHOR_ID_RE.test(arg))) {
|
|
@@ -424,13 +487,13 @@ try {
|
|
|
424
487
|
// rotate-observations
|
|
425
488
|
// Archive the log rows no ledger row carries once 30 days have passed since
|
|
426
489
|
// their last activity, and delete the usage telemetry's leftovers
|
|
427
|
-
// (D-ROTATE-UNREFERENCED,
|
|
490
|
+
// (D-ROTATE-UNREFERENCED, curation-store.cjs). Takes no argument: the log and
|
|
428
491
|
// the archive are the project root's.
|
|
429
492
|
// stdout: rotated <N> observations
|
|
430
493
|
// -------------------------------------------------------------------------
|
|
431
494
|
case 'rotate-observations': {
|
|
432
495
|
if (args.length > 0) exitWithUsage('rotate-observations (no arguments; run from the project root)');
|
|
433
|
-
const result =
|
|
496
|
+
const result = curation().store.rotateObservations(process.cwd());
|
|
434
497
|
process.exitCode = emit(result, ({ rotated }) => `rotated ${rotated} observations`);
|
|
435
498
|
break;
|
|
436
499
|
}
|
|
@@ -438,7 +501,7 @@ try {
|
|
|
438
501
|
// -------------------------------------------------------------------------
|
|
439
502
|
// put-observation --create|--update|--reinforce
|
|
440
503
|
// Store one observation from the JSON object on stdin (D-PUT-NOT-MERGE,
|
|
441
|
-
// D-PUT-REPROJECTS,
|
|
504
|
+
// D-PUT-REPROJECTS, curation-store.cjs). Exactly one mode flag; no other argv.
|
|
442
505
|
// stdout: created <id> | updated <id> | unchanged <id> | reinforced <id> <n>,
|
|
443
506
|
// then one `reprojected <anchor>` line per entry re-projected
|
|
444
507
|
// -------------------------------------------------------------------------
|
|
@@ -448,7 +511,7 @@ try {
|
|
|
448
511
|
exitWithUsage('put-observation --create|--update|--reinforce (one JSON object on stdin; run from the project root)');
|
|
449
512
|
}
|
|
450
513
|
const input = readStdinJson('put-observation');
|
|
451
|
-
const result = input.ok ?
|
|
514
|
+
const result = input.ok ? curation().store.putObservation(process.cwd(), mode, input.value) : input;
|
|
452
515
|
process.exitCode = emit(result, put => [
|
|
453
516
|
put.outcome === 'reinforced' ? `reinforced ${put.id} ${put.observations}` : `${put.outcome} ${put.id}`,
|
|
454
517
|
...put.reprojected.map(anchorId => `reprojected ${anchorId}`),
|
|
@@ -459,25 +522,25 @@ try {
|
|
|
459
522
|
// -------------------------------------------------------------------------
|
|
460
523
|
// list
|
|
461
524
|
// Print the ledger and the log by section, read-only (readListing and
|
|
462
|
-
// formatListing,
|
|
525
|
+
// formatListing, curation-store.cjs). Takes no argument.
|
|
463
526
|
// stdout: the ACTIVE, INACTIVE, OBSERVATIONS and INTEGRITY sections, then
|
|
464
527
|
// MALFORMED when lines were skipped
|
|
465
528
|
// -------------------------------------------------------------------------
|
|
466
529
|
case 'list': {
|
|
467
530
|
if (args.length > 0) exitWithUsage('list (no arguments; run from the project root)');
|
|
468
|
-
const { store } =
|
|
531
|
+
const { store } = curation();
|
|
469
532
|
process.exitCode = emit(store.readListing(process.cwd()), store.formatListing);
|
|
470
533
|
break;
|
|
471
534
|
}
|
|
472
535
|
|
|
473
536
|
// -------------------------------------------------------------------------
|
|
474
537
|
// show <anchor|obs_id>
|
|
475
|
-
// Print one entry, read-only (showByKey,
|
|
538
|
+
// Print one entry, read-only (showByKey, curation-store.cjs).
|
|
476
539
|
// stdout: pretty JSON { key, ledger, log, history_versions, flags }, plus
|
|
477
540
|
// malformed when lines were skipped
|
|
478
541
|
// -------------------------------------------------------------------------
|
|
479
542
|
case 'show': {
|
|
480
|
-
const { store } =
|
|
543
|
+
const { store } = curation();
|
|
481
544
|
if (args.length !== 1 || !(store.ANCHOR_ID_RE.test(args[0]) || store.OBS_ID_RE.test(args[0]))) {
|
|
482
545
|
exitWithUsage('show <anchor|obs_id> (run from the project root)');
|
|
483
546
|
}
|
|
@@ -488,13 +551,13 @@ try {
|
|
|
488
551
|
// -------------------------------------------------------------------------
|
|
489
552
|
// claim-due
|
|
490
553
|
// Hand out the entries due for maintenance and lease each for a day
|
|
491
|
-
// (claimDue,
|
|
554
|
+
// (claimDue, curation-store.cjs: D-DUE-ORDER, D-VERIFY-REF). Takes no argument.
|
|
492
555
|
// stdout: ref <origin/HEAD|HEAD> <sha12>, or ref none; then one
|
|
493
556
|
// `<anchor> <reason> <bytes>` line per entry handed out, or due none
|
|
494
557
|
// -------------------------------------------------------------------------
|
|
495
558
|
case 'claim-due': {
|
|
496
559
|
if (args.length > 0) exitWithUsage('claim-due (no arguments; run from the project root)');
|
|
497
|
-
const { store } =
|
|
560
|
+
const { store } = curation();
|
|
498
561
|
process.exitCode = emit(store.claimDue(process.cwd()), ({ ref, due }) => [
|
|
499
562
|
ref === null ? 'ref none' : `ref ${ref.ref} ${ref.commit.slice(0, 12)}`,
|
|
500
563
|
...(due.length > 0 ? due.map(entry => `${store.singleLine(entry.anchor_id)} ${entry.reason} ${entry.bytes}`) : ['due none']),
|
|
@@ -504,13 +567,13 @@ try {
|
|
|
504
567
|
|
|
505
568
|
// -------------------------------------------------------------------------
|
|
506
569
|
// claim-queue
|
|
507
|
-
// Claim the
|
|
570
|
+
// Claim the curation queue for this run (D-OWNED-CLAIM, curation-store.cjs).
|
|
508
571
|
// Takes no argument; mints the token itself.
|
|
509
572
|
// stdout: claimed <token> | claimed <token> takeover | busy | none
|
|
510
573
|
// -------------------------------------------------------------------------
|
|
511
574
|
case 'claim-queue': {
|
|
512
575
|
if (args.length > 0) exitWithUsage('claim-queue (no arguments; run from the project root)');
|
|
513
|
-
const result =
|
|
576
|
+
const result = curation().store.claimQueue(process.cwd());
|
|
514
577
|
process.exitCode = emit(result, claim => (claim.state === 'claimed'
|
|
515
578
|
? `claimed ${claim.token}${claim.takeover ? ' takeover' : ''}`
|
|
516
579
|
: claim.state));
|
|
@@ -519,11 +582,11 @@ try {
|
|
|
519
582
|
|
|
520
583
|
// -------------------------------------------------------------------------
|
|
521
584
|
// release-claim <token>
|
|
522
|
-
// Release the claim the token owns (D-OWNED-CLAIM,
|
|
585
|
+
// Release the claim the token owns (D-OWNED-CLAIM, curation-store.cjs).
|
|
523
586
|
// stdout: released | not-owner | gone
|
|
524
587
|
// -------------------------------------------------------------------------
|
|
525
588
|
case 'release-claim': {
|
|
526
|
-
const { store } =
|
|
589
|
+
const { store } = curation();
|
|
527
590
|
if (args.length !== 1 || !store.CLAIM_TOKEN_RE.test(args[0])) {
|
|
528
591
|
exitWithUsage('release-claim <token> (the 16 hex characters claim-queue printed)');
|
|
529
592
|
}
|
|
@@ -531,12 +594,43 @@ try {
|
|
|
531
594
|
break;
|
|
532
595
|
}
|
|
533
596
|
|
|
597
|
+
// -------------------------------------------------------------------------
|
|
598
|
+
// move-ledger <root>
|
|
599
|
+
// Move the project's old ledger folder to the new name, once (moveLedger,
|
|
600
|
+
// curation-move.cjs). Not a curation op: it sends no heartbeat, because a
|
|
601
|
+
// heartbeat would refresh a claim the move has to judge stale. Unlike the
|
|
602
|
+
// curation ops it takes the root as an argument, so a caller outside the project
|
|
603
|
+
// root can name it; the root stays one argv word, never a shell word. The two
|
|
604
|
+
// folder names are the path helpers' own: no argument names them.
|
|
605
|
+
// stdout: none | busy | busy claim | skipped tracked | skipped unknown | moved <mode>,
|
|
606
|
+
// then `backup <name>` when a backup was made, `cause <word>` for `skipped unknown`
|
|
607
|
+
// (one fixed word that says why git did not answer), and one `note <text>` line for
|
|
608
|
+
// each note. `busy claim` means a fresh queue claim holds the move, which can last 15
|
|
609
|
+
// minutes; plain `busy` means a lock does. `skipped tracked` means git tracks files in
|
|
610
|
+
// a curation data folder, `skipped unknown` that git did not answer: nothing changed.
|
|
611
|
+
// Every line is scrubbed here (singleLine):
|
|
612
|
+
// a note may name a file the repository holds, and no line may carry a control
|
|
613
|
+
// character or break into two.
|
|
614
|
+
// -------------------------------------------------------------------------
|
|
615
|
+
case 'move-ledger': {
|
|
616
|
+
if (args.length !== 1) exitWithUsage('move-ledger <root> (the root is an absolute path)');
|
|
617
|
+
const { store } = curation();
|
|
618
|
+
process.exitCode = emit(curationMove().moveLedger(args[0]), moved => [
|
|
619
|
+
moveAnswerWord(moved),
|
|
620
|
+
...(moved.state === 'moved' && moved.backup !== null ? [`backup ${moved.backup}`] : []),
|
|
621
|
+
...(moved.state === 'skipped' && moved.cause !== undefined ? [`cause ${moved.cause}`] : []),
|
|
622
|
+
...moved.notes.map(note => `note ${note}`),
|
|
623
|
+
].map(line => store.singleLine(line)).join('\n'));
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
|
|
534
627
|
default:
|
|
535
628
|
process.stderr.write(`json-helper: unknown operation "${op}"\n`);
|
|
536
629
|
process.exit(1);
|
|
537
630
|
}
|
|
538
631
|
} catch (err) {
|
|
539
|
-
|
|
632
|
+
const reason = err && err.message ? err.message : String(err);
|
|
633
|
+
process.stderr.write(`json-helper error: ${curationModules === null ? reason : curationModules.store.singleLine(reason)}\n`);
|
|
540
634
|
process.exit(1);
|
|
541
635
|
}
|
|
542
636
|
} // end if (require.main === module)
|
|
@@ -69,6 +69,21 @@ json_string_field_file() {
|
|
|
69
69
|
fi
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
# The element count of an array field in a JSON file. Usage: json_array_length_file "/path/to/file.json" "field"
|
|
73
|
+
# Prints the count as digits when the field is an array, and the word `unknown` for an
|
|
74
|
+
# absent field or any other type (a number, string, object, boolean or null), so a
|
|
75
|
+
# caller can tell "none" from "not reported". Prints nothing, and fails, when the file
|
|
76
|
+
# cannot be read or parsed. Silent on stderr and read the same way on both backends
|
|
77
|
+
# (the node path reads the file on stdin, with its stderr redirected first).
|
|
78
|
+
json_array_length_file() {
|
|
79
|
+
local file="$1" field="$2"
|
|
80
|
+
if [ "$_HAS_JQ" = "true" ]; then
|
|
81
|
+
jq -r "(try .$field catch null) | if type == \"array\" then length else \"unknown\" end" "$file" 2>/dev/null
|
|
82
|
+
else
|
|
83
|
+
node "$_JSON_HELPER" array-length "$field" 2>/dev/null < "$file"
|
|
84
|
+
fi
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
# --- Multi-field batched extraction ---
|
|
73
88
|
|
|
74
89
|
# Extract cwd and an arbitrary top-level field from stdin JSON in a single
|
|
@@ -103,9 +118,9 @@ json_extract_cwd_prompt() {
|
|
|
103
118
|
# D-SYSTEMMESSAGE-ENVELOPE: the optional second argument is a message shown to the
|
|
104
119
|
# user, carried in the top-level `systemMessage` key beside (never inside)
|
|
105
120
|
# `hookSpecificOutput`; it does not enter the model's context. With ONE argument the
|
|
106
|
-
# envelope
|
|
107
|
-
#
|
|
108
|
-
#
|
|
121
|
+
# envelope holds the context alone, byte for byte (ensure-proxy and
|
|
122
|
+
# session-start-orchestrator pass one; session-start-context and session-start-memory
|
|
123
|
+
# pass two). With two:
|
|
109
124
|
# - a message and a context -> both keys, hookSpecificOutput first;
|
|
110
125
|
# - a message and no context -> `systemMessage` alone, with NO hookSpecificOutput key;
|
|
111
126
|
# - an empty message -> the one-argument envelope for that context.
|
|
@@ -155,7 +170,7 @@ json_prompt_output() {
|
|
|
155
170
|
# Build pre-compact backup JSON. Usage: json_backup_construct --arg ts X --arg branch Y ...
|
|
156
171
|
json_backup_construct() {
|
|
157
172
|
if [ "$_HAS_JQ" = "true" ]; then
|
|
158
|
-
jq -n "$@" '{timestamp: $ts, trigger: "pre-compact",
|
|
173
|
+
jq -n "$@" '{timestamp: $ts, trigger: "pre-compact", git: {branch: $branch, status: $status, log: $log, diff_stat: $diff}}'
|
|
159
174
|
else
|
|
160
175
|
node "$_JSON_HELPER" backup-construct "$@"
|
|
161
176
|
fi
|