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
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
|
+
import * as os from 'os';
|
|
3
4
|
import * as path from 'path';
|
|
4
5
|
import * as p from '@clack/prompts';
|
|
5
6
|
import color from 'picocolors';
|
|
6
|
-
import {
|
|
7
|
+
import { CURATION_TUNING_FILE_NAME, LEGACY_NAMES, getCurationDir, getGlobalCurateTuningConfigPath, } from '../../core/project-paths.js';
|
|
7
8
|
import { loadShippedAgentDefaults } from '../../core/agent-models.js';
|
|
8
9
|
import { readMachineFeature, writeMachineFeature } from '../../core/feature-switch.js';
|
|
9
10
|
import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../core/evidence-policy.js';
|
|
10
11
|
import { getClaudeDirectory, getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
|
|
11
|
-
import {
|
|
12
|
+
import { applyCurationToggle, describeCurationConverge, installedScriptsSkew, readRunningVersion, } from '../../targets/claude-code/curation-install.js';
|
|
12
13
|
import { getLedgerRoot } from '../../core/ledger-root.js';
|
|
13
|
-
import {
|
|
14
|
+
import { drainCurationQueue, drainDisabledCurationQueue, formatCurationNotDrained } from '../../core/curation-queue-cleanup.js';
|
|
14
15
|
import { firstSymbolicLink } from '../../core/linked-path.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
16
|
+
import { describeMoveText, describeOutcome } from './curation-move-step.js';
|
|
17
|
+
import { describeMoveNotice, gatherMoveNotice } from '../../core/curation-move-notice.js';
|
|
18
|
+
import { isMoveSkipReason } from '../../core/curation-move-skip.js';
|
|
19
|
+
import { runSharedMove } from './curation-move-io.js';
|
|
20
|
+
import { formatCurationStoreUnavailable, loadCurationStore, } from '../../core/curation-store.js';
|
|
21
|
+
import { loadCurationMove } from '../../core/curation-move.js';
|
|
17
22
|
// ---------------------------------------------------------------------------
|
|
18
23
|
// Shared helpers
|
|
19
24
|
// ---------------------------------------------------------------------------
|
|
20
|
-
/** What the read and restore commands say about a project with no `.devflow/
|
|
21
|
-
const
|
|
25
|
+
/** What the read and restore commands say about a project with no `.devflow/curation/`. */
|
|
26
|
+
const NO_CURATION_DATA = 'No curation data in this project yet.';
|
|
22
27
|
/**
|
|
23
|
-
* How long a writer here (--restore, --clear, --reset) waits for the
|
|
28
|
+
* How long a writer here (--restore, --clear, --reset) waits for the curation lock
|
|
24
29
|
* before it refuses as busy. An op holds the lock for milliseconds, so a longer
|
|
25
30
|
* wait means a stuck holder, and the store's own 30 s wait would leave the command
|
|
26
31
|
* looking hung.
|
|
@@ -58,22 +63,22 @@ function counted(count, noun) {
|
|
|
58
63
|
return `${count} ${noun}${count === 1 ? '' : 's'}`;
|
|
59
64
|
}
|
|
60
65
|
/**
|
|
61
|
-
* The
|
|
66
|
+
* The curation store (D-LEARNING-STORE-SEAM), or null after reporting why it
|
|
62
67
|
* cannot be used and setting exit code 1.
|
|
63
68
|
*/
|
|
64
69
|
function requireStore() {
|
|
65
|
-
const loaded =
|
|
70
|
+
const loaded = loadCurationStore();
|
|
66
71
|
if (loaded.ok)
|
|
67
72
|
return loaded.value;
|
|
68
|
-
p.log.error(`
|
|
73
|
+
p.log.error(`Curation: ${formatCurationStoreUnavailable(loaded.error)}`);
|
|
69
74
|
process.exitCode = 1;
|
|
70
75
|
return null;
|
|
71
76
|
}
|
|
72
77
|
/** What a writer says when the store refused; `undone` completes "Nothing was …". */
|
|
73
78
|
function storeRefusal(error, undone) {
|
|
74
79
|
switch (error.kind) {
|
|
75
|
-
case 'no-
|
|
76
|
-
case 'busy': return `The
|
|
80
|
+
case 'no-curation-dir': return NO_CURATION_DATA;
|
|
81
|
+
case 'busy': return `The curation store is busy: another run holds its lock. Nothing was ${undone}; try again in a moment.`;
|
|
77
82
|
default: return error.message;
|
|
78
83
|
}
|
|
79
84
|
}
|
|
@@ -104,20 +109,91 @@ async function isDirectory(dir) {
|
|
|
104
109
|
throw err;
|
|
105
110
|
}
|
|
106
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Whether the old ledger folder name is present under `root`'s `.devflow`: a folder, a
|
|
114
|
+
* file or a link, dangling or not. The cheap state check that keeps the store unloaded
|
|
115
|
+
* where there is nothing to move. A probe that fails for another reason reads as
|
|
116
|
+
* present, so the move itself reports the problem.
|
|
117
|
+
*/
|
|
118
|
+
async function oldLedgerFolderPresent(root) {
|
|
119
|
+
try {
|
|
120
|
+
await fs.lstat(path.join(root, '.devflow', LEGACY_NAMES.folder));
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
const code = err.code;
|
|
125
|
+
return code !== 'ENOENT' && code !== 'ENOTDIR';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** The lines a move that did not complete says: its own, or the store's reason when it could not load. */
|
|
129
|
+
function moveProblemLines(outcome) {
|
|
130
|
+
return outcome.kind === 'unavailable'
|
|
131
|
+
? [`The curation data was not moved: ${describeMoveText(outcome.message)}.`]
|
|
132
|
+
: describeOutcome(outcome);
|
|
133
|
+
}
|
|
134
|
+
/** The move notice of a read-only command, or null when there is none. The old ledger is never opened. */
|
|
135
|
+
async function moveReadNotice(ledgerRoot) {
|
|
136
|
+
const newFolderPresent = await isDirectory(getCurationDir(ledgerRoot)).catch(() => false);
|
|
137
|
+
const notice = gatherMoveNotice(ledgerRoot, os.homedir());
|
|
138
|
+
if (notice === null)
|
|
139
|
+
return null;
|
|
140
|
+
if (!newFolderPresent)
|
|
141
|
+
return { kind: 'instead', line: describeMoveNotice(notice) };
|
|
142
|
+
return isMoveSkipReason(notice) ? { kind: 'beside', line: describeMoveNotice(notice) } : null;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The gate every writing subcommand passes first (--restore, --clear, --reset and the
|
|
146
|
+
* project scope of --configure). Installed scripts of another version read and create
|
|
147
|
+
* another ledger folder, so they refuse with the skew line. Then, where the old folder
|
|
148
|
+
* is present, the shared move runs with no wait: a busy lock prints a retry message,
|
|
149
|
+
* a queue claim that holds the move prints its own message, a refused or failed move
|
|
150
|
+
* prints its reason, and a skipped move (git tracks files in a data folder, or git did
|
|
151
|
+
* not answer) prints the fixed notice; in each case nothing is written.
|
|
152
|
+
* `undone` completes "Nothing was …". Answers true when the caller may write.
|
|
153
|
+
*/
|
|
154
|
+
async function prepareToWrite(ledgerRoot, undone) {
|
|
155
|
+
const skew = await installedScriptsSkew(getDevFlowDirectory());
|
|
156
|
+
if (skew !== null) {
|
|
157
|
+
p.log.error(skew);
|
|
158
|
+
process.exitCode = 1;
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (!(await oldLedgerFolderPresent(ledgerRoot)))
|
|
162
|
+
return true;
|
|
163
|
+
const moved = await runSharedMove(ledgerRoot);
|
|
164
|
+
if (moved.kind === 'moved' || moved.kind === 'unchanged')
|
|
165
|
+
return true;
|
|
166
|
+
p.log.error(moved.kind === 'busy'
|
|
167
|
+
? (moved.reason === 'claim' ? claimHoldsMoveLine(undone) : movingLine(undone))
|
|
168
|
+
: describeMoveText(`${moveProblemLines(moved).join(' ')} Nothing was ${undone}.`));
|
|
169
|
+
process.exitCode = 1;
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
/** The line a writer prints when a lock holds the move of the old folder. `undone` completes "Nothing was …". */
|
|
173
|
+
function movingLine(undone) {
|
|
174
|
+
return `Another run is moving this project's curation data. Nothing was ${undone}; try again in a moment.`;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The line a writer prints when a Curate run holds the queue claim, which holds the move:
|
|
178
|
+
* no other run moves anything, and the claim lasts up to 15 minutes. `undone` completes "Nothing was …".
|
|
179
|
+
*/
|
|
180
|
+
function claimHoldsMoveLine(undone) {
|
|
181
|
+
return `A Curate run holds this project's curation queue, and the move waits for it. Nothing was ${undone}; try again after the run finishes (up to 15 minutes).`;
|
|
182
|
+
}
|
|
107
183
|
// ---------------------------------------------------------------------------
|
|
108
184
|
// Sub-command handlers
|
|
109
185
|
// ---------------------------------------------------------------------------
|
|
110
186
|
function printUsage() {
|
|
111
|
-
p.intro(color.bgCyan(color.black('
|
|
112
|
-
p.note(`${color.cyan('devflow
|
|
113
|
-
`${color.cyan('devflow
|
|
114
|
-
`${color.cyan('devflow
|
|
115
|
-
`${color.cyan('devflow
|
|
116
|
-
`${color.cyan('devflow
|
|
117
|
-
`${color.cyan('devflow
|
|
118
|
-
`${color.cyan('devflow
|
|
119
|
-
`${color.cyan('devflow
|
|
120
|
-
`${color.cyan('devflow
|
|
187
|
+
p.intro(color.bgCyan(color.black(' Curation ')));
|
|
188
|
+
p.note(`${color.cyan('devflow curation --enable')} Enable curation in every project (a repository can opt out)\n` +
|
|
189
|
+
`${color.cyan('devflow curation --disable')} Disable curation in every project (drains this project's queue)\n` +
|
|
190
|
+
`${color.cyan('devflow curation --status')} Show curation status and entry counts\n` +
|
|
191
|
+
`${color.cyan('devflow curation --list')} List entries, inactive entries and observations\n` +
|
|
192
|
+
`${color.cyan('devflow curation --show <id>')} Print one entry or observation as JSON\n` +
|
|
193
|
+
`${color.cyan('devflow curation --restore <id>')} Make an inactive entry active again\n` +
|
|
194
|
+
`${color.cyan('devflow curation --configure')} Configuration wizard\n` +
|
|
195
|
+
`${color.cyan('devflow curation --clear')} Drop the observations no entry uses, and drain the queue\n` +
|
|
196
|
+
`${color.cyan('devflow curation --reset')} Remove all curation state files`, 'Usage');
|
|
121
197
|
p.outro(color.dim('Detects architectural decisions and known pitfalls from your sessions'));
|
|
122
198
|
}
|
|
123
199
|
/**
|
|
@@ -157,18 +233,50 @@ function entryCountLines(listing, logRowCount, inactiveStatuses) {
|
|
|
157
233
|
`Observations: ${logRowCount} in the log, ${listing.observations.length} not yet promoted`,
|
|
158
234
|
];
|
|
159
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* The `--status` backup lines: one per backup folder a move left under `.devflow`, with
|
|
238
|
+
* the rows its unswept queue files still hold, one more line for the folders past
|
|
239
|
+
* the listing limit (up to the 500th, which are read), and one for those past the 500th
|
|
240
|
+
* (not read, so their rows are not counted). A backup is inert, so the count tells the
|
|
241
|
+
* user when deleting it loses nothing. A count that fails is a warning, not an error.
|
|
242
|
+
*/
|
|
243
|
+
function backupLines(ledgerRoot) {
|
|
244
|
+
const mover = loadCurationMove();
|
|
245
|
+
if (!mover.ok)
|
|
246
|
+
return { lines: [], warning: `Backup rows were not counted: ${formatCurationStoreUnavailable(mover.error)}` };
|
|
247
|
+
const rows = mover.value.backupPendingRows(ledgerRoot);
|
|
248
|
+
if (!rows.ok)
|
|
249
|
+
return { lines: [], warning: `Backup rows were not counted: ${describeMoveText(rows.error.message)}` };
|
|
250
|
+
const lines = rows.value.backups.map(({ name, pendingRows }) => `Backup: ${describeMoveText(path.join(ledgerRoot, '.devflow', name))}: ${counted(pendingRows, 'pending row')} in the backup`);
|
|
251
|
+
const beyond = rows.value.beyondLimit;
|
|
252
|
+
if (beyond.backups > 0) {
|
|
253
|
+
const one = beyond.backups === 1;
|
|
254
|
+
lines.push(`Backup: ${counted(beyond.backups, 'more backup folder')} ${one ? 'is' : 'are'} not listed above: `
|
|
255
|
+
+ `${counted(beyond.pendingRows, 'pending row')} in ${one ? 'it' : 'them'}`);
|
|
256
|
+
}
|
|
257
|
+
if (beyond.unread > 0) {
|
|
258
|
+
const one = beyond.unread === 1;
|
|
259
|
+
lines.push(`Backup: ${counted(beyond.unread, 'more backup folder')} past the first 500 ${one ? 'is' : 'are'} not read: `
|
|
260
|
+
+ `${one ? 'its' : 'their'} pending rows are not counted`);
|
|
261
|
+
}
|
|
262
|
+
return { lines, warning: null };
|
|
263
|
+
}
|
|
160
264
|
/**
|
|
161
265
|
* `--status`: the machine switch, then the counts the store reads. Reads only:
|
|
162
|
-
* malformed lines are counted, never quarantined, and no scope is checked.
|
|
266
|
+
* malformed lines are counted, never quarantined, and no scope is checked. A project
|
|
267
|
+
* whose ledger still sits in the old folder gets the HUD's pointer (or refused line, or
|
|
268
|
+
* the notice of a skipped move) in place of the counts; a project with both folders and
|
|
269
|
+
* a skipped move gets the notice with the counts; a project with backup folders gets one
|
|
270
|
+
* line for each.
|
|
163
271
|
*/
|
|
164
272
|
async function handleStatus() {
|
|
165
273
|
// D-FEATURES-NARROW-ONLY: the machine switch is the manifest's and reads the
|
|
166
274
|
// same from every directory; a repository layer can only narrow it, and adds a
|
|
167
275
|
// line only when it does. The entry counts are per-project.
|
|
168
|
-
const enabled = await readMachineFeature(getDevFlowDirectory(), '
|
|
276
|
+
const enabled = await readMachineFeature(getDevFlowDirectory(), 'curation');
|
|
169
277
|
const settingsModule = loadSettingsModule();
|
|
170
|
-
const narrowed = enabled ? narrowedSwitchLabel(settingsModule, { dir: process.cwd() }, '
|
|
171
|
-
const stateLine = `
|
|
278
|
+
const narrowed = enabled ? narrowedSwitchLabel(settingsModule, { dir: process.cwd() }, 'curation') : null;
|
|
279
|
+
const stateLine = `Curation: ${enabled ? 'enabled' : 'disabled'}`
|
|
172
280
|
+ (narrowed === null ? '' : `\nEffective here: ${narrowed}`);
|
|
173
281
|
const trackedWarning = personalConfigTrackedWarning(settingsModule, { dir: process.cwd() });
|
|
174
282
|
if (trackedWarning !== null)
|
|
@@ -178,15 +286,24 @@ async function handleStatus() {
|
|
|
178
286
|
p.log.info(`${stateLine}\nEntries: not in a git project`);
|
|
179
287
|
return;
|
|
180
288
|
}
|
|
181
|
-
const
|
|
289
|
+
const notice = await moveReadNotice(ledgerRoot);
|
|
290
|
+
if (notice?.kind === 'instead') {
|
|
291
|
+
p.log.info(`${stateLine}\n${notice.line}`);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const head = notice === null ? stateLine : `${stateLine}\n${notice.line}`;
|
|
295
|
+
const loaded = loadCurationStore();
|
|
182
296
|
if (!loaded.ok) {
|
|
183
|
-
p.log.info(`${
|
|
297
|
+
p.log.info(`${head}\nEntries: unavailable (${formatCurationStoreUnavailable(loaded.error)})`);
|
|
184
298
|
return;
|
|
185
299
|
}
|
|
186
300
|
const store = loaded.value;
|
|
187
|
-
const { ledgerRows, logRows, rejected } = store.
|
|
301
|
+
const { ledgerRows, logRows, rejected } = store.readCurationState(ledgerRoot);
|
|
188
302
|
const listing = store.buildListing(ledgerRows, logRows, { rejected });
|
|
189
|
-
|
|
303
|
+
const backups = backupLines(ledgerRoot);
|
|
304
|
+
p.log.info([head, ...entryCountLines(listing, logRows.length, store.INACTIVE_STATUSES), ...backups.lines].join('\n'));
|
|
305
|
+
if (backups.warning !== null)
|
|
306
|
+
p.log.warn(backups.warning);
|
|
190
307
|
const { ledger, log } = listing.malformed;
|
|
191
308
|
if (ledger + log > 0) {
|
|
192
309
|
p.log.warn(`Malformed lines skipped: ${ledger} in the ledger, ${log} in the log. ` +
|
|
@@ -195,17 +312,25 @@ async function handleStatus() {
|
|
|
195
312
|
}
|
|
196
313
|
/**
|
|
197
314
|
* `--list`: the store's listing — the same text json-helper's `list` op prints —
|
|
198
|
-
* on stdout. Read-only. Outside a git project it reads the current directory.
|
|
315
|
+
* on stdout. Read-only. Outside a git project it reads the current directory. Where
|
|
316
|
+
* the ledger still sits in the old folder it prints the pointer, the refused line or the
|
|
317
|
+
* notice of a skipped move in place of the listing; with both folders and a skipped move
|
|
318
|
+
* it prints the notice before the listing.
|
|
199
319
|
*/
|
|
200
320
|
async function handleList() {
|
|
201
321
|
const root = (await getLedgerRoot()) ?? process.cwd();
|
|
322
|
+
const notice = await moveReadNotice(root);
|
|
323
|
+
if (notice !== null)
|
|
324
|
+
p.log.info(notice.line);
|
|
325
|
+
if (notice?.kind === 'instead')
|
|
326
|
+
return;
|
|
202
327
|
const store = requireStore();
|
|
203
328
|
if (!store)
|
|
204
329
|
return;
|
|
205
330
|
const listed = store.readListing(root);
|
|
206
331
|
if (!listed.ok) {
|
|
207
|
-
if (listed.error.kind === 'no-
|
|
208
|
-
p.log.info(
|
|
332
|
+
if (listed.error.kind === 'no-curation-dir') {
|
|
333
|
+
p.log.info(NO_CURATION_DATA);
|
|
209
334
|
return;
|
|
210
335
|
}
|
|
211
336
|
p.log.error(listed.error.message);
|
|
@@ -217,16 +342,27 @@ async function handleList() {
|
|
|
217
342
|
/**
|
|
218
343
|
* `--show <id>`: one entry, by its anchor or its observation id, as the JSON
|
|
219
344
|
* json-helper's `show` op prints, made terminal-safe. Read-only. Outside a git
|
|
220
|
-
* project it reads the current directory.
|
|
345
|
+
* project it reads the current directory. Where the ledger still sits in the old
|
|
346
|
+
* folder it prints the pointer, the refused line or the notice of a skipped move as the
|
|
347
|
+
* refusal; with both folders and a skipped move it prints the notice on stderr and goes on.
|
|
221
348
|
*/
|
|
222
349
|
async function handleShow(key) {
|
|
223
350
|
const root = (await getLedgerRoot()) ?? process.cwd();
|
|
351
|
+
const notice = await moveReadNotice(root);
|
|
352
|
+
if (notice?.kind === 'instead') {
|
|
353
|
+
p.log.error(notice.line);
|
|
354
|
+
process.exitCode = 1;
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
// The JSON goes to stdout, so the notice of a skip goes to stderr.
|
|
358
|
+
if (notice !== null)
|
|
359
|
+
process.stderr.write(`${notice.line}\n`);
|
|
224
360
|
const store = requireStore();
|
|
225
361
|
if (!store)
|
|
226
362
|
return;
|
|
227
363
|
const shown = store.showByKey(root, key);
|
|
228
364
|
if (!shown.ok) {
|
|
229
|
-
p.log.error(shown.error.kind === 'no-
|
|
365
|
+
p.log.error(shown.error.kind === 'no-curation-dir' ? NO_CURATION_DATA : shown.error.message);
|
|
230
366
|
process.exitCode = 1;
|
|
231
367
|
return;
|
|
232
368
|
}
|
|
@@ -235,7 +371,8 @@ async function handleShow(key) {
|
|
|
235
371
|
/**
|
|
236
372
|
* `--restore <id>`: make an inactive entry active again through the store's
|
|
237
373
|
* restoreAnchor, which clears its notes and its verification so maintenance
|
|
238
|
-
* reviews it again, and re-renders the files.
|
|
374
|
+
* reviews it again, and re-renders the files. It writes through the store lock, so
|
|
375
|
+
* it moves the old folder first (prepareToWrite). A refusal writes nothing.
|
|
239
376
|
*/
|
|
240
377
|
async function handleRestore(anchor) {
|
|
241
378
|
const ledgerRoot = await requireLedgerRoot('restore not performed');
|
|
@@ -251,6 +388,8 @@ async function handleRestore(anchor) {
|
|
|
251
388
|
process.exitCode = 1;
|
|
252
389
|
return;
|
|
253
390
|
}
|
|
391
|
+
if (!(await prepareToWrite(ledgerRoot, 'restored')))
|
|
392
|
+
return;
|
|
254
393
|
const restored = store.restoreAnchor(ledgerRoot, anchor, { timeoutMs: LOCK_WAIT_MS });
|
|
255
394
|
if (!restored.ok) {
|
|
256
395
|
p.log.error(storeRefusal(restored.error, 'restored'));
|
|
@@ -260,7 +399,7 @@ async function handleRestore(anchor) {
|
|
|
260
399
|
p.log.success(`Restored ${restored.value.anchor_id} (${restored.value.status}); it is due for review again.`);
|
|
261
400
|
}
|
|
262
401
|
/**
|
|
263
|
-
* The scope choices of `devflow
|
|
402
|
+
* The scope choices of `devflow curation --configure`.
|
|
264
403
|
*
|
|
265
404
|
* D-LEARNING-MODEL-PRECEDENCE: the session-start hook takes the first layer that
|
|
266
405
|
* supplies a model — the project file, then a `devflow agents` Curate mapping,
|
|
@@ -268,14 +407,14 @@ async function handleRestore(anchor) {
|
|
|
268
407
|
* such a mapping, and its hint says so.
|
|
269
408
|
*/
|
|
270
409
|
export const CONFIGURE_SCOPE_OPTIONS = [
|
|
271
|
-
{ value: 'project', label: 'Project', hint: 'This project only (.devflow/
|
|
410
|
+
{ value: 'project', label: 'Project', hint: 'This project only (.devflow/curation/curation.json)' },
|
|
272
411
|
{
|
|
273
412
|
value: 'global',
|
|
274
413
|
label: 'Global',
|
|
275
|
-
hint: 'All projects (~/.devflow/
|
|
414
|
+
hint: 'All projects (~/.devflow/curation.json); a devflow agents Curate mapping takes precedence over it',
|
|
276
415
|
},
|
|
277
416
|
];
|
|
278
|
-
/** The models `devflow
|
|
417
|
+
/** The models `devflow curation --configure` offers, each with what it is good for. */
|
|
279
418
|
const CURATE_MODEL_CHOICES = [
|
|
280
419
|
{ value: 'opus', label: 'Opus', note: 'highest quality for detection + curation judgment' },
|
|
281
420
|
{ value: 'sonnet', label: 'Sonnet', note: 'good balance of quality and speed' },
|
|
@@ -298,7 +437,7 @@ export function curateModelOptions(shippedModel) {
|
|
|
298
437
|
}));
|
|
299
438
|
}
|
|
300
439
|
async function handleConfigure() {
|
|
301
|
-
p.intro(color.bgCyan(color.black('
|
|
440
|
+
p.intro(color.bgCyan(color.black(' Curation Configuration ')));
|
|
302
441
|
const shippedModel = (await loadShippedAgentDefaults()).curate?.model;
|
|
303
442
|
const model = await p.select({
|
|
304
443
|
message: 'Model for decision detection',
|
|
@@ -332,36 +471,52 @@ async function handleConfigure() {
|
|
|
332
471
|
if (scope === 'global') {
|
|
333
472
|
const globalDir = getDevFlowDirectory();
|
|
334
473
|
await fs.mkdir(globalDir, { recursive: true });
|
|
335
|
-
|
|
336
|
-
|
|
474
|
+
const globalConfigPath = getGlobalCurateTuningConfigPath(globalDir);
|
|
475
|
+
await fs.writeFile(globalConfigPath, configJson, 'utf-8');
|
|
476
|
+
p.log.success(`Global config written to ${color.dim(globalConfigPath)}`);
|
|
337
477
|
}
|
|
338
478
|
else {
|
|
339
479
|
// D-LEDGER-MAIN-WORKTREE: session-start-context reads the project tuning config
|
|
340
|
-
// from the ledger ($LEDGER_ROOT/.devflow/
|
|
480
|
+
// from the ledger ($LEDGER_ROOT/.devflow/curation/), so write it there — the
|
|
341
481
|
// main checkout in a linked worktree; the current directory outside git.
|
|
342
482
|
const projectRoot = (await getLedgerRoot()) ?? process.cwd();
|
|
343
|
-
|
|
344
|
-
|
|
483
|
+
// Skew or a move that cannot run (busy, refused, failed) writes nothing, so the
|
|
484
|
+
// config never lands in a folder the readers do not use.
|
|
485
|
+
if (!(await prepareToWrite(projectRoot, 'written')))
|
|
486
|
+
return;
|
|
487
|
+
// The creator rule: this writer makes the new folder only when the old name is
|
|
488
|
+
// absent, so it looks once more. A folder of the old name can stand again after the
|
|
489
|
+
// move, and the new folder made beside it would hide its ledger from every reader.
|
|
490
|
+
if (await oldLedgerFolderPresent(projectRoot)) {
|
|
491
|
+
p.log.error(movingLine('written'));
|
|
492
|
+
process.exitCode = 1;
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
// The folder that is checked is the folder that is written.
|
|
496
|
+
const curationDir = getCurationDir(projectRoot);
|
|
497
|
+
const projectConfigPath = path.join(curationDir, CURATION_TUNING_FILE_NAME);
|
|
345
498
|
// D-CLI-NO-SYMLINK (core/linked-path.ts): nothing is written through a .devflow,
|
|
346
|
-
// a
|
|
347
|
-
const linked = await firstSymbolicLink([path.dirname(
|
|
499
|
+
// a curation folder or a curation.json that is a symbolic link.
|
|
500
|
+
const linked = await firstSymbolicLink([path.dirname(curationDir), curationDir, projectConfigPath]);
|
|
348
501
|
if (linked !== null) {
|
|
349
502
|
p.log.error(`Project config not written: ${linked} is a symbolic link, and devflow writes nothing through one`);
|
|
350
503
|
process.exitCode = 1;
|
|
351
504
|
return;
|
|
352
505
|
}
|
|
353
|
-
await fs.mkdir(
|
|
506
|
+
await fs.mkdir(curationDir, { recursive: true });
|
|
354
507
|
await fs.writeFile(projectConfigPath, configJson, 'utf-8');
|
|
355
508
|
p.log.success(`Project config written to ${color.dim(projectConfigPath)}`);
|
|
356
509
|
}
|
|
357
510
|
p.outro(color.green('Configuration saved.'));
|
|
358
511
|
}
|
|
359
512
|
/**
|
|
360
|
-
* `--reset`: remove all
|
|
513
|
+
* `--reset`: remove all curation state — entries, observations, rendered files,
|
|
361
514
|
* the tuning config, and the queue with its claim — through the store's
|
|
362
|
-
*
|
|
363
|
-
* (D-ONE-LEARNING-LOCK, D-RESET-UNDER-LOCK).
|
|
364
|
-
*
|
|
515
|
+
* resetCuration, under the curation lock every curation writer takes
|
|
516
|
+
* (D-ONE-LEARNING-LOCK, D-RESET-UNDER-LOCK). The old folder moves first
|
|
517
|
+
* (prepareToWrite), so the reset reaches the data the hooks use; a backup folder a
|
|
518
|
+
* move left stays in place. A project with no curation directory has nothing to
|
|
519
|
+
* reset, and nothing is created there (D-NO-STRAY-TREE).
|
|
365
520
|
*/
|
|
366
521
|
async function handleReset() {
|
|
367
522
|
const ledgerRoot = await requireLedgerRoot('reset not performed');
|
|
@@ -370,30 +525,36 @@ async function handleReset() {
|
|
|
370
525
|
const store = requireStore();
|
|
371
526
|
if (!store)
|
|
372
527
|
return;
|
|
373
|
-
if (!(await
|
|
374
|
-
|
|
528
|
+
if (!(await prepareToWrite(ledgerRoot, 'reset')))
|
|
529
|
+
return;
|
|
530
|
+
if (!(await isDirectory(getCurationDir(ledgerRoot)))) {
|
|
531
|
+
p.log.info('No curation data to reset.');
|
|
375
532
|
return;
|
|
376
533
|
}
|
|
377
|
-
const proceed = await confirmOnTerminal('Remove all
|
|
534
|
+
const proceed = await confirmOnTerminal('Remove all curation state files? This cannot be undone.', 'Reset cancelled.');
|
|
378
535
|
if (!proceed)
|
|
379
536
|
return;
|
|
380
|
-
const reset = store.
|
|
537
|
+
const reset = store.resetCuration(ledgerRoot, { timeoutMs: LOCK_WAIT_MS });
|
|
381
538
|
if (!reset.ok) {
|
|
382
|
-
if (reset.error.kind === 'no-
|
|
383
|
-
p.log.info('No
|
|
539
|
+
if (reset.error.kind === 'no-curation-dir') {
|
|
540
|
+
p.log.info('No curation data to reset.');
|
|
384
541
|
return;
|
|
385
542
|
}
|
|
386
543
|
p.log.error(storeRefusal(reset.error, 'reset'));
|
|
387
544
|
process.exitCode = 1;
|
|
388
545
|
return;
|
|
389
546
|
}
|
|
390
|
-
p.log.success('Reset complete — removed .devflow/
|
|
547
|
+
p.log.success('Reset complete — removed .devflow/curation/ state.');
|
|
391
548
|
}
|
|
392
549
|
/**
|
|
393
550
|
* `--clear`: drop the observations no entry uses (D-CLEAR-UNREFERENCED), then
|
|
394
|
-
* drain the
|
|
551
|
+
* drain the curation queue. The queue is drained only once the clear succeeded:
|
|
395
552
|
* a busy lock or a refusal leaves both the log and the queue as they were, so a
|
|
396
|
-
* failed clear changes nothing.
|
|
553
|
+
* failed clear changes nothing. The old folder moves first (prepareToWrite), so the
|
|
554
|
+
* clear and the drain reach the data the hooks use; a backup folder a move left
|
|
555
|
+
* stays in place. When git tracks any entry under a data folder or a backup folder, or
|
|
556
|
+
* does not answer, the drain deletes no queue file and a warning carries the fixed
|
|
557
|
+
* notice.
|
|
397
558
|
*/
|
|
398
559
|
async function handleClear() {
|
|
399
560
|
const ledgerRoot = await requireLedgerRoot('clear not performed');
|
|
@@ -402,8 +563,10 @@ async function handleClear() {
|
|
|
402
563
|
const store = requireStore();
|
|
403
564
|
if (!store)
|
|
404
565
|
return;
|
|
405
|
-
if (!(await
|
|
406
|
-
|
|
566
|
+
if (!(await prepareToWrite(ledgerRoot, 'cleared')))
|
|
567
|
+
return;
|
|
568
|
+
if (!(await isDirectory(getCurationDir(ledgerRoot)))) {
|
|
569
|
+
p.log.info('No curation data to clear.');
|
|
407
570
|
return;
|
|
408
571
|
}
|
|
409
572
|
const proceed = await confirmOnTerminal('Drop every observation no entry uses? Entries and the observations they use are kept. This cannot be undone.', 'Clear cancelled.');
|
|
@@ -411,8 +574,8 @@ async function handleClear() {
|
|
|
411
574
|
return;
|
|
412
575
|
const cleared = store.clearUnreferenced(ledgerRoot, { timeoutMs: LOCK_WAIT_MS });
|
|
413
576
|
if (!cleared.ok) {
|
|
414
|
-
if (cleared.error.kind === 'no-
|
|
415
|
-
p.log.info('No
|
|
577
|
+
if (cleared.error.kind === 'no-curation-dir') {
|
|
578
|
+
p.log.info('No curation data to clear.');
|
|
416
579
|
return;
|
|
417
580
|
}
|
|
418
581
|
p.log.error(storeRefusal(cleared.error, 'cleared'));
|
|
@@ -421,15 +584,15 @@ async function handleClear() {
|
|
|
421
584
|
}
|
|
422
585
|
// A mid-run Curate agent whose claimed batch vanishes stops without further
|
|
423
586
|
// writes — the desired outcome of clearing.
|
|
424
|
-
const drain = await
|
|
587
|
+
const drain = await drainCurationQueue(ledgerRoot);
|
|
425
588
|
p.log.success(`Cleared ${counted(cleared.value.cleared, 'observation')} no entry uses and kept ${cleared.value.kept} ` +
|
|
426
|
-
`that entries use${drain.drained ? '; drained the
|
|
589
|
+
`that entries use${drain.drained ? '; drained the curation queue.' : '.'}`);
|
|
427
590
|
if (!drain.drained)
|
|
428
|
-
p.log.warn(
|
|
591
|
+
p.log.warn(formatCurationNotDrained(drain));
|
|
429
592
|
}
|
|
430
593
|
/**
|
|
431
594
|
* Make the installed prompts match the switch just written
|
|
432
|
-
* (D-LEARNING-VARIANT-INSTALL): the
|
|
595
|
+
* (D-LEARNING-VARIANT-INSTALL): the curation-on or curation-off commands and
|
|
433
596
|
* agents, and the apply-decisions skill. The same convergence `devflow init` runs,
|
|
434
597
|
* followed by reapplying the saved agent model mapping so `devflow agents`
|
|
435
598
|
* overrides survive.
|
|
@@ -437,70 +600,110 @@ async function handleClear() {
|
|
|
437
600
|
* Warn-not-fail: the switch itself is already recorded, and either mismatch is
|
|
438
601
|
* safe (an on variant is still gated at run time, an off variant loads no
|
|
439
602
|
* decisions), so a problem here never changes the exit code.
|
|
603
|
+
*
|
|
604
|
+
* Answers whether the converge ran, which means the installed files are the ones
|
|
605
|
+
* this CLI writes. The toggle moves the ledger folder only then: under version skew
|
|
606
|
+
* (or when that could not be told) it moves nothing.
|
|
440
607
|
*/
|
|
441
608
|
async function convergeInstalledVariants(enabled) {
|
|
442
609
|
try {
|
|
443
|
-
const outcome = await
|
|
610
|
+
const outcome = await applyCurationToggle({
|
|
444
611
|
claudeDir: getClaudeDirectory(),
|
|
445
612
|
devflowDir: getDevFlowDirectory(),
|
|
446
|
-
|
|
613
|
+
curation: enabled,
|
|
447
614
|
runningVersion: await readRunningVersion(),
|
|
448
615
|
warn: (msg) => p.log.warn(msg),
|
|
449
616
|
});
|
|
450
617
|
if (outcome.kind === 'skipped') {
|
|
451
618
|
p.log.warn(outcome.message);
|
|
452
|
-
return;
|
|
619
|
+
return 'not-converged';
|
|
453
620
|
}
|
|
454
|
-
const changed =
|
|
621
|
+
const changed = describeCurationConverge(outcome.result, enabled);
|
|
455
622
|
if (changed !== null)
|
|
456
623
|
p.log.info(color.dim(`Installed prompts: ${changed}`));
|
|
624
|
+
return 'converged';
|
|
457
625
|
}
|
|
458
626
|
catch (err) {
|
|
459
|
-
p.log.warn(`Could not update the installed prompts for the new
|
|
627
|
+
p.log.warn(`Could not update the installed prompts for the new curation setting — run ${color.cyan('devflow init')}: ${String(err)}`);
|
|
628
|
+
return 'not-converged';
|
|
460
629
|
}
|
|
461
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* The toggle's move of the current project's ledger folder: tried once with no wait,
|
|
633
|
+
* and only where the old folder is present. A busy move is not an error — the project
|
|
634
|
+
* moves at its next event — and a refusal, a failure or a skip (git tracks files in a
|
|
635
|
+
* data folder, or git did not answer) is a warning: the switch is already recorded, so
|
|
636
|
+
* none of them changes the exit code. Answers the skip reason whose fixed notice it
|
|
637
|
+
* printed, or null when it printed none.
|
|
638
|
+
*/
|
|
639
|
+
async function tryMoveForToggle(ledgerRoot) {
|
|
640
|
+
if (!(await oldLedgerFolderPresent(ledgerRoot)))
|
|
641
|
+
return null;
|
|
642
|
+
const outcome = await runSharedMove(ledgerRoot);
|
|
643
|
+
if (outcome.kind === 'moved' || outcome.kind === 'unchanged')
|
|
644
|
+
return null;
|
|
645
|
+
for (const line of moveProblemLines(outcome)) {
|
|
646
|
+
if (outcome.kind === 'busy')
|
|
647
|
+
p.log.info(color.dim(describeMoveText(line)));
|
|
648
|
+
else
|
|
649
|
+
p.log.warn(describeMoveText(line));
|
|
650
|
+
}
|
|
651
|
+
return outcome.kind === 'left-alone' ? outcome.reason : null;
|
|
652
|
+
}
|
|
462
653
|
/**
|
|
463
654
|
* `--enable` / `--disable`: the machine-wide switch (D-FEATURES-NARROW-ONLY),
|
|
464
|
-
* converged exactly as `devflow init --
|
|
465
|
-
* the manifest value, the installed prompt
|
|
466
|
-
* (D-LEARNING-VARIANT-INSTALL),
|
|
467
|
-
*
|
|
655
|
+
* converged exactly as `devflow init --curation / --no-curation` converges it —
|
|
656
|
+
* the manifest value (both machine keys, written first), the installed prompt
|
|
657
|
+
* variants and skill (D-LEARNING-VARIANT-INSTALL), the current project's ledger
|
|
658
|
+
* folder move, and on disable a drained queue in every ledger folder that exists and
|
|
659
|
+
* in the backups a move left. The drain deletes no queue file, and prints the fixed
|
|
660
|
+
* notice of a skipped move once, when git tracks any entry under a data folder or a
|
|
661
|
+
* backup folder, or does not answer.
|
|
662
|
+
* Never requires a git root: the switch is not a per-project setting.
|
|
663
|
+
*
|
|
664
|
+
* Under version skew the toggle records the switch and moves nothing; otherwise it
|
|
665
|
+
* tries the shared move with no wait. Only `--disable` drains, skew included.
|
|
468
666
|
*/
|
|
469
667
|
async function handleToggle(enabled) {
|
|
470
|
-
const recorded = await writeMachineFeature(getDevFlowDirectory(), '
|
|
668
|
+
const recorded = await writeMachineFeature(getDevFlowDirectory(), 'curation', enabled);
|
|
471
669
|
if (!recorded.ok) {
|
|
472
670
|
p.log.error(`Devflow is not installed on this machine — run ${color.cyan('devflow init')} first`);
|
|
473
671
|
process.exitCode = 1;
|
|
474
672
|
return;
|
|
475
673
|
}
|
|
476
|
-
await convergeInstalledVariants(enabled);
|
|
674
|
+
const converged = await convergeInstalledVariants(enabled);
|
|
675
|
+
const ledgerRoot = await getLedgerRoot();
|
|
676
|
+
const shownSkip = converged === 'converged' && ledgerRoot ? await tryMoveForToggle(ledgerRoot) : null;
|
|
477
677
|
if (enabled) {
|
|
478
|
-
p.log.success('
|
|
678
|
+
p.log.success('Curation enabled in every project (a repository can opt out)');
|
|
479
679
|
p.log.info(color.dim('Architectural decisions and pitfalls will be detected from your sessions'));
|
|
480
680
|
return;
|
|
481
681
|
}
|
|
482
|
-
// Drain the current project's
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
|
|
682
|
+
// Drain the current project's curation (decisions-detection) queue, in each ledger
|
|
683
|
+
// folder that exists and in the backups a move left, so stale turns don't process on
|
|
684
|
+
// re-enable. A mid-run Curate agent whose claimed batch vanishes aborts without
|
|
685
|
+
// changes — the desired outcome of disabling. A project whose data folders git tracks
|
|
686
|
+
// keeps every file: the switch is recorded, and the drain warns instead.
|
|
486
687
|
if (ledgerRoot) {
|
|
487
|
-
const drain = await
|
|
688
|
+
const drain = await drainDisabledCurationQueue(ledgerRoot);
|
|
488
689
|
if (!drain.drained)
|
|
489
|
-
p.log.warn(
|
|
690
|
+
p.log.warn(formatCurationNotDrained(drain, shownSkip));
|
|
691
|
+
else if (drain.warning !== undefined)
|
|
692
|
+
p.log.warn(drain.warning);
|
|
490
693
|
}
|
|
491
|
-
p.log.success('
|
|
694
|
+
p.log.success('Curation disabled in every project');
|
|
492
695
|
}
|
|
493
|
-
export const
|
|
494
|
-
.description('Enable or disable
|
|
495
|
-
.option('--enable', 'Enable
|
|
496
|
-
.option('--disable', 'Disable
|
|
497
|
-
.option('--status', 'Show
|
|
696
|
+
export const curationCommand = new Command('curation')
|
|
697
|
+
.description('Enable or disable curation (decision/pitfall detection) in every project')
|
|
698
|
+
.option('--enable', 'Enable curation in every project (a repository can opt out)')
|
|
699
|
+
.option('--disable', 'Disable curation in every project')
|
|
700
|
+
.option('--status', 'Show curation status and entry counts')
|
|
498
701
|
.option('--list', 'List entries, inactive entries with their notes, and observations')
|
|
499
702
|
.option('--show <id>', 'Print one entry (ADR-NNN or PF-NNN) or observation (obs_...) as JSON')
|
|
500
703
|
.option('--restore <id>', 'Make an inactive entry active again')
|
|
501
|
-
.option('--configure', 'Interactive configuration wizard for
|
|
502
|
-
.option('--clear', 'Drop the observations no entry uses, and drain the
|
|
503
|
-
.option('--reset', 'Remove all
|
|
704
|
+
.option('--configure', 'Interactive configuration wizard for curation.json')
|
|
705
|
+
.option('--clear', 'Drop the observations no entry uses, and drain the curation queue')
|
|
706
|
+
.option('--reset', 'Remove all curation state files and artifacts')
|
|
504
707
|
.action(async (options) => {
|
|
505
708
|
const knownFlags = [
|
|
506
709
|
'enable', 'disable', 'status', 'list', 'show', 'restore', 'configure',
|
|
@@ -551,4 +754,4 @@ export const learningCommand = new Command('learning')
|
|
|
551
754
|
return;
|
|
552
755
|
}
|
|
553
756
|
});
|
|
554
|
-
//# sourceMappingURL=
|
|
757
|
+
//# sourceMappingURL=curation.js.map
|