devflow-kit 3.7.0 → 3.9.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 +479 -1
- package/README.md +15 -15
- package/dist/agents/code.md +6 -8
- package/dist/agents/design.md +1 -1
- package/dist/agents/fix.md +7 -8
- package/dist/agents/git.md +3 -3
- 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 +9 -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/curation.js +808 -0
- package/dist/cli/commands/gate-level-prompts.js +428 -0
- package/dist/cli/commands/gates.js +35 -11
- package/dist/cli/commands/init-seed.js +11 -8
- package/dist/cli/commands/init.js +244 -57
- package/dist/cli/commands/memory.js +60 -13
- package/dist/cli/commands/prompt-io.js +37 -0
- package/dist/cli/commands/uninstall.js +157 -17
- package/dist/cli.js +2 -2
- package/dist/commands/bug-analysis.md +25 -36
- package/dist/commands/code-review.md +40 -44
- package/dist/commands/debug.md +5 -5
- package/dist/commands/dynamic-build.md +26 -25
- package/dist/commands/dynamic-plan.md +5 -5
- package/dist/commands/dynamic-profile.md +5 -5
- package/dist/commands/dynamic-tickets.md +5 -5
- package/dist/commands/explore.md +6 -6
- package/dist/commands/implement.md +53 -82
- package/dist/commands/plan.md +6 -6
- package/dist/commands/release.md +5 -5
- package/dist/commands/research.md +6 -6
- package/dist/commands/resolve.md +39 -41
- package/dist/commands/self-review.md +5 -13
- package/dist/core/area-pause-notice.js +44 -0
- 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 +35 -0
- package/dist/core/curation-move.js +35 -0
- package/dist/core/curation-queue-cleanup.js +135 -0
- package/dist/core/curation-store.js +99 -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 +11 -9
- 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 +19 -19
- 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 +159 -0
- package/dist/core/tracker.js +1 -1
- package/dist/{learning-off → curation-off}/agents/code.md +6 -8
- package/dist/{learning-off → curation-off}/agents/fix.md +7 -8
- 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 +8 -1
- package/dist/{learning-off → curation-off}/commands/bug-analysis.md +20 -31
- package/dist/{learning-off → curation-off}/commands/code-review.md +37 -41
- package/dist/{learning-off → curation-off}/commands/debug.md +2 -2
- package/dist/{learning-off → curation-off}/commands/dynamic-build.md +23 -22
- package/dist/{learning-off → curation-off}/commands/explore.md +2 -2
- package/dist/{learning-off → curation-off}/commands/implement.md +50 -79
- package/dist/{learning-off → curation-off}/commands/plan.md +2 -2
- package/dist/{learning-off → curation-off}/commands/resolve.md +36 -38
- package/dist/{learning-off → curation-off}/commands/self-review.md +2 -10
- package/dist/hud/components/{learning-counts.js → curation-counts.js} +63 -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/post-resolution-summary.md +12 -11
- package/dist/skills/git/references/pr/post-review-summary.md +12 -11
- package/dist/skills/git/references/pr/update-pr-evidence.md +2 -2
- package/dist/skills/git/references/pr/validate-branch.md +8 -5
- package/dist/skills/git/references/publication-gate.md +6 -7
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/github/manage-debt.md +6 -2
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/jira/manage-debt.md +6 -2
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/linear/manage-debt.md +6 -2
- 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 +6 -6
- 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/evaluate.md +6 -12
- package/src/assets/agents/fix.mds +7 -6
- package/src/assets/agents/git.mds +3 -3
- 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 +29 -22
- package/src/assets/agents/validate.md +30 -50
- package/src/assets/commands/_partials/_decisions.mds +22 -20
- package/src/assets/commands/_partials/_engine.mds +8 -8
- package/src/assets/commands/_partials/_knowledge.mds +29 -15
- package/src/assets/commands/_partials/_preamble.mds +4 -4
- package/src/assets/commands/_partials/_settings.mds +3 -3
- package/src/assets/commands/_partials/_wave.mds +7 -7
- package/src/assets/commands/bug-analysis.mds +37 -48
- package/src/assets/commands/code-review.mds +53 -57
- 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 +66 -60
- package/src/assets/commands/self-review.mds +20 -20
- package/src/assets/mds/git/_ops.mds +9 -2
- package/src/assets/mds/git/_ops_pr.mds +21 -11
- package/src/assets/mds/git/_ops_steps.mds +20 -4
- package/src/assets/mds/git/_pr.mds +21 -18
- package/src/assets/mds/git/_references.mds +6 -7
- package/src/assets/mds/tracker/_github.mds +1 -4
- package/src/assets/mds/tracker/_jira.mds +1 -4
- package/src/assets/mds/tracker/_linear.mds +1 -4
- package/src/assets/rules/quality.md +1 -0
- package/src/assets/scripts/ci-wait.cjs +389 -92
- package/src/assets/scripts/hooks/background-memory-update +81 -3
- 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 +176 -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} +308 -122
- 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 +42 -22
- package/src/assets/scripts/hooks/lib/tracked-areas.cjs +452 -0
- package/src/assets/scripts/hooks/memory-permission +10 -2
- package/src/assets/scripts/hooks/memory-worker +12 -1
- package/src/assets/scripts/hooks/pre-compact-memory +17 -16
- package/src/assets/scripts/hooks/queue-append +444 -102
- package/src/assets/scripts/hooks/resolve-project-root +2 -2
- package/src/assets/scripts/hooks/session-start-context +198 -72
- package/src/assets/scripts/hooks/session-start-memory +42 -43
- package/src/assets/scripts/lib/project-config.cjs +12 -5
- package/src/assets/scripts/pr-evidence.cjs +5 -2
- package/src/assets/scripts/resolve-base.cjs +1043 -0
- package/src/assets/scripts/resolve-settings.cjs +283 -45
- package/src/assets/scripts/verify-evidence.cjs +307 -75
- package/src/assets/skills/agent-references/SKILL.md +11 -2
- package/src/assets/skills/agent-references/references/commands/implement-levels.md +29 -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 +25 -0
- package/src/assets/skills/agent-references/references/synthesize/review.md +4 -4
- package/src/assets/skills/agent-references/references/triage/severity-tiers.md +43 -0
- package/src/assets/skills/agent-references/references/validate/passed-alone.md +14 -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/cli/commands/learning.js +0 -554
- 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
|
@@ -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
|
|
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('./
|
|
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
|
|
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
|
|
4
|
-
// withDecisionsLock every
|
|
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
|
|
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/
|
|
28
|
-
function
|
|
29
|
-
return path.join(projectRoot, '.devflow',
|
|
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
|
|
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
|
-
//
|
|
97
|
+
// Curation queue files
|
|
53
98
|
// ---------------------------------------------------------------------------
|
|
54
99
|
|
|
55
|
-
/** .devflow/
|
|
56
|
-
function
|
|
57
|
-
return path.join(projectRoot, '.
|
|
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/
|
|
61
|
-
function
|
|
62
|
-
return path.join(projectRoot, '.
|
|
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/
|
|
66
|
-
function
|
|
67
|
-
return path.join(projectRoot, '.
|
|
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
|
-
//
|
|
116
|
+
// Curation content files
|
|
72
117
|
// ---------------------------------------------------------------------------
|
|
73
118
|
|
|
74
|
-
/** .devflow/
|
|
119
|
+
/** .devflow/curation/decisions.md */
|
|
75
120
|
function getDecisionsFilePath(projectRoot) {
|
|
76
|
-
return path.join(projectRoot, '
|
|
121
|
+
return path.join(getCurationDir(projectRoot), 'decisions.md');
|
|
77
122
|
}
|
|
78
123
|
|
|
79
|
-
/** .devflow/
|
|
124
|
+
/** .devflow/curation/pitfalls.md */
|
|
80
125
|
function getPitfallsFilePath(projectRoot) {
|
|
81
|
-
return path.join(projectRoot, '
|
|
126
|
+
return path.join(getCurationDir(projectRoot), 'pitfalls.md');
|
|
82
127
|
}
|
|
83
128
|
|
|
84
|
-
/** .devflow/
|
|
129
|
+
/** .devflow/curation/curation.json — project-level curate agent tuning config */
|
|
85
130
|
function getCurateTuningConfigPath(projectRoot) {
|
|
86
|
-
return path.join(projectRoot,
|
|
131
|
+
return path.join(getCurationDir(projectRoot), CURATION_TUNING_FILE_NAME);
|
|
87
132
|
}
|
|
88
133
|
|
|
89
|
-
/** .devflow/
|
|
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, '
|
|
136
|
+
return path.join(getCurationDir(projectRoot), 'decisions-ledger.jsonl');
|
|
92
137
|
}
|
|
93
138
|
|
|
94
|
-
/** .devflow/
|
|
139
|
+
/** .devflow/curation/decisions-log.jsonl */
|
|
95
140
|
function getDecisionsLogPath(projectRoot) {
|
|
96
|
-
return path.join(projectRoot, '
|
|
141
|
+
return path.join(getCurationDir(projectRoot), 'decisions-log.jsonl');
|
|
97
142
|
}
|
|
98
143
|
|
|
99
|
-
/** .devflow/
|
|
144
|
+
/** .devflow/curation/decisions-log.archive.jsonl — rotated-out stale observing rows (gitignored) */
|
|
100
145
|
function getDecisionsArchivePath(projectRoot) {
|
|
101
|
-
return path.join(projectRoot, '
|
|
146
|
+
return path.join(getCurationDir(projectRoot), 'decisions-log.archive.jsonl');
|
|
102
147
|
}
|
|
103
148
|
|
|
104
|
-
/** .devflow/
|
|
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, '
|
|
151
|
+
return path.join(getCurationDir(projectRoot), 'decisions-history.jsonl');
|
|
107
152
|
}
|
|
108
153
|
|
|
109
|
-
/** .devflow/
|
|
154
|
+
/** .devflow/curation/.decisions.lock — mkdir-based lock directory */
|
|
110
155
|
function getDecisionsLockDir(projectRoot) {
|
|
111
|
-
return path.join(projectRoot, '.
|
|
156
|
+
return path.join(getCurationDir(projectRoot), '.decisions.lock');
|
|
112
157
|
}
|
|
113
158
|
|
|
114
|
-
/** .devflow/
|
|
159
|
+
/** .devflow/curation/index.md — pre-rendered compact index written by render-decisions.cjs */
|
|
115
160
|
function getDecisionsIndexPath(projectRoot) {
|
|
116
|
-
return path.join(projectRoot, '
|
|
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
|
-
|
|
258
|
+
getCurationDir,
|
|
171
259
|
getFeaturesDir,
|
|
172
260
|
getDocsDir,
|
|
173
261
|
// Feature config
|
|
174
262
|
getFeatureConfigPath,
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
//
|
|
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
|
|
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
|
|
19
|
-
// Ledger file: .devflow/
|
|
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,11 @@ const {
|
|
|
48
50
|
isActive,
|
|
49
51
|
isV2,
|
|
50
52
|
readJsonl,
|
|
51
|
-
|
|
53
|
+
hasCurationDir,
|
|
54
|
+
trackedCurationArea,
|
|
52
55
|
withDecisionsLock,
|
|
53
56
|
writeFileAtomic,
|
|
54
|
-
} = require('./
|
|
57
|
+
} = require('./curation-store.cjs');
|
|
55
58
|
|
|
56
59
|
// ---------------------------------------------------------------------------
|
|
57
60
|
// Ledger parsing
|
|
@@ -227,16 +230,26 @@ function renderDecisionsFile(rows, kind) {
|
|
|
227
230
|
/**
|
|
228
231
|
* Render decisions.md, pitfalls.md and index.md from the ledger rows, in memory,
|
|
229
232
|
* in the order they are written: the body files first, the index last. `render`
|
|
230
|
-
* and the
|
|
233
|
+
* and the curation store's writers write exactly these contents, and `--check`
|
|
231
234
|
* compares exactly these.
|
|
232
235
|
*
|
|
236
|
+
* The files belong in `outputDir`, which the caller names: the index lists the
|
|
237
|
+
* absolute path of the decisions and pitfalls files, so it names the folder it is
|
|
238
|
+
* rendered for and no other. The file names are the path helpers'.
|
|
239
|
+
*
|
|
233
240
|
* @param {string} worktreePath - Absolute path to the worktree root.
|
|
234
241
|
* @param {object[]} rows - All rows from the ledger (unfiltered).
|
|
242
|
+
* @param {string} outputDir - Absolute path of the folder the three files belong in.
|
|
235
243
|
* @returns {Array<{ path: string, content: string }>}
|
|
244
|
+
* @throws {TypeError} when `outputDir` is not an absolute path
|
|
236
245
|
*/
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
246
|
+
function renderCurationFiles(worktreePath, rows, outputDir) {
|
|
247
|
+
if (typeof outputDir !== 'string' || !path.isAbsolute(outputDir)) {
|
|
248
|
+
throw new TypeError('renderCurationFiles: outputDir must be an absolute path');
|
|
249
|
+
}
|
|
250
|
+
const inOutputDir = file => path.join(outputDir, path.basename(file));
|
|
251
|
+
const decisionsFilePath = inOutputDir(getDecisionsFilePath(worktreePath));
|
|
252
|
+
const pitfallsFilePath = inOutputDir(getPitfallsFilePath(worktreePath));
|
|
240
253
|
|
|
241
254
|
// Hoist active-row selection: computed once per kind, reused for both the
|
|
242
255
|
// body render and the index build — avoids two redundant selectActiveRows passes.
|
|
@@ -259,7 +272,7 @@ function renderLearningFiles(worktreePath, rows) {
|
|
|
259
272
|
},
|
|
260
273
|
{
|
|
261
274
|
// Compact index: a write-time artifact consumed via plain Read.
|
|
262
|
-
path: getDecisionsIndexPath(worktreePath),
|
|
275
|
+
path: inOutputDir(getDecisionsIndexPath(worktreePath)),
|
|
263
276
|
content: buildIndexContent(activeDecisionRows, activePitfallRows, {
|
|
264
277
|
decisionsFilePath,
|
|
265
278
|
pitfallsFilePath,
|
|
@@ -273,18 +286,18 @@ function renderLearningFiles(worktreePath, rows) {
|
|
|
273
286
|
/**
|
|
274
287
|
* Render decisions.md, pitfalls.md and index.md from the given ledger rows and
|
|
275
288
|
* write them atomically. It takes no lock: the caller holds .decisions.lock
|
|
276
|
-
* (D-ONE-LEARNING-LOCK). It writes only into an existing `.devflow/
|
|
289
|
+
* (D-ONE-LEARNING-LOCK). It writes only into an existing `.devflow/curation/`:
|
|
277
290
|
* without one it throws before writing anything (D-NO-STRAY-TREE).
|
|
278
291
|
*
|
|
279
292
|
* @param {string} worktreePath - Absolute path to the worktree root.
|
|
280
293
|
* @param {object[]} rows - All rows from the ledger (unfiltered).
|
|
281
|
-
* @throws when `<worktreePath>/.devflow/
|
|
294
|
+
* @throws when `<worktreePath>/.devflow/curation/` does not exist, or a write fails
|
|
282
295
|
*/
|
|
283
296
|
function renderAndWriteAll(worktreePath, rows) {
|
|
284
|
-
if (!
|
|
285
|
-
throw new Error(`renderAndWriteAll: no .devflow/
|
|
297
|
+
if (!hasCurationDir(worktreePath)) {
|
|
298
|
+
throw new Error(`renderAndWriteAll: no .devflow/curation/ under ${worktreePath}`);
|
|
286
299
|
}
|
|
287
|
-
const [decisions, pitfalls, index] =
|
|
300
|
+
const [decisions, pitfalls, index] = renderCurationFiles(worktreePath, rows, getCurationDir(worktreePath));
|
|
288
301
|
|
|
289
302
|
// Write body files first; index last. On a crash between body writes and the
|
|
290
303
|
// index write: on the FIRST render the index is absent (reader falls back to
|
|
@@ -365,7 +378,7 @@ function readIfPresent(file) {
|
|
|
365
378
|
/**
|
|
366
379
|
* `render`: read the ledger under .decisions.lock and write the three files from
|
|
367
380
|
* 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
|
|
381
|
+
* is rendered rather than overwritten. Refuses without the curation directory
|
|
369
382
|
* (D-NO-STRAY-TREE), and when it or `.devflow` is a symbolic link (D-NO-LINKED-TREE).
|
|
370
383
|
*
|
|
371
384
|
* @param {string} root
|
|
@@ -385,18 +398,25 @@ function renderCommand(root) {
|
|
|
385
398
|
* `--check`: render in memory and compare with the files on disk, naming each
|
|
386
399
|
* file that differs or is missing; exit 1 on any drift. It writes nothing and
|
|
387
400
|
* takes no lock — taking it would create the lock directory — and refuses
|
|
388
|
-
* without the
|
|
401
|
+
* without the curation directory (D-NO-STRAY-TREE). Where git tracks files in the
|
|
402
|
+
* curation data folders, or does not say whether it does, it refuses first, exit 1:
|
|
403
|
+
* it reads no file there (trackedCurationArea).
|
|
389
404
|
*
|
|
390
405
|
* @param {string} root
|
|
391
406
|
* @returns {number} exit code
|
|
392
407
|
*/
|
|
393
408
|
function checkCommand(root) {
|
|
394
|
-
|
|
395
|
-
|
|
409
|
+
const paused = trackedCurationArea(CLI_NAME, root);
|
|
410
|
+
if (paused !== null) {
|
|
411
|
+
process.stderr.write(`${paused.error.message}\n`);
|
|
412
|
+
return 1;
|
|
413
|
+
}
|
|
414
|
+
if (!hasCurationDir(root)) {
|
|
415
|
+
process.stderr.write(`${CLI_NAME}: no .devflow/curation/ under ${root} — run from the project root\n`);
|
|
396
416
|
return 1;
|
|
397
417
|
}
|
|
398
418
|
let drift = false;
|
|
399
|
-
for (const file of
|
|
419
|
+
for (const file of renderCurationFiles(root, readLedgerRows(root), getCurationDir(root))) {
|
|
400
420
|
const onDisk = readIfPresent(file.path);
|
|
401
421
|
if (onDisk !== file.content) {
|
|
402
422
|
process.stderr.write(`[render-decisions] DRIFT: ${file.path}${onDisk === null ? ' (missing)' : ''}\n`);
|
|
@@ -408,7 +428,7 @@ function checkCommand(root) {
|
|
|
408
428
|
|
|
409
429
|
/**
|
|
410
430
|
* Run the CLI on its arguments and return the exit code. Neither mode creates the
|
|
411
|
-
*
|
|
431
|
+
* curation directory (D-NO-STRAY-TREE).
|
|
412
432
|
*
|
|
413
433
|
* @param {string[]} argv - the arguments after the script path
|
|
414
434
|
* @returns {number}
|
|
@@ -446,7 +466,7 @@ if (require.main === module) {
|
|
|
446
466
|
|
|
447
467
|
module.exports = {
|
|
448
468
|
renderDecisionsFile,
|
|
449
|
-
|
|
469
|
+
renderCurationFiles,
|
|
450
470
|
renderAndWriteAll,
|
|
451
471
|
selectActiveRows,
|
|
452
472
|
selectInactiveRows,
|