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.
- package/CHANGELOG.md +438 -1
- package/README.md +15 -11
- package/dist/agents/code.md +9 -15
- package/dist/agents/design.md +1 -1
- package/dist/agents/fix.md +11 -13
- package/dist/agents/git.md +1 -1
- package/dist/agents/harden.md +1 -1
- package/dist/agents/refactor.md +7 -10
- package/dist/agents/review.md +6 -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 +299 -0
- package/dist/cli/commands/init-seed.js +16 -5
- package/dist/cli/commands/init.js +263 -54
- package/dist/cli/commands/memory.js +20 -2
- package/dist/cli/commands/prompt-io.js +37 -0
- package/dist/cli/commands/uninstall.js +81 -9
- package/dist/cli.js +4 -2
- package/dist/commands/bug-analysis.md +25 -36
- package/dist/commands/code-review.md +77 -73
- package/dist/commands/debug.md +5 -5
- package/dist/commands/dynamic-build.md +76 -83
- 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 +68 -102
- package/dist/commands/plan.md +19 -29
- package/dist/commands/release.md +5 -5
- package/dist/commands/research.md +6 -6
- package/dist/commands/resolve.md +29 -31
- package/dist/commands/self-review.md +5 -13
- 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 +48 -0
- package/dist/core/evidence-policy.js +18 -6
- package/dist/core/feature-switch.js +95 -19
- package/dist/core/fs-atomic.js +1 -1
- package/dist/core/gate-level.js +221 -0
- 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 +84 -24
- package/dist/core/migrations.js +1 -13
- package/dist/core/model-discovery.js +7 -1
- 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 +6 -34
- package/dist/{learning-off → curation-off}/agents/code.md +9 -15
- package/dist/{learning-off → curation-off}/agents/fix.md +11 -13
- package/dist/{learning-off → curation-off}/agents/refactor.md +7 -10
- package/dist/{learning-off → curation-off}/agents/review.md +5 -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 +74 -70
- package/dist/{learning-off → curation-off}/commands/debug.md +2 -2
- package/dist/{learning-off → curation-off}/commands/dynamic-build.md +73 -80
- package/dist/{learning-off → curation-off}/commands/explore.md +2 -2
- package/dist/{learning-off → curation-off}/commands/implement.md +65 -99
- package/dist/{learning-off → curation-off}/commands/plan.md +15 -25
- package/dist/{learning-off → curation-off}/commands/resolve.md +26 -28
- package/dist/{learning-off → curation-off}/commands/self-review.md +2 -10
- 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 +20 -14
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +20 -20
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +20 -20
- package/dist/targets/claude-code/{learning-install.js → curation-install.js} +99 -67
- 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 +16 -17
- package/src/assets/agents/code.mds +4 -7
- package/src/assets/agents/curate.md +12 -14
- 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 +45 -43
- package/src/assets/agents/skim.mds +11 -11
- package/src/assets/agents/synthesize.md +2 -2
- 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 +3 -3
- package/src/assets/commands/_partials/_wave.mds +17 -24
- package/src/assets/commands/bug-analysis.mds +37 -48
- package/src/assets/commands/code-review.mds +90 -86
- package/src/assets/commands/debug.mds +14 -14
- package/src/assets/commands/dynamic-build.mds +78 -78
- 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 +92 -118
- package/src/assets/commands/plan.mds +41 -51
- 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 +13 -7
- package/src/assets/mds/git/_pr.mds +7 -4
- package/src/assets/mds/tracker/_common.mds +23 -12
- package/src/assets/mds/tracker/_github.mds +2 -9
- package/src/assets/mds/tracker/_jira.mds +2 -15
- package/src/assets/mds/tracker/_linear.mds +2 -15
- package/src/assets/rules/quality.md +1 -0
- package/src/assets/scripts/ci-wait.cjs +22 -4
- package/src/assets/scripts/claude-md-audit.cjs +66 -51
- package/src/assets/scripts/hooks/background-memory-update +406 -97
- 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 +145 -51
- package/src/assets/scripts/hooks/json-parse +19 -4
- 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-permission +358 -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 +145 -71
- package/src/assets/scripts/hooks/session-start-memory +27 -46
- package/src/assets/scripts/lib/project-config.cjs +31 -9
- package/src/assets/scripts/resolve-base.cjs +1043 -0
- package/src/assets/scripts/resolve-settings.cjs +238 -46
- package/src/assets/skills/agent-references/SKILL.md +9 -2
- package/src/assets/skills/agent-references/references/commands/code-review-levels.md +19 -0
- 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/plan-levels.md +40 -0
- package/src/assets/skills/agent-references/references/commands/resolve-levels.md +24 -0
- package/src/assets/skills/agent-references/references/synthesize/planning.md +9 -22
- package/src/assets/skills/agent-references/references/synthesize/review.md +11 -11
- 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
|
@@ -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,10 @@ const {
|
|
|
48
50
|
isActive,
|
|
49
51
|
isV2,
|
|
50
52
|
readJsonl,
|
|
51
|
-
|
|
53
|
+
hasCurationDir,
|
|
52
54
|
withDecisionsLock,
|
|
53
55
|
writeFileAtomic,
|
|
54
|
-
} = require('./
|
|
56
|
+
} = require('./curation-store.cjs');
|
|
55
57
|
|
|
56
58
|
// ---------------------------------------------------------------------------
|
|
57
59
|
// Ledger parsing
|
|
@@ -227,16 +229,26 @@ function renderDecisionsFile(rows, kind) {
|
|
|
227
229
|
/**
|
|
228
230
|
* Render decisions.md, pitfalls.md and index.md from the ledger rows, in memory,
|
|
229
231
|
* in the order they are written: the body files first, the index last. `render`
|
|
230
|
-
* and the
|
|
232
|
+
* and the curation store's writers write exactly these contents, and `--check`
|
|
231
233
|
* compares exactly these.
|
|
232
234
|
*
|
|
235
|
+
* The files belong in `outputDir`, which the caller names: the index lists the
|
|
236
|
+
* absolute path of the decisions and pitfalls files, so it names the folder it is
|
|
237
|
+
* rendered for and no other. The file names are the path helpers'.
|
|
238
|
+
*
|
|
233
239
|
* @param {string} worktreePath - Absolute path to the worktree root.
|
|
234
240
|
* @param {object[]} rows - All rows from the ledger (unfiltered).
|
|
241
|
+
* @param {string} outputDir - Absolute path of the folder the three files belong in.
|
|
235
242
|
* @returns {Array<{ path: string, content: string }>}
|
|
243
|
+
* @throws {TypeError} when `outputDir` is not an absolute path
|
|
236
244
|
*/
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
245
|
+
function renderCurationFiles(worktreePath, rows, outputDir) {
|
|
246
|
+
if (typeof outputDir !== 'string' || !path.isAbsolute(outputDir)) {
|
|
247
|
+
throw new TypeError('renderCurationFiles: outputDir must be an absolute path');
|
|
248
|
+
}
|
|
249
|
+
const inOutputDir = file => path.join(outputDir, path.basename(file));
|
|
250
|
+
const decisionsFilePath = inOutputDir(getDecisionsFilePath(worktreePath));
|
|
251
|
+
const pitfallsFilePath = inOutputDir(getPitfallsFilePath(worktreePath));
|
|
240
252
|
|
|
241
253
|
// Hoist active-row selection: computed once per kind, reused for both the
|
|
242
254
|
// body render and the index build — avoids two redundant selectActiveRows passes.
|
|
@@ -259,7 +271,7 @@ function renderLearningFiles(worktreePath, rows) {
|
|
|
259
271
|
},
|
|
260
272
|
{
|
|
261
273
|
// Compact index: a write-time artifact consumed via plain Read.
|
|
262
|
-
path: getDecisionsIndexPath(worktreePath),
|
|
274
|
+
path: inOutputDir(getDecisionsIndexPath(worktreePath)),
|
|
263
275
|
content: buildIndexContent(activeDecisionRows, activePitfallRows, {
|
|
264
276
|
decisionsFilePath,
|
|
265
277
|
pitfallsFilePath,
|
|
@@ -273,18 +285,18 @@ function renderLearningFiles(worktreePath, rows) {
|
|
|
273
285
|
/**
|
|
274
286
|
* Render decisions.md, pitfalls.md and index.md from the given ledger rows and
|
|
275
287
|
* write them atomically. It takes no lock: the caller holds .decisions.lock
|
|
276
|
-
* (D-ONE-LEARNING-LOCK). It writes only into an existing `.devflow/
|
|
288
|
+
* (D-ONE-LEARNING-LOCK). It writes only into an existing `.devflow/curation/`:
|
|
277
289
|
* without one it throws before writing anything (D-NO-STRAY-TREE).
|
|
278
290
|
*
|
|
279
291
|
* @param {string} worktreePath - Absolute path to the worktree root.
|
|
280
292
|
* @param {object[]} rows - All rows from the ledger (unfiltered).
|
|
281
|
-
* @throws when `<worktreePath>/.devflow/
|
|
293
|
+
* @throws when `<worktreePath>/.devflow/curation/` does not exist, or a write fails
|
|
282
294
|
*/
|
|
283
295
|
function renderAndWriteAll(worktreePath, rows) {
|
|
284
|
-
if (!
|
|
285
|
-
throw new Error(`renderAndWriteAll: no .devflow/
|
|
296
|
+
if (!hasCurationDir(worktreePath)) {
|
|
297
|
+
throw new Error(`renderAndWriteAll: no .devflow/curation/ under ${worktreePath}`);
|
|
286
298
|
}
|
|
287
|
-
const [decisions, pitfalls, index] =
|
|
299
|
+
const [decisions, pitfalls, index] = renderCurationFiles(worktreePath, rows, getCurationDir(worktreePath));
|
|
288
300
|
|
|
289
301
|
// Write body files first; index last. On a crash between body writes and the
|
|
290
302
|
// index write: on the FIRST render the index is absent (reader falls back to
|
|
@@ -365,7 +377,7 @@ function readIfPresent(file) {
|
|
|
365
377
|
/**
|
|
366
378
|
* `render`: read the ledger under .decisions.lock and write the three files from
|
|
367
379
|
* what it read (D-ONE-LEARNING-LOCK), so a ledger write that lands while it waits
|
|
368
|
-
* is rendered rather than overwritten. Refuses without the
|
|
380
|
+
* is rendered rather than overwritten. Refuses without the curation directory
|
|
369
381
|
* (D-NO-STRAY-TREE), and when it or `.devflow` is a symbolic link (D-NO-LINKED-TREE).
|
|
370
382
|
*
|
|
371
383
|
* @param {string} root
|
|
@@ -385,18 +397,18 @@ function renderCommand(root) {
|
|
|
385
397
|
* `--check`: render in memory and compare with the files on disk, naming each
|
|
386
398
|
* file that differs or is missing; exit 1 on any drift. It writes nothing and
|
|
387
399
|
* takes no lock — taking it would create the lock directory — and refuses
|
|
388
|
-
* without the
|
|
400
|
+
* without the curation directory (D-NO-STRAY-TREE).
|
|
389
401
|
*
|
|
390
402
|
* @param {string} root
|
|
391
403
|
* @returns {number} exit code
|
|
392
404
|
*/
|
|
393
405
|
function checkCommand(root) {
|
|
394
|
-
if (!
|
|
395
|
-
process.stderr.write(`${CLI_NAME}: no .devflow/
|
|
406
|
+
if (!hasCurationDir(root)) {
|
|
407
|
+
process.stderr.write(`${CLI_NAME}: no .devflow/curation/ under ${root} — run from the project root\n`);
|
|
396
408
|
return 1;
|
|
397
409
|
}
|
|
398
410
|
let drift = false;
|
|
399
|
-
for (const file of
|
|
411
|
+
for (const file of renderCurationFiles(root, readLedgerRows(root), getCurationDir(root))) {
|
|
400
412
|
const onDisk = readIfPresent(file.path);
|
|
401
413
|
if (onDisk !== file.content) {
|
|
402
414
|
process.stderr.write(`[render-decisions] DRIFT: ${file.path}${onDisk === null ? ' (missing)' : ''}\n`);
|
|
@@ -408,7 +420,7 @@ function checkCommand(root) {
|
|
|
408
420
|
|
|
409
421
|
/**
|
|
410
422
|
* Run the CLI on its arguments and return the exit code. Neither mode creates the
|
|
411
|
-
*
|
|
423
|
+
* curation directory (D-NO-STRAY-TREE).
|
|
412
424
|
*
|
|
413
425
|
* @param {string[]} argv - the arguments after the script path
|
|
414
426
|
* @returns {number}
|
|
@@ -446,7 +458,7 @@ if (require.main === module) {
|
|
|
446
458
|
|
|
447
459
|
module.exports = {
|
|
448
460
|
renderDecisionsFile,
|
|
449
|
-
|
|
461
|
+
renderCurationFiles,
|
|
450
462
|
renderAndWriteAll,
|
|
451
463
|
selectActiveRows,
|
|
452
464
|
selectInactiveRows,
|