devflow-kit 3.0.1 → 3.2.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 +49 -0
- package/README.md +1 -1
- package/dist/agents/git.md +2 -2
- package/dist/cli/agents-view/index.js +1 -1
- package/dist/cli/agents-view/render.js +71 -17
- package/dist/cli/agents-view/state.js +42 -16
- package/dist/cli/agents-view/terminal.js +5 -5
- package/dist/cli/commands/agents.js +142 -51
- package/dist/cli/commands/ambient.js +1 -1
- package/dist/cli/commands/attribution-prompts.js +8 -8
- package/dist/cli/commands/capture.js +1 -1
- package/dist/cli/commands/compliance-prompts.js +8 -8
- package/dist/cli/commands/compliance.js +8 -7
- package/dist/cli/commands/flags.js +33 -31
- package/dist/cli/commands/hud.js +1 -1
- package/dist/cli/commands/init-seed.js +9 -9
- package/dist/cli/commands/init.js +162 -85
- package/dist/cli/commands/install-report.js +10 -10
- package/dist/cli/commands/learning.js +302 -136
- package/dist/cli/commands/memory.js +36 -15
- package/dist/cli/commands/proxy.js +23 -23
- package/dist/cli/commands/rules.js +6 -5
- package/dist/cli/commands/tracker-prompts.js +6 -6
- package/dist/cli/commands/tracker.js +9 -9
- package/dist/cli/commands/uninstall.js +183 -59
- package/dist/cli/flags-view/render.js +5 -5
- package/dist/cli/flags-view/state.js +9 -9
- package/dist/cli/flags-view/terminal.js +4 -4
- package/dist/cli/tui/cells.js +1 -1
- package/dist/cli/tui/terminal.js +6 -6
- package/dist/commands/code-review.md +0 -2
- package/dist/commands/debug.md +14 -11
- package/dist/commands/dynamic-build.md +51 -47
- package/dist/commands/dynamic-plan.md +27 -7
- package/dist/commands/dynamic-profile.md +17 -3
- package/dist/commands/dynamic-tickets.md +18 -4
- package/dist/commands/explore.md +9 -3
- package/dist/commands/implement.md +20 -16
- package/dist/commands/plan.md +13 -9
- package/dist/commands/release.md +23 -3
- package/dist/commands/research.md +9 -3
- package/dist/commands/resolve.md +9 -12
- package/dist/commands/self-review.md +0 -2
- package/dist/core/agent-frontmatter.js +28 -3
- package/dist/core/agent-models.js +204 -42
- package/dist/core/agent-state.js +28 -6
- package/dist/core/ansi.js +2 -2
- package/dist/core/assets.js +1 -1
- package/dist/core/cache.js +7 -8
- package/dist/core/codex-auth-inspect.js +4 -4
- package/dist/core/compliance-compose.js +3 -3
- package/dist/core/compliance.js +3 -4
- package/dist/core/evidence-policy.js +14 -13
- package/dist/core/external-models.js +1 -1
- package/dist/core/feature-config.js +71 -13
- package/dist/core/feature-switch.js +3 -3
- package/dist/core/flags.js +49 -25
- package/dist/core/fs-atomic.js +6 -7
- package/dist/core/learning-queue-cleanup.js +16 -81
- package/dist/core/learning-store.js +61 -0
- package/dist/core/linked-path.js +46 -0
- package/dist/core/manifest.js +5 -5
- package/dist/core/mds-variants.js +13 -13
- package/dist/core/model-discovery.js +8 -8
- package/dist/core/observations.js +17 -101
- package/dist/core/orphan-sweep.js +4 -4
- package/dist/core/plugins.js +13 -8
- package/dist/core/project-paths.js +9 -13
- package/dist/core/proxy-log.js +8 -8
- package/dist/core/proxy-state.js +3 -3
- package/dist/core/queue-drain.js +31 -0
- package/dist/core/reference-sweep.js +6 -6
- package/dist/core/teammate-mode-cleanup.js +1 -1
- package/dist/core/tracker.js +14 -14
- package/dist/hud/colors.js +2 -2
- package/dist/hud/components/learning-counts.js +54 -22
- package/dist/hud/components/version-badge.js +1 -1
- package/dist/skills/git/references/pr/resolve-review-threads.md +2 -2
- package/dist/skills/git/references/tracker/github/create-release.md +2 -2
- package/dist/skills/git/references/tracker/jira/create-release.md +2 -2
- package/dist/skills/git/references/tracker/linear/create-release.md +2 -2
- package/dist/targets/claude-code/compliance-install.js +17 -15
- package/dist/targets/claude-code/hooks.js +2 -2
- package/dist/targets/claude-code/installer.js +59 -32
- package/dist/targets/claude-code/legacy.js +1 -1
- package/dist/targets/claude-code/post-install.js +135 -45
- package/dist/targets/claude-code/tracker-install.js +2 -2
- package/package.json +1 -1
- package/src/assets/agents/code.md +15 -21
- package/src/assets/agents/design.md +4 -2
- package/src/assets/agents/diagnose.md +3 -1
- package/src/assets/agents/evaluate.md +4 -0
- package/src/assets/agents/git.mds +2 -2
- package/src/assets/agents/knowledge.md +5 -3
- package/src/assets/agents/learning.md +281 -196
- package/src/assets/agents/research.md +3 -1
- package/src/assets/agents/review.md +5 -3
- package/src/assets/agents/scrutinize.md +5 -1
- package/src/assets/agents/simplify.md +4 -0
- package/src/assets/agents/skim.md +4 -2
- package/src/assets/agents/synthesize.md +6 -0
- package/src/assets/agents/test.md +18 -10
- package/src/assets/agents/triage.md +11 -9
- package/src/assets/agents/validate.md +14 -10
- package/src/assets/commands/_partials/_decisions.mds +8 -3
- package/src/assets/commands/_partials/_docs_root.mds +3 -3
- package/src/assets/commands/_partials/_engine.mds +16 -32
- package/src/assets/commands/_partials/_knowledge.mds +0 -2
- package/src/assets/commands/_partials/_preamble.mds +6 -2
- package/src/assets/commands/_partials/_settings.mds +2 -2
- package/src/assets/commands/_partials/_tracker.mds +1 -1
- package/src/assets/commands/code-review.mds +0 -2
- package/src/assets/commands/debug.mds +13 -8
- package/src/assets/commands/dynamic-build.mds +18 -12
- package/src/assets/commands/dynamic-plan.mds +10 -4
- package/src/assets/commands/dynamic-profile.mds +1 -1
- package/src/assets/commands/dynamic-tickets.mds +2 -2
- package/src/assets/commands/explore.mds +9 -1
- package/src/assets/commands/implement.mds +19 -13
- package/src/assets/commands/plan.mds +12 -8
- package/src/assets/commands/release.md +23 -3
- package/src/assets/commands/research.mds +9 -3
- package/src/assets/commands/resolve.mds +9 -10
- package/src/assets/mds/git/_pr.mds +3 -3
- package/src/assets/mds/tracker/_common.mds +1 -1
- package/src/assets/mds/tracker/_github.mds +3 -3
- package/src/assets/mds/tracker/_jira.mds +3 -3
- package/src/assets/mds/tracker/_linear.mds +3 -3
- package/src/assets/mds/tracker/_mcp.mds +6 -5
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -2
- package/src/assets/scripts/hooks/background-memory-update +97 -33
- package/src/assets/scripts/hooks/capture-prompt +4 -3
- package/src/assets/scripts/hooks/capture-question +4 -3
- package/src/assets/scripts/hooks/capture-turn +5 -20
- package/src/assets/scripts/hooks/ensure-devflow-init +14 -2
- package/src/assets/scripts/hooks/ensure-proxy +5 -6
- package/src/assets/scripts/hooks/ensure-root-gitignore +123 -11
- package/src/assets/scripts/hooks/git-marker +71 -0
- package/src/assets/scripts/hooks/is-hex-sha +1 -1
- package/src/assets/scripts/hooks/json-helper.cjs +345 -944
- package/src/assets/scripts/hooks/json-parse +25 -129
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +205 -156
- package/src/assets/scripts/hooks/lib/learning-store.cjs +3207 -0
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +7 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +13 -19
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +253 -226
- package/src/assets/scripts/hooks/memory-worker +10 -0
- package/src/assets/scripts/hooks/pre-compact-memory +66 -14
- package/src/assets/scripts/hooks/preamble +9 -1
- package/src/assets/scripts/hooks/queue-append +55 -23
- package/src/assets/scripts/hooks/resolve-project-root +3 -4
- package/src/assets/scripts/hooks/session-start-context +146 -45
- package/src/assets/scripts/hooks/session-start-memory +33 -11
- package/src/assets/scripts/lib/project-config.cjs +2 -2
- package/src/assets/scripts/pr-evidence.cjs +3 -3
- package/src/assets/scripts/redact-secrets.cjs +20 -20
- package/src/assets/scripts/release-trace.cjs +1 -1
- package/src/assets/scripts/resolve-evidence-policy.cjs +3 -3
- package/src/assets/scripts/resolve-settings.cjs +3 -3
- package/src/assets/scripts/verify-evidence.cjs +2 -2
- package/src/assets/skills/apply-decisions/SKILL.md +37 -17
- package/src/assets/skills/docs-framework/SKILL.md +2 -2
- package/src/assets/skills/feature-knowledge/SKILL.md +6 -5
- package/src/assets/skills/test-driven-development/SKILL.md +6 -4
- package/dist/core/observation-io.js +0 -50
- package/src/assets/scripts/hooks/decisions-usage-scan.cjs +0 -131
|
@@ -7,7 +7,9 @@ import { getClaudeDirectory, getDevFlowDirectory } from '../../targets/claude-co
|
|
|
7
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
|
+
import { firstSymbolicLink } from '../../core/linked-path.js';
|
|
10
11
|
import { getMemoryDir, getPendingTurnsPath, getPendingTurnsProcessingPath, } from '../../core/project-paths.js';
|
|
12
|
+
import { drainQueueFiles, formatRefusedDrain } from '../../core/queue-drain.js';
|
|
11
13
|
import { HOOKS_DIR_SUFFIX, devflowHookOwner, endsWithAny, ensureHook, hasHook, removeHooks, runHookCommand, runHookSuffix, } from '../../targets/claude-code/hooks.js';
|
|
12
14
|
import { readMachineFeature, writeMachineFeature } from '../../core/feature-switch.js';
|
|
13
15
|
import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../core/evidence-policy.js';
|
|
@@ -83,7 +85,7 @@ export function addMemoryHooks(settingsJson, devflowDir) {
|
|
|
83
85
|
export function removeMemoryHooks(input) {
|
|
84
86
|
const settingsJson = typeof input === 'string' ? input : JSON.stringify(input);
|
|
85
87
|
const settings = typeof input === 'string' ? JSON.parse(input) : structuredClone(input);
|
|
86
|
-
// Evaluate every removal into a local — never short-circuit
|
|
88
|
+
// Evaluate every removal into a local — never short-circuit.
|
|
87
89
|
let changed = false;
|
|
88
90
|
for (const [hookType, marker] of Object.entries(MEMORY_HOOK_CONFIG)) {
|
|
89
91
|
const removed = removeHooks(settings, hookType, isMemoryHook(marker));
|
|
@@ -142,15 +144,16 @@ export function convergeMemoryHooks(settingsJson, enabled, devflowDir) {
|
|
|
142
144
|
/**
|
|
143
145
|
* Drain a project's pending memory queue (and a claimed batch) so stale turns
|
|
144
146
|
* are not processed when memory is next switched on. Shared by `devflow init
|
|
145
|
-
* --no-memory` and `devflow memory --disable`.
|
|
146
|
-
*
|
|
147
|
+
* --no-memory` and `devflow memory --disable`. Refused, deleting nothing, when
|
|
148
|
+
* `.devflow` or `.devflow/memory` under `projectRoot` is a symbolic link
|
|
149
|
+
* (D-CLI-NO-SYMLINK). ENOENT-tolerant; any other error propagates to the command
|
|
150
|
+
* boundary, like drainLearningQueue.
|
|
147
151
|
*/
|
|
148
152
|
export async function drainMemoryQueue(projectRoot) {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
fs.unlink(getPendingTurnsProcessingPath(projectRoot)).catch(ignoreMissing),
|
|
153
|
+
const memoryDir = getMemoryDir(projectRoot);
|
|
154
|
+
return drainQueueFiles([path.dirname(memoryDir), memoryDir], [
|
|
155
|
+
getPendingTurnsPath(projectRoot),
|
|
156
|
+
getPendingTurnsProcessingPath(projectRoot),
|
|
154
157
|
]);
|
|
155
158
|
}
|
|
156
159
|
/**
|
|
@@ -181,12 +184,24 @@ export async function filterProjectsWithMemory(gitRoots) {
|
|
|
181
184
|
}
|
|
182
185
|
/**
|
|
183
186
|
* Clean up memory queue files from the given project paths.
|
|
184
|
-
* Skips projects where the background updater lock is held to avoid data loss
|
|
185
|
-
*
|
|
187
|
+
* Skips projects where the background updater lock is held to avoid data loss,
|
|
188
|
+
* and refuses, deleting nothing there, a project whose `.devflow` or
|
|
189
|
+
* `.devflow/memory` is a symbolic link (D-CLI-NO-SYMLINK); `refused` names each
|
|
190
|
+
* such link. Returns the count of projects from which at least one file was removed.
|
|
186
191
|
*/
|
|
187
192
|
export async function cleanQueueFiles(projectPaths) {
|
|
188
193
|
const results = await Promise.all(projectPaths.map(async (project) => {
|
|
189
194
|
const memDir = getMemoryDir(project);
|
|
195
|
+
let linkedFolder;
|
|
196
|
+
try {
|
|
197
|
+
linkedFolder = await firstSymbolicLink([path.dirname(memDir), memDir]);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// The folders cannot be checked: delete nothing there, and go on to the others.
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
if (linkedFolder !== null)
|
|
204
|
+
return { refused: linkedFolder };
|
|
190
205
|
const lockDir = path.join(memDir, '.working-memory.lock');
|
|
191
206
|
try {
|
|
192
207
|
await fs.access(lockDir);
|
|
@@ -200,10 +215,11 @@ export async function cleanQueueFiles(projectPaths) {
|
|
|
200
215
|
fs.unlink(getPendingTurnsPath(project)).then(() => true).catch(() => false),
|
|
201
216
|
fs.unlink(getPendingTurnsProcessingPath(project)).then(() => true).catch(() => false),
|
|
202
217
|
]);
|
|
203
|
-
return (q || pr) ? project : null;
|
|
218
|
+
return (q || pr) ? { cleaned: project } : null;
|
|
204
219
|
}));
|
|
205
|
-
const cleanedProjects = results.
|
|
206
|
-
|
|
220
|
+
const cleanedProjects = results.flatMap((r) => (r !== null && 'cleaned' in r ? [r.cleaned] : []));
|
|
221
|
+
const refused = results.flatMap((r) => (r !== null && 'refused' in r ? [r.refused] : []));
|
|
222
|
+
return { cleaned: cleanedProjects.length, projects: cleanedProjects, refused };
|
|
207
223
|
}
|
|
208
224
|
export const memoryCommand = new Command('memory')
|
|
209
225
|
.description('Enable, disable, or clean up working memory (session context preservation)')
|
|
@@ -263,10 +279,13 @@ export const memoryCommand = new Command('memory')
|
|
|
263
279
|
}
|
|
264
280
|
targets = scope === 'local' && currentProject ? [currentProject] : allProjects;
|
|
265
281
|
}
|
|
266
|
-
const { cleaned, projects: cleanedProjects } = await cleanQueueFiles(targets);
|
|
282
|
+
const { cleaned, projects: cleanedProjects, refused } = await cleanQueueFiles(targets);
|
|
267
283
|
for (const project of cleanedProjects) {
|
|
268
284
|
p.log.info(color.dim(`Cleaned: ${project}`));
|
|
269
285
|
}
|
|
286
|
+
for (const linkedFolder of refused) {
|
|
287
|
+
p.log.warn(formatRefusedDrain('memory', linkedFolder));
|
|
288
|
+
}
|
|
270
289
|
p.log.success(cleaned > 0
|
|
271
290
|
? `Cleaned queue files from ${cleaned} project${cleaned > 1 ? 's' : ''}`
|
|
272
291
|
: 'No queue files found to clean');
|
|
@@ -341,7 +360,9 @@ export const memoryCommand = new Command('memory')
|
|
|
341
360
|
// there is no project queue to drain, and the switch itself still applies.
|
|
342
361
|
const gitRoot = await getGitRoot();
|
|
343
362
|
if (gitRoot) {
|
|
344
|
-
await drainMemoryQueue(gitRoot);
|
|
363
|
+
const drain = await drainMemoryQueue(gitRoot);
|
|
364
|
+
if (!drain.drained)
|
|
365
|
+
p.log.warn(formatRefusedDrain('memory', drain.linkedFolder));
|
|
345
366
|
}
|
|
346
367
|
p.log.success('Working memory disabled in every project');
|
|
347
368
|
});
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* devflow proxy — Enable, disable, and check status of external model routing
|
|
3
3
|
* (GPT models via your OpenAI/Codex subscription).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* CLI-layer module; all core logic lives in src/core/proxy-state.ts
|
|
6
6
|
* and src/core/agent-models.ts.
|
|
7
|
-
*
|
|
8
|
-
* from the async action handler for all early-exit paths.
|
|
9
|
-
*
|
|
7
|
+
* Never process.exit() inside a finally-guarded scope (it would skip the finally);
|
|
8
|
+
* use return from the async action handler for all early-exit paths.
|
|
9
|
+
* Hook output strings use fixed templates; port number interpolation
|
|
10
10
|
* is acceptable (digit-validated integer, not user-controlled content).
|
|
11
11
|
*
|
|
12
12
|
* Branding note: "subswitch" must NEVER appear in user-visible strings. Internal
|
|
@@ -117,7 +117,7 @@ const UNKNOWN_MODEL_WINDOW_ENV = 'CLAUDE_CODE_DISABLE_UNKNOWN_MODEL_WINDOW_ENFOR
|
|
|
117
117
|
* Mutate a parsed Settings object in place: set ANTHROPIC_BASE_URL to our relay
|
|
118
118
|
* and set UNKNOWN_MODEL_WINDOW_ENV to '1'.
|
|
119
119
|
*
|
|
120
|
-
* D-P4-1: Each condition is evaluated independently (
|
|
120
|
+
* D-P4-1: Each condition is evaluated independently (no short-circuit that
|
|
121
121
|
* skips the second write when the first reports no change).
|
|
122
122
|
*
|
|
123
123
|
* Returns true when the object was changed by either assignment.
|
|
@@ -127,7 +127,7 @@ function _applyProxyEnvToObject(settings, port) {
|
|
|
127
127
|
s.env = s.env ?? {};
|
|
128
128
|
const env = s.env;
|
|
129
129
|
const newUrl = proxyBaseUrl(port);
|
|
130
|
-
// D-P4-1: evaluate each condition independently before combining (
|
|
130
|
+
// D-P4-1: evaluate each condition independently before combining (no short-circuit)
|
|
131
131
|
const urlChanged = env.ANTHROPIC_BASE_URL !== newUrl;
|
|
132
132
|
const windowVarChanged = env[UNKNOWN_MODEL_WINDOW_ENV] !== '1';
|
|
133
133
|
env.ANTHROPIC_BASE_URL = newUrl;
|
|
@@ -149,8 +149,8 @@ function _applyProxyEnvToObject(settings, port) {
|
|
|
149
149
|
* - uninstall → proxy.json.port (or DEFAULT_PROXY_PORT)
|
|
150
150
|
*
|
|
151
151
|
* D-P4-1: URL ownership gates the URL delete only; the window var is always ours to
|
|
152
|
-
* remove
|
|
153
|
-
* afterwards — never short-circuit composed inline
|
|
152
|
+
* remove. Each outcome is evaluated into a local and OR-ed
|
|
153
|
+
* afterwards — never short-circuit composed inline.
|
|
154
154
|
*
|
|
155
155
|
* Returns true when the object was changed by either deletion.
|
|
156
156
|
*/
|
|
@@ -163,7 +163,7 @@ function _stripProxyEnvFromObject(settings, managedPort) {
|
|
|
163
163
|
// managed the proxy — `proxyJsonExists()` — and the enable caller is taking ownership
|
|
164
164
|
// of the key anyway, so reaching this line means the value is Devflow's to remove.
|
|
165
165
|
// Removal is therefore unconditional: unlike ANTHROPIC_BASE_URL (port-scoped below),
|
|
166
|
-
// this key has no foreign value to protect.
|
|
166
|
+
// this key has no foreign value to protect.
|
|
167
167
|
const hadWindowVar = env[UNKNOWN_MODEL_WINDOW_ENV] !== undefined;
|
|
168
168
|
delete env[UNKNOWN_MODEL_WINDOW_ENV];
|
|
169
169
|
let removedUrl = false;
|
|
@@ -174,7 +174,7 @@ function _stripProxyEnvFromObject(settings, managedPort) {
|
|
|
174
174
|
}
|
|
175
175
|
if (Object.keys(env).length === 0)
|
|
176
176
|
delete s.env;
|
|
177
|
-
return removedUrl || hadWindowVar; // OR the locals — never compose with || inline
|
|
177
|
+
return removedUrl || hadWindowVar; // OR the locals — never compose with || inline
|
|
178
178
|
}
|
|
179
179
|
// ─── Pure env functions (exported for testing and cross-module reuse) ─────────
|
|
180
180
|
/**
|
|
@@ -234,7 +234,7 @@ export function readProxyEnvState(settingsJson, port) {
|
|
|
234
234
|
*/
|
|
235
235
|
export function addProxyHooks(settings, devflowDir) {
|
|
236
236
|
const command = runHookCommand(devflowDir, PROXY_HOOK_MARKER);
|
|
237
|
-
// Evaluate each event into its own local — never short-circuit
|
|
237
|
+
// Evaluate each event into its own local — never short-circuit.
|
|
238
238
|
const [addedSession, addedPrompt] = PROXY_HOOK_EVENTS.map((event) => ensureHook(settings, event, isProxyHook, { hooks: [{ type: 'command', command, timeout: 15 }] }));
|
|
239
239
|
return addedSession || addedPrompt;
|
|
240
240
|
}
|
|
@@ -246,7 +246,7 @@ export function addProxyHooks(settings, devflowDir) {
|
|
|
246
246
|
* Mutates settings in place. Returns true when any hook was removed.
|
|
247
247
|
*/
|
|
248
248
|
export function removeProxyHooks(settings) {
|
|
249
|
-
// Evaluate each event into its own local — never short-circuit
|
|
249
|
+
// Evaluate each event into its own local — never short-circuit.
|
|
250
250
|
const [removedSession, removedPrompt] = PROXY_HOOK_EVENTS.map((event) => removeHooks(settings, event, isProxyHook));
|
|
251
251
|
return removedSession || removedPrompt;
|
|
252
252
|
}
|
|
@@ -503,7 +503,7 @@ export async function realHttpGet(url, timeoutMs) {
|
|
|
503
503
|
/** Production doctor subprocess implementation. */
|
|
504
504
|
async function realSpawnDoctor(binPath, env, timeoutMs, logFile) {
|
|
505
505
|
// openProxyLog: 0700 parent dir + 0600 file creation + best-effort chmod for
|
|
506
|
-
// pre-existing wider modes (SEC-2). Non-fatal on chmod failure
|
|
506
|
+
// pre-existing wider modes (SEC-2). Non-fatal on chmod failure.
|
|
507
507
|
const logFd = await openProxyLog(logFile);
|
|
508
508
|
try {
|
|
509
509
|
return await new Promise((resolve) => {
|
|
@@ -558,7 +558,7 @@ async function realSpawnDoctor(binPath, env, timeoutMs, logFile) {
|
|
|
558
558
|
* Centralises the three private implementations so both runEnable and init.ts
|
|
559
559
|
* can consume them without byte-identical inline copies.
|
|
560
560
|
*
|
|
561
|
-
*
|
|
561
|
+
* Pure configuration factory; I/O implementations factored once.
|
|
562
562
|
*/
|
|
563
563
|
export function buildRealPreflightDeps(opts) {
|
|
564
564
|
const { settingsPath, onWarn, swallowSettingsReadError = false } = opts;
|
|
@@ -599,7 +599,7 @@ export function buildRealPreflightDeps(opts) {
|
|
|
599
599
|
* - OS-level spawn error (EMFILE, ENOMEM, EAGAIN) — always handled via the
|
|
600
600
|
* injected onError callback, never an uncaught exception
|
|
601
601
|
*
|
|
602
|
-
*
|
|
602
|
+
* No process.exit() — returns Result; caller decides error handling.
|
|
603
603
|
*/
|
|
604
604
|
export async function spawnRelayAndWaitForPort(port, binPath, configPath, logPath, pidPath, adopted, deps) {
|
|
605
605
|
if (adopted) {
|
|
@@ -784,7 +784,7 @@ async function applyEnableSettingsPass(settingsPath, devflowDir, port) {
|
|
|
784
784
|
* -n no host-name resolution -P no port-name resolution
|
|
785
785
|
* -sTCP:LISTEN only LISTEN-state sockets -t PIDs only
|
|
786
786
|
*
|
|
787
|
-
*
|
|
787
|
+
* Fixed argument array — port is validated as a safe integer
|
|
788
788
|
* by the caller before this function is invoked; it is never interpolated
|
|
789
789
|
* into a shell string.
|
|
790
790
|
*
|
|
@@ -855,8 +855,8 @@ async function readPidFile(pidPath) {
|
|
|
855
855
|
*
|
|
856
856
|
* applies D-EFR-5: signal 0 and health check are independent facts; lsof
|
|
857
857
|
* binds the pid-file PID to the port owner before any signal is sent.
|
|
858
|
-
*
|
|
859
|
-
*
|
|
858
|
+
* All sub-operations are non-fatal; a kill failure never aborts disable.
|
|
859
|
+
* Never process.exit() inside a finally-guarded scope (it would skip the finally).
|
|
860
860
|
*
|
|
861
861
|
* @param deps Optional injectable dependencies (default: real lsof implementation).
|
|
862
862
|
* Provide a stub in tests to exercise every branch without a real binary.
|
|
@@ -1211,7 +1211,7 @@ async function runStatus() {
|
|
|
1211
1211
|
}
|
|
1212
1212
|
}
|
|
1213
1213
|
// External models registry (cache-only, zero spawns — avoids multi-second silent pause in --status)
|
|
1214
|
-
const cacheDir = modelCacheDir(devflowDir); // authoritative path from cache.ts
|
|
1214
|
+
const cacheDir = modelCacheDir(devflowDir); // authoritative path from cache.ts
|
|
1215
1215
|
const catalog = getExternalModelsCached(cacheDir);
|
|
1216
1216
|
p.log.info(formatExternalModelsLine(catalog, logPath));
|
|
1217
1217
|
// Log path
|
|
@@ -1236,7 +1236,7 @@ async function runEnable(portOption) {
|
|
|
1236
1236
|
const configPath = path.join(devflowDir, 'proxy-routing.json');
|
|
1237
1237
|
const logPath = path.join(devflowDir, 'logs', 'proxy.log');
|
|
1238
1238
|
const pidPath = path.join(devflowDir, 'proxy.pid');
|
|
1239
|
-
const cacheDir = modelCacheDir(devflowDir); // authoritative path from cache.ts
|
|
1239
|
+
const cacheDir = modelCacheDir(devflowDir); // authoritative path from cache.ts
|
|
1240
1240
|
// Step 1: Read prior proxy.json (remembered port); --port flag overrides
|
|
1241
1241
|
const priorStateResult = await readProxyState(devflowDir);
|
|
1242
1242
|
const priorPort = priorStateResult.ok ? priorStateResult.value.port : DEFAULT_PROXY_PORT;
|
|
@@ -1260,7 +1260,7 @@ async function runEnable(portOption) {
|
|
|
1260
1260
|
await rotateProxyLogIfLarge(logPath);
|
|
1261
1261
|
// Read existing routing config to preserve user-added anthropic/limits/logLevel/providers
|
|
1262
1262
|
// blocks. A missing or malformed file falls back to clean defaults inside
|
|
1263
|
-
// buildRoutingConfigJson (non-fatal
|
|
1263
|
+
// buildRoutingConfigJson (non-fatal).
|
|
1264
1264
|
let existingRoutingContent;
|
|
1265
1265
|
try {
|
|
1266
1266
|
existingRoutingContent = await fs.readFile(configPath, 'utf-8');
|
|
@@ -1389,7 +1389,7 @@ async function runEnable(portOption) {
|
|
|
1389
1389
|
s.message('Warming model cache...');
|
|
1390
1390
|
await discoverExternalModels(cacheDir, logPath).catch(() => { });
|
|
1391
1391
|
s.stop(color.green('External model routing enabled'));
|
|
1392
|
-
// D-P4-1
|
|
1392
|
+
// D-P4-1: applies-on-restart — env var takes effect only for new sessions
|
|
1393
1393
|
p.log.info(color.dim('Context-window enforcement disabled for relay-routed models — applies to new Claude Code sessions'));
|
|
1394
1394
|
if (adopted) {
|
|
1395
1395
|
p.log.info(`Relay already running on port ${port} — adopted`);
|
|
@@ -1476,7 +1476,7 @@ async function runDisable() {
|
|
|
1476
1476
|
p.log.success('External model routing disabled');
|
|
1477
1477
|
// Step 5: Terminate the relay process.
|
|
1478
1478
|
// Identity is confirmed via TCP + health before signalling — a recycled PID
|
|
1479
|
-
// must never be killed.
|
|
1479
|
+
// must never be killed. All sub-operations are non-fatal.
|
|
1480
1480
|
const spawnLockPath = path.join(devflowDir, '.proxy-spawn.lock');
|
|
1481
1481
|
const terminateResult = await terminateRelay(pidPath, spawnLockPath, managedPort);
|
|
1482
1482
|
switch (terminateResult) {
|
|
@@ -114,7 +114,7 @@ async function printRulesList(claudeDir, devflowDir) {
|
|
|
114
114
|
* Returns which tier seeded: 'installed' | 'source' | 'none'.
|
|
115
115
|
* Creates the shadow directory before copying.
|
|
116
116
|
*
|
|
117
|
-
* D35 — seedRuleShadow tiers
|
|
117
|
+
* D35 — seedRuleShadow tiers:
|
|
118
118
|
* Tier 1 — installed rule at rulesTarget/{name}.md (fastest path when rules are enabled).
|
|
119
119
|
* SKIPPED for FEATURE_OWNED_RULES: the installed file is already stamped
|
|
120
120
|
* (${DEVFLOW_COMPLIANCE_FRAMEWORKS} replaced with label text), so seeding from
|
|
@@ -198,9 +198,10 @@ async function handleRuleUnshadow(name, allRules, devflowDir) {
|
|
|
198
198
|
* Compliance convergence runs unconditionally — convergeComplianceArtifacts handles the
|
|
199
199
|
* disabled case by removing stale artifacts, so no enabled-gate is needed here. Running
|
|
200
200
|
* converge regardless of the rules-install outcome ensures that a rules-install failure
|
|
201
|
-
* cannot leave compliance artifacts in a half-converged state
|
|
201
|
+
* cannot leave compliance artifacts in a half-converged state.
|
|
202
202
|
*
|
|
203
|
-
*
|
|
203
|
+
* Warn-not-throw on per-item compliance convergence failure, so a convergence
|
|
204
|
+
* failure never undoes the rules install.
|
|
204
205
|
*/
|
|
205
206
|
export async function runRulesEnable(claudeDir, devflowDir) {
|
|
206
207
|
const rulesTarget = path.join(claudeDir, 'rules', 'devflow');
|
|
@@ -241,7 +242,7 @@ export async function runRulesEnable(claudeDir, devflowDir) {
|
|
|
241
242
|
// disabled case itself (removes stale artifacts). Running this regardless of installFailed
|
|
242
243
|
// prevents a rules-install failure from leaving the compliance rule in a stale state.
|
|
243
244
|
// rulesEnabledOverride reflects the actual settled state: false when install failed so the
|
|
244
|
-
// compliance rule is not installed into a wiped, rules-disabled directory
|
|
245
|
+
// compliance rule is not installed into a wiped, rules-disabled directory.
|
|
245
246
|
try {
|
|
246
247
|
await convergeFromManifest({
|
|
247
248
|
claudeDir,
|
|
@@ -252,7 +253,7 @@ export async function runRulesEnable(claudeDir, devflowDir) {
|
|
|
252
253
|
});
|
|
253
254
|
}
|
|
254
255
|
catch (err) {
|
|
255
|
-
//
|
|
256
|
+
// Warn-not-abort — compliance convergence failure does not undo rules install
|
|
256
257
|
p.log.warn(`Compliance rule convergence failed — rules installed but compliance rule may be stale: ${err instanceof Error ? err.message : String(err)}`);
|
|
257
258
|
}
|
|
258
259
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tracker prompt helpers for devflow init.
|
|
3
3
|
*
|
|
4
|
-
* CLI-layer module
|
|
4
|
+
* CLI-layer module: prompt-rendering logic lives in src/cli/commands/,
|
|
5
5
|
* core business logic stays in src/core/tracker.ts.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* The wizard gate keys on `modePromptShown` (was the Setup-mode
|
|
8
8
|
* p.select prompt actually shown?), never on the mode name, so --recommended
|
|
9
9
|
* (flag, no prompt) and the non-TTY fallback preserve their promptless contracts.
|
|
10
|
-
*
|
|
10
|
+
* runTrackerStep never calls process.exit() or throws — callers
|
|
11
11
|
* own the cancel idiom (p.cancel + process.exit(0)), keeping try/finally safe.
|
|
12
12
|
* The shared DI seam (PromptOutcome, WizardPromptIO, clackNote, clackSelect) is
|
|
13
13
|
* defined once in prompt-io.ts and imported here, never re-declared.
|
|
@@ -55,7 +55,7 @@ export function formatTrackerSummary(provider) {
|
|
|
55
55
|
/**
|
|
56
56
|
* Determines whether the tracker wizard step should run for a given init invocation.
|
|
57
57
|
*
|
|
58
|
-
* Gate table (
|
|
58
|
+
* Gate table (key on modePromptShown, never on the mode name):
|
|
59
59
|
*
|
|
60
60
|
* --recommended flag / !isTTY fallback → no (promptless contract preserved)
|
|
61
61
|
* Interactive mode-prompt → Recommended → yes (modePromptShown=true)
|
|
@@ -102,14 +102,14 @@ export function buildClackTrackerPrompts() {
|
|
|
102
102
|
* 1. Note — "Current setting: …" header then the provider catalogue.
|
|
103
103
|
* 2. Provider select — labelled GitHub / Jira / Linear with hints, seeded from
|
|
104
104
|
* the prior state. `p.select`, never `p.confirm`: Enter-through must be an
|
|
105
|
-
* INFORMED keep of a named provider, not a y/N reflex
|
|
105
|
+
* INFORMED keep of a named provider, not a y/N reflex.
|
|
106
106
|
*
|
|
107
107
|
* Returns:
|
|
108
108
|
* {kind:'resolved', state, messages} — step completed; `state` is the chosen
|
|
109
109
|
* TrackerFeatureState; `messages` are emitted by the caller.
|
|
110
110
|
* {kind:'cancelled'} — user pressed Escape; caller runs p.cancel + process.exit(0).
|
|
111
111
|
*
|
|
112
|
-
* Invariants
|
|
112
|
+
* Invariants:
|
|
113
113
|
* - Never calls process.exit(), never throws.
|
|
114
114
|
* - The returned state is always a fresh object, never the seed.
|
|
115
115
|
* - All I/O is routed through the `prompts` parameter (injectable for tests).
|
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
*
|
|
4
4
|
* D-TRACKER-PAIR [DR-25]: `src/core/tracker.ts` (domain) +
|
|
5
5
|
* `src/cli/commands/tracker.ts` (CLI) mirrors the `compliance.ts` pair
|
|
6
|
-
* exactly;
|
|
7
|
-
* exist. A reviewer meeting several `tracker*` files in one
|
|
8
|
-
* has no signal that the duplication is deliberate.
|
|
6
|
+
* exactly; the split of pure core logic (src/core/) from the I/O layer is the
|
|
7
|
+
* reason both names exist. A reviewer meeting several `tracker*` files in one
|
|
8
|
+
* commit otherwise has no signal that the duplication is deliberate.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* CLI-layer module; the provider domain, the strict parser and
|
|
11
11
|
* the ~/.devflow file lifecycle all live in src/core/tracker.ts.
|
|
12
12
|
* The manifest holds the MACHINE default. A repository may select its own
|
|
13
13
|
* provider in its committed `.devflow/project.json` (resolved by
|
|
14
14
|
* resolve-settings.cjs); `--status` names it on an `Effective:` line when one
|
|
15
15
|
* does, and `--set` never touches it.
|
|
16
|
-
*
|
|
16
|
+
* --set converges the sentinel in BOTH directions, so flipping
|
|
17
17
|
* back to github removes what flipping away wrote.
|
|
18
|
-
*
|
|
18
|
+
* A failed re-arm or sentinel step warns, it never aborts.
|
|
19
19
|
*/
|
|
20
20
|
import { Command } from 'commander';
|
|
21
21
|
import { promises as fs } from 'fs';
|
|
@@ -31,7 +31,7 @@ import { prefixSkillName } from '../../core/plugins.js';
|
|
|
31
31
|
/**
|
|
32
32
|
* Read the provenance header of `~/.devflow/tracker/{provider}.md`.
|
|
33
33
|
*
|
|
34
|
-
* Never throws
|
|
34
|
+
* Never throws: an absent, unreadable, or non-regular path is `absent`.
|
|
35
35
|
* Only the bounded head of the file is read and only the leading frontmatter
|
|
36
36
|
* block is scanned — through the one frontmatter parser the migration also uses —
|
|
37
37
|
* and nothing read here is trusted: every value is rendered through
|
|
@@ -52,7 +52,7 @@ export async function readTrackerProvenance(devflowDir, provider) {
|
|
|
52
52
|
* every install carries them all (D-INSTALL-ALL-PROVIDERS) — rather than listing
|
|
53
53
|
* the directory: the question a user asks `--status` is "can the agent load what
|
|
54
54
|
* it is told to load?", and a stray file in the tree is not an answer to it.
|
|
55
|
-
* Never throws
|
|
55
|
+
* Never throws.
|
|
56
56
|
*/
|
|
57
57
|
export async function readTrackerMechanics(claudeDir) {
|
|
58
58
|
const root = path.join(claudeDir, 'skills', prefixSkillName(SKILL_REFS_SKILL_NAME), 'references');
|
|
@@ -227,7 +227,7 @@ export const trackerCommand = new Command('tracker')
|
|
|
227
227
|
// undocumented dotfile. Every other --status in this CLI is a pure read,
|
|
228
228
|
// so this one reports the write it makes as a line of the note below — a
|
|
229
229
|
// machine-state change the output does not mention is a change the user
|
|
230
|
-
// cannot audit. Non-fatal exactly as on the --set path
|
|
230
|
+
// cannot audit. Non-fatal exactly as on the --set path: a
|
|
231
231
|
// failed re-arm warns, it never aborts the report the user asked for.
|
|
232
232
|
const statusRearm = await rearmTrackerInference(devflowDir);
|
|
233
233
|
const inference = statusRearm.ok
|