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.
Files changed (197) hide show
  1. package/CHANGELOG.md +438 -1
  2. package/README.md +15 -11
  3. package/dist/agents/code.md +9 -15
  4. package/dist/agents/design.md +1 -1
  5. package/dist/agents/fix.md +11 -13
  6. package/dist/agents/git.md +1 -1
  7. package/dist/agents/harden.md +1 -1
  8. package/dist/agents/refactor.md +7 -10
  9. package/dist/agents/review.md +6 -4
  10. package/dist/agents/skim.md +2 -2
  11. package/dist/agents/triage.md +2 -2
  12. package/dist/cli/commands/capture.js +1 -1
  13. package/dist/cli/commands/curation-move-io.js +102 -0
  14. package/dist/cli/commands/curation-move-step.js +374 -0
  15. package/dist/cli/commands/{learning.js → curation.js} +302 -99
  16. package/dist/cli/commands/gate-level-prompts.js +426 -0
  17. package/dist/cli/commands/gates.js +299 -0
  18. package/dist/cli/commands/init-seed.js +16 -5
  19. package/dist/cli/commands/init.js +263 -54
  20. package/dist/cli/commands/memory.js +20 -2
  21. package/dist/cli/commands/prompt-io.js +37 -0
  22. package/dist/cli/commands/uninstall.js +81 -9
  23. package/dist/cli.js +4 -2
  24. package/dist/commands/bug-analysis.md +25 -36
  25. package/dist/commands/code-review.md +77 -73
  26. package/dist/commands/debug.md +5 -5
  27. package/dist/commands/dynamic-build.md +76 -83
  28. package/dist/commands/dynamic-plan.md +5 -5
  29. package/dist/commands/dynamic-profile.md +5 -5
  30. package/dist/commands/dynamic-tickets.md +5 -5
  31. package/dist/commands/explore.md +6 -6
  32. package/dist/commands/implement.md +68 -102
  33. package/dist/commands/plan.md +19 -29
  34. package/dist/commands/release.md +5 -5
  35. package/dist/commands/research.md +6 -6
  36. package/dist/commands/resolve.md +29 -31
  37. package/dist/commands/self-review.md +5 -13
  38. package/dist/core/assets.js +20 -20
  39. package/dist/core/curate-tuning-config.js +5 -6
  40. package/dist/core/curation-move-notice.js +84 -0
  41. package/dist/core/curation-move-skip.js +36 -0
  42. package/dist/core/curation-move.js +35 -0
  43. package/dist/core/curation-queue-cleanup.js +134 -0
  44. package/dist/core/curation-store.js +98 -0
  45. package/dist/core/{learning-variants.js → curation-variants.js} +65 -41
  46. package/dist/core/echo-value.js +48 -0
  47. package/dist/core/evidence-policy.js +18 -6
  48. package/dist/core/feature-switch.js +95 -19
  49. package/dist/core/fs-atomic.js +1 -1
  50. package/dist/core/gate-level.js +221 -0
  51. package/dist/core/git.js +16 -14
  52. package/dist/core/ledger-root.js +82 -22
  53. package/dist/core/linked-path.js +4 -4
  54. package/dist/core/manifest-lock.js +238 -0
  55. package/dist/core/manifest.js +84 -24
  56. package/dist/core/migrations.js +1 -13
  57. package/dist/core/model-discovery.js +7 -1
  58. package/dist/core/observations.js +2 -2
  59. package/dist/core/plugins.js +15 -15
  60. package/dist/core/pool.js +26 -0
  61. package/dist/core/project-paths.js +103 -34
  62. package/dist/core/queue-drain.js +2 -2
  63. package/dist/core/tracked-areas.js +35 -0
  64. package/dist/core/tracker.js +6 -34
  65. package/dist/{learning-off → curation-off}/agents/code.md +9 -15
  66. package/dist/{learning-off → curation-off}/agents/fix.md +11 -13
  67. package/dist/{learning-off → curation-off}/agents/refactor.md +7 -10
  68. package/dist/{learning-off → curation-off}/agents/review.md +5 -3
  69. package/dist/{learning-off → curation-off}/agents/triage.md +1 -1
  70. package/dist/{learning-off → curation-off}/commands/bug-analysis.md +20 -31
  71. package/dist/{learning-off → curation-off}/commands/code-review.md +74 -70
  72. package/dist/{learning-off → curation-off}/commands/debug.md +2 -2
  73. package/dist/{learning-off → curation-off}/commands/dynamic-build.md +73 -80
  74. package/dist/{learning-off → curation-off}/commands/explore.md +2 -2
  75. package/dist/{learning-off → curation-off}/commands/implement.md +65 -99
  76. package/dist/{learning-off → curation-off}/commands/plan.md +15 -25
  77. package/dist/{learning-off → curation-off}/commands/resolve.md +26 -28
  78. package/dist/{learning-off → curation-off}/commands/self-review.md +2 -10
  79. package/dist/hud/components/{learning-counts.js → curation-counts.js} +36 -17
  80. package/dist/hud/config.js +1 -1
  81. package/dist/hud/index.js +6 -6
  82. package/dist/hud/render.js +3 -3
  83. package/dist/skills/git/references/pr/validate-branch.md +8 -5
  84. package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +20 -14
  85. package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +20 -20
  86. package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +20 -20
  87. package/dist/targets/claude-code/{learning-install.js → curation-install.js} +99 -67
  88. package/dist/targets/claude-code/installer.js +15 -15
  89. package/dist/targets/claude-code/language-stamp.js +5 -5
  90. package/dist/targets/claude-code/post-install.js +2 -2
  91. package/dist/targets/claude-code/tracker-servers.js +1 -1
  92. package/package.json +3 -2
  93. package/src/assets/agents/_partials/_code.mds +16 -17
  94. package/src/assets/agents/code.mds +4 -7
  95. package/src/assets/agents/curate.md +12 -14
  96. package/src/assets/agents/debug.mds +6 -6
  97. package/src/assets/agents/design.mds +18 -18
  98. package/src/assets/agents/fix.mds +6 -5
  99. package/src/assets/agents/git.mds +1 -1
  100. package/src/assets/agents/harden.mds +7 -7
  101. package/src/assets/agents/knowledge.mds +23 -23
  102. package/src/assets/agents/refactor.mds +2 -2
  103. package/src/assets/agents/research.mds +15 -15
  104. package/src/assets/agents/review.mds +45 -43
  105. package/src/assets/agents/skim.mds +11 -11
  106. package/src/assets/agents/synthesize.md +2 -2
  107. package/src/assets/agents/triage.mds +22 -22
  108. package/src/assets/agents/validate.md +13 -26
  109. package/src/assets/commands/_partials/_decisions.mds +13 -13
  110. package/src/assets/commands/_partials/_engine.mds +8 -8
  111. package/src/assets/commands/_partials/_knowledge.mds +29 -15
  112. package/src/assets/commands/_partials/_preamble.mds +4 -4
  113. package/src/assets/commands/_partials/_settings.mds +3 -3
  114. package/src/assets/commands/_partials/_wave.mds +17 -24
  115. package/src/assets/commands/bug-analysis.mds +37 -48
  116. package/src/assets/commands/code-review.mds +90 -86
  117. package/src/assets/commands/debug.mds +14 -14
  118. package/src/assets/commands/dynamic-build.mds +78 -78
  119. package/src/assets/commands/dynamic-plan.mds +22 -22
  120. package/src/assets/commands/dynamic-profile.mds +2 -2
  121. package/src/assets/commands/dynamic-tickets.mds +8 -8
  122. package/src/assets/commands/explore.mds +20 -20
  123. package/src/assets/commands/implement.mds +92 -118
  124. package/src/assets/commands/plan.mds +41 -51
  125. package/src/assets/commands/release.mds +12 -12
  126. package/src/assets/commands/research.mds +19 -19
  127. package/src/assets/commands/resolve.mds +56 -50
  128. package/src/assets/commands/self-review.mds +20 -20
  129. package/src/assets/mds/git/_ops.mds +3 -1
  130. package/src/assets/mds/git/_ops_pr.mds +1 -1
  131. package/src/assets/mds/git/_ops_steps.mds +13 -7
  132. package/src/assets/mds/git/_pr.mds +7 -4
  133. package/src/assets/mds/tracker/_common.mds +23 -12
  134. package/src/assets/mds/tracker/_github.mds +2 -9
  135. package/src/assets/mds/tracker/_jira.mds +2 -15
  136. package/src/assets/mds/tracker/_linear.mds +2 -15
  137. package/src/assets/rules/quality.md +1 -0
  138. package/src/assets/scripts/ci-wait.cjs +22 -4
  139. package/src/assets/scripts/claude-md-audit.cjs +66 -51
  140. package/src/assets/scripts/hooks/background-memory-update +406 -97
  141. package/src/assets/scripts/hooks/capture-prompt +12 -12
  142. package/src/assets/scripts/hooks/capture-question +11 -11
  143. package/src/assets/scripts/hooks/capture-turn +12 -12
  144. package/src/assets/scripts/hooks/{learning-lock → curation-lock} +2 -2
  145. package/src/assets/scripts/hooks/curation-move +293 -0
  146. package/src/assets/scripts/hooks/ensure-devflow-init +45 -13
  147. package/src/assets/scripts/hooks/ensure-proxy +4 -4
  148. package/src/assets/scripts/hooks/ensure-root-gitignore +4 -4
  149. package/src/assets/scripts/hooks/git-marker +81 -0
  150. package/src/assets/scripts/hooks/json-helper.cjs +145 -51
  151. package/src/assets/scripts/hooks/json-parse +19 -4
  152. package/src/assets/scripts/hooks/lib/curation-move.cjs +1582 -0
  153. package/src/assets/scripts/hooks/lib/{learning-store.cjs → curation-store.cjs} +242 -118
  154. package/src/assets/scripts/hooks/lib/decisions-format.cjs +3 -3
  155. package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +24 -7
  156. package/src/assets/scripts/hooks/lib/project-paths.cjs +132 -39
  157. package/src/assets/scripts/hooks/lib/render-decisions.cjs +34 -22
  158. package/src/assets/scripts/hooks/lib/tracked-areas.cjs +259 -0
  159. package/src/assets/scripts/hooks/memory-permission +358 -0
  160. package/src/assets/scripts/hooks/memory-worker +1 -1
  161. package/src/assets/scripts/hooks/pre-compact-memory +6 -16
  162. package/src/assets/scripts/hooks/queue-append +228 -103
  163. package/src/assets/scripts/hooks/resolve-project-root +2 -2
  164. package/src/assets/scripts/hooks/session-start-context +145 -71
  165. package/src/assets/scripts/hooks/session-start-memory +27 -46
  166. package/src/assets/scripts/lib/project-config.cjs +31 -9
  167. package/src/assets/scripts/resolve-base.cjs +1043 -0
  168. package/src/assets/scripts/resolve-settings.cjs +238 -46
  169. package/src/assets/skills/agent-references/SKILL.md +9 -2
  170. package/src/assets/skills/agent-references/references/commands/code-review-levels.md +19 -0
  171. package/src/assets/skills/agent-references/references/commands/implement-levels.md +27 -0
  172. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +6 -4
  173. package/src/assets/skills/agent-references/references/commands/plan-levels.md +40 -0
  174. package/src/assets/skills/agent-references/references/commands/resolve-levels.md +24 -0
  175. package/src/assets/skills/agent-references/references/synthesize/planning.md +9 -22
  176. package/src/assets/skills/agent-references/references/synthesize/review.md +11 -11
  177. package/src/assets/skills/apply-decisions/SKILL.md +1 -1
  178. package/src/assets/skills/docs-framework/SKILL.md +7 -7
  179. package/src/assets/skills/docs-framework/references/patterns.md +3 -4
  180. package/src/assets/skills/quality-gates/SKILL.md +1 -1
  181. package/src/assets/skills/worktree-support/references/roots.md +2 -2
  182. package/templates/lint-pack/.lint-pack.conf +86 -0
  183. package/templates/lint-pack/README.md +190 -0
  184. package/templates/lint-pack/lint-pack.sh +905 -0
  185. package/dist/core/learning-queue-cleanup.js +0 -27
  186. package/dist/core/learning-store.js +0 -61
  187. /package/dist/{learning-off → curation-off}/agents/debug.md +0 -0
  188. /package/dist/{learning-off → curation-off}/agents/design.md +0 -0
  189. /package/dist/{learning-off → curation-off}/agents/harden.md +0 -0
  190. /package/dist/{learning-off → curation-off}/agents/knowledge.md +0 -0
  191. /package/dist/{learning-off → curation-off}/agents/research.md +0 -0
  192. /package/dist/{learning-off → curation-off}/agents/skim.md +0 -0
  193. /package/dist/{learning-off → curation-off}/commands/dynamic-plan.md +0 -0
  194. /package/dist/{learning-off → curation-off}/commands/dynamic-profile.md +0 -0
  195. /package/dist/{learning-off → curation-off}/commands/dynamic-tickets.md +0 -0
  196. /package/dist/{learning-off → curation-off}/commands/release.md +0 -0
  197. /package/dist/{learning-off → curation-off}/commands/research.md +0 -0
@@ -3,7 +3,7 @@
3
3
  // Shared pure formatting helpers for decisions.md and pitfalls.md output.
4
4
  //
5
5
  // DESIGN: Shared pure formatting helpers for render-decisions.cjs, the one
6
- // renderer: its CLI and every learning-store writer render through it, so all of
6
+ // renderer: its CLI and every curation-store writer render through it, so all of
7
7
  // them share the EXACT same format functions. This is the single source of truth
8
8
  // for the byte-compat output strings — any drift here will break the
9
9
  // renderer/session-start-context TL;DR parser.
@@ -82,7 +82,7 @@ const {
82
82
  isV2,
83
83
  singleLine,
84
84
  inactiveNote,
85
- } = require('./learning-store.cjs');
85
+ } = require('./curation-store.cjs');
86
86
 
87
87
  /** JS LineTerminator set — /m `^` matches after each of these and `.` excludes them. */
88
88
  const LINE_TERMINATORS = /[\r\n\u2028\u2029]/g;
@@ -226,7 +226,7 @@ const PF_KEYS = /** @type {const} */ (['area', 'issue', 'impact', 'resolution'])
226
226
 
227
227
  /** The line under each rendered file's title: who writes the file and what it lists. */
228
228
  const GENERATED_NOTICE =
229
- 'Generated from the local learning ledger by devflow; do not edit. ' +
229
+ 'Generated from the local curation ledger by devflow; do not edit. ' +
230
230
  'Active entries follow; retired ones are listed under Inactive.';
231
231
 
232
232
  /**
@@ -1,7 +1,7 @@
1
1
  // src/assets/scripts/hooks/lib/mkdir-lock.cjs
2
2
  //
3
- // Shared mkdir-based locking helpers used by learning-store.cjs — whose
4
- // withDecisionsLock every learning op that writes and the render CLI take, and
3
+ // Shared mkdir-based locking helpers used by curation-store.cjs — whose
4
+ // withDecisionsLock every curation op that writes and the render CLI take, and
5
5
  // whose queue claim also takes the queue's own lock — and any other CJS hook that
6
6
  // needs exclusive access to a shared resource.
7
7
  //
@@ -50,12 +50,16 @@ function _idleSleep50() {
50
50
  * (default 60 s) is forcibly removed and the caller retries. This protects against
51
51
  * crashed holders but creates a narrow TOCTOU window: if a holder is actively
52
52
  * working and takes longer than 60 s, its lock can be stolen — leading to concurrent
53
- * ledger writes. Current callers (the learning ops through withDecisionsLock, and the
53
+ * ledger writes. Current callers (the curation ops through withDecisionsLock, and the
54
54
  * render CLI) perform only synchronous file I/O + JSON parse and complete well under 60 s in practice,
55
55
  * so this window is not reachable under normal operation. For long-running callers
56
56
  * call refreshLock(lockDir) periodically to reset the mtime and push the deadline
57
57
  * out by another staleMs interval.
58
58
  *
59
+ * A stale lock that cannot be removed (it holds files, it is not a directory, or its
60
+ * folder refuses the rmdir) is waited on like a live one, so `timeoutMs` bounds the
61
+ * loop: with a timeout of 0 the call answers false at once.
62
+ *
59
63
  * @param {string} lockDir - path to lock directory
60
64
  * @param {number} [timeoutMs=30000] - max wait in milliseconds
61
65
  * @param {number} [staleMs=60000] - age after which lock is considered stale
@@ -72,10 +76,7 @@ function acquireMkdirLock(lockDir, timeoutMs = 30000, staleMs = 60000) {
72
76
  try {
73
77
  const stat = fs.statSync(lockDir);
74
78
  const age = Date.now() - stat.mtimeMs;
75
- if (age > staleMs) {
76
- try { fs.rmdirSync(lockDir); } catch { /* already gone */ }
77
- continue;
78
- }
79
+ if (age > staleMs && breakStaleLock(lockDir)) continue;
79
80
  } catch { /* lock gone between check and stat */ }
80
81
  if (Date.now() - start >= timeoutMs) return false;
81
82
  _idleSleep50();
@@ -83,6 +84,22 @@ function acquireMkdirLock(lockDir, timeoutMs = 30000, staleMs = 60000) {
83
84
  }
84
85
  }
85
86
 
87
+ /**
88
+ * Remove a stale lock directory. True when it is gone (removed here, or already
89
+ * removed by another process); false when the rmdir failed for any other reason.
90
+ *
91
+ * @param {string} lockDir
92
+ * @returns {boolean}
93
+ */
94
+ function breakStaleLock(lockDir) {
95
+ try {
96
+ fs.rmdirSync(lockDir);
97
+ return true;
98
+ } catch (err) {
99
+ return Boolean(err) && err.code === 'ENOENT';
100
+ }
101
+ }
102
+
86
103
  /**
87
104
  * Release a mkdir-based lock. No-op if already released.
88
105
  *
@@ -13,8 +13,43 @@
13
13
 
14
14
  'use strict';
15
15
 
16
+ const crypto = require('crypto');
16
17
  const path = require('path');
17
18
 
19
+ // ---------------------------------------------------------------------------
20
+ // The rename's names: the old names in one frozen block, the new ones beside it
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /**
24
+ * The names the rename retires, kept in one place so the one-time ledger move, the
25
+ * backup readers and uninstall all spell them the same way. The TypeScript mirror
26
+ * and the shell move shim list the same names (tests/project-paths.test.ts pins both).
27
+ */
28
+ const LEGACY_NAMES = Object.freeze({
29
+ /** The old ledger folder under .devflow/. */
30
+ folder: 'learning',
31
+ /** The prefix of the folder the move leaves behind: `<prefix><UTC stamp>-<random>`. */
32
+ backupPrefix: 'learning.backup-',
33
+ /** The old project tuning file, inside the old folder. */
34
+ tuningFile: 'learning.json',
35
+ });
36
+
37
+ /** The new ledger folder under .devflow/. */
38
+ const CURATION_DIR_NAME = 'curation';
39
+
40
+ /** The new project tuning file, inside the new folder. */
41
+ const CURATION_TUNING_FILE_NAME = 'curation.json';
42
+
43
+ /**
44
+ * The names of the ledger move's lock and journal in the machine directory (`~/.devflow`):
45
+ * `<prefix><16 hex characters of the root key><suffix>`, the journal beside the lock. The
46
+ * TypeScript mirror holds the same three values for uninstall (tests/project-paths.test.ts
47
+ * pins both).
48
+ */
49
+ const MOVE_LOCK_PREFIX = '.curation-move-';
50
+ const MOVE_LOCK_SUFFIX = '.lock';
51
+ const MOVE_JOURNAL_SUFFIX = '.journal';
52
+
18
53
  // ---------------------------------------------------------------------------
19
54
  // Core directories
20
55
  // ---------------------------------------------------------------------------
@@ -24,9 +59,9 @@ function getMemoryDir(projectRoot) {
24
59
  return path.join(projectRoot, '.devflow', 'memory');
25
60
  }
26
61
 
27
- /** .devflow/learning/ — learning state root */
28
- function getLearningDir(projectRoot) {
29
- return path.join(projectRoot, '.devflow', 'learning');
62
+ /** .devflow/curation/ — curation state root */
63
+ function getCurationDir(projectRoot) {
64
+ return path.join(projectRoot, '.devflow', CURATION_DIR_NAME);
30
65
  }
31
66
 
32
67
  /** .devflow/features/ — per-feature knowledge bases */
@@ -39,8 +74,18 @@ function getDocsDir(projectRoot) {
39
74
  return path.join(projectRoot, '.devflow', 'docs');
40
75
  }
41
76
 
77
+ /**
78
+ * The global curate-agent tuning file inside the machine directory (`~/.devflow`).
79
+ * The one place that names it: the configure writer, the TypeScript reader, the
80
+ * shell reader (`df_global_tuning_file` in the hooks' git-marker library) and the
81
+ * global-file lists all come through here.
82
+ */
83
+ function getGlobalCurateTuningConfigPath(machineDir) {
84
+ return path.join(machineDir, CURATION_TUNING_FILE_NAME);
85
+ }
86
+
42
87
  // ---------------------------------------------------------------------------
43
- // Feature config (neutral .devflow root — not inside learning/)
88
+ // Feature config (neutral .devflow root — not inside curation/)
44
89
  // ---------------------------------------------------------------------------
45
90
 
46
91
  /** .devflow/config.json — per-repo facts {reviewPublication, tracker override} */
@@ -49,71 +94,71 @@ function getFeatureConfigPath(projectRoot) {
49
94
  }
50
95
 
51
96
  // ---------------------------------------------------------------------------
52
- // Learning queue files
97
+ // Curation queue files
53
98
  // ---------------------------------------------------------------------------
54
99
 
55
- /** .devflow/learning/.pending-turns.jsonl — decisions detection queue */
56
- function getLearningPendingTurnsPath(projectRoot) {
57
- return path.join(projectRoot, '.devflow', 'learning', '.pending-turns.jsonl');
100
+ /** .devflow/curation/.pending-turns.jsonl — decisions detection queue */
101
+ function getCurationPendingTurnsPath(projectRoot) {
102
+ return path.join(getCurationDir(projectRoot), '.pending-turns.jsonl');
58
103
  }
59
104
 
60
- /** .devflow/learning/.pending-turns.processing — the claimed batch a Curate run holds while it processes it */
61
- function getLearningPendingTurnsProcessingPath(projectRoot) {
62
- return path.join(projectRoot, '.devflow', 'learning', '.pending-turns.processing');
105
+ /** .devflow/curation/.pending-turns.processing — the claimed batch a Curate run holds while it processes it */
106
+ function getCurationPendingTurnsProcessingPath(projectRoot) {
107
+ return path.join(getCurationDir(projectRoot), '.pending-turns.processing');
63
108
  }
64
109
 
65
- /** .devflow/learning/.pending-turns.owner — the token of the run that holds the claim (learning-store.cjs) */
66
- function getLearningClaimOwnerPath(projectRoot) {
67
- return path.join(projectRoot, '.devflow', 'learning', '.pending-turns.owner');
110
+ /** .devflow/curation/.pending-turns.owner — the token of the run that holds the claim (curation-store.cjs) */
111
+ function getCurationClaimOwnerPath(projectRoot) {
112
+ return path.join(getCurationDir(projectRoot), '.pending-turns.owner');
68
113
  }
69
114
 
70
115
  // ---------------------------------------------------------------------------
71
- // Learning content files
116
+ // Curation content files
72
117
  // ---------------------------------------------------------------------------
73
118
 
74
- /** .devflow/learning/decisions.md */
119
+ /** .devflow/curation/decisions.md */
75
120
  function getDecisionsFilePath(projectRoot) {
76
- return path.join(projectRoot, '.devflow', 'learning', 'decisions.md');
121
+ return path.join(getCurationDir(projectRoot), 'decisions.md');
77
122
  }
78
123
 
79
- /** .devflow/learning/pitfalls.md */
124
+ /** .devflow/curation/pitfalls.md */
80
125
  function getPitfallsFilePath(projectRoot) {
81
- return path.join(projectRoot, '.devflow', 'learning', 'pitfalls.md');
126
+ return path.join(getCurationDir(projectRoot), 'pitfalls.md');
82
127
  }
83
128
 
84
- /** .devflow/learning/learning.json — project-level curate agent tuning config */
129
+ /** .devflow/curation/curation.json — project-level curate agent tuning config */
85
130
  function getCurateTuningConfigPath(projectRoot) {
86
- return path.join(projectRoot, '.devflow', 'learning', 'learning.json');
131
+ return path.join(getCurationDir(projectRoot), CURATION_TUNING_FILE_NAME);
87
132
  }
88
133
 
89
- /** .devflow/learning/decisions-ledger.jsonl — anchored ledger rows (single source of truth for rendering) */
134
+ /** .devflow/curation/decisions-ledger.jsonl — anchored ledger rows (single source of truth for rendering) */
90
135
  function getDecisionsLedgerPath(projectRoot) {
91
- return path.join(projectRoot, '.devflow', 'learning', 'decisions-ledger.jsonl');
136
+ return path.join(getCurationDir(projectRoot), 'decisions-ledger.jsonl');
92
137
  }
93
138
 
94
- /** .devflow/learning/decisions-log.jsonl */
139
+ /** .devflow/curation/decisions-log.jsonl */
95
140
  function getDecisionsLogPath(projectRoot) {
96
- return path.join(projectRoot, '.devflow', 'learning', 'decisions-log.jsonl');
141
+ return path.join(getCurationDir(projectRoot), 'decisions-log.jsonl');
97
142
  }
98
143
 
99
- /** .devflow/learning/decisions-log.archive.jsonl — rotated-out stale observing rows (gitignored) */
144
+ /** .devflow/curation/decisions-log.archive.jsonl — rotated-out stale observing rows (gitignored) */
100
145
  function getDecisionsArchivePath(projectRoot) {
101
- return path.join(projectRoot, '.devflow', 'learning', 'decisions-log.archive.jsonl');
146
+ return path.join(getCurationDir(projectRoot), 'decisions-log.archive.jsonl');
102
147
  }
103
148
 
104
- /** .devflow/learning/decisions-history.jsonl — prior content versions of rewritten entries (learning-store.cjs) */
149
+ /** .devflow/curation/decisions-history.jsonl — prior content versions of rewritten entries (curation-store.cjs) */
105
150
  function getDecisionsHistoryPath(projectRoot) {
106
- return path.join(projectRoot, '.devflow', 'learning', 'decisions-history.jsonl');
151
+ return path.join(getCurationDir(projectRoot), 'decisions-history.jsonl');
107
152
  }
108
153
 
109
- /** .devflow/learning/.decisions.lock — mkdir-based lock directory */
154
+ /** .devflow/curation/.decisions.lock — mkdir-based lock directory */
110
155
  function getDecisionsLockDir(projectRoot) {
111
- return path.join(projectRoot, '.devflow', 'learning', '.decisions.lock');
156
+ return path.join(getCurationDir(projectRoot), '.decisions.lock');
112
157
  }
113
158
 
114
- /** .devflow/learning/index.md — pre-rendered compact index written by render-decisions.cjs */
159
+ /** .devflow/curation/index.md — pre-rendered compact index written by render-decisions.cjs */
115
160
  function getDecisionsIndexPath(projectRoot) {
116
- return path.join(projectRoot, '.devflow', 'learning', 'index.md');
161
+ return path.join(getCurationDir(projectRoot), 'index.md');
117
162
  }
118
163
 
119
164
  // ---------------------------------------------------------------------------
@@ -164,22 +209,66 @@ function getResearchDir(projectRoot) {
164
209
  return path.join(projectRoot, '.devflow', 'docs', 'research');
165
210
  }
166
211
 
212
+ // ---------------------------------------------------------------------------
213
+ // The ledger move's machine-side names
214
+ // ---------------------------------------------------------------------------
215
+ //
216
+ // Not project paths: the move lock and the move journal stand in the machine
217
+ // directory (`$HOME/.devflow`), keyed by the ledger root, never inside a repository.
218
+ // They sit here, and not in curation-move.cjs, because the store's write guard reads
219
+ // the journal and must not load the move module to do it. CJS only: no TypeScript code
220
+ // builds them (a named exception of the export parity test). Their names, which
221
+ // uninstall also matches, are the three constants above.
222
+
223
+ /**
224
+ * The key of a ledger root's move lock: the first 16 hex characters of the sha256 of
225
+ * the root's physical path. Two spellings of one root give one key, so two movers
226
+ * meet at one lock.
227
+ *
228
+ * @param {string} realRoot - the root's realpath
229
+ * @returns {string}
230
+ */
231
+ function curationMoveLockKey(realRoot) {
232
+ return crypto.createHash('sha256').update(realRoot).digest('hex').slice(0, 16);
233
+ }
234
+
235
+ /** The move lock's directory, under the machine directory. */
236
+ function curationMoveLockDir(machineDir, realRoot) {
237
+ return path.join(machineDir, `${MOVE_LOCK_PREFIX}${curationMoveLockKey(realRoot)}${MOVE_LOCK_SUFFIX}`);
238
+ }
239
+
240
+ /**
241
+ * The move journal's file, under the machine directory and keyed exactly like the move
242
+ * lock. While it exists every store write on the root returns busy.
243
+ */
244
+ function curationMoveJournalPath(machineDir, realRoot) {
245
+ return path.join(machineDir, `${MOVE_LOCK_PREFIX}${curationMoveLockKey(realRoot)}${MOVE_JOURNAL_SUFFIX}`);
246
+ }
247
+
167
248
  module.exports = {
249
+ // The rename's names
250
+ LEGACY_NAMES,
251
+ CURATION_DIR_NAME,
252
+ CURATION_TUNING_FILE_NAME,
253
+ MOVE_LOCK_PREFIX,
254
+ MOVE_LOCK_SUFFIX,
255
+ MOVE_JOURNAL_SUFFIX,
168
256
  // Core directories
169
257
  getMemoryDir,
170
- getLearningDir,
258
+ getCurationDir,
171
259
  getFeaturesDir,
172
260
  getDocsDir,
173
261
  // Feature config
174
262
  getFeatureConfigPath,
175
- // Learning queue files
176
- getLearningPendingTurnsPath,
177
- getLearningPendingTurnsProcessingPath,
178
- getLearningClaimOwnerPath,
179
- // Learning content files
263
+ // Curation queue files
264
+ getCurationPendingTurnsPath,
265
+ getCurationPendingTurnsProcessingPath,
266
+ getCurationClaimOwnerPath,
267
+ // Curation content files
180
268
  getDecisionsFilePath,
181
269
  getPitfallsFilePath,
182
270
  getCurateTuningConfigPath,
271
+ getGlobalCurateTuningConfigPath,
183
272
  getDecisionsLedgerPath,
184
273
  getDecisionsLogPath,
185
274
  getDecisionsArchivePath,
@@ -196,4 +285,8 @@ module.exports = {
196
285
  getReviewsDir,
197
286
  getDesignDir,
198
287
  getResearchDir,
288
+ // The ledger move's machine-side names
289
+ curationMoveLockKey,
290
+ curationMoveLockDir,
291
+ curationMoveJournalPath,
199
292
  };
@@ -12,11 +12,11 @@
12
12
  // Filtering rules (must match AC-F3):
13
13
  // - anchor_id must be set (unanchored observing rows are excluded)
14
14
  // - type must match kind: 'decision' rows → decisions.md; 'pitfall' rows → pitfalls.md
15
- // - an active row (isActive from learning-store.cjs, the one status list) renders
15
+ // - an active row (isActive from curation-store.cjs, the one status list) renders
16
16
  // its body; an inactive one is listed in the file's Inactive table instead
17
17
  //
18
- // Row shape: a v2 ledger row (schema 2) or a v1 one; see learning-store.cjs.
19
- // Ledger file: .devflow/learning/decisions-ledger.jsonl (anchored rows only).
18
+ // Row shape: a v2 ledger row (schema 2) or a v1 one; see curation-store.cjs.
19
+ // Ledger file: .devflow/curation/decisions-ledger.jsonl (anchored rows only).
20
20
  // If absent, treat as empty corpus.
21
21
  //
22
22
  // Byte-compat: formatDecisionBody / formatPitfallBody / formatEntryBodyV2 /
@@ -26,6 +26,7 @@
26
26
  'use strict';
27
27
 
28
28
  const fs = require('fs');
29
+ const path = require('path');
29
30
 
30
31
  const {
31
32
  initDecisionsContent,
@@ -38,6 +39,7 @@ const {
38
39
  } = require('./decisions-format.cjs');
39
40
 
40
41
  const {
42
+ getCurationDir,
41
43
  getDecisionsFilePath,
42
44
  getPitfallsFilePath,
43
45
  getDecisionsIndexPath,
@@ -48,10 +50,10 @@ const {
48
50
  isActive,
49
51
  isV2,
50
52
  readJsonl,
51
- hasLearningDir,
53
+ hasCurationDir,
52
54
  withDecisionsLock,
53
55
  writeFileAtomic,
54
- } = require('./learning-store.cjs');
56
+ } = require('./curation-store.cjs');
55
57
 
56
58
  // ---------------------------------------------------------------------------
57
59
  // Ledger parsing
@@ -227,16 +229,26 @@ function renderDecisionsFile(rows, kind) {
227
229
  /**
228
230
  * Render decisions.md, pitfalls.md and index.md from the ledger rows, in memory,
229
231
  * in the order they are written: the body files first, the index last. `render`
230
- * and the learning store's writers write exactly these contents, and `--check`
232
+ * and the curation store's writers write exactly these contents, and `--check`
231
233
  * compares exactly these.
232
234
  *
235
+ * The files belong in `outputDir`, which the caller names: the index lists the
236
+ * absolute path of the decisions and pitfalls files, so it names the folder it is
237
+ * rendered for and no other. The file names are the path helpers'.
238
+ *
233
239
  * @param {string} worktreePath - Absolute path to the worktree root.
234
240
  * @param {object[]} rows - All rows from the ledger (unfiltered).
241
+ * @param {string} outputDir - Absolute path of the folder the three files belong in.
235
242
  * @returns {Array<{ path: string, content: string }>}
243
+ * @throws {TypeError} when `outputDir` is not an absolute path
236
244
  */
237
- function renderLearningFiles(worktreePath, rows) {
238
- const decisionsFilePath = getDecisionsFilePath(worktreePath);
239
- const pitfallsFilePath = getPitfallsFilePath(worktreePath);
245
+ function renderCurationFiles(worktreePath, rows, outputDir) {
246
+ if (typeof outputDir !== 'string' || !path.isAbsolute(outputDir)) {
247
+ throw new TypeError('renderCurationFiles: outputDir must be an absolute path');
248
+ }
249
+ const inOutputDir = file => path.join(outputDir, path.basename(file));
250
+ const decisionsFilePath = inOutputDir(getDecisionsFilePath(worktreePath));
251
+ const pitfallsFilePath = inOutputDir(getPitfallsFilePath(worktreePath));
240
252
 
241
253
  // Hoist active-row selection: computed once per kind, reused for both the
242
254
  // body render and the index build — avoids two redundant selectActiveRows passes.
@@ -259,7 +271,7 @@ function renderLearningFiles(worktreePath, rows) {
259
271
  },
260
272
  {
261
273
  // Compact index: a write-time artifact consumed via plain Read.
262
- path: getDecisionsIndexPath(worktreePath),
274
+ path: inOutputDir(getDecisionsIndexPath(worktreePath)),
263
275
  content: buildIndexContent(activeDecisionRows, activePitfallRows, {
264
276
  decisionsFilePath,
265
277
  pitfallsFilePath,
@@ -273,18 +285,18 @@ function renderLearningFiles(worktreePath, rows) {
273
285
  /**
274
286
  * Render decisions.md, pitfalls.md and index.md from the given ledger rows and
275
287
  * write them atomically. It takes no lock: the caller holds .decisions.lock
276
- * (D-ONE-LEARNING-LOCK). It writes only into an existing `.devflow/learning/`:
288
+ * (D-ONE-LEARNING-LOCK). It writes only into an existing `.devflow/curation/`:
277
289
  * without one it throws before writing anything (D-NO-STRAY-TREE).
278
290
  *
279
291
  * @param {string} worktreePath - Absolute path to the worktree root.
280
292
  * @param {object[]} rows - All rows from the ledger (unfiltered).
281
- * @throws when `<worktreePath>/.devflow/learning/` does not exist, or a write fails
293
+ * @throws when `<worktreePath>/.devflow/curation/` does not exist, or a write fails
282
294
  */
283
295
  function renderAndWriteAll(worktreePath, rows) {
284
- if (!hasLearningDir(worktreePath)) {
285
- throw new Error(`renderAndWriteAll: no .devflow/learning/ under ${worktreePath}`);
296
+ if (!hasCurationDir(worktreePath)) {
297
+ throw new Error(`renderAndWriteAll: no .devflow/curation/ under ${worktreePath}`);
286
298
  }
287
- const [decisions, pitfalls, index] = renderLearningFiles(worktreePath, rows);
299
+ const [decisions, pitfalls, index] = renderCurationFiles(worktreePath, rows, getCurationDir(worktreePath));
288
300
 
289
301
  // Write body files first; index last. On a crash between body writes and the
290
302
  // index write: on the FIRST render the index is absent (reader falls back to
@@ -365,7 +377,7 @@ function readIfPresent(file) {
365
377
  /**
366
378
  * `render`: read the ledger under .decisions.lock and write the three files from
367
379
  * what it read (D-ONE-LEARNING-LOCK), so a ledger write that lands while it waits
368
- * is rendered rather than overwritten. Refuses without the learning directory
380
+ * is rendered rather than overwritten. Refuses without the curation directory
369
381
  * (D-NO-STRAY-TREE), and when it or `.devflow` is a symbolic link (D-NO-LINKED-TREE).
370
382
  *
371
383
  * @param {string} root
@@ -385,18 +397,18 @@ function renderCommand(root) {
385
397
  * `--check`: render in memory and compare with the files on disk, naming each
386
398
  * file that differs or is missing; exit 1 on any drift. It writes nothing and
387
399
  * takes no lock — taking it would create the lock directory — and refuses
388
- * without the learning directory (D-NO-STRAY-TREE).
400
+ * without the curation directory (D-NO-STRAY-TREE).
389
401
  *
390
402
  * @param {string} root
391
403
  * @returns {number} exit code
392
404
  */
393
405
  function checkCommand(root) {
394
- if (!hasLearningDir(root)) {
395
- process.stderr.write(`${CLI_NAME}: no .devflow/learning/ under ${root} — run from the project root\n`);
406
+ if (!hasCurationDir(root)) {
407
+ process.stderr.write(`${CLI_NAME}: no .devflow/curation/ under ${root} — run from the project root\n`);
396
408
  return 1;
397
409
  }
398
410
  let drift = false;
399
- for (const file of renderLearningFiles(root, readLedgerRows(root))) {
411
+ for (const file of renderCurationFiles(root, readLedgerRows(root), getCurationDir(root))) {
400
412
  const onDisk = readIfPresent(file.path);
401
413
  if (onDisk !== file.content) {
402
414
  process.stderr.write(`[render-decisions] DRIFT: ${file.path}${onDisk === null ? ' (missing)' : ''}\n`);
@@ -408,7 +420,7 @@ function checkCommand(root) {
408
420
 
409
421
  /**
410
422
  * Run the CLI on its arguments and return the exit code. Neither mode creates the
411
- * learning directory (D-NO-STRAY-TREE).
423
+ * curation directory (D-NO-STRAY-TREE).
412
424
  *
413
425
  * @param {string[]} argv - the arguments after the script path
414
426
  * @returns {number}
@@ -446,7 +458,7 @@ if (require.main === module) {
446
458
 
447
459
  module.exports = {
448
460
  renderDecisionsFile,
449
- renderLearningFiles,
461
+ renderCurationFiles,
450
462
  renderAndWriteAll,
451
463
  selectActiveRows,
452
464
  selectInactiveRows,