devflow-kit 3.7.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +359 -1
- package/README.md +11 -11
- package/dist/agents/code.md +2 -4
- package/dist/agents/design.md +1 -1
- package/dist/agents/fix.md +6 -7
- package/dist/agents/git.md +1 -1
- package/dist/agents/harden.md +1 -1
- package/dist/agents/refactor.md +2 -4
- package/dist/agents/review.md +4 -4
- package/dist/agents/skim.md +2 -2
- package/dist/agents/triage.md +2 -2
- package/dist/cli/commands/capture.js +1 -1
- package/dist/cli/commands/curation-move-io.js +102 -0
- package/dist/cli/commands/curation-move-step.js +374 -0
- package/dist/cli/commands/{learning.js → curation.js} +302 -99
- package/dist/cli/commands/gate-level-prompts.js +426 -0
- package/dist/cli/commands/gates.js +34 -11
- package/dist/cli/commands/init-seed.js +11 -8
- package/dist/cli/commands/init.js +240 -54
- package/dist/cli/commands/memory.js +2 -2
- package/dist/cli/commands/prompt-io.js +37 -0
- package/dist/cli/commands/uninstall.js +81 -9
- package/dist/cli.js +2 -2
- package/dist/commands/bug-analysis.md +24 -35
- package/dist/commands/code-review.md +32 -36
- package/dist/commands/debug.md +4 -4
- package/dist/commands/dynamic-build.md +25 -24
- package/dist/commands/dynamic-plan.md +4 -4
- package/dist/commands/dynamic-profile.md +4 -4
- package/dist/commands/dynamic-tickets.md +4 -4
- package/dist/commands/explore.md +5 -5
- package/dist/commands/implement.md +52 -81
- package/dist/commands/plan.md +5 -5
- package/dist/commands/release.md +4 -4
- package/dist/commands/research.md +5 -5
- package/dist/commands/resolve.md +28 -30
- package/dist/commands/self-review.md +4 -12
- package/dist/core/assets.js +20 -20
- package/dist/core/curate-tuning-config.js +5 -6
- package/dist/core/curation-move-notice.js +84 -0
- package/dist/core/curation-move-skip.js +36 -0
- package/dist/core/curation-move.js +35 -0
- package/dist/core/curation-queue-cleanup.js +134 -0
- package/dist/core/curation-store.js +98 -0
- package/dist/core/{learning-variants.js → curation-variants.js} +65 -41
- package/dist/core/echo-value.js +11 -5
- package/dist/core/evidence-policy.js +1 -1
- package/dist/core/feature-switch.js +95 -19
- package/dist/core/fs-atomic.js +1 -1
- package/dist/core/gate-level.js +10 -2
- package/dist/core/git.js +16 -14
- package/dist/core/ledger-root.js +82 -22
- package/dist/core/linked-path.js +4 -4
- package/dist/core/manifest-lock.js +238 -0
- package/dist/core/manifest.js +58 -14
- package/dist/core/migrations.js +1 -13
- package/dist/core/observations.js +2 -2
- package/dist/core/plugins.js +15 -15
- package/dist/core/pool.js +26 -0
- package/dist/core/project-paths.js +103 -34
- package/dist/core/queue-drain.js +2 -2
- package/dist/core/tracked-areas.js +35 -0
- package/dist/core/tracker.js +1 -1
- package/dist/{learning-off → curation-off}/agents/code.md +2 -4
- package/dist/{learning-off → curation-off}/agents/fix.md +6 -7
- package/dist/{learning-off → curation-off}/agents/refactor.md +2 -4
- package/dist/{learning-off → curation-off}/agents/review.md +3 -3
- package/dist/{learning-off → curation-off}/agents/triage.md +1 -1
- package/dist/{learning-off → curation-off}/commands/bug-analysis.md +20 -31
- package/dist/{learning-off → curation-off}/commands/code-review.md +29 -33
- package/dist/{learning-off → curation-off}/commands/debug.md +1 -1
- package/dist/{learning-off → curation-off}/commands/dynamic-build.md +22 -21
- package/dist/{learning-off → curation-off}/commands/explore.md +1 -1
- package/dist/{learning-off → curation-off}/commands/implement.md +49 -78
- package/dist/{learning-off → curation-off}/commands/plan.md +1 -1
- package/dist/{learning-off → curation-off}/commands/resolve.md +25 -27
- package/dist/{learning-off → curation-off}/commands/self-review.md +1 -9
- package/dist/hud/components/{learning-counts.js → curation-counts.js} +36 -17
- package/dist/hud/config.js +1 -1
- package/dist/hud/index.js +6 -6
- package/dist/hud/render.js +3 -3
- package/dist/skills/git/references/pr/validate-branch.md +8 -5
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +13 -5
- package/dist/targets/claude-code/{learning-install.js → curation-install.js} +85 -65
- package/dist/targets/claude-code/installer.js +15 -15
- package/dist/targets/claude-code/language-stamp.js +5 -5
- package/dist/targets/claude-code/post-install.js +2 -2
- package/dist/targets/claude-code/tracker-servers.js +1 -1
- package/package.json +3 -2
- package/src/assets/agents/_partials/_code.mds +11 -11
- package/src/assets/agents/code.mds +2 -2
- package/src/assets/agents/curate.md +8 -8
- package/src/assets/agents/debug.mds +6 -6
- package/src/assets/agents/design.mds +18 -18
- package/src/assets/agents/fix.mds +6 -5
- package/src/assets/agents/git.mds +1 -1
- package/src/assets/agents/harden.mds +7 -7
- package/src/assets/agents/knowledge.mds +23 -23
- package/src/assets/agents/refactor.mds +2 -2
- package/src/assets/agents/research.mds +15 -15
- package/src/assets/agents/review.mds +43 -43
- package/src/assets/agents/skim.mds +11 -11
- package/src/assets/agents/triage.mds +22 -22
- package/src/assets/agents/validate.md +13 -26
- package/src/assets/commands/_partials/_decisions.mds +13 -13
- package/src/assets/commands/_partials/_engine.mds +8 -8
- package/src/assets/commands/_partials/_knowledge.mds +29 -15
- package/src/assets/commands/_partials/_preamble.mds +4 -4
- package/src/assets/commands/_partials/_settings.mds +2 -2
- package/src/assets/commands/_partials/_wave.mds +7 -7
- package/src/assets/commands/bug-analysis.mds +37 -48
- package/src/assets/commands/code-review.mds +46 -50
- package/src/assets/commands/debug.mds +14 -14
- package/src/assets/commands/dynamic-build.mds +38 -37
- package/src/assets/commands/dynamic-plan.mds +22 -22
- package/src/assets/commands/dynamic-profile.mds +2 -2
- package/src/assets/commands/dynamic-tickets.mds +8 -8
- package/src/assets/commands/explore.mds +20 -20
- package/src/assets/commands/implement.mds +77 -98
- package/src/assets/commands/plan.mds +27 -27
- package/src/assets/commands/release.mds +12 -12
- package/src/assets/commands/research.mds +19 -19
- package/src/assets/commands/resolve.mds +56 -50
- package/src/assets/commands/self-review.mds +20 -20
- package/src/assets/mds/git/_ops.mds +3 -1
- package/src/assets/mds/git/_ops_pr.mds +1 -1
- package/src/assets/mds/git/_ops_steps.mds +10 -4
- package/src/assets/mds/git/_pr.mds +7 -4
- package/src/assets/rules/quality.md +1 -0
- package/src/assets/scripts/hooks/background-memory-update +2 -2
- package/src/assets/scripts/hooks/capture-prompt +12 -12
- package/src/assets/scripts/hooks/capture-question +11 -11
- package/src/assets/scripts/hooks/capture-turn +12 -12
- package/src/assets/scripts/hooks/{learning-lock → curation-lock} +2 -2
- package/src/assets/scripts/hooks/curation-move +293 -0
- package/src/assets/scripts/hooks/ensure-devflow-init +45 -13
- package/src/assets/scripts/hooks/ensure-proxy +4 -4
- package/src/assets/scripts/hooks/ensure-root-gitignore +4 -4
- package/src/assets/scripts/hooks/git-marker +81 -0
- package/src/assets/scripts/hooks/json-helper.cjs +134 -51
- package/src/assets/scripts/hooks/json-parse +1 -1
- package/src/assets/scripts/hooks/lib/curation-move.cjs +1582 -0
- package/src/assets/scripts/hooks/lib/{learning-store.cjs → curation-store.cjs} +242 -118
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +3 -3
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +24 -7
- package/src/assets/scripts/hooks/lib/project-paths.cjs +132 -39
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +34 -22
- package/src/assets/scripts/hooks/lib/tracked-areas.cjs +259 -0
- package/src/assets/scripts/hooks/memory-worker +1 -1
- package/src/assets/scripts/hooks/pre-compact-memory +6 -16
- package/src/assets/scripts/hooks/queue-append +228 -103
- package/src/assets/scripts/hooks/resolve-project-root +2 -2
- package/src/assets/scripts/hooks/session-start-context +143 -69
- package/src/assets/scripts/hooks/session-start-memory +3 -43
- package/src/assets/scripts/lib/project-config.cjs +12 -5
- package/src/assets/scripts/resolve-base.cjs +1043 -0
- package/src/assets/scripts/resolve-settings.cjs +42 -18
- package/src/assets/skills/agent-references/SKILL.md +5 -2
- package/src/assets/skills/agent-references/references/commands/implement-levels.md +27 -0
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +6 -4
- package/src/assets/skills/agent-references/references/commands/resolve-levels.md +24 -0
- package/src/assets/skills/apply-decisions/SKILL.md +1 -1
- package/src/assets/skills/docs-framework/SKILL.md +7 -7
- package/src/assets/skills/docs-framework/references/patterns.md +3 -4
- package/src/assets/skills/quality-gates/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/references/roots.md +2 -2
- package/templates/lint-pack/.lint-pack.conf +86 -0
- package/templates/lint-pack/README.md +190 -0
- package/templates/lint-pack/lint-pack.sh +905 -0
- package/dist/core/learning-queue-cleanup.js +0 -27
- package/dist/core/learning-store.js +0 -61
- /package/dist/{learning-off → curation-off}/agents/debug.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/design.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/harden.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/knowledge.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/research.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/skim.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-plan.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-profile.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-tickets.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/release.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/research.md +0 -0
|
@@ -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,10 +2,10 @@
|
|
|
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
|
//
|
|
@@ -37,13 +37,17 @@
|
|
|
37
37
|
// show <anchor|obs_id> Read-only: print one entry as pretty JSON
|
|
38
38
|
// claim-due Hand out the entries due for maintenance, leased
|
|
39
39
|
// for a day, after the ref their claims are checked at
|
|
40
|
-
// claim-queue Claim the
|
|
40
|
+
// claim-queue Claim the curation queue for this run; prints
|
|
41
41
|
// claimed <token>[ takeover] | busy | none
|
|
42
42
|
// release-claim <token> Release the claim the token owns; prints
|
|
43
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
|
|
44
48
|
//
|
|
45
|
-
// Every
|
|
46
|
-
// 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).
|
|
47
51
|
|
|
48
52
|
'use strict';
|
|
49
53
|
|
|
@@ -53,21 +57,35 @@ const { constants: { MAX_STRING_LENGTH } } = require('buffer');
|
|
|
53
57
|
const op = process.argv[2];
|
|
54
58
|
const args = process.argv.slice(3);
|
|
55
59
|
|
|
56
|
-
/** The
|
|
57
|
-
let
|
|
60
|
+
/** The curation modules, once loaded; see curation(). */
|
|
61
|
+
let curationModules = null;
|
|
58
62
|
|
|
59
63
|
/**
|
|
60
|
-
* The
|
|
64
|
+
* The curation store, loaded on first use and memoized. The generic ops never
|
|
61
65
|
* call it, so a hook that falls back from jq to node never pays for loading it,
|
|
62
66
|
* and the store loads the renderer only when an op renders.
|
|
63
67
|
*
|
|
64
68
|
* @returns {{ store: object }}
|
|
65
69
|
*/
|
|
66
|
-
function
|
|
67
|
-
if (
|
|
68
|
-
|
|
70
|
+
function curation() {
|
|
71
|
+
if (curationModules === null) {
|
|
72
|
+
curationModules = { store: require('./lib/curation-store.cjs') };
|
|
69
73
|
}
|
|
70
|
-
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;
|
|
71
89
|
}
|
|
72
90
|
|
|
73
91
|
/** The bytes each read of stdin asks for. */
|
|
@@ -84,7 +102,7 @@ const STDIN_WAIT_CELL = new Int32Array(new SharedArrayBuffer(4));
|
|
|
84
102
|
|
|
85
103
|
/**
|
|
86
104
|
* Read stdin to its end, keeping at most `maxBytes`. Every op that reads stdin
|
|
87
|
-
* 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.
|
|
88
106
|
* Never throws: a failure is a refusal.
|
|
89
107
|
*
|
|
90
108
|
* D-STDIN-FD0: stdin is read from file descriptor 0 itself, in reads repeated
|
|
@@ -168,14 +186,16 @@ function parseArgs(argList) {
|
|
|
168
186
|
}
|
|
169
187
|
|
|
170
188
|
// ---------------------------------------------------------------------------
|
|
171
|
-
//
|
|
189
|
+
// Curation-op adapter
|
|
172
190
|
// ---------------------------------------------------------------------------
|
|
173
191
|
|
|
174
192
|
/**
|
|
175
|
-
* Print a
|
|
193
|
+
* Print a curation op's Result: `format(value)` and a newline on stdout, or the
|
|
176
194
|
* error message and a newline on stderr. Returns the exit code for the op to set
|
|
177
195
|
* as process.exitCode, so the process exits once, after the op has returned and
|
|
178
|
-
* 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.
|
|
179
199
|
*
|
|
180
200
|
* @param {{ ok: true, value: unknown } | { ok: false, error: { message: string } }} result
|
|
181
201
|
* @param {(value: any) => string} format - the stdout text for the value
|
|
@@ -186,10 +206,43 @@ function emit(result, format) {
|
|
|
186
206
|
process.stdout.write(`${format(result.value)}\n`);
|
|
187
207
|
return 0;
|
|
188
208
|
}
|
|
189
|
-
process.stderr.write(`${result.error.message}\n`);
|
|
209
|
+
process.stderr.write(`${refusalText(result.error.message)}\n`);
|
|
190
210
|
return 1;
|
|
191
211
|
}
|
|
192
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
|
+
|
|
193
246
|
/**
|
|
194
247
|
* Refuse a malformed command line: print `<op>: usage: <usage>` on stderr and exit 1,
|
|
195
248
|
* before the op takes any lock.
|
|
@@ -202,11 +255,11 @@ function exitWithUsage(usage) {
|
|
|
202
255
|
process.exit(1);
|
|
203
256
|
}
|
|
204
257
|
|
|
205
|
-
/** 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. */
|
|
206
259
|
const STDIN_JSON_MAX_BYTES = 64 * 1024;
|
|
207
260
|
|
|
208
261
|
/**
|
|
209
|
-
* A
|
|
262
|
+
* A curation op's stdin as one JSON object: the one way text reaches a curation
|
|
210
263
|
* op, so no field of it ever passes through argv or a shell word. Never throws.
|
|
211
264
|
*
|
|
212
265
|
* @param {string} opName - for the message
|
|
@@ -235,30 +288,30 @@ const PUT_MODES = new Map([['--create', 'create'], ['--update', 'update'], ['--r
|
|
|
235
288
|
const ENTRY_TYPES = new Set(['decision', 'pitfall']);
|
|
236
289
|
|
|
237
290
|
/**
|
|
238
|
-
* The
|
|
291
|
+
* The curation ops whose run sends the claim heartbeat first (D-OWNED-CLAIM).
|
|
239
292
|
* claim-queue and release-claim manage the claim themselves, and the generic ops
|
|
240
|
-
* never touch it. A new
|
|
293
|
+
* never touch it. A new curation op joins this set.
|
|
241
294
|
*/
|
|
242
|
-
const
|
|
295
|
+
const CURATION_OPS = new Set([
|
|
243
296
|
'assign-anchor', 'retire-anchor', 'restore-anchor', 'refresh-anchor', 'rotate-observations',
|
|
244
297
|
'put-observation', 'list', 'show', 'claim-due',
|
|
245
298
|
]);
|
|
246
299
|
|
|
247
300
|
/** Send the claim heartbeat; a failure is reported on stderr and never stops the op. */
|
|
248
301
|
function heartbeat(root) {
|
|
249
|
-
const beat =
|
|
302
|
+
const beat = curation().store.touchClaim(root);
|
|
250
303
|
if (!beat.ok) process.stderr.write(`${beat.error.message}\n`);
|
|
251
304
|
}
|
|
252
305
|
|
|
253
|
-
// The
|
|
306
|
+
// The curation ops run from the project root and take no path to a curation file:
|
|
254
307
|
// each builds its paths from the current directory. Every op that writes takes the
|
|
255
|
-
// store's one
|
|
256
|
-
// 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)
|
|
257
310
|
// or when it, or .devflow, is a symbolic link (D-NO-LINKED-TREE).
|
|
258
311
|
// A locked body returns its Result; emit prints it once the lock is released.
|
|
259
312
|
if (require.main === module) {
|
|
260
313
|
try {
|
|
261
|
-
if (
|
|
314
|
+
if (CURATION_OPS.has(op)) heartbeat(process.cwd());
|
|
262
315
|
switch (op) {
|
|
263
316
|
case 'get-field': {
|
|
264
317
|
const input = JSON.parse(readStdin());
|
|
@@ -338,7 +391,6 @@ try {
|
|
|
338
391
|
console.log(JSON.stringify({
|
|
339
392
|
timestamp: data.ts || '',
|
|
340
393
|
trigger: 'pre-compact',
|
|
341
|
-
memory_snapshot: data.memory || '',
|
|
342
394
|
git: {
|
|
343
395
|
branch: data.branch || '',
|
|
344
396
|
status: data.status || '',
|
|
@@ -353,12 +405,12 @@ try {
|
|
|
353
405
|
// assign-anchor <decision|pitfall> <obs_id>
|
|
354
406
|
// Promote a v2 observation no ledger row carries to the next entry of its
|
|
355
407
|
// type, skipping each number a tracked file cites (assignAnchor,
|
|
356
|
-
//
|
|
408
|
+
// curation-store.cjs: D-LEDGER-REGISTRY, D-E4-SKIP). It never writes the log.
|
|
357
409
|
// stdout: the anchor; stderr: `assign-anchor: skipped <anchor>, cited in
|
|
358
410
|
// <path>:<line>` for each number skipped
|
|
359
411
|
// -------------------------------------------------------------------------
|
|
360
412
|
case 'assign-anchor': {
|
|
361
|
-
const { store } =
|
|
413
|
+
const { store } = curation();
|
|
362
414
|
if (args.length !== 2 || !ENTRY_TYPES.has(args[0]) || !store.OBS_ID_RE.test(args[1])) {
|
|
363
415
|
exitWithUsage('assign-anchor <decision|pitfall> <obs_id> (run from the project root)');
|
|
364
416
|
}
|
|
@@ -377,12 +429,12 @@ try {
|
|
|
377
429
|
// Make an active entry inactive with the one JSON object on stdin its status
|
|
378
430
|
// takes: { reason } for Retired and Deprecated, { by } for Superseded, and
|
|
379
431
|
// { at, quote } for Encoded, the quote checked at the verify ref
|
|
380
|
-
// (retireAnchor,
|
|
432
|
+
// (retireAnchor, curation-store.cjs: D-ENCODED-QUOTE).
|
|
381
433
|
// stdout: the new status in lower case and the anchor, then `repointed
|
|
382
434
|
// <anchor>` for each entry re-pointed to the successor
|
|
383
435
|
// -------------------------------------------------------------------------
|
|
384
436
|
case 'retire-anchor': {
|
|
385
|
-
const { store } =
|
|
437
|
+
const { store } = curation();
|
|
386
438
|
if (args.length !== 2 || !store.ANCHOR_ID_RE.test(args[0]) || !store.INACTIVE_STATUSES.includes(args[1])) {
|
|
387
439
|
exitWithUsage('retire-anchor <anchor> <Encoded|Superseded|Retired|Deprecated> (one JSON object on stdin; run from the project root)');
|
|
388
440
|
}
|
|
@@ -400,11 +452,11 @@ try {
|
|
|
400
452
|
// Make an inactive entry active again, its notes, last_verified and
|
|
401
453
|
// last_attempt cleared so it is due for maintenance again, ordered after
|
|
402
454
|
// integrity problems and legacy entries, or among them when it is one
|
|
403
|
-
// (restoreAnchor,
|
|
455
|
+
// (restoreAnchor, curation-store.cjs: D-DUE-ORDER).
|
|
404
456
|
// stdout: restored <anchor>
|
|
405
457
|
// -------------------------------------------------------------------------
|
|
406
458
|
case 'restore-anchor': {
|
|
407
|
-
const { store } =
|
|
459
|
+
const { store } = curation();
|
|
408
460
|
if (args.length !== 1 || !store.ANCHOR_ID_RE.test(args[0])) {
|
|
409
461
|
exitWithUsage('restore-anchor <anchor> (run from the project root)');
|
|
410
462
|
}
|
|
@@ -415,12 +467,12 @@ try {
|
|
|
415
467
|
// -------------------------------------------------------------------------
|
|
416
468
|
// refresh-anchor <anchor> [<anchor>...] [--verified]
|
|
417
469
|
// Re-project active v2 entries from their log rows, or under --verified stamp
|
|
418
|
-
// them verified today; all or nothing (refreshAnchors,
|
|
470
|
+
// them verified today; all or nothing (refreshAnchors, curation-store.cjs).
|
|
419
471
|
// stdout: `reprojected <anchor>` or `unchanged <anchor>` per anchor, or
|
|
420
472
|
// `verified <anchor>` under --verified, in the order given
|
|
421
473
|
// -------------------------------------------------------------------------
|
|
422
474
|
case 'refresh-anchor': {
|
|
423
|
-
const { store } =
|
|
475
|
+
const { store } = curation();
|
|
424
476
|
const verifiedFlags = args.filter(arg => arg === '--verified').length;
|
|
425
477
|
const anchors = args.filter(arg => arg !== '--verified');
|
|
426
478
|
if (verifiedFlags > 1 || anchors.length === 0 || !anchors.every(arg => store.ANCHOR_ID_RE.test(arg))) {
|
|
@@ -435,13 +487,13 @@ try {
|
|
|
435
487
|
// rotate-observations
|
|
436
488
|
// Archive the log rows no ledger row carries once 30 days have passed since
|
|
437
489
|
// their last activity, and delete the usage telemetry's leftovers
|
|
438
|
-
// (D-ROTATE-UNREFERENCED,
|
|
490
|
+
// (D-ROTATE-UNREFERENCED, curation-store.cjs). Takes no argument: the log and
|
|
439
491
|
// the archive are the project root's.
|
|
440
492
|
// stdout: rotated <N> observations
|
|
441
493
|
// -------------------------------------------------------------------------
|
|
442
494
|
case 'rotate-observations': {
|
|
443
495
|
if (args.length > 0) exitWithUsage('rotate-observations (no arguments; run from the project root)');
|
|
444
|
-
const result =
|
|
496
|
+
const result = curation().store.rotateObservations(process.cwd());
|
|
445
497
|
process.exitCode = emit(result, ({ rotated }) => `rotated ${rotated} observations`);
|
|
446
498
|
break;
|
|
447
499
|
}
|
|
@@ -449,7 +501,7 @@ try {
|
|
|
449
501
|
// -------------------------------------------------------------------------
|
|
450
502
|
// put-observation --create|--update|--reinforce
|
|
451
503
|
// Store one observation from the JSON object on stdin (D-PUT-NOT-MERGE,
|
|
452
|
-
// D-PUT-REPROJECTS,
|
|
504
|
+
// D-PUT-REPROJECTS, curation-store.cjs). Exactly one mode flag; no other argv.
|
|
453
505
|
// stdout: created <id> | updated <id> | unchanged <id> | reinforced <id> <n>,
|
|
454
506
|
// then one `reprojected <anchor>` line per entry re-projected
|
|
455
507
|
// -------------------------------------------------------------------------
|
|
@@ -459,7 +511,7 @@ try {
|
|
|
459
511
|
exitWithUsage('put-observation --create|--update|--reinforce (one JSON object on stdin; run from the project root)');
|
|
460
512
|
}
|
|
461
513
|
const input = readStdinJson('put-observation');
|
|
462
|
-
const result = input.ok ?
|
|
514
|
+
const result = input.ok ? curation().store.putObservation(process.cwd(), mode, input.value) : input;
|
|
463
515
|
process.exitCode = emit(result, put => [
|
|
464
516
|
put.outcome === 'reinforced' ? `reinforced ${put.id} ${put.observations}` : `${put.outcome} ${put.id}`,
|
|
465
517
|
...put.reprojected.map(anchorId => `reprojected ${anchorId}`),
|
|
@@ -470,25 +522,25 @@ try {
|
|
|
470
522
|
// -------------------------------------------------------------------------
|
|
471
523
|
// list
|
|
472
524
|
// Print the ledger and the log by section, read-only (readListing and
|
|
473
|
-
// formatListing,
|
|
525
|
+
// formatListing, curation-store.cjs). Takes no argument.
|
|
474
526
|
// stdout: the ACTIVE, INACTIVE, OBSERVATIONS and INTEGRITY sections, then
|
|
475
527
|
// MALFORMED when lines were skipped
|
|
476
528
|
// -------------------------------------------------------------------------
|
|
477
529
|
case 'list': {
|
|
478
530
|
if (args.length > 0) exitWithUsage('list (no arguments; run from the project root)');
|
|
479
|
-
const { store } =
|
|
531
|
+
const { store } = curation();
|
|
480
532
|
process.exitCode = emit(store.readListing(process.cwd()), store.formatListing);
|
|
481
533
|
break;
|
|
482
534
|
}
|
|
483
535
|
|
|
484
536
|
// -------------------------------------------------------------------------
|
|
485
537
|
// show <anchor|obs_id>
|
|
486
|
-
// Print one entry, read-only (showByKey,
|
|
538
|
+
// Print one entry, read-only (showByKey, curation-store.cjs).
|
|
487
539
|
// stdout: pretty JSON { key, ledger, log, history_versions, flags }, plus
|
|
488
540
|
// malformed when lines were skipped
|
|
489
541
|
// -------------------------------------------------------------------------
|
|
490
542
|
case 'show': {
|
|
491
|
-
const { store } =
|
|
543
|
+
const { store } = curation();
|
|
492
544
|
if (args.length !== 1 || !(store.ANCHOR_ID_RE.test(args[0]) || store.OBS_ID_RE.test(args[0]))) {
|
|
493
545
|
exitWithUsage('show <anchor|obs_id> (run from the project root)');
|
|
494
546
|
}
|
|
@@ -499,13 +551,13 @@ try {
|
|
|
499
551
|
// -------------------------------------------------------------------------
|
|
500
552
|
// claim-due
|
|
501
553
|
// Hand out the entries due for maintenance and lease each for a day
|
|
502
|
-
// (claimDue,
|
|
554
|
+
// (claimDue, curation-store.cjs: D-DUE-ORDER, D-VERIFY-REF). Takes no argument.
|
|
503
555
|
// stdout: ref <origin/HEAD|HEAD> <sha12>, or ref none; then one
|
|
504
556
|
// `<anchor> <reason> <bytes>` line per entry handed out, or due none
|
|
505
557
|
// -------------------------------------------------------------------------
|
|
506
558
|
case 'claim-due': {
|
|
507
559
|
if (args.length > 0) exitWithUsage('claim-due (no arguments; run from the project root)');
|
|
508
|
-
const { store } =
|
|
560
|
+
const { store } = curation();
|
|
509
561
|
process.exitCode = emit(store.claimDue(process.cwd()), ({ ref, due }) => [
|
|
510
562
|
ref === null ? 'ref none' : `ref ${ref.ref} ${ref.commit.slice(0, 12)}`,
|
|
511
563
|
...(due.length > 0 ? due.map(entry => `${store.singleLine(entry.anchor_id)} ${entry.reason} ${entry.bytes}`) : ['due none']),
|
|
@@ -515,13 +567,13 @@ try {
|
|
|
515
567
|
|
|
516
568
|
// -------------------------------------------------------------------------
|
|
517
569
|
// claim-queue
|
|
518
|
-
// Claim the
|
|
570
|
+
// Claim the curation queue for this run (D-OWNED-CLAIM, curation-store.cjs).
|
|
519
571
|
// Takes no argument; mints the token itself.
|
|
520
572
|
// stdout: claimed <token> | claimed <token> takeover | busy | none
|
|
521
573
|
// -------------------------------------------------------------------------
|
|
522
574
|
case 'claim-queue': {
|
|
523
575
|
if (args.length > 0) exitWithUsage('claim-queue (no arguments; run from the project root)');
|
|
524
|
-
const result =
|
|
576
|
+
const result = curation().store.claimQueue(process.cwd());
|
|
525
577
|
process.exitCode = emit(result, claim => (claim.state === 'claimed'
|
|
526
578
|
? `claimed ${claim.token}${claim.takeover ? ' takeover' : ''}`
|
|
527
579
|
: claim.state));
|
|
@@ -530,11 +582,11 @@ try {
|
|
|
530
582
|
|
|
531
583
|
// -------------------------------------------------------------------------
|
|
532
584
|
// release-claim <token>
|
|
533
|
-
// Release the claim the token owns (D-OWNED-CLAIM,
|
|
585
|
+
// Release the claim the token owns (D-OWNED-CLAIM, curation-store.cjs).
|
|
534
586
|
// stdout: released | not-owner | gone
|
|
535
587
|
// -------------------------------------------------------------------------
|
|
536
588
|
case 'release-claim': {
|
|
537
|
-
const { store } =
|
|
589
|
+
const { store } = curation();
|
|
538
590
|
if (args.length !== 1 || !store.CLAIM_TOKEN_RE.test(args[0])) {
|
|
539
591
|
exitWithUsage('release-claim <token> (the 16 hex characters claim-queue printed)');
|
|
540
592
|
}
|
|
@@ -542,12 +594,43 @@ try {
|
|
|
542
594
|
break;
|
|
543
595
|
}
|
|
544
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
|
+
|
|
545
627
|
default:
|
|
546
628
|
process.stderr.write(`json-helper: unknown operation "${op}"\n`);
|
|
547
629
|
process.exit(1);
|
|
548
630
|
}
|
|
549
631
|
} catch (err) {
|
|
550
|
-
|
|
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`);
|
|
551
634
|
process.exit(1);
|
|
552
635
|
}
|
|
553
636
|
} // end if (require.main === module)
|
|
@@ -170,7 +170,7 @@ json_prompt_output() {
|
|
|
170
170
|
# Build pre-compact backup JSON. Usage: json_backup_construct --arg ts X --arg branch Y ...
|
|
171
171
|
json_backup_construct() {
|
|
172
172
|
if [ "$_HAS_JQ" = "true" ]; then
|
|
173
|
-
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}}'
|
|
174
174
|
else
|
|
175
175
|
node "$_JSON_HELPER" backup-construct "$@"
|
|
176
176
|
fi
|