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
@@ -0,0 +1,134 @@
1
+ /**
2
+ * @file curation-queue-cleanup.ts
3
+ *
4
+ * The curation queue drains: `drainCurationQueue`, which `devflow curation --clear`
5
+ * runs (src/cli/commands/curation.ts), and `drainDisabledCurationQueue`, which `devflow
6
+ * curation --disable` and the drain `devflow init` runs when curation is switched off
7
+ * (src/cli/commands/init.ts) run: the same drain, and the pending rows of the backups
8
+ * a move left as well.
9
+ *
10
+ * Both drains answer `skipped`, and delete nothing, when git tracks any entry under a
11
+ * data folder or a backup folder, or does not answer (the tracked-areas check). The
12
+ * line a caller prints for such a drain is {@link formatCurationNotDrained}.
13
+ */
14
+ import * as path from 'path';
15
+ import { LEGACY_NAMES, getCurationClaimOwnerPath, getCurationDir, getCurationPendingTurnsPath, getCurationPendingTurnsProcessingPath, } from './project-paths.js';
16
+ import { loadCurationMove } from './curation-move.js';
17
+ import { MOVE_SKIP_NOTICE } from './curation-move-skip.js';
18
+ import { formatCurationStoreUnavailable } from './curation-store.js';
19
+ import { drainQueueFiles, formatRefusedDrain } from './queue-drain.js';
20
+ import { loadTrackedAreas } from './tracked-areas.js';
21
+ /** The folders to check for links, and the queue files to delete, in the live and the old ledger folder of `gitRoot`. */
22
+ function liveQueueTargets(gitRoot) {
23
+ const curationDir = getCurationDir(gitRoot);
24
+ const devflowDir = path.dirname(curationDir);
25
+ const oldDir = path.join(devflowDir, LEGACY_NAMES.folder);
26
+ const queueFiles = [
27
+ getCurationPendingTurnsPath(gitRoot),
28
+ getCurationPendingTurnsProcessingPath(gitRoot),
29
+ getCurationClaimOwnerPath(gitRoot),
30
+ ];
31
+ return {
32
+ folders: [devflowDir, curationDir, oldDir],
33
+ files: [...queueFiles, ...queueFiles.map(file => path.join(oldDir, path.basename(file)))],
34
+ };
35
+ }
36
+ /**
37
+ * Drain the curation (decisions-detection) pending-turns queue, its claimed
38
+ * batch and the claim's owner file so stale turns don't process later — used by
39
+ * `--clear`. A mid-run Curate agent whose claimed batch vanishes aborts without
40
+ * changes, which is the desired outcome.
41
+ *
42
+ * Drains each ledger folder that exists, the new name and the old one: a move that
43
+ * was busy or refused leaves the old folder in place, and its queue must not process
44
+ * on a later re-enable. A folder or file that is absent is skipped. Only the three
45
+ * queue files go; the ledger, the log and the tuning file stay, and so does every
46
+ * backup folder a move left (see {@link drainDisabledCurationQueue}).
47
+ *
48
+ * Asks first the question the disable drain asks (see {@link drainDisabledCurationQueue}):
49
+ * when git tracks any entry under the data folders or a backup folder of `gitRoot`, or
50
+ * a `.git` entry stands and git does not answer, nothing is deleted and the answer is
51
+ * `skipped`.
52
+ *
53
+ * Refused, deleting nothing in either folder, when `.devflow` or either ledger
54
+ * folder under `gitRoot` is a symbolic link (D-CLI-NO-SYMLINK). ENOENT-tolerant;
55
+ * other errors propagate.
56
+ *
57
+ * `loadTracking` is the check's loader; its default is the package's own copy.
58
+ */
59
+ export async function drainCurationQueue(gitRoot, loadTracking = loadTrackedAreas) {
60
+ const skipped = trackedSkip(gitRoot, loadTracking);
61
+ if (skipped !== null)
62
+ return skipped;
63
+ const { folders, files } = liveQueueTargets(gitRoot);
64
+ return drainQueueFiles(folders, files);
65
+ }
66
+ /**
67
+ * The drain of a switched-off curation: {@link drainCurationQueue}, and the pending
68
+ * rows of the backup folders a move left as well. A backup's queue, claim, private and
69
+ * overflow files go (the files `devflow curation --status` counts as pending rows);
70
+ * its ledger files, its swept files and the claim's owner file stay. Without this, a
71
+ * later re-enable and init would merge the backup's rows into the live queue, which is
72
+ * the turns the drain exists to discard. The backups are read within the sweep's
73
+ * bounds: 500 backup folders, and 200 files in each.
74
+ *
75
+ * Asks first whether git tracks any entry under the data folders or a backup folder of
76
+ * `gitRoot`, the question the move asks (the tracked-areas check). A folder that git
77
+ * tracks is the repository's content, so the drain leaves the project alone: it deletes
78
+ * nothing in the live folder, the old folder or any backup, and answers `skipped` with
79
+ * the reason `tracked`, or `unknown` when a `.git` entry stands and git did not answer
80
+ * (a check that cannot be loaded, or a root that is not absolute, answers `unknown`
81
+ * too). A root with no `.git` entry makes no git call and drains as usual.
82
+ *
83
+ * Refused, deleting nothing anywhere, when a link is in the way: `.devflow`, either
84
+ * ledger folder, a backup folder that may hold rows, or one of a backup's queue files.
85
+ * The answer names the link. ENOENT-tolerant. Any other failure of the backup list, a
86
+ * move module that does not load or a folder that cannot be read, never stops the
87
+ * drain of the live and the old folder: those drain as usual, and the answer carries
88
+ * a `warning` that the backups were not drained, which the caller prints.
89
+ *
90
+ * `load` is the move module's loader and `loadTracking` the check's; each default is
91
+ * the package's own copy.
92
+ */
93
+ export async function drainDisabledCurationQueue(gitRoot, load = loadCurationMove, loadTracking = loadTrackedAreas) {
94
+ const skipped = trackedSkip(gitRoot, loadTracking);
95
+ if (skipped !== null)
96
+ return skipped;
97
+ const { folders, files } = liveQueueTargets(gitRoot);
98
+ const loaded = load();
99
+ const listed = loaded.ok ? loaded.value.backupQueueFiles(gitRoot) : null;
100
+ if (listed !== null && !listed.ok && listed.error.kind === 'refused' && listed.error.path !== undefined) {
101
+ return { drained: false, linkedFolder: listed.error.path };
102
+ }
103
+ const drain = await drainQueueFiles(folders, [...files, ...(listed !== null && listed.ok ? listed.value.files : [])]);
104
+ if (!drain.drained)
105
+ return drain;
106
+ const failure = !loaded.ok ? formatCurationStoreUnavailable(loaded.error) : listed !== null && !listed.ok ? listed.error.message : null;
107
+ return failure === null ? drain : { drained: true, warning: formatBackupsNotDrained(failure) };
108
+ }
109
+ /**
110
+ * The skipped answer for `root`, or null when the drain may go on. The tracked-areas
111
+ * check decides; a check that cannot be loaded cannot answer, so it is `unknown`.
112
+ */
113
+ function trackedSkip(root, load) {
114
+ const loaded = load();
115
+ const answer = loaded.ok ? loaded.value.checkTrackedAreas(root) : 'unknown';
116
+ return answer === 'untracked' ? null : { drained: false, skipped: answer };
117
+ }
118
+ /**
119
+ * The line a caller prints for a drain that deleted nothing: the fixed notice of a
120
+ * skipped project (no repository content in it), or the refusal at a link. When the
121
+ * caller has just printed that notice (`shown`, the reason it showed), the line does
122
+ * not repeat it.
123
+ */
124
+ export function formatCurationNotDrained(drain, shown = null) {
125
+ if (!('skipped' in drain))
126
+ return formatRefusedDrain('curation', drain.linkedFolder);
127
+ const line = 'The curation queue was not drained.';
128
+ return shown === drain.skipped ? line : `${line} ${MOVE_SKIP_NOTICE[drain.skipped]}`;
129
+ }
130
+ /** The warning for a disable drain that could not list the backups: the live queue is drained, the backups are not. */
131
+ function formatBackupsNotDrained(reason) {
132
+ return `The queue files of the backup folders were not drained: ${reason}. The live curation queue was drained.`;
133
+ }
134
+ //# sourceMappingURL=curation-queue-cleanup.js.map
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The CLI's view of the curation store — a typed seam onto the package's own
3
+ * `hooks/lib/curation-store.cjs`, never a second implementation of it.
4
+ *
5
+ * D-LEARNING-STORE-SEAM: `devflow curation` reads and writes the curation tree only
6
+ * through the store module json-helper's ops and the hooks run, loaded with the
7
+ * evidence-policy seam's `loadScript` from the package's own scripts directory and
8
+ * shape-checked against CURATION_STORE_SURFACE; the interfaces below are
9
+ * TRANSCRIBED from the store's JSDoc and are this side's only shape authority.
10
+ * Reason: the CLI's own TypeScript reader judged every row by the v1 shape, so it
11
+ * counted v2 observations as invalid, and its `--clear` truncated the log without
12
+ * the lock every curation writer takes.
13
+ *
14
+ * As with the evidence resolver (D-POLICY-CJS-SEAM), the CLI loads the package
15
+ * copy, which `npm` installs with this CLI, not `~/.devflow/scripts`, which
16
+ * `devflow init` refreshes: the two can differ until the next init.
17
+ */
18
+ import { join } from 'path';
19
+ import { scriptsDir } from './assets.js';
20
+ import { loadScript } from './evidence-policy.js';
21
+ /** The store, relative to src/assets/scripts/ (and ~/.devflow/scripts/). */
22
+ export const CURATION_STORE_SCRIPT_NAME = join('hooks', 'lib', 'curation-store.cjs');
23
+ /**
24
+ * Every kind a store or move function can refuse with: the `kind` literals in
25
+ * curation-store.cjs and curation-move.cjs, the refusal builders' kinds, and the
26
+ * `successor-` kinds the retire op derives from the kind of its successor lookup.
27
+ * A test keeps this list equal to the kinds those modules name.
28
+ */
29
+ export const CURATION_STORE_ERROR_KINDS = [
30
+ 'no-curation-dir',
31
+ 'not-a-directory',
32
+ 'not-a-file',
33
+ 'busy',
34
+ 'refused',
35
+ 'io-error',
36
+ 'invalid-input',
37
+ 'invalid-key',
38
+ 'not-found',
39
+ 'duplicate-anchor',
40
+ 'ledger-malformed',
41
+ 'heartbeat-failed',
42
+ 'restore-first',
43
+ 'duplicate-log-id',
44
+ 'cannot-reproject',
45
+ 'not-in-log',
46
+ 'already-promoted',
47
+ 'v1-observation',
48
+ 'type-mismatch',
49
+ 'cited-numbers-exhausted',
50
+ 'no-verify-ref',
51
+ 'not-at-ref',
52
+ 'git-failed',
53
+ 'quote-not-found',
54
+ 'already-inactive',
55
+ 'already-active',
56
+ 'successor-not-found',
57
+ 'successor-duplicate-anchor',
58
+ 'successor-inactive',
59
+ ];
60
+ /**
61
+ * Every key of CurationStoreModule and the runtime kind the loader requires of
62
+ * it. `satisfies` makes the compiler reject an interface key missing here.
63
+ */
64
+ export const CURATION_STORE_SURFACE = Object.freeze({
65
+ INACTIVE_STATUSES: 'string-array',
66
+ ANCHOR_ID_RE: 'regexp',
67
+ readCurationState: 'function',
68
+ buildListing: 'function',
69
+ readListing: 'function',
70
+ formatListing: 'function',
71
+ showByKey: 'function',
72
+ restoreAnchor: 'function',
73
+ clearUnreferenced: 'function',
74
+ resetCuration: 'function',
75
+ });
76
+ /**
77
+ * Load the curation store from `dir` (default: the package's own scripts
78
+ * directory) and shape-check its surface. Never throws: a missing file is
79
+ * `not-found`; a module that throws on load or lacks a surface key is `unusable`.
80
+ */
81
+ export function loadCurationStore(dir = scriptsDir()) {
82
+ return loadScript(join(dir, CURATION_STORE_SCRIPT_NAME), CURATION_STORE_SURFACE);
83
+ }
84
+ /**
85
+ * Why the store cannot be used, and the remedy: a package reinstall, since the
86
+ * CLI loads the package's own copy, which `devflow init` does not restore.
87
+ */
88
+ export function formatCurationStoreUnavailable(error) {
89
+ switch (error.kind) {
90
+ case 'not-found': return 'curation store not found — reinstall devflow-kit';
91
+ case 'unusable': return 'curation store failed to load — reinstall devflow-kit';
92
+ default: {
93
+ const exhaustive = error;
94
+ return exhaustive;
95
+ }
96
+ }
97
+ }
98
+ //# sourceMappingURL=curation-store.js.map
@@ -1,26 +1,26 @@
1
1
  /**
2
- * Learning-variant splitter: one compiled prompt body in, its learning-on and
3
- * learning-off variants out.
2
+ * Curation-variant splitter: one compiled prompt body in, its curation-on and
3
+ * curation-off variants out.
4
4
  *
5
5
  * Pure module, zero I/O. Every refusal is a Result; the exiting shell is
6
6
  * scripts/build-mds.ts, which renders these errors into the build's single exit.
7
7
  *
8
- * D-LEARNING-VARIANTS: when learning is off on a machine, the decisions text is
8
+ * D-LEARNING-VARIANTS: when curation is off on a machine, the decisions text is
9
9
  * ABSENT from every prompt it would have reached, not merely gated at run time.
10
- * The build therefore emits two variants of a prompt that carries learning text:
11
- * the learning-on variant at today's path (dist/commands/x.md, dist/agents/x.md)
12
- * and the learning-off variant at dist/learning-off/{commands,agents}/x.md, only
10
+ * The build therefore emits two variants of a prompt that carries curation text:
11
+ * the curation-on variant at today's path (dist/commands/x.md, dist/agents/x.md)
12
+ * and the curation-off variant at dist/curation-off/{commands,agents}/x.md, only
13
13
  * for a host that has an arm. A prompt with no arm has one variant and no
14
- * learning-off file.
14
+ * curation-off file.
15
15
  *
16
16
  * The arms are whole-line markers in the host or in a partial it expands:
17
17
  *
18
- * <!-- learning:on --> opens an arm that only the learning-on variant keeps
19
- * <!-- learning:off --> opens an arm that only the learning-off variant keeps;
18
+ * <!-- curation:on --> opens an arm that only the curation-on variant keeps
19
+ * <!-- curation:off --> opens an arm that only the curation-off variant keeps;
20
20
  * directly after an on arm it is that arm's else
21
- * <!-- learning:end --> closes the arm
21
+ * <!-- curation:end --> closes the arm
22
22
  *
23
- * A marker line matches LEARNING_MARKER_RE. It may be indented and may sit inside
23
+ * A marker line matches CURATION_MARKER_RE. It may be indented and may sit inside
24
24
  * a fenced block, a list, a define body or an agent's second frontmatter block,
25
25
  * because MDS passes an HTML comment through untouched. The splitter runs on the
26
26
  * COMPILED body, after the MDS compile, so it adds no compileFile call, no import
@@ -36,21 +36,26 @@
36
36
  * fence- and indent-aware `@if` (dean0x/mdscript#452). That swap, with the
37
37
  * @mdscript/mds upgrade it rides on proven byte-identical in dist/ and this module
38
38
  * deleted, is dean0x/devflow#439. The mapping is one to one: `on` becomes
39
- * `@if learning:`, `off` becomes `@else:` and `end` becomes `@end`. A standalone
40
- * `off` arm, with no on arm before it, becomes an `@if learning:` with an empty
39
+ * `@if curation:`, `off` becomes `@else:` and `end` becomes `@end`. A standalone
40
+ * `off` arm, with no on arm before it, becomes an `@if curation:` with an empty
41
41
  * body followed by `@else:`.
42
42
  *
43
- * What the splitter refuses, each as a LearningSplitError with the source line:
43
+ * What the splitter refuses, each as a CurationSplitError with the source line:
44
44
  * - nesting: an `on` marker while an arm is open, or a second `off` in one arm;
45
45
  * - an `end` with no open arm;
46
46
  * - an arm still open at the end of the body;
47
47
  * - an empty arm (no line with content between its marker and the next);
48
- * - a stray marker: a line that names `<!-- learning:` but is not a whole-line
48
+ * - a retired marker: a line that names the pre-rename spelling of the marker, in
49
+ * any spacing or case, which would otherwise ship as prompt text;
50
+ * - a stray marker: a line that names `<!-- curation:` but is not a whole-line
49
51
  * marker, which would otherwise ship as prompt text.
50
52
  *
53
+ * The retired spelling is never accepted as an alias: the build fails on it, so a
54
+ * host that still carries it cannot ship a prompt with both variants silently equal.
55
+ *
51
56
  * Scope guarantee: this module answers one question (what are the two variants of
52
57
  * this body?) and one cheap one for the build to ask of a body that must carry no
53
- * arms (containsLearningMarker). It decides nothing about WHICH text belongs in an
58
+ * arms (containsCurationMarker). It decides nothing about WHICH text belongs in an
54
59
  * arm: that is authoring, and the guards over the built trees hold it.
55
60
  */
56
61
  function Ok(value) {
@@ -60,52 +65,65 @@ function Err(error) {
60
65
  return { ok: false, error };
61
66
  }
62
67
  /**
63
- * Repo-relative root of the learning-off variants.
68
+ * Repo-relative root of the curation-off variants.
64
69
  *
65
70
  * Exported because the build's orphan prune names this directory even when no
66
71
  * host carries an arm, which is exactly the case where every file in it is an
67
72
  * orphan, so it cannot be derived from the plan.
68
73
  */
69
- export const LEARNING_OFF_OUTPUT_DIR = 'dist/learning-off';
74
+ export const CURATION_OFF_OUTPUT_DIR = 'dist/curation-off';
75
+ /**
76
+ * Repo-relative root of the retired off tree, from before the rename.
77
+ *
78
+ * The build removes this folder whole after a clean run, so a checkout that built
79
+ * before the rename keeps no stale prompts there: nothing reads the folder, and the
80
+ * package would otherwise ship it.
81
+ */
82
+ export const LEGACY_OFF_OUTPUT_DIR = 'dist/learning-off';
70
83
  /**
71
- * POSIX repo-relative path of the learning-off variant of an output file.
84
+ * POSIX repo-relative path of the curation-off variant of an output file.
72
85
  *
73
- * `fileName` is the already-validated basename the learning-on variant is written
86
+ * `fileName` is the already-validated basename the curation-on variant is written
74
87
  * under (`implement.md`), so the two variants of one host always share a name.
75
88
  */
76
- export function learningOffRelPath(kind, fileName) {
77
- return `${LEARNING_OFF_OUTPUT_DIR}/${kind}/${fileName}`;
89
+ export function curationOffRelPath(kind, fileName) {
90
+ return `${CURATION_OFF_OUTPUT_DIR}/${kind}/${fileName}`;
78
91
  }
79
92
  // ---------------------------------------------------------------------------
80
93
  // Markers
81
94
  // ---------------------------------------------------------------------------
82
95
  /**
83
- * A whole-line learning marker. The group captures `on`, `off` or `end`.
96
+ * A whole-line curation marker. The group captures `on`, `off` or `end`.
84
97
  *
85
98
  * Anchored at both ends with no nested quantifier, so a match is linear in the
86
99
  * line length. Leading and trailing space or tab is allowed; anything else on the
87
100
  * line is not a marker.
88
101
  */
89
- export const LEARNING_MARKER_RE = /^[ \t]*<!-- learning:(on|off|end) -->[ \t]*$/;
102
+ export const CURATION_MARKER_RE = /^[ \t]*<!-- curation:(on|off|end) -->[ \t]*$/;
90
103
  /**
91
104
  * The fragment that makes a line a marker ATTEMPT: loose on spacing and case so a
92
105
  * malformed marker is caught rather than shipped. Every valid marker contains it.
93
106
  */
94
- const MARKER_FRAGMENT_RE = /<!--\s*learning\s*:/i;
95
- /** True when the text names a learning marker anywhere, valid or not. */
96
- export function containsLearningMarker(text) {
97
- return MARKER_FRAGMENT_RE.test(text);
107
+ const MARKER_FRAGMENT_RE = /<!--\s*curation\s*:/i;
108
+ /**
109
+ * The same attempt test for the retired spelling of the marker. No spelling of it is
110
+ * valid, so a line that matches is always refused.
111
+ */
112
+ const LEGACY_MARKER_FRAGMENT_RE = /<!--\s*learning\s*:/i;
113
+ /** True when the text names a marker anywhere, in the current or the retired spelling, valid or not. */
114
+ export function containsCurationMarker(text) {
115
+ return MARKER_FRAGMENT_RE.test(text) || LEGACY_MARKER_FRAGMENT_RE.test(text);
98
116
  }
99
117
  function markerOf(line) {
100
118
  // A CRLF body carries the carriage return on the line; it is not part of the marker.
101
119
  const bare = line.endsWith('\r') ? line.slice(0, -1) : line;
102
- const match = LEARNING_MARKER_RE.exec(bare);
120
+ const match = CURATION_MARKER_RE.exec(bare);
103
121
  return match === null ? null : match[1];
104
122
  }
105
123
  /** The longest stretch of a stray line a refusal quotes. */
106
124
  const STRAY_QUOTE_MAX = 80;
107
125
  /**
108
- * Split a compiled body into its learning-on and learning-off variants.
126
+ * Split a compiled body into its curation-on and curation-off variants.
109
127
  *
110
128
  * Total and pure: the same input gives the same output, and a refusal is a
111
129
  * Result. The loop is one pass over the body's lines, so its bound is the body's
@@ -115,7 +133,7 @@ const STRAY_QUOTE_MAX = 80;
115
133
  * refused as empty, because it would add blank lines to one variant and nothing to
116
134
  * the other, which is a mistake rather than a design.
117
135
  */
118
- export function splitLearningVariants(body) {
136
+ export function splitCurationVariants(body) {
119
137
  const lines = body.split('\n');
120
138
  const on = [];
121
139
  const off = [];
@@ -130,6 +148,9 @@ export function splitLearningVariants(body) {
130
148
  const lineNo = index + 1;
131
149
  const marker = markerOf(line);
132
150
  if (marker === null) {
151
+ if (LEGACY_MARKER_FRAGMENT_RE.test(line)) {
152
+ return Err({ kind: 'legacy-marker', line: lineNo, text: line.trim().slice(0, STRAY_QUOTE_MAX) });
153
+ }
133
154
  if (MARKER_FRAGMENT_RE.test(line)) {
134
155
  return Err({ kind: 'stray-marker', line: lineNo, text: line.trim().slice(0, STRAY_QUOTE_MAX) });
135
156
  }
@@ -186,28 +207,31 @@ export function splitLearningVariants(body) {
186
207
  * fall through into a message that does not describe it. Shared by the build and
187
208
  * the tests, so the text asserted is the text shipped.
188
209
  */
189
- export function describeLearningSplitError(error) {
210
+ export function describeCurationSplitError(error) {
190
211
  switch (error.kind) {
191
212
  case 'nested-arm':
192
- return (`line ${error.line}: a learning:on marker inside the ${error.arm} arm opened at line ` +
193
- `${error.openedAt} — arms do not nest; close it with learning:end first`);
213
+ return (`line ${error.line}: a curation:on marker inside the ${error.arm} arm opened at line ` +
214
+ `${error.openedAt} — arms do not nest; close it with curation:end first`);
194
215
  case 'repeated-off':
195
- return (`line ${error.line}: a second learning:off marker inside the off arm opened at line ` +
216
+ return (`line ${error.line}: a second curation:off marker inside the off arm opened at line ` +
196
217
  `${error.openedAt} — an arm has at most one else`);
197
218
  case 'unopened-end':
198
- return `line ${error.line}: a learning:end marker with no open arm`;
219
+ return `line ${error.line}: a curation:end marker with no open arm`;
199
220
  case 'unclosed-arm':
200
- return `the ${error.arm} arm opened at line ${error.openedAt} is never closed with learning:end`;
221
+ return `the ${error.arm} arm opened at line ${error.openedAt} is never closed with curation:end`;
201
222
  case 'empty-arm':
202
223
  return (`lines ${error.openedAt}-${error.closedAt}: the ${error.arm} arm holds no line with content — ` +
203
224
  `delete the arm or put its text inside it`);
225
+ case 'legacy-marker':
226
+ return (`line ${error.line}: "${error.text}" uses the retired learning marker — rename it to ` +
227
+ `<!-- curation:on -->, <!-- curation:off --> or <!-- curation:end -->`);
204
228
  case 'stray-marker':
205
- return (`line ${error.line}: "${error.text}" names a learning marker but is not one — a marker ` +
206
- `is a whole line, exactly <!-- learning:on -->, <!-- learning:off --> or <!-- learning:end -->`);
229
+ return (`line ${error.line}: "${error.text}" names a curation marker but is not one — a marker ` +
230
+ `is a whole line, exactly <!-- curation:on -->, <!-- curation:off --> or <!-- curation:end -->`);
207
231
  default: {
208
232
  const unhandled = error;
209
- return `unhandled learning split error ${JSON.stringify(unhandled)}`;
233
+ return `unhandled curation split error ${JSON.stringify(unhandled)}`;
210
234
  }
211
235
  }
212
236
  }
213
- //# sourceMappingURL=learning-variants.js.map
237
+ //# sourceMappingURL=curation-variants.js.map
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Render an untrusted value for a terminal message.
3
+ *
4
+ * A neutral home for the sanitizer that every parser echoing a rejected value
5
+ * shares (the tracker provider and the gate level today), so no parser imports
6
+ * another feature's module just to reach it.
7
+ */
8
+ /** Longest rejected value echoed back to the terminal, in CODE POINTS, unless the caller names another. */
9
+ const MAX_ECHOED_VALUE = 40;
10
+ /**
11
+ * Render an untrusted token for a terminal message.
12
+ *
13
+ * Rejected values are echoed so the user can see what they typed, so they are
14
+ * third-party input at a display sink: control characters (terminal escapes,
15
+ * BEL, newlines) are replaced and the value is truncated.
16
+ *
17
+ * `maxLength` is the cut, in code points. A caller that echoes a whole message (a name
18
+ * inside a sentence) names a longer bound; anything that is not a positive integer takes
19
+ * the default, so the render stays bounded whatever a caller passes.
20
+ *
21
+ * Takes `unknown`, and a non-string renders as its TYPE: the never-throws
22
+ * contract of the callers has to hold for what reaches this sink, not
23
+ * only for what the signature says — a caller reads hand-editable
24
+ * files, and a caller-side guard is one edit from being gone. Naming the type
25
+ * also keeps the render total, where `String()` would hand control to a
26
+ * caller-supplied `toString` — itself both a throw path and an echo path this
27
+ * function exists to close.
28
+ */
29
+ export function describeEchoedValue(raw, maxLength = MAX_ECHOED_VALUE) {
30
+ if (typeof raw !== 'string')
31
+ return `<${raw === null ? 'null' : typeof raw}>`;
32
+ const limit = Number.isInteger(maxLength) && maxLength > 0 ? maxLength : MAX_ECHOED_VALUE;
33
+ // The class is written with ESCAPES, never literal control bytes. A raw NUL
34
+ // makes grep classify this whole file as binary — it prints "Binary file
35
+ // matches" and skips the lines — so every grep-based sweep over src/core/
36
+ // silently stops covering the file while still exiting 0. Pinned by
37
+ // tests/guards/no-control-bytes.test.ts.
38
+ // C0, DEL and C1: a terminal acts on all three (U+009B is the one-character CSI).
39
+ // eslint-disable-next-line no-control-regex
40
+ const stripped = raw.replace(/[\x00-\x1f\x7f-\x9f]/g, '?');
41
+ // Cut by CODE POINT. `slice` cuts UTF-16 code units, so a cut landing inside an
42
+ // astral character (emoji, CJK ext-B) emits the lone surrogate half of it.
43
+ const points = [...stripped];
44
+ return points.length > limit
45
+ ? `${points.slice(0, limit).join('')}…`
46
+ : stripped;
47
+ }
48
+ //# sourceMappingURL=echo-value.js.map
@@ -102,7 +102,7 @@ function surfaceMismatches(value, surface) {
102
102
  * throws: a missing file is `not-found`; a module that throws on load or lacks a
103
103
  * surface key is `unusable`. The caller's type parameter is justified by the
104
104
  * surface check, which `satisfies` ties to the interface's keys. Exported for
105
- * src/core/learning-store.ts, which loads the learning store the same way.
105
+ * src/core/curation-store.ts, which loads the curation store the same way.
106
106
  */
107
107
  export function loadScript(scriptPath, surface) {
108
108
  let loaded;
@@ -331,15 +331,27 @@ export function repoComplianceStatusLines(loaded, opts) {
331
331
  * tracked by git, or null (D-PERSONAL-UNTRACKED). The resolver ignores such a file
332
332
  * and says so on stderr, but prompts run it with stderr discarded, so a status
333
333
  * command is where the user sees why their personal settings have no effect.
334
+ *
335
+ * Takes the ONE resolved result, so a caller that already holds it adds no second
336
+ * resolve (each resolve forks git).
337
+ */
338
+ export function trackedConfigWarning(settings) {
339
+ if (!settings.personalTracked)
340
+ return null;
341
+ const file = settingsSourceFile('personal');
342
+ return `Git tracks an entry at or under ${file}, so devflow ignores that file. It holds personal settings. ` +
343
+ 'Find the entry in the output of: git ls-files. Untrack it with: git rm --cached <entry>. ' +
344
+ 'Then check the file contents, or delete the file. ' +
345
+ 'Untracking leaves the committed bytes on disk as your personal file.';
346
+ }
347
+ /**
348
+ * {@link trackedConfigWarning} for a caller that holds the loaded module and not a
349
+ * resolved result: it resolves once and returns null when the module is unusable.
334
350
  */
335
351
  export function personalConfigTrackedWarning(loaded, opts) {
336
352
  if (!loaded.ok)
337
353
  return null;
338
- if (!loaded.value.resolveSettings(opts).personalTracked)
339
- return null;
340
- const file = settingsSourceFile('personal');
341
- return `${file} is tracked by git, so devflow ignores it — it holds personal settings. ` +
342
- `Untrack it with: git rm --cached ${file}`;
354
+ return trackedConfigWarning(loaded.value.resolveSettings(opts));
343
355
  }
344
356
  /** The policies the hint maps, in the order it prints them. */
345
357
  const HINT_POLICIES = ['standard', 'required'];