devflow-kit 2.4.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 +229 -0
- package/README.md +111 -18
- package/dist/agents/git.md +822 -0
- package/dist/cli/commands/agents.js +6 -1
- package/dist/cli/commands/ambient.js +160 -145
- package/dist/cli/commands/attribution-prompts.js +1 -1
- package/dist/cli/commands/capture.js +29 -55
- package/dist/cli/commands/compliance-prompts.js +1 -1
- package/dist/cli/commands/compliance.js +48 -55
- 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 +61 -27
- package/dist/cli/commands/init.js +649 -240
- package/dist/cli/commands/install-report.js +200 -0
- package/dist/cli/commands/knowledge/index.js +2 -2
- package/dist/cli/commands/knowledge/toggle.js +35 -37
- package/dist/cli/commands/learning.js +79 -57
- package/dist/cli/commands/legacy-hooks.js +11 -14
- package/dist/cli/commands/memory.js +134 -135
- package/dist/cli/commands/prompt-io.js +4 -4
- package/dist/cli/commands/proxy.js +23 -41
- package/dist/cli/commands/security.js +81 -29
- package/dist/cli/commands/skills.js +71 -7
- package/dist/cli/commands/tracker-prompts.js +145 -0
- package/dist/cli/commands/tracker.js +277 -0
- package/dist/cli/commands/uninstall.js +520 -169
- package/dist/cli.js +2 -0
- package/dist/commands/bug-analysis.md +58 -14
- package/dist/commands/code-review.md +110 -32
- package/dist/commands/debug.md +55 -11
- package/dist/commands/dynamic-build.md +344 -73
- package/dist/commands/dynamic-plan.md +77 -27
- package/dist/commands/dynamic-profile.md +25 -11
- package/dist/commands/dynamic-tickets.md +76 -15
- package/dist/commands/explore.md +37 -7
- package/dist/commands/implement.md +314 -62
- package/dist/commands/plan.md +146 -32
- package/dist/commands/release.md +64 -17
- package/dist/commands/research.md +34 -8
- package/dist/commands/resolve.md +196 -68
- package/dist/commands/self-review.md +45 -9
- package/dist/core/agent-models.js +55 -12
- package/dist/core/assets.js +58 -2
- package/dist/core/compliance-compose.js +27 -27
- package/dist/core/evidence-policy.js +363 -0
- package/dist/core/feature-config.js +200 -65
- package/dist/core/feature-switch.js +112 -0
- package/dist/core/flags.js +34 -6
- 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 +38 -10
- package/dist/core/mds-variants.js +798 -0
- package/dist/core/migrations.js +49 -23
- package/dist/core/model-discovery.js +12 -1
- package/dist/core/plugins.js +361 -12
- package/dist/core/project-paths.js +1 -18
- package/dist/core/proxy-log.js +8 -6
- package/dist/core/proxy-state.js +11 -8
- package/dist/core/reference-sweep.js +136 -0
- package/dist/core/same-location.js +25 -0
- package/dist/core/tracker.js +494 -0
- 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/decision-markers.md +19 -0
- package/dist/skills/git/references/learn-conventions.md +56 -0
- package/dist/skills/git/references/pr/check-ci-status.md +14 -0
- package/dist/skills/git/references/pr/check-merge-readiness.md +28 -0
- package/dist/skills/git/references/pr/ensure-pr-ready.md +24 -0
- package/dist/skills/git/references/pr/fetch-review-threads.md +22 -0
- package/dist/skills/git/references/pr/post-resolution-summary.md +40 -0
- package/dist/skills/git/references/pr/post-review-summary.md +42 -0
- package/dist/skills/git/references/pr/resolve-review-threads.md +35 -0
- package/dist/skills/git/references/pr/update-pr-evidence.md +14 -0
- package/dist/skills/git/references/pr/validate-branch.md +18 -0
- package/dist/skills/git/references/publication-gate.md +13 -0
- package/dist/skills/git/references/tracker/_mcp.md +153 -0
- package/dist/skills/git/references/tracker/github/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +40 -0
- package/dist/skills/git/references/tracker/github/create-release.md +11 -0
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +16 -0
- package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +69 -0
- package/dist/skills/git/references/tracker/github/fetch-issue.md +32 -0
- package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +17 -0
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +19 -0
- package/dist/skills/git/references/tracker/github/manage-debt.md +101 -0
- package/dist/skills/git/references/tracker/github/post-wave-report.md +28 -0
- package/dist/skills/git/references/tracker/github/setup-task.md +26 -0
- package/dist/skills/git/references/tracker/jira/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +49 -0
- package/dist/skills/git/references/tracker/jira/create-release.md +17 -0
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +22 -0
- package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +53 -0
- package/dist/skills/git/references/tracker/jira/fetch-issue.md +14 -0
- package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +15 -0
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +18 -0
- package/dist/skills/git/references/tracker/jira/manage-debt.md +37 -0
- package/dist/skills/git/references/tracker/jira/post-wave-report.md +33 -0
- package/dist/skills/git/references/tracker/jira/setup-task.md +31 -0
- package/dist/skills/git/references/tracker/linear/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +53 -0
- package/dist/skills/git/references/tracker/linear/create-release.md +17 -0
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +22 -0
- package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +53 -0
- package/dist/skills/git/references/tracker/linear/fetch-issue.md +14 -0
- package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +15 -0
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +18 -0
- package/dist/skills/git/references/tracker/linear/manage-debt.md +37 -0
- package/dist/skills/git/references/tracker/linear/post-wave-report.md +33 -0
- package/dist/skills/git/references/tracker/linear/setup-task.md +32 -0
- package/dist/skills/git/references/trust-rule.md +7 -0
- 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 +1187 -32
- package/dist/targets/claude-code/legacy.js +5 -0
- package/dist/targets/claude-code/post-install.js +366 -151
- package/dist/targets/claude-code/tracker-install.js +134 -0
- package/package.json +8 -6
- package/src/assets/agents/code.md +45 -6
- package/src/assets/agents/design.md +2 -1
- package/src/assets/agents/git.mds +825 -0
- package/src/assets/agents/knowledge.md +3 -3
- package/src/assets/agents/learning.md +11 -0
- package/src/assets/agents/review.md +3 -1
- package/src/assets/agents/synthesize.md +1 -1
- package/src/assets/agents/test.md +16 -5
- package/src/assets/agents/tracker.md +474 -0
- package/src/assets/agents/validate.md +7 -5
- 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 +13 -11
- package/src/assets/commands/_partials/_evidence_policy.mds +30 -0
- 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 +22 -7
- package/src/assets/commands/_partials/_preamble.mds +2 -2
- package/src/assets/commands/_partials/_publication.mds +8 -2
- package/src/assets/commands/_partials/_settings.mds +28 -0
- package/src/assets/commands/_partials/_ticket_template.mds +3 -2
- package/src/assets/commands/_partials/_tracker.mds +18 -0
- package/src/assets/commands/_partials/_wave.mds +16 -10
- package/src/assets/commands/bug-analysis.mds +31 -19
- package/src/assets/commands/code-review.mds +67 -41
- package/src/assets/commands/debug.mds +13 -7
- package/src/assets/commands/dynamic-build.mds +274 -66
- package/src/assets/commands/dynamic-plan.mds +50 -23
- package/src/assets/commands/dynamic-profile.mds +24 -11
- package/src/assets/commands/dynamic-tickets.mds +63 -16
- package/src/assets/commands/explore.mds +4 -5
- package/src/assets/commands/implement.mds +234 -67
- package/src/assets/commands/plan.mds +91 -33
- package/src/assets/commands/release.md +64 -17
- package/src/assets/commands/research.mds +11 -9
- package/src/assets/commands/resolve.mds +150 -78
- package/src/assets/commands/self-review.mds +24 -25
- package/src/assets/mds/git/_pr.mds +331 -0
- package/src/assets/mds/git/_references.mds +135 -0
- package/src/assets/mds/tracker/_common.mds +156 -0
- package/src/assets/mds/tracker/_github.mds +472 -0
- package/src/assets/mds/tracker/_jira.mds +407 -0
- package/src/assets/mds/tracker/_linear.mds +449 -0
- package/src/assets/mds/tracker/_mcp.mds +305 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +5 -8
- package/src/assets/scripts/hooks/background-memory-update +40 -19
- package/src/assets/scripts/hooks/capture-prompt +18 -8
- package/src/assets/scripts/hooks/capture-question +18 -8
- package/src/assets/scripts/hooks/capture-turn +27 -13
- 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 +236 -60
- package/src/assets/scripts/hooks/git-marker +48 -0
- package/src/assets/scripts/hooks/hook-log-init +3 -1
- package/src/assets/scripts/hooks/json-helper.cjs +228 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +1 -20
- package/src/assets/scripts/hooks/log-paths +80 -0
- package/src/assets/scripts/hooks/memory-worker +22 -13
- package/src/assets/scripts/hooks/pre-compact-memory +44 -15
- package/src/assets/scripts/hooks/preamble +1 -4
- package/src/assets/scripts/hooks/queue-append +146 -28
- package/src/assets/scripts/hooks/resolve-project-root +101 -7
- package/src/assets/scripts/hooks/session-start-context +534 -20
- package/src/assets/scripts/hooks/session-start-memory +38 -15
- package/src/assets/scripts/lib/project-config.cjs +633 -0
- package/src/assets/scripts/pr-evidence.cjs +1961 -0
- package/src/assets/scripts/redact-secrets.cjs +490 -62
- package/src/assets/scripts/release-trace.cjs +1143 -0
- package/src/assets/scripts/resolve-evidence-policy.cjs +1145 -0
- package/src/assets/scripts/resolve-settings.cjs +1054 -0
- package/src/assets/scripts/verify-evidence.cjs +1822 -0
- package/src/assets/skills/compliance/SKILL.md +4 -2
- package/src/assets/skills/docs-framework/SKILL.md +11 -10
- 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/SKILL.md +8 -78
- package/src/assets/skills/git/references/github-api.md +179 -141
- package/src/assets/skills/git/references/patterns.md +11 -6
- package/src/assets/skills/review-methodology/SKILL.md +1 -1
- package/src/assets/skills/review-methodology/references/patterns.md +6 -61
- package/src/assets/skills/review-methodology/references/violations.md +14 -22
- 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
- package/src/assets/agents/git.md +0 -938
|
@@ -4,12 +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 {
|
|
8
|
-
import { writeFileAtomicExclusive } from '../../core/fs-atomic.js';
|
|
7
|
+
import { writeSettingsFileAtomic } from '../../core/fs-atomic.js';
|
|
9
8
|
import { discoverProjectGitRoots } from '../../targets/claude-code/post-install.js';
|
|
10
9
|
import { getGitRoot } from '../../core/git.js';
|
|
11
10
|
import { getMemoryDir, getPendingTurnsPath, getPendingTurnsProcessingPath, } from '../../core/project-paths.js';
|
|
12
|
-
import {
|
|
11
|
+
import { HOOKS_DIR_SUFFIX, devflowHookOwner, endsWithAny, ensureHook, hasHook, removeHooks, runHookCommand, runHookSuffix, } from '../../targets/claude-code/hooks.js';
|
|
12
|
+
import { readMachineFeature, writeMachineFeature } from '../../core/feature-switch.js';
|
|
13
|
+
import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../core/evidence-policy.js';
|
|
13
14
|
/**
|
|
14
15
|
* Map of hook event type → filename marker for the memory hooks.
|
|
15
16
|
* Three hooks total: Stop, SessionStart, PreCompact.
|
|
@@ -19,10 +20,11 @@ import { updateFeature, isFeatureEnabled } from '../../core/feature-config.js';
|
|
|
19
20
|
* at the hook-registration level), and decisions detection is a SessionStart-spawned
|
|
20
21
|
* detached worker rather than a SessionEnd hook (see legacy-hooks.ts).
|
|
21
22
|
*
|
|
22
|
-
* Stop-
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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.
|
|
26
28
|
*/
|
|
27
29
|
const MEMORY_HOOK_CONFIG = {
|
|
28
30
|
Stop: 'memory-worker',
|
|
@@ -30,14 +32,32 @@ const MEMORY_HOOK_CONFIG = {
|
|
|
30
32
|
PreCompact: 'pre-compact-memory',
|
|
31
33
|
};
|
|
32
34
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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.
|
|
35
43
|
*/
|
|
36
|
-
const
|
|
37
|
-
UserPromptSubmit: ['prompt-capture-memory', 'sidecar-dispatch', 'dream-dispatch'],
|
|
38
|
-
Stop: [
|
|
39
|
-
|
|
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),
|
|
40
56
|
};
|
|
57
|
+
/** D-EXACT-HOOK-OWNER: the current memory hook for `marker` (hooks.ts). */
|
|
58
|
+
function isMemoryHook(marker) {
|
|
59
|
+
return devflowHookOwner([marker]);
|
|
60
|
+
}
|
|
41
61
|
/**
|
|
42
62
|
* Add all 3 memory hooks (Stop, SessionStart, PreCompact) to settings JSON.
|
|
43
63
|
* Idempotent — skips hooks that already exist. Returns unchanged JSON if all 3 present.
|
|
@@ -47,28 +67,10 @@ export function addMemoryHooks(settingsJson, devflowDir) {
|
|
|
47
67
|
if (hasMemoryHooks(settings)) {
|
|
48
68
|
return settingsJson;
|
|
49
69
|
}
|
|
50
|
-
if (!settings.hooks) {
|
|
51
|
-
settings.hooks = {};
|
|
52
|
-
}
|
|
53
70
|
for (const [hookType, marker] of Object.entries(MEMORY_HOOK_CONFIG)) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const hookCommand = path.join(devflowDir, 'scripts', 'hooks', 'run-hook') + ` ${marker}`;
|
|
58
|
-
const newEntry = {
|
|
59
|
-
hooks: [
|
|
60
|
-
{
|
|
61
|
-
type: 'command',
|
|
62
|
-
command: hookCommand,
|
|
63
|
-
timeout: 10,
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
};
|
|
67
|
-
if (!settings.hooks[hookType]) {
|
|
68
|
-
settings.hooks[hookType] = [];
|
|
69
|
-
}
|
|
70
|
-
settings.hooks[hookType].push(newEntry);
|
|
71
|
-
}
|
|
71
|
+
ensureHook(settings, hookType, isMemoryHook(marker), {
|
|
72
|
+
hooks: [{ type: 'command', command: runHookCommand(devflowDir, marker), timeout: 10 }],
|
|
73
|
+
});
|
|
72
74
|
}
|
|
73
75
|
return JSON.stringify(settings, null, 2) + '\n';
|
|
74
76
|
}
|
|
@@ -81,36 +83,16 @@ export function addMemoryHooks(settingsJson, devflowDir) {
|
|
|
81
83
|
export function removeMemoryHooks(input) {
|
|
82
84
|
const settingsJson = typeof input === 'string' ? input : JSON.stringify(input);
|
|
83
85
|
const settings = typeof input === 'string' ? JSON.parse(input) : structuredClone(input);
|
|
84
|
-
|
|
85
|
-
return settingsJson;
|
|
86
|
-
}
|
|
86
|
+
// Evaluate every removal into a local — never short-circuit (PF-015).
|
|
87
87
|
let changed = false;
|
|
88
88
|
for (const [hookType, marker] of Object.entries(MEMORY_HOOK_CONFIG)) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
const before = settings.hooks[hookType].length;
|
|
93
|
-
settings.hooks[hookType] = settings.hooks[hookType].filter((matcher) => !matcher.hooks.some((h) => h.command.includes(marker)));
|
|
94
|
-
if (settings.hooks[hookType].length !== before) {
|
|
95
|
-
changed = true;
|
|
96
|
-
}
|
|
97
|
-
if (settings.hooks[hookType].length === 0) {
|
|
98
|
-
delete settings.hooks[hookType];
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
// Remove legacy pre-dream hooks from upgrading users
|
|
102
|
-
for (const [hookType, markers] of Object.entries(LEGACY_HOOK_MARKERS)) {
|
|
103
|
-
if (!settings.hooks[hookType])
|
|
104
|
-
continue;
|
|
105
|
-
const before = settings.hooks[hookType].length;
|
|
106
|
-
settings.hooks[hookType] = settings.hooks[hookType].filter((matcher) => !matcher.hooks.some((h) => markers.some((m) => h.command.includes(m))));
|
|
107
|
-
if (settings.hooks[hookType].length !== before)
|
|
108
|
-
changed = true;
|
|
109
|
-
if (settings.hooks[hookType].length === 0)
|
|
110
|
-
delete settings.hooks[hookType];
|
|
89
|
+
const removed = removeHooks(settings, hookType, isMemoryHook(marker));
|
|
90
|
+
changed = changed || removed;
|
|
111
91
|
}
|
|
112
|
-
|
|
113
|
-
|
|
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;
|
|
114
96
|
}
|
|
115
97
|
if (!changed) {
|
|
116
98
|
return settingsJson;
|
|
@@ -129,18 +111,48 @@ export function hasMemoryHooks(input) {
|
|
|
129
111
|
*/
|
|
130
112
|
export function countMemoryHooks(input) {
|
|
131
113
|
const settings = typeof input === 'string' ? JSON.parse(input) : input;
|
|
132
|
-
if (!settings.hooks) {
|
|
133
|
-
return 0;
|
|
134
|
-
}
|
|
135
114
|
let count = 0;
|
|
136
115
|
for (const [hookType, marker] of Object.entries(MEMORY_HOOK_CONFIG)) {
|
|
137
|
-
|
|
138
|
-
if (matchers.some((matcher) => matcher.hooks.some((h) => h.command.includes(marker)))) {
|
|
116
|
+
if (hasHook(settings, hookType, isMemoryHook(marker)))
|
|
139
117
|
count++;
|
|
140
|
-
}
|
|
141
118
|
}
|
|
142
119
|
return count;
|
|
143
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Converge the memory hooks in a settings JSON string to `enabled`. Pure.
|
|
123
|
+
*
|
|
124
|
+
* D-FEATURES-NARROW-ONLY: the ONE settings transform for the memory feature,
|
|
125
|
+
* shared by `devflow init` (inside its single settings read-modify-write pass)
|
|
126
|
+
* and `devflow memory --enable/--disable`, so the two controls of the same
|
|
127
|
+
* machine-wide switch leave settings.json byte-for-byte alike. Always
|
|
128
|
+
* remove-then-add, which also upgrades an older hook format (e.g. `.sh` →
|
|
129
|
+
* `run-hook`) in place.
|
|
130
|
+
*
|
|
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.
|
|
137
|
+
*/
|
|
138
|
+
export function convergeMemoryHooks(settingsJson, enabled, devflowDir) {
|
|
139
|
+
const cleaned = removeMemoryHooks(settingsJson);
|
|
140
|
+
return enabled ? addMemoryHooks(cleaned, devflowDir) : cleaned;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Drain a project's pending memory queue (and a claimed batch) so stale turns
|
|
144
|
+
* are not processed when memory is next switched on. Shared by `devflow init
|
|
145
|
+
* --no-memory` and `devflow memory --disable`. ENOENT-tolerant; any other
|
|
146
|
+
* error propagates to the command boundary, like drainLearningQueue.
|
|
147
|
+
*/
|
|
148
|
+
export async function drainMemoryQueue(projectRoot) {
|
|
149
|
+
const ignoreMissing = (e) => { if (e.code !== 'ENOENT')
|
|
150
|
+
throw e; };
|
|
151
|
+
await Promise.all([
|
|
152
|
+
fs.unlink(getPendingTurnsPath(projectRoot)).catch(ignoreMissing),
|
|
153
|
+
fs.unlink(getPendingTurnsProcessingPath(projectRoot)).catch(ignoreMissing),
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
144
156
|
/**
|
|
145
157
|
* Returns true if the given project root contains a `.devflow/memory/` directory.
|
|
146
158
|
* Treats unexpected errors (e.g. EACCES) as absent to avoid false positives.
|
|
@@ -195,16 +207,16 @@ export async function cleanQueueFiles(projectPaths) {
|
|
|
195
207
|
}
|
|
196
208
|
export const memoryCommand = new Command('memory')
|
|
197
209
|
.description('Enable, disable, or clean up working memory (session context preservation)')
|
|
198
|
-
.option('--enable', 'Enable working memory')
|
|
199
|
-
.option('--disable', 'Disable working memory')
|
|
210
|
+
.option('--enable', 'Enable working memory in every project (a repository can opt out)')
|
|
211
|
+
.option('--disable', 'Disable working memory in every project')
|
|
200
212
|
.option('--status', 'Show current state')
|
|
201
213
|
.option('--clear', 'Clean up queue files from projects')
|
|
202
214
|
.action(async (options) => {
|
|
203
215
|
const hasFlag = options.enable || options.disable || options.status || options.clear;
|
|
204
216
|
if (!hasFlag) {
|
|
205
217
|
p.intro(color.bgCyan(color.white(' Working Memory ')));
|
|
206
|
-
p.note(`${color.cyan('devflow memory --enable')}
|
|
207
|
-
`${color.cyan('devflow memory --disable')}
|
|
218
|
+
p.note(`${color.cyan('devflow memory --enable')} Enable working memory (every project; a repository can opt out)\n` +
|
|
219
|
+
`${color.cyan('devflow memory --disable')} Disable working memory (every project)\n` +
|
|
208
220
|
`${color.cyan('devflow memory --status')} Check current state\n` +
|
|
209
221
|
`${color.cyan('devflow memory --clear')} Clean up queue files`, 'Usage');
|
|
210
222
|
p.outro(color.dim('Memory hooks provide automatic session context preservation'));
|
|
@@ -213,7 +225,7 @@ export const memoryCommand = new Command('memory')
|
|
|
213
225
|
if (options.clear) {
|
|
214
226
|
p.intro(color.bgCyan(color.white(' Memory Cleanup ')));
|
|
215
227
|
// Discover current project and all known projects in parallel
|
|
216
|
-
const [gitRoots, gitRoot] = await Promise.all([discoverProjectGitRoots(), getGitRoot()]);
|
|
228
|
+
const [gitRoots, gitRoot] = await Promise.all([discoverProjectGitRoots(getClaudeDirectory()), getGitRoot()]);
|
|
217
229
|
const [projectsWithMemory, currentProjectHasMem] = await Promise.all([
|
|
218
230
|
filterProjectsWithMemory(gitRoots),
|
|
219
231
|
gitRoot ? hasMemoryDir(gitRoot) : Promise.resolve(false),
|
|
@@ -260,90 +272,77 @@ export const memoryCommand = new Command('memory')
|
|
|
260
272
|
: 'No queue files found to clean');
|
|
261
273
|
return;
|
|
262
274
|
}
|
|
263
|
-
const
|
|
264
|
-
const
|
|
275
|
+
const settingsPath = path.join(getClaudeDirectory(), 'settings.json');
|
|
276
|
+
const devflowDir = getDevFlowDirectory();
|
|
265
277
|
let settingsContent;
|
|
266
278
|
try {
|
|
267
279
|
settingsContent = await fs.readFile(settingsPath, 'utf-8');
|
|
268
280
|
}
|
|
269
281
|
catch {
|
|
270
|
-
if (options.status) {
|
|
271
|
-
p.log.info('Working memory: disabled (no settings.json found)');
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
// Create minimal settings.json
|
|
275
282
|
settingsContent = '{}';
|
|
276
283
|
}
|
|
277
|
-
// Resolve current project root for feature config
|
|
278
|
-
const gitRoot = await getGitRoot();
|
|
279
284
|
if (options.status) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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.
|
|
289
|
+
const enabled = await readMachineFeature(devflowDir, 'memory');
|
|
284
290
|
const count = countMemoryHooks(settingsContent);
|
|
285
291
|
const total = Object.keys(MEMORY_HOOK_CONFIG).length;
|
|
286
|
-
|
|
287
|
-
const featureEnabled = await isFeatureEnabled(gitRoot, 'memory');
|
|
288
|
-
if (count === total && featureEnabled) {
|
|
292
|
+
if (enabled && count === total) {
|
|
289
293
|
p.log.info(`Working memory: ${color.green('enabled')} (${total}/${total} hooks)`);
|
|
290
294
|
}
|
|
291
|
-
else if (
|
|
295
|
+
else if (!enabled) {
|
|
292
296
|
p.log.info(`Working memory: ${color.dim('disabled')}`);
|
|
293
297
|
}
|
|
294
298
|
else {
|
|
295
|
-
p.log.info(`Working memory: ${color.yellow(`
|
|
299
|
+
p.log.info(`Working memory: ${color.yellow(`enabled, but ${count}/${total} hooks registered`)} — ` +
|
|
300
|
+
`run ${color.cyan('devflow memory --enable')} to fix`);
|
|
296
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);
|
|
297
309
|
return;
|
|
298
310
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
p.log.success('Working memory enabled — configuration updated');
|
|
313
|
-
p.log.info(color.dim('Session context will be automatically preserved across conversations'));
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
const updated = addMemoryHooks(settingsContent, devflowDir);
|
|
317
|
-
await writeFileAtomicExclusive(settingsPath, updated);
|
|
318
|
-
p.log.success('Working memory enabled — hooks registered');
|
|
319
|
-
p.log.info(color.dim('Session context will be automatically preserved across conversations'));
|
|
320
|
-
}
|
|
321
|
-
// Update config to enable memory feature
|
|
322
|
-
if (gitRoot) {
|
|
323
|
-
await updateFeature(gitRoot, 'memory', true);
|
|
324
|
-
}
|
|
325
|
-
await syncManifestFeature(getDevFlowDirectory(), 'memory', true);
|
|
311
|
+
// --enable / --disable: the machine-wide switch, converged exactly as
|
|
312
|
+
// `devflow init --memory / --no-memory` converges it (D-FEATURES-NARROW-ONLY).
|
|
313
|
+
// The settings transform runs FIRST: it is the step that can reject its
|
|
314
|
+
// input (malformed JSON), and the switch must not be recorded unless the
|
|
315
|
+
// hooks that enact it can follow.
|
|
316
|
+
const enabled = options.enable === true;
|
|
317
|
+
let converged;
|
|
318
|
+
try {
|
|
319
|
+
converged = convergeMemoryHooks(settingsContent, enabled, devflowDir);
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
p.log.error(`Could not update ${settingsPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
323
|
+
process.exitCode = 1;
|
|
326
324
|
return;
|
|
327
325
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
await updateFeature(gitRoot, 'memory', false);
|
|
333
|
-
// Drain orphaned queue files so stale turns don't process on re-enable
|
|
334
|
-
await Promise.all([
|
|
335
|
-
fs.unlink(getPendingTurnsPath(gitRoot)).catch((e) => { if (e.code !== 'ENOENT')
|
|
336
|
-
throw e; }),
|
|
337
|
-
fs.unlink(getPendingTurnsProcessingPath(gitRoot)).catch((e) => { if (e.code !== 'ENOENT')
|
|
338
|
-
throw e; }),
|
|
339
|
-
]);
|
|
340
|
-
await syncManifestFeature(getDevFlowDirectory(), 'memory', false);
|
|
341
|
-
p.log.success('Working memory disabled — configuration updated');
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
p.log.warn('Could not resolve git root — configuration not updated');
|
|
345
|
-
}
|
|
326
|
+
const recorded = await writeMachineFeature(devflowDir, 'memory', enabled);
|
|
327
|
+
if (!recorded.ok) {
|
|
328
|
+
p.log.error(`Devflow is not installed on this machine — run ${color.cyan('devflow init')} first`);
|
|
329
|
+
process.exitCode = 1;
|
|
346
330
|
return;
|
|
347
331
|
}
|
|
332
|
+
if (converged !== settingsContent) {
|
|
333
|
+
await writeSettingsFileAtomic(settingsPath, converged);
|
|
334
|
+
}
|
|
335
|
+
if (enabled) {
|
|
336
|
+
p.log.success('Working memory enabled in every project (a repository can opt out)');
|
|
337
|
+
p.log.info(color.dim('Session context will be automatically preserved across conversations'));
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
// Drain the current project's queue, as init does. Outside a git project
|
|
341
|
+
// there is no project queue to drain, and the switch itself still applies.
|
|
342
|
+
const gitRoot = await getGitRoot();
|
|
343
|
+
if (gitRoot) {
|
|
344
|
+
await drainMemoryQueue(gitRoot);
|
|
345
|
+
}
|
|
346
|
+
p.log.success('Working memory disabled in every project');
|
|
348
347
|
});
|
|
349
348
|
//# sourceMappingURL=memory.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared wizard prompt-IO seam for devflow init wizard steps.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* One-definition seam: PromptOutcome and WizardPromptIO were byte-identical
|
|
5
|
+
* duplicates across attribution-prompts.ts and compliance-prompts.ts
|
|
6
|
+
* (architecture-03 / consistency-06). They are defined ONCE here and re-used
|
|
7
|
+
* via import.
|
|
8
8
|
*
|
|
9
9
|
* D-PROMPT-IO: WizardPromptIO is the base DI seam for all two-action wizard
|
|
10
10
|
* steps (note + boolean select). Modules that add a third prompt extend this
|
|
@@ -24,7 +24,7 @@ import * as p from '@clack/prompts';
|
|
|
24
24
|
import color from 'picocolors';
|
|
25
25
|
import { readProxyState, writeProxyState, buildProxyState, buildRoutingConfigJson, proxyBaseUrl, proxyJsonExists, resolveProxyBin, DEFAULT_PROXY_PORT, } from '../../core/proxy-state.js';
|
|
26
26
|
import { syncManifestFeature, readManifest } from '../../core/manifest.js';
|
|
27
|
-
import {
|
|
27
|
+
import { writeSettingsFileAtomic } from '../../core/fs-atomic.js';
|
|
28
28
|
import { scrubChildEnv, openProxyLog, rotateProxyLogIfLarge } from '../../core/proxy-log.js';
|
|
29
29
|
import { inspectCodexAuth, classifyCodexAuthReadError } from '../../core/codex-auth-inspect.js';
|
|
30
30
|
import { stripAnsi } from '../../hud/colors.js';
|
|
@@ -32,6 +32,7 @@ import { reapplyAgentMapping, revertExternalAgents, countExternalMappedAgents, r
|
|
|
32
32
|
import { getExternalModelsCached, discoverExternalModels, } from '../../core/model-discovery.js';
|
|
33
33
|
import { modelCacheDir } from '../../core/cache.js';
|
|
34
34
|
import { getClaudeDirectory, getDevFlowDirectory, getHomeDirectory, } from '../../targets/claude-code/claude-paths.js';
|
|
35
|
+
import { devflowHookOwner, ensureHook, hasHook, removeHooks, runHookCommand, } from '../../targets/claude-code/hooks.js';
|
|
35
36
|
function Ok(value) {
|
|
36
37
|
return { ok: true, value };
|
|
37
38
|
}
|
|
@@ -39,8 +40,17 @@ function Err(error) {
|
|
|
39
40
|
return { ok: false, error };
|
|
40
41
|
}
|
|
41
42
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
42
|
-
/**
|
|
43
|
+
/** The run-hook marker of the ensure-proxy hook. */
|
|
43
44
|
const PROXY_HOOK_MARKER = 'ensure-proxy';
|
|
45
|
+
/**
|
|
46
|
+
* D-EXACT-HOOK-OWNER: the ensure-proxy hook is devflow's only when its command ends
|
|
47
|
+
* in `/scripts/hooks/run-hook ensure-proxy` (hooks.ts), under any directory — the one
|
|
48
|
+
* form it has ever been registered in. A user's hook that merely mentions the word
|
|
49
|
+
* is theirs.
|
|
50
|
+
*/
|
|
51
|
+
const isProxyHook = devflowHookOwner([PROXY_HOOK_MARKER]);
|
|
52
|
+
/** The events the ensure-proxy hook is registered on. */
|
|
53
|
+
const PROXY_HOOK_EVENTS = ['SessionStart', 'UserPromptSubmit'];
|
|
44
54
|
/** Pattern matching our relay's ANTHROPIC_BASE_URL value. */
|
|
45
55
|
const OUR_BASE_URL_PATTERN = /^http:\/\/127\.0\.0\.1:\d+$/;
|
|
46
56
|
/** Timeout for individual TCP connect probes and HTTP health checks (ms). */
|
|
@@ -166,34 +176,6 @@ function _stripProxyEnvFromObject(settings, managedPort) {
|
|
|
166
176
|
delete s.env;
|
|
167
177
|
return removedUrl || hadWindowVar; // OR the locals — never compose with || inline (PF-015)
|
|
168
178
|
}
|
|
169
|
-
/** Internal: add ensure-proxy hook to one event. Returns true when added. */
|
|
170
|
-
function _ensureProxyHook(settings, eventName, hookCmd) {
|
|
171
|
-
const existing = settings.hooks?.[eventName];
|
|
172
|
-
if (existing?.some((m) => m.hooks.some((h) => h.command.includes(PROXY_HOOK_MARKER)))) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
settings.hooks ??= {};
|
|
176
|
-
settings.hooks[eventName] ??= [];
|
|
177
|
-
const entry = {
|
|
178
|
-
hooks: [{ type: 'command', command: hookCmd, timeout: 15 }],
|
|
179
|
-
};
|
|
180
|
-
settings.hooks[eventName].push(entry);
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
/** Internal: remove ensure-proxy hooks from one event. Returns true when removed. */
|
|
184
|
-
function _filterProxyHooks(settings, eventName) {
|
|
185
|
-
if (!settings.hooks?.[eventName])
|
|
186
|
-
return false;
|
|
187
|
-
const before = settings.hooks[eventName].length;
|
|
188
|
-
settings.hooks[eventName] = settings.hooks[eventName].filter((m) => !m.hooks.some((h) => h.command.includes(PROXY_HOOK_MARKER)));
|
|
189
|
-
if (settings.hooks[eventName].length === before)
|
|
190
|
-
return false;
|
|
191
|
-
if (settings.hooks[eventName].length === 0)
|
|
192
|
-
delete settings.hooks[eventName];
|
|
193
|
-
if (Object.keys(settings.hooks).length === 0)
|
|
194
|
-
delete settings.hooks;
|
|
195
|
-
return true;
|
|
196
|
-
}
|
|
197
179
|
// ─── Pure env functions (exported for testing and cross-module reuse) ─────────
|
|
198
180
|
/**
|
|
199
181
|
* Apply ANTHROPIC_BASE_URL=http://127.0.0.1:<port> to settings JSON.
|
|
@@ -251,20 +233,21 @@ export function readProxyEnvState(settingsJson, port) {
|
|
|
251
233
|
* Mutates settings in place. Returns true when any hook was added.
|
|
252
234
|
*/
|
|
253
235
|
export function addProxyHooks(settings, devflowDir) {
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
const addedPrompt =
|
|
236
|
+
const command = runHookCommand(devflowDir, PROXY_HOOK_MARKER);
|
|
237
|
+
// Evaluate each event into its own local — never short-circuit (PF-015).
|
|
238
|
+
const [addedSession, addedPrompt] = PROXY_HOOK_EVENTS.map((event) => ensureHook(settings, event, isProxyHook, { hooks: [{ type: 'command', command, timeout: 15 }] }));
|
|
257
239
|
return addedSession || addedPrompt;
|
|
258
240
|
}
|
|
259
241
|
/**
|
|
260
242
|
* Remove ensure-proxy hooks from all events.
|
|
261
243
|
* Idempotent — no-op when hooks are not present.
|
|
262
|
-
*
|
|
244
|
+
* Removes single hooks, so the other hooks of a shared matcher group stay in place
|
|
245
|
+
* (D-EXACT-HOOK-OWNER). Cleans empty arrays/objects.
|
|
263
246
|
* Mutates settings in place. Returns true when any hook was removed.
|
|
264
247
|
*/
|
|
265
248
|
export function removeProxyHooks(settings) {
|
|
266
|
-
|
|
267
|
-
const removedPrompt =
|
|
249
|
+
// Evaluate each event into its own local — never short-circuit (PF-015).
|
|
250
|
+
const [removedSession, removedPrompt] = PROXY_HOOK_EVENTS.map((event) => removeHooks(settings, event, isProxyHook));
|
|
268
251
|
return removedSession || removedPrompt;
|
|
269
252
|
}
|
|
270
253
|
/**
|
|
@@ -319,8 +302,7 @@ export function applyProxyTeardownToSettings(settings, managedPort) {
|
|
|
319
302
|
*/
|
|
320
303
|
export function hasProxyHooks(input) {
|
|
321
304
|
const settings = typeof input === 'string' ? JSON.parse(input) : input;
|
|
322
|
-
|
|
323
|
-
return check('SessionStart') || check('UserPromptSubmit');
|
|
305
|
+
return PROXY_HOOK_EVENTS.some((event) => hasHook(settings, event, isProxyHook));
|
|
324
306
|
}
|
|
325
307
|
// ─── Health-check identity helper ────────────────────────────────────────────
|
|
326
308
|
/**
|
|
@@ -747,7 +729,7 @@ export async function runPostSpawnVerification(binPath, configPath, logPath, spa
|
|
|
747
729
|
* Perform the single atomic settings.json pass for enable:
|
|
748
730
|
* strip old hooks + env, then apply new hooks + env in one write.
|
|
749
731
|
*
|
|
750
|
-
* The
|
|
732
|
+
* The writeSettingsFileAtomic call is guarded — ENOSPC/EACCES returns Err
|
|
751
733
|
* instead of crashing with an unhandled rejection.
|
|
752
734
|
*
|
|
753
735
|
* Returns Ok(undefined) on success, Err(reason) on hard failure.
|
|
@@ -777,7 +759,7 @@ async function applyEnableSettingsPass(settingsPath, devflowDir, port) {
|
|
|
777
759
|
addProxyHooks(parsedSettings, devflowDir);
|
|
778
760
|
_applyProxyEnvToObject(parsedSettings, port);
|
|
779
761
|
try {
|
|
780
|
-
await
|
|
762
|
+
await writeSettingsFileAtomic(settingsPath, JSON.stringify(parsedSettings, null, 2) + '\n');
|
|
781
763
|
}
|
|
782
764
|
catch (err) {
|
|
783
765
|
return Err(`Could not write settings.json: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1465,7 +1447,7 @@ async function runDisable() {
|
|
|
1465
1447
|
if (changed) {
|
|
1466
1448
|
// Guard ENOSPC/EACCES — unhandled rejection leaves proxy in partial state
|
|
1467
1449
|
try {
|
|
1468
|
-
await
|
|
1450
|
+
await writeSettingsFileAtomic(settingsPath, JSON.stringify(parsedSettings, null, 2) + '\n');
|
|
1469
1451
|
}
|
|
1470
1452
|
catch (err) {
|
|
1471
1453
|
p.log.error(`Could not write settings.json: ${err instanceof Error ? err.message : String(err)}`);
|