devflow-kit 2.5.0 → 3.0.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 +73 -0
- package/README.md +44 -19
- package/dist/agents/git.md +13 -15
- package/dist/cli/commands/ambient.js +160 -145
- package/dist/cli/commands/capture.js +29 -55
- package/dist/cli/commands/compliance.js +32 -61
- package/dist/cli/commands/context.js +17 -32
- package/dist/cli/commands/debug.js +65 -26
- package/dist/cli/commands/flags.js +3 -3
- package/dist/cli/commands/hud.js +34 -10
- package/dist/cli/commands/init-seed.js +40 -4
- package/dist/cli/commands/init.js +249 -271
- package/dist/cli/commands/install-report.js +10 -15
- package/dist/cli/commands/knowledge/index.js +1 -1
- package/dist/cli/commands/knowledge/toggle.js +11 -3
- package/dist/cli/commands/learning.js +52 -37
- package/dist/cli/commands/legacy-hooks.js +11 -14
- package/dist/cli/commands/memory.js +67 -78
- package/dist/cli/commands/proxy.js +23 -41
- package/dist/cli/commands/security.js +5 -13
- package/dist/cli/commands/skills.js +21 -3
- package/dist/cli/commands/tracker.js +100 -228
- package/dist/cli/commands/uninstall.js +343 -138
- package/dist/commands/bug-analysis.md +38 -12
- package/dist/commands/code-review.md +70 -21
- package/dist/commands/debug.md +37 -7
- package/dist/commands/dynamic-build.md +66 -17
- package/dist/commands/dynamic-plan.md +19 -8
- package/dist/commands/dynamic-profile.md +24 -10
- package/dist/commands/dynamic-tickets.md +22 -11
- package/dist/commands/explore.md +37 -7
- package/dist/commands/implement.md +96 -32
- package/dist/commands/plan.md +62 -19
- package/dist/commands/release.md +2 -2
- package/dist/commands/research.md +34 -8
- package/dist/commands/resolve.md +65 -17
- package/dist/commands/self-review.md +45 -9
- package/dist/core/compliance-compose.js +27 -27
- package/dist/core/evidence-policy.js +240 -24
- package/dist/core/feature-config.js +94 -25
- package/dist/core/feature-switch.js +1 -1
- package/dist/core/flags.js +30 -2
- package/dist/core/fs-atomic.js +27 -0
- package/dist/core/hook-log-dirs.js +104 -0
- package/dist/core/learning-tuning-config.js +5 -3
- package/dist/core/ledger-root.js +102 -0
- package/dist/core/manifest.js +6 -4
- package/dist/core/mds-variants.js +34 -97
- package/dist/core/migrations.js +49 -23
- package/dist/core/plugins.js +5 -4
- package/dist/core/project-paths.js +0 -17
- package/dist/core/same-location.js +25 -0
- package/dist/core/tracker.js +226 -139
- package/dist/hud/components/config-counts.js +15 -4
- package/dist/hud/components/learning-counts.js +14 -0
- package/dist/hud/config.js +2 -1
- package/dist/hud/cost-history.js +2 -4
- package/dist/hud/git.js +52 -7
- package/dist/hud/index.js +7 -9
- package/dist/skills/git/references/pr/check-merge-readiness.md +1 -1
- package/dist/skills/git/references/pr/ensure-pr-ready.md +1 -1
- package/dist/skills/git/references/pr/update-pr-evidence.md +1 -1
- package/dist/skills/git/references/tracker/_mcp.md +1 -1
- package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +1 -1
- package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +1 -1
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +2 -2
- package/dist/skills/git/references/tracker/github/manage-debt.md +3 -3
- package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +1 -1
- package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +1 -1
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +2 -2
- package/dist/skills/git/references/tracker/jira/manage-debt.md +1 -1
- package/dist/skills/git/references/tracker/jira/post-wave-report.md +1 -1
- package/dist/skills/git/references/tracker/jira/setup-task.md +1 -1
- package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +1 -1
- package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +1 -1
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +2 -2
- package/dist/skills/git/references/tracker/linear/manage-debt.md +1 -1
- package/dist/skills/git/references/tracker/linear/post-wave-report.md +1 -1
- package/dist/skills/git/references/tracker/linear/setup-task.md +1 -1
- package/dist/targets/claude-code/claude-paths.js +59 -57
- package/dist/targets/claude-code/compliance-install.js +49 -65
- package/dist/targets/claude-code/hooks.js +108 -3
- package/dist/targets/claude-code/installer.js +30 -57
- package/dist/targets/claude-code/post-install.js +232 -139
- package/dist/targets/claude-code/tracker-install.js +38 -65
- package/package.json +5 -4
- package/src/assets/agents/code.md +4 -3
- package/src/assets/agents/design.md +1 -0
- package/src/assets/agents/git.mds +55 -57
- package/src/assets/agents/knowledge.md +2 -2
- package/src/assets/agents/review.md +3 -1
- package/src/assets/agents/tracker.md +37 -30
- package/src/assets/commands/_partials/_compliance.mds +19 -1
- package/src/assets/commands/_partials/_decisions.mds +15 -3
- package/src/assets/commands/_partials/_docs_root.mds +35 -0
- package/src/assets/commands/_partials/_engine.mds +2 -2
- package/src/assets/commands/_partials/_evidence_policy.mds +3 -3
- package/src/assets/commands/_partials/_factory.mds +1 -1
- package/src/assets/commands/_partials/_knowledge.mds +27 -9
- package/src/assets/commands/_partials/_plan_contract.mds +2 -2
- package/src/assets/commands/_partials/_preamble.mds +1 -1
- package/src/assets/commands/_partials/_publication.mds +6 -2
- package/src/assets/commands/_partials/_settings.mds +28 -0
- package/src/assets/commands/_partials/_ticket_template.mds +3 -3
- package/src/assets/commands/_partials/_tracker.mds +4 -4
- package/src/assets/commands/_partials/_wave.mds +4 -4
- package/src/assets/commands/bug-analysis.mds +19 -17
- package/src/assets/commands/code-review.mds +39 -33
- package/src/assets/commands/debug.mds +4 -5
- package/src/assets/commands/dynamic-build.mds +75 -53
- package/src/assets/commands/dynamic-plan.mds +20 -15
- package/src/assets/commands/dynamic-profile.mds +24 -11
- package/src/assets/commands/dynamic-tickets.mds +25 -20
- package/src/assets/commands/explore.mds +4 -5
- package/src/assets/commands/implement.mds +58 -45
- package/src/assets/commands/plan.mds +34 -29
- package/src/assets/commands/release.md +2 -2
- package/src/assets/commands/research.mds +11 -9
- package/src/assets/commands/resolve.mds +41 -39
- package/src/assets/commands/self-review.mds +24 -25
- package/src/assets/mds/git/_pr.mds +61 -61
- package/src/assets/mds/git/_references.mds +19 -19
- package/src/assets/mds/tracker/_common.mds +8 -8
- package/src/assets/mds/tracker/_github.mds +71 -71
- package/src/assets/mds/tracker/_jira.mds +74 -74
- package/src/assets/mds/tracker/_linear.mds +75 -75
- package/src/assets/mds/tracker/_mcp.mds +23 -17
- package/src/assets/scripts/hooks/background-memory-update +35 -19
- package/src/assets/scripts/hooks/capture-prompt +18 -12
- package/src/assets/scripts/hooks/capture-question +18 -12
- package/src/assets/scripts/hooks/capture-turn +27 -17
- package/src/assets/scripts/hooks/debug-trace +11 -6
- package/src/assets/scripts/hooks/ensure-devflow-init +33 -6
- package/src/assets/scripts/hooks/ensure-proxy +9 -8
- package/src/assets/scripts/hooks/ensure-root-gitignore +111 -36
- package/src/assets/scripts/hooks/git-marker +48 -0
- package/src/assets/scripts/hooks/json-helper.cjs +6 -1
- package/src/assets/scripts/hooks/lib/project-paths.cjs +0 -19
- package/src/assets/scripts/hooks/log-paths +80 -0
- package/src/assets/scripts/hooks/memory-worker +17 -15
- package/src/assets/scripts/hooks/pre-compact-memory +41 -16
- package/src/assets/scripts/hooks/queue-append +104 -30
- package/src/assets/scripts/hooks/resolve-project-root +101 -7
- package/src/assets/scripts/hooks/session-start-context +289 -122
- package/src/assets/scripts/hooks/session-start-memory +35 -16
- package/src/assets/scripts/lib/project-config.cjs +633 -0
- package/src/assets/scripts/resolve-evidence-policy.cjs +300 -220
- package/src/assets/scripts/resolve-settings.cjs +1054 -0
- package/src/assets/scripts/verify-evidence.cjs +1 -1
- package/src/assets/skills/compliance/SKILL.md +2 -2
- package/src/assets/skills/docs-framework/SKILL.md +6 -7
- package/src/assets/skills/docs-framework/references/patterns.md +10 -17
- package/src/assets/skills/gap-analysis/SKILL.md +2 -2
- package/src/assets/skills/git/references/github-api.md +9 -9
- package/src/assets/skills/git/references/patterns.md +1 -1
- package/src/assets/skills/worktree-support/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/references/roots.md +29 -0
- package/src/targets/claude-code/templates/managed-settings.json +25 -9
|
@@ -24,9 +24,6 @@ import { prefixSkillName } from '../../core/plugins.js';
|
|
|
24
24
|
*
|
|
25
25
|
* Pure function — returns lines, logs nothing (applies ADR-013).
|
|
26
26
|
*
|
|
27
|
-
* @param provider - The resolved tracker provider the overlay converged to. The
|
|
28
|
-
* count alone cannot say WHICH mechanics are installed, and after the install
|
|
29
|
-
* became selection-scoped that is the number's whole meaning.
|
|
30
27
|
* @param skillName - Bare name of the skill hosting the generated references,
|
|
31
28
|
* rendered `devflow:`-prefixed. Defaults to the core constant the build path and
|
|
32
29
|
* the installer's overlay trigger both read, so the renderer is never a third
|
|
@@ -34,14 +31,13 @@ import { prefixSkillName } from '../../core/plugins.js';
|
|
|
34
31
|
* describes, where changing the answer means finding every retyped spelling and
|
|
35
32
|
* nothing fails if one is missed.
|
|
36
33
|
*/
|
|
37
|
-
export function formatOverlaySummary(report,
|
|
34
|
+
export function formatOverlaySummary(report, skillName = SKILL_REFS_SKILL_NAME) {
|
|
38
35
|
const lines = [];
|
|
39
36
|
if (report.overlaidRefs.length > 0) {
|
|
40
|
-
const scope = provider === undefined ? '' : ` (${provider} tracker mechanics)`;
|
|
41
37
|
lines.push({
|
|
42
38
|
level: 'info',
|
|
43
39
|
message: `Installed ${report.overlaidRefs.length} generated skill reference(s) for ` +
|
|
44
|
-
prefixSkillName(skillName)
|
|
40
|
+
prefixSkillName(skillName),
|
|
45
41
|
});
|
|
46
42
|
}
|
|
47
43
|
for (const failure of report.overlayFailures) {
|
|
@@ -94,16 +90,15 @@ export function describeOverlayFailureState(state) {
|
|
|
94
90
|
/**
|
|
95
91
|
* The install summary's tracker rows.
|
|
96
92
|
*
|
|
97
|
-
* Two facts the
|
|
98
|
-
* selection-scoped both of them decide what the user actually has:
|
|
93
|
+
* Two facts the install has no other visible trace of:
|
|
99
94
|
*
|
|
100
|
-
* - WHICH provider is
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* chose it" from "github because nothing was chosen"; `(was jira)`
|
|
104
|
-
* makes a self-heal or a `--reset` collapse legible rather than silent.
|
|
105
|
-
* - WHAT
|
|
106
|
-
*
|
|
95
|
+
* - WHICH provider is the machine default. The sentinel is a dotfile and every
|
|
96
|
+
* provider's mechanics are installed (D-INSTALL-ALL-PROVIDERS), so nothing on
|
|
97
|
+
* disk says which one the machine selected. `(default)` distinguishes "github
|
|
98
|
+
* because I chose it" from "github because nothing was chosen"; `(was jira)`
|
|
99
|
+
* is what makes a self-heal or a `--reset` collapse legible rather than silent.
|
|
100
|
+
* - WHAT this run moved: references written or pruned, and the Tracker agent
|
|
101
|
+
* when this run wrote it.
|
|
107
102
|
*
|
|
108
103
|
* The delta line is emitted only when something moved: on a steady-state re-init
|
|
109
104
|
* the counts are noise.
|
|
@@ -10,7 +10,7 @@ import { handleToggle } from './toggle.js';
|
|
|
10
10
|
import { handleList } from './list.js';
|
|
11
11
|
export const knowledgeCommand = new Command('knowledge')
|
|
12
12
|
.description('Manage per-feature knowledge bases')
|
|
13
|
-
.option('--enable', 'Enable per-feature knowledge bases in every project')
|
|
13
|
+
.option('--enable', 'Enable per-feature knowledge bases in every project (a repository can opt out)')
|
|
14
14
|
.option('--disable', 'Disable per-feature knowledge bases in every project')
|
|
15
15
|
.option('--status', 'Show knowledge base feature status')
|
|
16
16
|
.action(async (options) => {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Handle the enable/disable/status toggle actions for `devflow knowledge`.
|
|
3
3
|
*
|
|
4
|
-
* D-FEATURES-
|
|
4
|
+
* D-FEATURES-NARROW-ONLY (src/core/feature-switch.ts): knowledge write-back is
|
|
5
5
|
* switched for the whole machine by `features.knowledge` in
|
|
6
6
|
* ~/.devflow/manifest.json. `--enable`/`--disable` write that value — the same
|
|
7
7
|
* one `devflow init --knowledge / --no-knowledge` writes — and `--status`
|
|
8
|
-
* reports it.
|
|
8
|
+
* reports it, plus the repository's narrowing when a repo layer narrows it.
|
|
9
9
|
*/
|
|
10
10
|
import { promises as fs } from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
@@ -14,6 +14,7 @@ import color from 'picocolors';
|
|
|
14
14
|
import { getGitRoot } from '../../../core/git.js';
|
|
15
15
|
import { getDevFlowDirectory } from '../../../targets/claude-code/claude-paths.js';
|
|
16
16
|
import { readMachineFeature, writeMachineFeature } from '../../../core/feature-switch.js';
|
|
17
|
+
import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../../core/evidence-policy.js';
|
|
17
18
|
import { getFeaturesDir } from '../../../core/project-paths.js';
|
|
18
19
|
async function getWorktreePath() {
|
|
19
20
|
return (await getGitRoot()) ?? process.cwd();
|
|
@@ -48,6 +49,13 @@ export async function handleToggle(options) {
|
|
|
48
49
|
const enabled = await readMachineFeature(devflowDir, 'knowledge');
|
|
49
50
|
const kbCount = await countKnowledgeBases(await getWorktreePath());
|
|
50
51
|
p.log.info(`Status: ${enabled ? color.green('enabled') : color.yellow('disabled')}`);
|
|
52
|
+
const settingsModule = loadSettingsModule();
|
|
53
|
+
const narrowed = enabled ? narrowedSwitchLabel(settingsModule, { dir: process.cwd() }, 'knowledge') : null;
|
|
54
|
+
if (narrowed !== null)
|
|
55
|
+
p.log.info(`Effective here: ${color.yellow(narrowed)}`);
|
|
56
|
+
const trackedWarning = personalConfigTrackedWarning(settingsModule, { dir: process.cwd() });
|
|
57
|
+
if (trackedWarning !== null)
|
|
58
|
+
p.log.warn(trackedWarning);
|
|
51
59
|
p.log.info(`Knowledge bases: ${kbCount}`);
|
|
52
60
|
p.outro('');
|
|
53
61
|
return;
|
|
@@ -62,7 +70,7 @@ export async function handleToggle(options) {
|
|
|
62
70
|
return;
|
|
63
71
|
}
|
|
64
72
|
if (enabled) {
|
|
65
|
-
p.log.success('Feature knowledge bases enabled in every project');
|
|
73
|
+
p.log.success('Feature knowledge bases enabled in every project (a repository can opt out)');
|
|
66
74
|
p.log.info('Knowledge bases are created automatically when workflows detect documented area changes.');
|
|
67
75
|
}
|
|
68
76
|
else {
|
|
@@ -5,8 +5,9 @@ import * as p from '@clack/prompts';
|
|
|
5
5
|
import color from 'picocolors';
|
|
6
6
|
import { getLearningDir, getLearningTuningConfigPath, getDecisionsLogPath, getDecisionsLockDir, } from '../../core/project-paths.js';
|
|
7
7
|
import { readMachineFeature, writeMachineFeature } from '../../core/feature-switch.js';
|
|
8
|
+
import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../core/evidence-policy.js';
|
|
8
9
|
import { getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
|
|
9
|
-
import {
|
|
10
|
+
import { getLedgerRoot } from '../../core/ledger-root.js';
|
|
10
11
|
import { sweepLegacyDreamMarkers, drainLearningQueue } from '../../core/learning-queue-cleanup.js';
|
|
11
12
|
import { readObservations, warnIfInvalid, } from '../../core/observation-io.js';
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
@@ -14,7 +15,7 @@ import { readObservations, warnIfInvalid, } from '../../core/observation-io.js';
|
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
function printUsage() {
|
|
16
17
|
p.intro(color.bgCyan(color.black(' Learning ')));
|
|
17
|
-
p.note(`${color.cyan('devflow learning --enable')} Enable learning in every project\n` +
|
|
18
|
+
p.note(`${color.cyan('devflow learning --enable')} Enable learning in every project (a repository can opt out)\n` +
|
|
18
19
|
`${color.cyan('devflow learning --disable')} Disable learning in every project (drains this project's queue)\n` +
|
|
19
20
|
`${color.cyan('devflow learning --status')} Show learning status\n` +
|
|
20
21
|
`${color.cyan('devflow learning --list')} Show all observations\n` +
|
|
@@ -24,28 +25,39 @@ function printUsage() {
|
|
|
24
25
|
p.outro(color.dim('Detects architectural decisions and known pitfalls from your sessions'));
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
|
-
* Resolve the
|
|
28
|
+
* Resolve the ledger root for a state-mutating subcommand, warning and
|
|
28
29
|
* returning null if the caller isn't inside a git project. `actionSuffix`
|
|
29
30
|
* completes "Could not resolve git root — {actionSuffix}".
|
|
31
|
+
*
|
|
32
|
+
* D-LEDGER-MAIN-WORKTREE: every subcommand here resolves the ledger with
|
|
33
|
+
* getLedgerRoot — the hooks' DF_LEDGER_ROOT rule — so in a linked worktree it
|
|
34
|
+
* reads, clears and drains the main checkout's ledger the hooks write.
|
|
30
35
|
*/
|
|
31
|
-
async function
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
36
|
+
async function requireLedgerRoot(actionSuffix) {
|
|
37
|
+
const ledgerRoot = await getLedgerRoot();
|
|
38
|
+
if (!ledgerRoot) {
|
|
34
39
|
p.log.warn(`Could not resolve git root — ${actionSuffix}`);
|
|
35
40
|
}
|
|
36
|
-
return
|
|
41
|
+
return ledgerRoot;
|
|
37
42
|
}
|
|
38
43
|
async function handleStatus() {
|
|
39
|
-
// D-FEATURES-
|
|
40
|
-
//
|
|
44
|
+
// D-FEATURES-NARROW-ONLY: the machine switch is the manifest's and reads the
|
|
45
|
+
// same from every directory; a repository layer can only narrow it, and adds a
|
|
46
|
+
// line only when it does. The observation counts are per-project.
|
|
41
47
|
const enabled = await readMachineFeature(getDevFlowDirectory(), 'learning');
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
48
|
+
const settingsModule = loadSettingsModule();
|
|
49
|
+
const narrowed = enabled ? narrowedSwitchLabel(settingsModule, { dir: process.cwd() }, 'learning') : null;
|
|
50
|
+
const stateLine = `Learning: ${enabled ? 'enabled' : 'disabled'}`
|
|
51
|
+
+ (narrowed === null ? '' : `\nEffective here: ${narrowed}`);
|
|
52
|
+
const trackedWarning = personalConfigTrackedWarning(settingsModule, { dir: process.cwd() });
|
|
53
|
+
if (trackedWarning !== null)
|
|
54
|
+
p.log.warn(trackedWarning);
|
|
55
|
+
const ledgerRoot = await getLedgerRoot();
|
|
56
|
+
if (!ledgerRoot) {
|
|
45
57
|
p.log.info(`${stateLine}\nObservations: not in a git project`);
|
|
46
58
|
return;
|
|
47
59
|
}
|
|
48
|
-
const logPath = getDecisionsLogPath(
|
|
60
|
+
const logPath = getDecisionsLogPath(ledgerRoot);
|
|
49
61
|
const { observations, invalidCount } = await readObservations(logPath);
|
|
50
62
|
const decisionObs = observations.filter(o => o.type === 'decision' || o.type === 'pitfall');
|
|
51
63
|
const decisions = observations.filter(o => o.type === 'decision');
|
|
@@ -67,12 +79,12 @@ async function handleStatus() {
|
|
|
67
79
|
warnIfInvalid(invalidCount);
|
|
68
80
|
}
|
|
69
81
|
async function handleList() {
|
|
70
|
-
// Resolve the log from the
|
|
71
|
-
// --disable) so `--list` run from a subdirectory
|
|
72
|
-
// instead of a nonexistent one under process.cwd(). Falls back
|
|
73
|
-
// when not in a git project, preserving the prior behavior for that case.
|
|
74
|
-
const
|
|
75
|
-
const logPath = getDecisionsLogPath(
|
|
82
|
+
// Resolve the log from the ledger root (matches --status, --clear, --reset,
|
|
83
|
+
// --disable) so `--list` run from a subdirectory or a linked worktree finds
|
|
84
|
+
// the real log instead of a nonexistent one under process.cwd(). Falls back
|
|
85
|
+
// to cwd when not in a git project, preserving the prior behavior for that case.
|
|
86
|
+
const ledgerRoot = await getLedgerRoot();
|
|
87
|
+
const logPath = getDecisionsLogPath(ledgerRoot ?? process.cwd());
|
|
76
88
|
let logExists = true;
|
|
77
89
|
try {
|
|
78
90
|
await fs.access(logPath);
|
|
@@ -150,19 +162,22 @@ async function handleConfigure() {
|
|
|
150
162
|
p.log.success(`Global config written to ${color.dim(path.join(globalDir, 'learning.json'))}`);
|
|
151
163
|
}
|
|
152
164
|
else {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
165
|
+
// D-LEDGER-MAIN-WORKTREE: session-start-context reads the project tuning config
|
|
166
|
+
// from the ledger ($LEDGER_ROOT/.devflow/learning/), so write it there — the
|
|
167
|
+
// main checkout in a linked worktree; the current directory outside git.
|
|
168
|
+
const projectRoot = (await getLedgerRoot()) ?? process.cwd();
|
|
169
|
+
await fs.mkdir(getLearningDir(projectRoot), { recursive: true });
|
|
170
|
+
const projectConfigPath = getLearningTuningConfigPath(projectRoot);
|
|
156
171
|
await fs.writeFile(projectConfigPath, configJson, 'utf-8');
|
|
157
172
|
p.log.success(`Project config written to ${color.dim(projectConfigPath)}`);
|
|
158
173
|
}
|
|
159
174
|
p.outro(color.green('Configuration saved.'));
|
|
160
175
|
}
|
|
161
176
|
async function handleReset() {
|
|
162
|
-
const
|
|
163
|
-
if (!
|
|
177
|
+
const ledgerRoot = await requireLedgerRoot('reset not performed');
|
|
178
|
+
if (!ledgerRoot)
|
|
164
179
|
return;
|
|
165
|
-
const lockDir = getDecisionsLockDir(
|
|
180
|
+
const lockDir = getDecisionsLockDir(ledgerRoot);
|
|
166
181
|
// Ensure the parent directory exists so a second reset (after .devflow/learning/
|
|
167
182
|
// was already removed) does not fail with ENOENT and emit a false contention error.
|
|
168
183
|
await fs.mkdir(path.dirname(lockDir), { recursive: true });
|
|
@@ -189,13 +204,13 @@ async function handleReset() {
|
|
|
189
204
|
// Remove the entire learning directory (contains queue files, content files,
|
|
190
205
|
// ledger, and tuning config). Single-dir semantics: all learning state lives here.
|
|
191
206
|
try {
|
|
192
|
-
await fs.rm(getLearningDir(
|
|
207
|
+
await fs.rm(getLearningDir(ledgerRoot), { recursive: true, force: true });
|
|
193
208
|
}
|
|
194
209
|
catch { /* best effort */ }
|
|
195
210
|
// Clean legacy dream marker-pipeline stamps from old installs.
|
|
196
211
|
// Best-effort: sweeps the now-absent dir silently (ENOENT-tolerant).
|
|
197
212
|
try {
|
|
198
|
-
await sweepLegacyDreamMarkers(getLearningDir(
|
|
213
|
+
await sweepLegacyDreamMarkers(getLearningDir(ledgerRoot));
|
|
199
214
|
}
|
|
200
215
|
catch { /* best effort */ }
|
|
201
216
|
p.log.success('Reset complete — removed .devflow/learning/ state.');
|
|
@@ -208,10 +223,10 @@ async function handleReset() {
|
|
|
208
223
|
}
|
|
209
224
|
}
|
|
210
225
|
async function handleClear() {
|
|
211
|
-
const
|
|
212
|
-
if (!
|
|
226
|
+
const ledgerRoot = await requireLedgerRoot('clear not performed');
|
|
227
|
+
if (!ledgerRoot)
|
|
213
228
|
return;
|
|
214
|
-
const decisionsLogPath = getDecisionsLogPath(
|
|
229
|
+
const decisionsLogPath = getDecisionsLogPath(ledgerRoot);
|
|
215
230
|
try {
|
|
216
231
|
await fs.access(decisionsLogPath);
|
|
217
232
|
}
|
|
@@ -234,11 +249,11 @@ async function handleClear() {
|
|
|
234
249
|
// on the next session — mirrors memory.ts's drain-on-disable behavior for
|
|
235
250
|
// the sibling memory queue. A mid-run Learning agent whose claimed batch
|
|
236
251
|
// vanishes aborts without changes — the desired outcome of clearing.
|
|
237
|
-
await drainLearningQueue(
|
|
252
|
+
await drainLearningQueue(ledgerRoot);
|
|
238
253
|
p.log.success('Decisions log cleared.');
|
|
239
254
|
}
|
|
240
255
|
/**
|
|
241
|
-
* `--enable` / `--disable`: the machine-wide switch (D-FEATURES-
|
|
256
|
+
* `--enable` / `--disable`: the machine-wide switch (D-FEATURES-NARROW-ONLY),
|
|
242
257
|
* converged exactly as `devflow init --learning / --no-learning` converges it —
|
|
243
258
|
* the manifest value, and on disable a drained queue in the current project.
|
|
244
259
|
* Never requires a git root: the switch is not a per-project setting.
|
|
@@ -251,22 +266,22 @@ async function handleToggle(enabled) {
|
|
|
251
266
|
return;
|
|
252
267
|
}
|
|
253
268
|
if (enabled) {
|
|
254
|
-
p.log.success('Learning enabled in every project');
|
|
269
|
+
p.log.success('Learning enabled in every project (a repository can opt out)');
|
|
255
270
|
p.log.info(color.dim('Architectural decisions and pitfalls will be detected from your sessions'));
|
|
256
271
|
return;
|
|
257
272
|
}
|
|
258
273
|
// Drain the current project's learning (decisions-detection) queue so stale
|
|
259
274
|
// turns don't process on re-enable. A mid-run Learning agent whose claimed
|
|
260
275
|
// batch vanishes aborts without changes — the desired outcome of disabling.
|
|
261
|
-
const
|
|
262
|
-
if (
|
|
263
|
-
await drainLearningQueue(
|
|
276
|
+
const ledgerRoot = await getLedgerRoot();
|
|
277
|
+
if (ledgerRoot) {
|
|
278
|
+
await drainLearningQueue(ledgerRoot);
|
|
264
279
|
}
|
|
265
280
|
p.log.success('Learning disabled in every project');
|
|
266
281
|
}
|
|
267
282
|
export const learningCommand = new Command('learning')
|
|
268
283
|
.description('Enable or disable learning (decision/pitfall detection) in every project')
|
|
269
|
-
.option('--enable', 'Enable learning in every project')
|
|
284
|
+
.option('--enable', 'Enable learning in every project (a repository can opt out)')
|
|
270
285
|
.option('--disable', 'Disable learning in every project')
|
|
271
286
|
.option('--status', 'Show learning status and observation counts')
|
|
272
287
|
.option('--list', 'Show all decision/pitfall observations sorted by confidence')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { devflowHookOwner, hasHook, removeHooks, } from '../../targets/claude-code/hooks.js';
|
|
1
2
|
// ─── Dream worker hook cleanup ──────────────────────────────────────────────
|
|
2
3
|
//
|
|
3
4
|
// The spawn-dream-worker SessionStart hook belonged to the retired detached
|
|
@@ -6,27 +7,23 @@
|
|
|
6
7
|
// own. remove/has exist for upgrade cleanup: init and uninstall strip any
|
|
7
8
|
// stale entry left in settings.json by a prior install.
|
|
8
9
|
const SPAWN_DREAM_WORKER_MARKER = 'spawn-dream-worker';
|
|
10
|
+
/**
|
|
11
|
+
* D-EXACT-HOOK-OWNER: the dream-worker hook is devflow's only when its command
|
|
12
|
+
* ends in `/scripts/hooks/run-hook spawn-dream-worker` (hooks.ts), under any
|
|
13
|
+
* directory — the one form it was ever registered in.
|
|
14
|
+
*/
|
|
15
|
+
const isDreamHook = devflowHookOwner([SPAWN_DREAM_WORKER_MARKER]);
|
|
9
16
|
/**
|
|
10
17
|
* Remove the spawn-dream-worker hook from settings JSON.
|
|
11
18
|
* Idempotent — returns unchanged JSON if hook not present.
|
|
12
|
-
*
|
|
19
|
+
* Removes the single hook, so the other hooks of its matcher group and every
|
|
20
|
+
* other SessionStart group (session-start-memory, session-start-context) stay in place.
|
|
13
21
|
*/
|
|
14
22
|
export function removeDreamHook(settingsJson) {
|
|
15
23
|
const settings = JSON.parse(settingsJson);
|
|
16
|
-
if (!settings
|
|
17
|
-
return settingsJson;
|
|
18
|
-
}
|
|
19
|
-
const before = settings.hooks.SessionStart.length;
|
|
20
|
-
settings.hooks.SessionStart = settings.hooks.SessionStart.filter((matcher) => !matcher.hooks.some((h) => h.command.includes(SPAWN_DREAM_WORKER_MARKER)));
|
|
21
|
-
if (settings.hooks.SessionStart.length === before) {
|
|
24
|
+
if (!removeHooks(settings, 'SessionStart', isDreamHook)) {
|
|
22
25
|
return settingsJson;
|
|
23
26
|
}
|
|
24
|
-
if (settings.hooks.SessionStart.length === 0) {
|
|
25
|
-
delete settings.hooks.SessionStart;
|
|
26
|
-
}
|
|
27
|
-
if (Object.keys(settings.hooks).length === 0) {
|
|
28
|
-
delete settings.hooks;
|
|
29
|
-
}
|
|
30
27
|
return JSON.stringify(settings, null, 2) + '\n';
|
|
31
28
|
}
|
|
32
29
|
/**
|
|
@@ -35,6 +32,6 @@ export function removeDreamHook(settingsJson) {
|
|
|
35
32
|
*/
|
|
36
33
|
export function hasDreamHook(input) {
|
|
37
34
|
const settings = typeof input === 'string' ? JSON.parse(input) : input;
|
|
38
|
-
return settings
|
|
35
|
+
return hasHook(settings, 'SessionStart', isDreamHook);
|
|
39
36
|
}
|
|
40
37
|
//# sourceMappingURL=legacy-hooks.js.map
|
|
@@ -4,11 +4,13 @@ import * as path from 'path';
|
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
5
|
import color from 'picocolors';
|
|
6
6
|
import { getClaudeDirectory, getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
|
|
7
|
-
import {
|
|
7
|
+
import { writeSettingsFileAtomic } from '../../core/fs-atomic.js';
|
|
8
8
|
import { discoverProjectGitRoots } from '../../targets/claude-code/post-install.js';
|
|
9
9
|
import { getGitRoot } from '../../core/git.js';
|
|
10
10
|
import { getMemoryDir, getPendingTurnsPath, getPendingTurnsProcessingPath, } from '../../core/project-paths.js';
|
|
11
|
+
import { HOOKS_DIR_SUFFIX, devflowHookOwner, endsWithAny, ensureHook, hasHook, removeHooks, runHookCommand, runHookSuffix, } from '../../targets/claude-code/hooks.js';
|
|
11
12
|
import { readMachineFeature, writeMachineFeature } from '../../core/feature-switch.js';
|
|
13
|
+
import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../core/evidence-policy.js';
|
|
12
14
|
/**
|
|
13
15
|
* Map of hook event type → filename marker for the memory hooks.
|
|
14
16
|
* Three hooks total: Stop, SessionStart, PreCompact.
|
|
@@ -18,10 +20,11 @@ import { readMachineFeature, writeMachineFeature } from '../../core/feature-swit
|
|
|
18
20
|
* at the hook-registration level), and decisions detection is a SessionStart-spawned
|
|
19
21
|
* detached worker rather than a SessionEnd hook (see legacy-hooks.ts).
|
|
20
22
|
*
|
|
21
|
-
* Stop-
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* Stop-event concurrency: Claude Code runs one event's hooks in parallel, so
|
|
24
|
+
* memory-worker can spawn background-memory-update before capture-turn has
|
|
25
|
+
* appended this turn's assistant row. The worker tolerates that — a queue that
|
|
26
|
+
* holds only user rows is left in place and the LLM run skipped
|
|
27
|
+
* (D-QUEUE-NO-ORPHAN-DELETE) — so nothing here depends on hook order.
|
|
25
28
|
*/
|
|
26
29
|
const MEMORY_HOOK_CONFIG = {
|
|
27
30
|
Stop: 'memory-worker',
|
|
@@ -29,14 +32,32 @@ const MEMORY_HOOK_CONFIG = {
|
|
|
29
32
|
PreCompact: 'pre-compact-memory',
|
|
30
33
|
};
|
|
31
34
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
35
|
+
* The command endings of the memory-era hooks earlier releases registered, per
|
|
36
|
+
* event. Removed by removeMemoryHooks so an upgrade leaves no hook pointing at a
|
|
37
|
+
* script that no longer exists; never counted as a current memory hook.
|
|
38
|
+
*
|
|
39
|
+
* D-EXACT-HOOK-OWNER (hooks.ts): matched as command ENDINGS under any directory —
|
|
40
|
+
* the v1 (≤ v1.2) direct `.sh` scripts, then the retired `run-hook` markers of the
|
|
41
|
+
* prompt-capture, learning, decisions, knowledge-refresh, sidecar and dream
|
|
42
|
+
* pipelines — never as substrings, so a user's hook that mentions one is theirs.
|
|
34
43
|
*/
|
|
35
|
-
const
|
|
36
|
-
UserPromptSubmit: ['prompt-capture-memory', 'sidecar-dispatch', 'dream-dispatch'],
|
|
37
|
-
Stop: [
|
|
38
|
-
|
|
44
|
+
const LEGACY_HOOK_SUFFIXES = {
|
|
45
|
+
UserPromptSubmit: ['prompt-capture-memory', 'sidecar-dispatch', 'dream-dispatch'].map(runHookSuffix),
|
|
46
|
+
Stop: [
|
|
47
|
+
`${HOOKS_DIR_SUFFIX}stop-update-memory.sh`,
|
|
48
|
+
...['stop-update-memory', 'stop-update-learning', 'sidecar-capture', 'dream-capture'].map(runHookSuffix),
|
|
49
|
+
],
|
|
50
|
+
SessionStart: [`${HOOKS_DIR_SUFFIX}session-start-memory.sh`],
|
|
51
|
+
PreCompact: [`${HOOKS_DIR_SUFFIX}pre-compact-memory.sh`],
|
|
52
|
+
SessionEnd: [
|
|
53
|
+
'session-end-learning', 'session-end-decisions', 'session-end-knowledge-refresh',
|
|
54
|
+
'sidecar-evaluate', 'dream-evaluate',
|
|
55
|
+
].map(runHookSuffix),
|
|
39
56
|
};
|
|
57
|
+
/** D-EXACT-HOOK-OWNER: the current memory hook for `marker` (hooks.ts). */
|
|
58
|
+
function isMemoryHook(marker) {
|
|
59
|
+
return devflowHookOwner([marker]);
|
|
60
|
+
}
|
|
40
61
|
/**
|
|
41
62
|
* Add all 3 memory hooks (Stop, SessionStart, PreCompact) to settings JSON.
|
|
42
63
|
* Idempotent — skips hooks that already exist. Returns unchanged JSON if all 3 present.
|
|
@@ -46,28 +67,10 @@ export function addMemoryHooks(settingsJson, devflowDir) {
|
|
|
46
67
|
if (hasMemoryHooks(settings)) {
|
|
47
68
|
return settingsJson;
|
|
48
69
|
}
|
|
49
|
-
if (!settings.hooks) {
|
|
50
|
-
settings.hooks = {};
|
|
51
|
-
}
|
|
52
70
|
for (const [hookType, marker] of Object.entries(MEMORY_HOOK_CONFIG)) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const hookCommand = path.join(devflowDir, 'scripts', 'hooks', 'run-hook') + ` ${marker}`;
|
|
57
|
-
const newEntry = {
|
|
58
|
-
hooks: [
|
|
59
|
-
{
|
|
60
|
-
type: 'command',
|
|
61
|
-
command: hookCommand,
|
|
62
|
-
timeout: 10,
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
};
|
|
66
|
-
if (!settings.hooks[hookType]) {
|
|
67
|
-
settings.hooks[hookType] = [];
|
|
68
|
-
}
|
|
69
|
-
settings.hooks[hookType].push(newEntry);
|
|
70
|
-
}
|
|
71
|
+
ensureHook(settings, hookType, isMemoryHook(marker), {
|
|
72
|
+
hooks: [{ type: 'command', command: runHookCommand(devflowDir, marker), timeout: 10 }],
|
|
73
|
+
});
|
|
71
74
|
}
|
|
72
75
|
return JSON.stringify(settings, null, 2) + '\n';
|
|
73
76
|
}
|
|
@@ -80,36 +83,16 @@ export function addMemoryHooks(settingsJson, devflowDir) {
|
|
|
80
83
|
export function removeMemoryHooks(input) {
|
|
81
84
|
const settingsJson = typeof input === 'string' ? input : JSON.stringify(input);
|
|
82
85
|
const settings = typeof input === 'string' ? JSON.parse(input) : structuredClone(input);
|
|
83
|
-
|
|
84
|
-
return settingsJson;
|
|
85
|
-
}
|
|
86
|
+
// Evaluate every removal into a local — never short-circuit (PF-015).
|
|
86
87
|
let changed = false;
|
|
87
88
|
for (const [hookType, marker] of Object.entries(MEMORY_HOOK_CONFIG)) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
const before = settings.hooks[hookType].length;
|
|
92
|
-
settings.hooks[hookType] = settings.hooks[hookType].filter((matcher) => !matcher.hooks.some((h) => h.command.includes(marker)));
|
|
93
|
-
if (settings.hooks[hookType].length !== before) {
|
|
94
|
-
changed = true;
|
|
95
|
-
}
|
|
96
|
-
if (settings.hooks[hookType].length === 0) {
|
|
97
|
-
delete settings.hooks[hookType];
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// Remove legacy pre-dream hooks from upgrading users
|
|
101
|
-
for (const [hookType, markers] of Object.entries(LEGACY_HOOK_MARKERS)) {
|
|
102
|
-
if (!settings.hooks[hookType])
|
|
103
|
-
continue;
|
|
104
|
-
const before = settings.hooks[hookType].length;
|
|
105
|
-
settings.hooks[hookType] = settings.hooks[hookType].filter((matcher) => !matcher.hooks.some((h) => markers.some((m) => h.command.includes(m))));
|
|
106
|
-
if (settings.hooks[hookType].length !== before)
|
|
107
|
-
changed = true;
|
|
108
|
-
if (settings.hooks[hookType].length === 0)
|
|
109
|
-
delete settings.hooks[hookType];
|
|
89
|
+
const removed = removeHooks(settings, hookType, isMemoryHook(marker));
|
|
90
|
+
changed = changed || removed;
|
|
110
91
|
}
|
|
111
|
-
|
|
112
|
-
|
|
92
|
+
// Remove the memory-era hooks of earlier releases from upgrading users
|
|
93
|
+
for (const [hookType, suffixes] of Object.entries(LEGACY_HOOK_SUFFIXES)) {
|
|
94
|
+
const removed = removeHooks(settings, hookType, endsWithAny(suffixes));
|
|
95
|
+
changed = changed || removed;
|
|
113
96
|
}
|
|
114
97
|
if (!changed) {
|
|
115
98
|
return settingsJson;
|
|
@@ -128,32 +111,29 @@ export function hasMemoryHooks(input) {
|
|
|
128
111
|
*/
|
|
129
112
|
export function countMemoryHooks(input) {
|
|
130
113
|
const settings = typeof input === 'string' ? JSON.parse(input) : input;
|
|
131
|
-
if (!settings.hooks) {
|
|
132
|
-
return 0;
|
|
133
|
-
}
|
|
134
114
|
let count = 0;
|
|
135
115
|
for (const [hookType, marker] of Object.entries(MEMORY_HOOK_CONFIG)) {
|
|
136
|
-
|
|
137
|
-
if (matchers.some((matcher) => matcher.hooks.some((h) => h.command.includes(marker)))) {
|
|
116
|
+
if (hasHook(settings, hookType, isMemoryHook(marker)))
|
|
138
117
|
count++;
|
|
139
|
-
}
|
|
140
118
|
}
|
|
141
119
|
return count;
|
|
142
120
|
}
|
|
143
121
|
/**
|
|
144
122
|
* Converge the memory hooks in a settings JSON string to `enabled`. Pure.
|
|
145
123
|
*
|
|
146
|
-
* D-FEATURES-
|
|
124
|
+
* D-FEATURES-NARROW-ONLY: the ONE settings transform for the memory feature,
|
|
147
125
|
* shared by `devflow init` (inside its single settings read-modify-write pass)
|
|
148
126
|
* and `devflow memory --enable/--disable`, so the two controls of the same
|
|
149
127
|
* machine-wide switch leave settings.json byte-for-byte alike. Always
|
|
150
128
|
* remove-then-add, which also upgrades an older hook format (e.g. `.sh` →
|
|
151
129
|
* `run-hook`) in place.
|
|
152
130
|
*
|
|
153
|
-
* Stop-array
|
|
154
|
-
* Stop array already holds, so it lands after capture-turn
|
|
155
|
-
* capture hooks
|
|
156
|
-
*
|
|
131
|
+
* Stop-array position (AC-C2): memory-worker is appended after whatever the
|
|
132
|
+
* Stop array already holds, so it lands after capture-turn — init registers the
|
|
133
|
+
* capture hooks earlier in the same pass, and on a standalone toggle they are
|
|
134
|
+
* already present. The position keeps settings.json identical across init and
|
|
135
|
+
* the toggle; it sequences nothing at run time, where the Stop hooks run in
|
|
136
|
+
* parallel.
|
|
157
137
|
*/
|
|
158
138
|
export function convergeMemoryHooks(settingsJson, enabled, devflowDir) {
|
|
159
139
|
const cleaned = removeMemoryHooks(settingsJson);
|
|
@@ -227,7 +207,7 @@ export async function cleanQueueFiles(projectPaths) {
|
|
|
227
207
|
}
|
|
228
208
|
export const memoryCommand = new Command('memory')
|
|
229
209
|
.description('Enable, disable, or clean up working memory (session context preservation)')
|
|
230
|
-
.option('--enable', 'Enable working memory in every project')
|
|
210
|
+
.option('--enable', 'Enable working memory in every project (a repository can opt out)')
|
|
231
211
|
.option('--disable', 'Disable working memory in every project')
|
|
232
212
|
.option('--status', 'Show current state')
|
|
233
213
|
.option('--clear', 'Clean up queue files from projects')
|
|
@@ -235,7 +215,7 @@ export const memoryCommand = new Command('memory')
|
|
|
235
215
|
const hasFlag = options.enable || options.disable || options.status || options.clear;
|
|
236
216
|
if (!hasFlag) {
|
|
237
217
|
p.intro(color.bgCyan(color.white(' Working Memory ')));
|
|
238
|
-
p.note(`${color.cyan('devflow memory --enable')} Enable working memory (every project)\n` +
|
|
218
|
+
p.note(`${color.cyan('devflow memory --enable')} Enable working memory (every project; a repository can opt out)\n` +
|
|
239
219
|
`${color.cyan('devflow memory --disable')} Disable working memory (every project)\n` +
|
|
240
220
|
`${color.cyan('devflow memory --status')} Check current state\n` +
|
|
241
221
|
`${color.cyan('devflow memory --clear')} Clean up queue files`, 'Usage');
|
|
@@ -245,7 +225,7 @@ export const memoryCommand = new Command('memory')
|
|
|
245
225
|
if (options.clear) {
|
|
246
226
|
p.intro(color.bgCyan(color.white(' Memory Cleanup ')));
|
|
247
227
|
// Discover current project and all known projects in parallel
|
|
248
|
-
const [gitRoots, gitRoot] = await Promise.all([discoverProjectGitRoots(), getGitRoot()]);
|
|
228
|
+
const [gitRoots, gitRoot] = await Promise.all([discoverProjectGitRoots(getClaudeDirectory()), getGitRoot()]);
|
|
249
229
|
const [projectsWithMemory, currentProjectHasMem] = await Promise.all([
|
|
250
230
|
filterProjectsWithMemory(gitRoots),
|
|
251
231
|
gitRoot ? hasMemoryDir(gitRoot) : Promise.resolve(false),
|
|
@@ -302,8 +282,10 @@ export const memoryCommand = new Command('memory')
|
|
|
302
282
|
settingsContent = '{}';
|
|
303
283
|
}
|
|
304
284
|
if (options.status) {
|
|
305
|
-
// D-FEATURES-
|
|
306
|
-
// reported beside it because the hooks are how that
|
|
285
|
+
// D-FEATURES-NARROW-ONLY: the machine switch, the manifest's, is reported
|
|
286
|
+
// first. The hook count is reported beside it because the hooks are how that
|
|
287
|
+
// switch takes effect. A repository layer can only narrow it, and says so on
|
|
288
|
+
// a line of its own — only when it does, so the output is otherwise unchanged.
|
|
307
289
|
const enabled = await readMachineFeature(devflowDir, 'memory');
|
|
308
290
|
const count = countMemoryHooks(settingsContent);
|
|
309
291
|
const total = Object.keys(MEMORY_HOOK_CONFIG).length;
|
|
@@ -317,10 +299,17 @@ export const memoryCommand = new Command('memory')
|
|
|
317
299
|
p.log.info(`Working memory: ${color.yellow(`enabled, but ${count}/${total} hooks registered`)} — ` +
|
|
318
300
|
`run ${color.cyan('devflow memory --enable')} to fix`);
|
|
319
301
|
}
|
|
302
|
+
const settingsModule = loadSettingsModule();
|
|
303
|
+
const narrowed = enabled ? narrowedSwitchLabel(settingsModule, { dir: process.cwd() }, 'memory') : null;
|
|
304
|
+
if (narrowed !== null)
|
|
305
|
+
p.log.info(`Effective here: ${color.yellow(narrowed)}`);
|
|
306
|
+
const trackedWarning = personalConfigTrackedWarning(settingsModule, { dir: process.cwd() });
|
|
307
|
+
if (trackedWarning !== null)
|
|
308
|
+
p.log.warn(trackedWarning);
|
|
320
309
|
return;
|
|
321
310
|
}
|
|
322
311
|
// --enable / --disable: the machine-wide switch, converged exactly as
|
|
323
|
-
// `devflow init --memory / --no-memory` converges it (D-FEATURES-
|
|
312
|
+
// `devflow init --memory / --no-memory` converges it (D-FEATURES-NARROW-ONLY).
|
|
324
313
|
// The settings transform runs FIRST: it is the step that can reject its
|
|
325
314
|
// input (malformed JSON), and the switch must not be recorded unless the
|
|
326
315
|
// hooks that enact it can follow.
|
|
@@ -341,10 +330,10 @@ export const memoryCommand = new Command('memory')
|
|
|
341
330
|
return;
|
|
342
331
|
}
|
|
343
332
|
if (converged !== settingsContent) {
|
|
344
|
-
await
|
|
333
|
+
await writeSettingsFileAtomic(settingsPath, converged);
|
|
345
334
|
}
|
|
346
335
|
if (enabled) {
|
|
347
|
-
p.log.success('Working memory enabled in every project');
|
|
336
|
+
p.log.success('Working memory enabled in every project (a repository can opt out)');
|
|
348
337
|
p.log.info(color.dim('Session context will be automatically preserved across conversations'));
|
|
349
338
|
return;
|
|
350
339
|
}
|