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.
Files changed (166) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +1 -1
  3. package/dist/agents/git.md +2 -2
  4. package/dist/cli/agents-view/index.js +1 -1
  5. package/dist/cli/agents-view/render.js +71 -17
  6. package/dist/cli/agents-view/state.js +42 -16
  7. package/dist/cli/agents-view/terminal.js +5 -5
  8. package/dist/cli/commands/agents.js +142 -51
  9. package/dist/cli/commands/ambient.js +1 -1
  10. package/dist/cli/commands/attribution-prompts.js +8 -8
  11. package/dist/cli/commands/capture.js +1 -1
  12. package/dist/cli/commands/compliance-prompts.js +8 -8
  13. package/dist/cli/commands/compliance.js +8 -7
  14. package/dist/cli/commands/flags.js +33 -31
  15. package/dist/cli/commands/hud.js +1 -1
  16. package/dist/cli/commands/init-seed.js +9 -9
  17. package/dist/cli/commands/init.js +162 -85
  18. package/dist/cli/commands/install-report.js +10 -10
  19. package/dist/cli/commands/learning.js +302 -136
  20. package/dist/cli/commands/memory.js +36 -15
  21. package/dist/cli/commands/proxy.js +23 -23
  22. package/dist/cli/commands/rules.js +6 -5
  23. package/dist/cli/commands/tracker-prompts.js +6 -6
  24. package/dist/cli/commands/tracker.js +9 -9
  25. package/dist/cli/commands/uninstall.js +183 -59
  26. package/dist/cli/flags-view/render.js +5 -5
  27. package/dist/cli/flags-view/state.js +9 -9
  28. package/dist/cli/flags-view/terminal.js +4 -4
  29. package/dist/cli/tui/cells.js +1 -1
  30. package/dist/cli/tui/terminal.js +6 -6
  31. package/dist/commands/code-review.md +0 -2
  32. package/dist/commands/debug.md +14 -11
  33. package/dist/commands/dynamic-build.md +51 -47
  34. package/dist/commands/dynamic-plan.md +27 -7
  35. package/dist/commands/dynamic-profile.md +17 -3
  36. package/dist/commands/dynamic-tickets.md +18 -4
  37. package/dist/commands/explore.md +9 -3
  38. package/dist/commands/implement.md +20 -16
  39. package/dist/commands/plan.md +13 -9
  40. package/dist/commands/release.md +23 -3
  41. package/dist/commands/research.md +9 -3
  42. package/dist/commands/resolve.md +9 -12
  43. package/dist/commands/self-review.md +0 -2
  44. package/dist/core/agent-frontmatter.js +28 -3
  45. package/dist/core/agent-models.js +204 -42
  46. package/dist/core/agent-state.js +28 -6
  47. package/dist/core/ansi.js +2 -2
  48. package/dist/core/assets.js +1 -1
  49. package/dist/core/cache.js +7 -8
  50. package/dist/core/codex-auth-inspect.js +4 -4
  51. package/dist/core/compliance-compose.js +3 -3
  52. package/dist/core/compliance.js +3 -4
  53. package/dist/core/evidence-policy.js +14 -13
  54. package/dist/core/external-models.js +1 -1
  55. package/dist/core/feature-config.js +71 -13
  56. package/dist/core/feature-switch.js +3 -3
  57. package/dist/core/flags.js +49 -25
  58. package/dist/core/fs-atomic.js +6 -7
  59. package/dist/core/learning-queue-cleanup.js +16 -81
  60. package/dist/core/learning-store.js +61 -0
  61. package/dist/core/linked-path.js +46 -0
  62. package/dist/core/manifest.js +5 -5
  63. package/dist/core/mds-variants.js +13 -13
  64. package/dist/core/model-discovery.js +8 -8
  65. package/dist/core/observations.js +17 -101
  66. package/dist/core/orphan-sweep.js +4 -4
  67. package/dist/core/plugins.js +13 -8
  68. package/dist/core/project-paths.js +9 -13
  69. package/dist/core/proxy-log.js +8 -8
  70. package/dist/core/proxy-state.js +3 -3
  71. package/dist/core/queue-drain.js +31 -0
  72. package/dist/core/reference-sweep.js +6 -6
  73. package/dist/core/teammate-mode-cleanup.js +1 -1
  74. package/dist/core/tracker.js +14 -14
  75. package/dist/hud/colors.js +2 -2
  76. package/dist/hud/components/learning-counts.js +54 -22
  77. package/dist/hud/components/version-badge.js +1 -1
  78. package/dist/skills/git/references/pr/resolve-review-threads.md +2 -2
  79. package/dist/skills/git/references/tracker/github/create-release.md +2 -2
  80. package/dist/skills/git/references/tracker/jira/create-release.md +2 -2
  81. package/dist/skills/git/references/tracker/linear/create-release.md +2 -2
  82. package/dist/targets/claude-code/compliance-install.js +17 -15
  83. package/dist/targets/claude-code/hooks.js +2 -2
  84. package/dist/targets/claude-code/installer.js +59 -32
  85. package/dist/targets/claude-code/legacy.js +1 -1
  86. package/dist/targets/claude-code/post-install.js +135 -45
  87. package/dist/targets/claude-code/tracker-install.js +2 -2
  88. package/package.json +1 -1
  89. package/src/assets/agents/code.md +15 -21
  90. package/src/assets/agents/design.md +4 -2
  91. package/src/assets/agents/diagnose.md +3 -1
  92. package/src/assets/agents/evaluate.md +4 -0
  93. package/src/assets/agents/git.mds +2 -2
  94. package/src/assets/agents/knowledge.md +5 -3
  95. package/src/assets/agents/learning.md +281 -196
  96. package/src/assets/agents/research.md +3 -1
  97. package/src/assets/agents/review.md +5 -3
  98. package/src/assets/agents/scrutinize.md +5 -1
  99. package/src/assets/agents/simplify.md +4 -0
  100. package/src/assets/agents/skim.md +4 -2
  101. package/src/assets/agents/synthesize.md +6 -0
  102. package/src/assets/agents/test.md +18 -10
  103. package/src/assets/agents/triage.md +11 -9
  104. package/src/assets/agents/validate.md +14 -10
  105. package/src/assets/commands/_partials/_decisions.mds +8 -3
  106. package/src/assets/commands/_partials/_docs_root.mds +3 -3
  107. package/src/assets/commands/_partials/_engine.mds +16 -32
  108. package/src/assets/commands/_partials/_knowledge.mds +0 -2
  109. package/src/assets/commands/_partials/_preamble.mds +6 -2
  110. package/src/assets/commands/_partials/_settings.mds +2 -2
  111. package/src/assets/commands/_partials/_tracker.mds +1 -1
  112. package/src/assets/commands/code-review.mds +0 -2
  113. package/src/assets/commands/debug.mds +13 -8
  114. package/src/assets/commands/dynamic-build.mds +18 -12
  115. package/src/assets/commands/dynamic-plan.mds +10 -4
  116. package/src/assets/commands/dynamic-profile.mds +1 -1
  117. package/src/assets/commands/dynamic-tickets.mds +2 -2
  118. package/src/assets/commands/explore.mds +9 -1
  119. package/src/assets/commands/implement.mds +19 -13
  120. package/src/assets/commands/plan.mds +12 -8
  121. package/src/assets/commands/release.md +23 -3
  122. package/src/assets/commands/research.mds +9 -3
  123. package/src/assets/commands/resolve.mds +9 -10
  124. package/src/assets/mds/git/_pr.mds +3 -3
  125. package/src/assets/mds/tracker/_common.mds +1 -1
  126. package/src/assets/mds/tracker/_github.mds +3 -3
  127. package/src/assets/mds/tracker/_jira.mds +3 -3
  128. package/src/assets/mds/tracker/_linear.mds +3 -3
  129. package/src/assets/mds/tracker/_mcp.mds +6 -5
  130. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +4 -2
  131. package/src/assets/scripts/hooks/background-memory-update +97 -33
  132. package/src/assets/scripts/hooks/capture-prompt +4 -3
  133. package/src/assets/scripts/hooks/capture-question +4 -3
  134. package/src/assets/scripts/hooks/capture-turn +5 -20
  135. package/src/assets/scripts/hooks/ensure-devflow-init +14 -2
  136. package/src/assets/scripts/hooks/ensure-proxy +5 -6
  137. package/src/assets/scripts/hooks/ensure-root-gitignore +123 -11
  138. package/src/assets/scripts/hooks/git-marker +71 -0
  139. package/src/assets/scripts/hooks/is-hex-sha +1 -1
  140. package/src/assets/scripts/hooks/json-helper.cjs +345 -944
  141. package/src/assets/scripts/hooks/json-parse +25 -129
  142. package/src/assets/scripts/hooks/lib/decisions-format.cjs +205 -156
  143. package/src/assets/scripts/hooks/lib/learning-store.cjs +3207 -0
  144. package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +7 -5
  145. package/src/assets/scripts/hooks/lib/project-paths.cjs +13 -19
  146. package/src/assets/scripts/hooks/lib/render-decisions.cjs +253 -226
  147. package/src/assets/scripts/hooks/memory-worker +10 -0
  148. package/src/assets/scripts/hooks/pre-compact-memory +66 -14
  149. package/src/assets/scripts/hooks/preamble +9 -1
  150. package/src/assets/scripts/hooks/queue-append +55 -23
  151. package/src/assets/scripts/hooks/resolve-project-root +3 -4
  152. package/src/assets/scripts/hooks/session-start-context +146 -45
  153. package/src/assets/scripts/hooks/session-start-memory +33 -11
  154. package/src/assets/scripts/lib/project-config.cjs +2 -2
  155. package/src/assets/scripts/pr-evidence.cjs +3 -3
  156. package/src/assets/scripts/redact-secrets.cjs +20 -20
  157. package/src/assets/scripts/release-trace.cjs +1 -1
  158. package/src/assets/scripts/resolve-evidence-policy.cjs +3 -3
  159. package/src/assets/scripts/resolve-settings.cjs +3 -3
  160. package/src/assets/scripts/verify-evidence.cjs +2 -2
  161. package/src/assets/skills/apply-decisions/SKILL.md +37 -17
  162. package/src/assets/skills/docs-framework/SKILL.md +2 -2
  163. package/src/assets/skills/feature-knowledge/SKILL.md +6 -5
  164. package/src/assets/skills/test-driven-development/SKILL.md +6 -4
  165. package/dist/core/observation-io.js +0 -50
  166. 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 (PF-015).
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`. ENOENT-tolerant; any other
146
- * error propagates to the command boundary, like drainLearningQueue.
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 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),
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
- * Returns the count of projects from which at least one file was removed.
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.filter((p) => p !== null);
206
- return { cleaned: cleanedProjects.length, projects: cleanedProjects };
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
- * applies ADR-013: CLI-layer module; all core logic lives in src/core/proxy-state.ts
5
+ * CLI-layer module; all core logic lives in src/core/proxy-state.ts
6
6
  * and src/core/agent-models.ts.
7
- * avoids PF-014: never process.exit() inside a finally-guarded scope; use return
8
- * from the async action handler for all early-exit paths.
9
- * avoids PF-001: hook output strings use fixed templates; port number interpolation
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 (PF-015 — no short-circuit that
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 (avoids PF-015 short-circuit)
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 (applies PF-015, ADR-003). Each outcome is evaluated into a local and OR-ed
153
- * afterwards — never short-circuit composed inline (PF-015).
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. (applies PF-015, ADR-003)
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 (PF-015)
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 (PF-015).
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 (PF-015).
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 per PF-009.
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
- * applies ADR-013: pure configuration factory; I/O implementations factored once.
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
- * avoids PF-014: no process.exit() — returns Result; caller decides error handling.
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
- * avoids PF-001: fixed argument array — port is validated as a safe integer
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
- * avoids PF-009: all sub-operations are non-fatal; a kill failure never aborts disable.
859
- * avoids PF-014: never process.exit() inside a finally-guarded scope.
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 (avoids PF-013)
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 (avoids PF-013)
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; avoids PF-009).
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 / PF-022: applies-on-restart — env var takes effect only for new sessions
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. avoids PF-009: all sub-operations are non-fatal.
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 (applies ADR-010):
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 (avoids PF-015 violation).
201
+ * cannot leave compliance artifacts in a half-converged state.
202
202
  *
203
- * Applies PF-009: warn-not-throw on per-item compliance convergence failure.
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 (avoids PF-015).
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
- // PF-009: warn-not-abort — compliance convergence failure does not undo rules install
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 (ADR-013): prompt-rendering logic lives in src/cli/commands/,
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
- * avoids PF-029: the wizard gate keys on `modePromptShown` (was the Setup-mode
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
- * avoids PF-014: runTrackerStep never calls process.exit() or throws — callers
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 (per PF-029: key on modePromptShown, never on the mode name):
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 (PF-029).
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 (PF-014):
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; ADR-013's pure-core / I/O-target split is the reason both names
7
- * exist. A reviewer meeting several `tracker*` files in one commit otherwise
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
- * Applies ADR-013: CLI-layer module; the provider domain, the strict parser and
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
- * Avoids PF-015: --set converges the sentinel in BOTH directions, so flipping
16
+ * --set converges the sentinel in BOTH directions, so flipping
17
17
  * back to github removes what flipping away wrote.
18
- * Avoids PF-009: a failed re-arm or sentinel step warns, it never aborts.
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 (PF-014): an absent, unreadable, or non-regular path is `absent`.
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 (PF-014).
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 (avoids PF-009): a
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