devflow-kit 3.0.1 → 3.1.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 (133) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/agents/git.md +2 -2
  3. package/dist/cli/agents-view/index.js +1 -1
  4. package/dist/cli/agents-view/render.js +2 -2
  5. package/dist/cli/agents-view/state.js +2 -2
  6. package/dist/cli/agents-view/terminal.js +5 -5
  7. package/dist/cli/commands/agents.js +7 -6
  8. package/dist/cli/commands/ambient.js +1 -1
  9. package/dist/cli/commands/attribution-prompts.js +8 -8
  10. package/dist/cli/commands/capture.js +1 -1
  11. package/dist/cli/commands/compliance-prompts.js +8 -8
  12. package/dist/cli/commands/compliance.js +8 -7
  13. package/dist/cli/commands/flags.js +33 -31
  14. package/dist/cli/commands/hud.js +1 -1
  15. package/dist/cli/commands/init-seed.js +9 -9
  16. package/dist/cli/commands/init.js +34 -32
  17. package/dist/cli/commands/install-report.js +10 -10
  18. package/dist/cli/commands/learning.js +267 -129
  19. package/dist/cli/commands/memory.js +1 -1
  20. package/dist/cli/commands/proxy.js +23 -23
  21. package/dist/cli/commands/rules.js +6 -5
  22. package/dist/cli/commands/tracker-prompts.js +6 -6
  23. package/dist/cli/commands/tracker.js +9 -9
  24. package/dist/cli/commands/uninstall.js +20 -20
  25. package/dist/cli/flags-view/render.js +5 -5
  26. package/dist/cli/flags-view/state.js +9 -9
  27. package/dist/cli/flags-view/terminal.js +4 -4
  28. package/dist/cli/tui/cells.js +1 -1
  29. package/dist/cli/tui/terminal.js +6 -6
  30. package/dist/commands/dynamic-build.md +18 -4
  31. package/dist/commands/dynamic-plan.md +19 -5
  32. package/dist/commands/dynamic-profile.md +17 -3
  33. package/dist/commands/dynamic-tickets.md +18 -4
  34. package/dist/commands/release.md +15 -1
  35. package/dist/commands/research.md +1 -1
  36. package/dist/commands/resolve.md +8 -9
  37. package/dist/core/agent-frontmatter.js +3 -3
  38. package/dist/core/agent-models.js +6 -6
  39. package/dist/core/agent-state.js +2 -2
  40. package/dist/core/ansi.js +2 -2
  41. package/dist/core/cache.js +7 -8
  42. package/dist/core/codex-auth-inspect.js +4 -4
  43. package/dist/core/compliance-compose.js +3 -3
  44. package/dist/core/compliance.js +3 -4
  45. package/dist/core/evidence-policy.js +14 -13
  46. package/dist/core/external-models.js +1 -1
  47. package/dist/core/feature-config.js +3 -3
  48. package/dist/core/feature-switch.js +3 -3
  49. package/dist/core/flags.js +25 -25
  50. package/dist/core/fs-atomic.js +6 -7
  51. package/dist/core/learning-queue-cleanup.js +16 -80
  52. package/dist/core/learning-store.js +61 -0
  53. package/dist/core/manifest.js +5 -5
  54. package/dist/core/mds-variants.js +13 -13
  55. package/dist/core/model-discovery.js +8 -8
  56. package/dist/core/observations.js +17 -101
  57. package/dist/core/orphan-sweep.js +4 -4
  58. package/dist/core/plugins.js +4 -5
  59. package/dist/core/project-paths.js +9 -13
  60. package/dist/core/proxy-log.js +8 -8
  61. package/dist/core/proxy-state.js +3 -3
  62. package/dist/core/reference-sweep.js +6 -6
  63. package/dist/core/teammate-mode-cleanup.js +1 -1
  64. package/dist/core/tracker.js +14 -14
  65. package/dist/hud/colors.js +2 -2
  66. package/dist/hud/components/learning-counts.js +2 -16
  67. package/dist/hud/components/version-badge.js +1 -1
  68. package/dist/skills/git/references/pr/resolve-review-threads.md +2 -2
  69. package/dist/targets/claude-code/compliance-install.js +17 -15
  70. package/dist/targets/claude-code/hooks.js +2 -2
  71. package/dist/targets/claude-code/installer.js +24 -24
  72. package/dist/targets/claude-code/legacy.js +1 -1
  73. package/dist/targets/claude-code/post-install.js +7 -7
  74. package/dist/targets/claude-code/tracker-install.js +2 -2
  75. package/package.json +1 -1
  76. package/src/assets/agents/code.md +1 -4
  77. package/src/assets/agents/design.md +2 -2
  78. package/src/assets/agents/diagnose.md +1 -1
  79. package/src/assets/agents/git.mds +2 -2
  80. package/src/assets/agents/knowledge.md +3 -3
  81. package/src/assets/agents/learning.md +281 -196
  82. package/src/assets/agents/research.md +1 -1
  83. package/src/assets/agents/review.md +3 -3
  84. package/src/assets/agents/scrutinize.md +1 -1
  85. package/src/assets/agents/skim.md +1 -1
  86. package/src/assets/agents/triage.md +9 -9
  87. package/src/assets/commands/_partials/_decisions.mds +8 -3
  88. package/src/assets/commands/_partials/_docs_root.mds +3 -3
  89. package/src/assets/commands/_partials/_engine.mds +1 -1
  90. package/src/assets/commands/_partials/_preamble.mds +6 -2
  91. package/src/assets/commands/_partials/_settings.mds +2 -2
  92. package/src/assets/commands/dynamic-build.mds +1 -1
  93. package/src/assets/commands/dynamic-plan.mds +3 -3
  94. package/src/assets/commands/dynamic-profile.mds +1 -1
  95. package/src/assets/commands/dynamic-tickets.mds +2 -2
  96. package/src/assets/commands/release.md +15 -1
  97. package/src/assets/commands/research.mds +1 -1
  98. package/src/assets/commands/resolve.mds +8 -9
  99. package/src/assets/mds/git/_pr.mds +3 -3
  100. package/src/assets/mds/tracker/_common.mds +1 -1
  101. package/src/assets/mds/tracker/_github.mds +1 -1
  102. package/src/assets/mds/tracker/_jira.mds +1 -1
  103. package/src/assets/mds/tracker/_linear.mds +1 -1
  104. package/src/assets/mds/tracker/_mcp.mds +6 -5
  105. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +1 -0
  106. package/src/assets/scripts/hooks/background-memory-update +28 -22
  107. package/src/assets/scripts/hooks/capture-turn +1 -17
  108. package/src/assets/scripts/hooks/ensure-devflow-init +1 -1
  109. package/src/assets/scripts/hooks/ensure-proxy +5 -6
  110. package/src/assets/scripts/hooks/ensure-root-gitignore +1 -1
  111. package/src/assets/scripts/hooks/is-hex-sha +1 -1
  112. package/src/assets/scripts/hooks/json-helper.cjs +348 -814
  113. package/src/assets/scripts/hooks/json-parse +3 -2
  114. package/src/assets/scripts/hooks/lib/decisions-format.cjs +205 -156
  115. package/src/assets/scripts/hooks/lib/learning-store.cjs +3102 -0
  116. package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +7 -5
  117. package/src/assets/scripts/hooks/lib/project-paths.cjs +13 -19
  118. package/src/assets/scripts/hooks/lib/render-decisions.cjs +253 -226
  119. package/src/assets/scripts/hooks/queue-append +2 -2
  120. package/src/assets/scripts/hooks/resolve-project-root +3 -4
  121. package/src/assets/scripts/hooks/session-start-context +40 -18
  122. package/src/assets/scripts/lib/project-config.cjs +2 -2
  123. package/src/assets/scripts/pr-evidence.cjs +3 -3
  124. package/src/assets/scripts/redact-secrets.cjs +20 -20
  125. package/src/assets/scripts/release-trace.cjs +1 -1
  126. package/src/assets/scripts/resolve-evidence-policy.cjs +3 -3
  127. package/src/assets/scripts/resolve-settings.cjs +3 -3
  128. package/src/assets/scripts/verify-evidence.cjs +2 -2
  129. package/src/assets/skills/apply-decisions/SKILL.md +37 -17
  130. package/src/assets/skills/docs-framework/SKILL.md +2 -2
  131. package/src/assets/skills/feature-knowledge/SKILL.md +6 -5
  132. package/dist/core/observation-io.js +0 -50
  133. package/src/assets/scripts/hooks/decisions-usage-scan.cjs +0 -131
@@ -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
@@ -178,7 +178,7 @@ export function formatDryRunPlan(assets) {
178
178
  * - "prompt" — interactive (isTTY=true); caller should ask the user
179
179
  *
180
180
  * @D1 Non-interactive-preserve invariant: when isTTY is false the result is
181
- * always "preserve", never "prompt" — avoids PF-004 half-applied-state hazard.
181
+ * always "preserve", never "prompt" — avoids a half-applied-state hazard.
182
182
  */
183
183
  export function resolveSecurityRemovalDecision(opts) {
184
184
  if (!opts.anySecurityPresent || opts.keepDocs)
@@ -193,7 +193,7 @@ export function resolveSecurityRemovalDecision(opts) {
193
193
  *
194
194
  * A cancel (user presses Ctrl-C on this prompt) is treated as decline: the
195
195
  * .devflow/ directory is preserved and the uninstall continues with the remaining
196
- * steps rather than aborting via process.exit() (applies PF-014, applies ADR-003).
196
+ * steps rather than aborting via process.exit().
197
197
  *
198
198
  * PURE — no I/O, fully testable.
199
199
  *
@@ -233,8 +233,8 @@ export function partitionProjectData(entries) {
233
233
  * realpaths, so macOS's `/var` → `/private/var` and a symlinked HOME still match.
234
234
  *
235
235
  * A `.devflow` that is a symbolic link to anywhere else is skipped too, and never
236
- * followed: its target is a directory devflow cannot prove it wrote (applies
237
- * ADR-024), so a confirmed cleanup must not empty it — nor unlink a link the user made.
236
+ * followed: its target is a directory devflow cannot prove it wrote, so a
237
+ * confirmed cleanup must not empty it — nor unlink a link the user made.
238
238
  */
239
239
  export async function resolveProjectDataPlan(opts) {
240
240
  if (opts.gitRoot === null)
@@ -320,9 +320,9 @@ export function formatProjectDataPlan(plan) {
320
320
  * @D5 Precondition guard: any anomalous devflowDir falls back to 'artifacts-only' rather
321
321
  * than throwing — business logic must not throw (engineering rule). The explicit guard
322
322
  * makes the invariant present in production code, not only in tests (reliability rule).
323
- * @D6 avoids PF-014: the caller must NOT process.exit() after a cancel/decline response;
323
+ * @D6 The caller must NOT process.exit() after a cancel/decline response;
324
324
  * removeAllDevFlow has already run by the time the prompt fires, so removeDevFlowInstallArtifacts
325
- * must execute on every non-confirm path to leave a clean end-state (applies ADR-003).
325
+ * must execute on every non-confirm path to leave a clean end-state.
326
326
  * @D7 keepDocs gate: when the caller passes --keep-docs, the entire ~/.devflow dir cleanup
327
327
  * prompt is suppressed — artifacts-only regardless of isTTY or userContent. This prevents
328
328
  * --keep-docs from triggering prompts about skill shadows or preference-profile.md.
@@ -517,7 +517,7 @@ export function installArtifactPaths(devflowDir) {
517
517
  { relPath: 'agent-models.json' },
518
518
  // cost history — auto-generated session telemetry, not user-authored.
519
519
  // The whole costs/ tree is removed so sessions/ and archive.jsonl cannot
520
- // drift from their write sites in src/hud/cost-history.ts (avoids PF-013).
520
+ // drift from their write sites in src/hud/cost-history.ts.
521
521
  { relPath: 'costs', isDir: true },
522
522
  // proxy artifacts
523
523
  { relPath: 'proxy.json' },
@@ -545,7 +545,7 @@ export function installArtifactPaths(devflowDir) {
545
545
  { relPath: 'logs', isDir: true },
546
546
  // Model-discovery + HUD component caches. hudCacheDir() is the authoritative
547
547
  // accessor for the cache/ parent (modelCacheDir() resolves beneath it), so the
548
- // removal site stays byte-locked to the write sites (avoids PF-013).
548
+ // removal site stays byte-locked to the write sites.
549
549
  { relPath: path.relative(devflowDir, hudCacheDir(devflowDir)), isDir: true },
550
550
  ];
551
551
  }
@@ -579,7 +579,7 @@ export async function removeDevFlowInstallArtifacts(devflowDir, verbose) {
579
579
  catch (error) {
580
580
  p.log.warn(`Could not remove manifest.json: ${error}`);
581
581
  }
582
- // Proxy install artifacts — remove non-fatally (per-item failure isolation, avoids PF-009)
582
+ // Proxy install artifacts — remove non-fatally (per-item failure isolation)
583
583
  // Inform the user if a proxy relay process is still running (never kill — informational only).
584
584
  try {
585
585
  const pidContent = await fs.readFile(path.join(devflowDir, 'proxy.pid'), 'utf-8');
@@ -594,7 +594,7 @@ export async function removeDevFlowInstallArtifacts(devflowDir, verbose) {
594
594
  }
595
595
  }
596
596
  catch { /* proxy.pid absent or unreadable — non-fatal */ }
597
- // All install artifacts removed non-fatally (avoids PF-009). Resolved against
597
+ // All install artifacts removed non-fatally. Resolved against
598
598
  // disk first, so a per-run staging basename is a real path by the time the
599
599
  // containment guard below sees it.
600
600
  for (const artifact of await resolveInstallArtifactPaths(devflowDir)) {
@@ -654,7 +654,7 @@ export async function isDevFlowInstalled(claudeDir) {
654
654
  * scope directories, intersected with what actually exists on disk.
655
655
  *
656
656
  * Extracted from the dry-run loop body so the enumeration logic is
657
- * independently testable (avoids PF-018: tests must exercise the production
657
+ * independently testable (tests must exercise the production
658
658
  * path, not only the pure helper functions).
659
659
  *
660
660
  * Coverage matches removeAllDevFlow exactly:
@@ -664,7 +664,7 @@ export async function isDevFlowInstalled(claudeDir) {
664
664
  * - Prefixed (devflow:name): live registry ∪ LEGACY_SKILL_NAMES.
665
665
  * - Bare (name or devflow-name legacy): LEGACY_SKILL_NAMES ONLY.
666
666
  * ~/.claude/skills/ is shared; bare dirs for live-registry skill names
667
- * are by construction foreign to Devflow (avoids PF-012).
667
+ * are by construction foreign to Devflow.
668
668
  * 3. manifest.json (removed separately in removeDevFlowInstallArtifacts).
669
669
  * 4. Every artifact path from installArtifactPaths(devflowDir) — the single
670
670
  * source of truth shared with the real removal loop.
@@ -688,7 +688,7 @@ export async function enumerateDryRunExtras(claudeDir, devflowDir) {
688
688
  catch { /* absent */ }
689
689
  }
690
690
  // 2. Skills: enumerate ALL removal candidates that exist on disk.
691
- // Mirrors removeAllDevFlow's split-pass approach (avoids PF-012 + PF-018):
691
+ // Mirrors removeAllDevFlow's split-pass approach:
692
692
  // Prefixed: live registry ∪ LEGACY_SKILL_NAMES ∪ FEATURE_OWNED_SKILLS
693
693
  // Bare: LEGACY_SKILL_NAMES only — shared skills/ dir; live-registry bare
694
694
  // dirs are by construction foreign to Devflow.
@@ -923,7 +923,7 @@ export async function runFullPhaseForScope(opts) {
923
923
  });
924
924
  if (p.isCancel(confirmFullCleanup)) {
925
925
  // removeAllDevFlow already ran — clean up the manifest to leave a consistent
926
- // state. avoids PF-014: process.exit() here would skip removeDevFlowInstallArtifacts,
926
+ // state. process.exit() here would skip removeDevFlowInstallArtifacts,
927
927
  // leaving a stale manifest.json that points to assets no longer on disk.
928
928
  await removeDevFlowInstallArtifacts(devflowDir, verbose);
929
929
  p.log.info(`${devflowDir} preserved (full removal cancelled; removing scripts and install artifacts only)`);
@@ -945,7 +945,7 @@ export async function runFullPhaseForScope(opts) {
945
945
  *
946
946
  * D-UNINSTALL-CARVE-OUT: DEVFLOW_TRACKED_PATHS are never removed. `--keep-docs`,
947
947
  * a non-interactive run, a decline and a cancel all leave the directory untouched,
948
- * and a cancel continues the uninstall rather than exiting (avoids PF-014). The
948
+ * and a cancel continues the uninstall rather than exiting. The
949
949
  * directory itself is removed only when nothing tracked was in it.
950
950
  */
951
951
  async function runProjectDataStep(plan, gates) {
@@ -1412,7 +1412,7 @@ export async function removeAllDevFlow(claudeDir, devflowScriptsDir, verbose) {
1412
1412
  }
1413
1413
  // Remove Devflow skill directories.
1414
1414
  // ~/.claude/skills/ is shared with other tools; the two passes use different
1415
- // name sets to avoid deleting foreign dirs (avoids PF-012):
1415
+ // name sets to avoid deleting foreign dirs:
1416
1416
  //
1417
1417
  // Prefixed pass (devflow:name): live registry ∪ LEGACY_SKILL_NAMES ∪ FEATURE_OWNED_SKILLS.
1418
1418
  // prefixSkillName() is idempotent for already-prefixed legacy entries.
@@ -1421,7 +1421,7 @@ export async function removeAllDevFlow(claudeDir, devflowScriptsDir, verbose) {
1421
1421
  // Bare pass (name or devflow-name legacy): LEGACY_SKILL_NAMES ONLY.
1422
1422
  // A bare dir whose name matches a live-registry skill is by construction
1423
1423
  // foreign to Devflow (the devflow: namespace shipped in dcecda3, 2026-03-30).
1424
- // A bare 'compliance/' dir is also foreign (not in LEGACY_SKILL_NAMES) — PF-012.
1424
+ // A bare 'compliance/' dir is also foreign (not in LEGACY_SKILL_NAMES).
1425
1425
  const prefixedSkillNames = new Set([...getAllSkillNames(), ...LEGACY_SKILL_NAMES, ...FEATURE_OWNED_SKILLS]);
1426
1426
  const skillsDir = path.join(claudeDir, 'skills');
1427
1427
  let skillsRemoved = 0;
@@ -1472,7 +1472,7 @@ export async function removeAllDevFlow(claudeDir, devflowScriptsDir, verbose) {
1472
1472
  * cleaned up promptly rather than accumulating on disk.
1473
1473
  *
1474
1474
  * knownNames spans ALL plugins for each asset type so assets belonging to
1475
- * plugins NOT being uninstalled are never swept (avoids PF-012).
1475
+ * plugins NOT being uninstalled are never swept.
1476
1476
  *
1477
1477
  * Removals are announced only under `verbose`, but removal FAILURES always warn:
1478
1478
  * a swept-but-not-actually-removed agent or command keeps loading in Claude Code,
@@ -1546,7 +1546,7 @@ export async function removeSelectedPlugins(claudeDir, plugins, verbose, install
1546
1546
  // exclusively by the frozen LEGACY_SKILL_NAMES pass in removeAllDevFlow and
1547
1547
  // in init.ts; live-registry skills never had bare installs (the devflow:
1548
1548
  // namespace shipped in dcecda3, 2026-03-30), so a bare dir for a current
1549
- // registry name is by construction foreign (avoids PF-012).
1549
+ // registry name is by construction foreign.
1550
1550
  try {
1551
1551
  await fs.rm(path.join(skillsDir, prefixSkillName(skill)), { recursive: true, force: true });
1552
1552
  }
@@ -1568,7 +1568,7 @@ export async function removeSelectedPlugins(claudeDir, plugins, verbose, install
1568
1568
  // Registry-diff sweep: agents, commands, AND skills — named step (A6).
1569
1569
  // Removes any orphaned files whose names left the registry (retired, renamed,
1570
1570
  // or deleted from all plugins). Spans ALL plugins so assets belonging to
1571
- // non-selected plugins are never swept (avoids PF-012).
1571
+ // non-selected plugins are never swept.
1572
1572
  await sweepDevflowNamespaces(claudeDir, verbose);
1573
1573
  }
1574
1574
  //# sourceMappingURL=uninstall.js.map
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Pure TUI frame renderer for the devflow flags view.
3
3
  *
4
- * applies ADR-013: CLI-layer view module; zero fs/tty imports.
5
- * avoids PF-014: pure function, no process.exit(), no I/O.
4
+ * CLI-layer view module; zero fs/tty imports.
5
+ * Pure function, no process.exit(), no I/O.
6
6
  *
7
7
  * Layout (FIXED_ROWS = 10, viewport = state.viewportHeight — single owner):
8
8
  * 1 Title " Devflow Flags"
@@ -59,14 +59,14 @@ export function computeViewportHeight(termRows) {
59
59
  * non-boolean at devflow default → plain string
60
60
  * non-boolean deviating from devflow default → bold string
61
61
  *
62
- * Colour vocabulary (one colour, one semantic — applies ADR-016's amendment lesson):
62
+ * Colour vocabulary (one colour, one semantic):
63
63
  * cyan = focus indicator (chevron wrapper ‹ › on the cursor row only)
64
64
  * yellow = dirty indicator (unconditional ●) and boolean 'off'
65
65
  * green = boolean 'on'
66
66
  * bold = non-boolean value deviating from devflow default
67
67
  *
68
68
  * disk-sourced values are routed through sanitizeCell to prevent TAB/LF
69
- * layout breaks inside the fixed-width TUI cell (avoids PF-023).
69
+ * layout breaks inside the fixed-width TUI cell.
70
70
  */
71
71
  function formatValue(row) {
72
72
  const v = row.configuredValue;
@@ -151,7 +151,7 @@ function renderRow(row, isCursor, isEditing, editBuffer, editCaret, labelW, valu
151
151
  // so an inner RESET (e.g. from green('on')) does not kill the outer cyan.
152
152
  // cyan('‹ ') + <styled-or-plain content> + cyan(' ›')
153
153
  // rather than cyan(`‹ ${content} ›`), which terminates the outer cyan at the
154
- // inner RESET, leaving the closing chevron unstyled (applies ADR-016 amendment lesson).
154
+ // inner RESET, leaving the closing chevron unstyled.
155
155
  const chevronBudget = valueW - 4;
156
156
  let valueCell;
157
157
  if (isCursor && isEditing) {
@@ -1,20 +1,20 @@
1
1
  /**
2
2
  * Pure keypress reducer for the devflow flags TUI.
3
3
  *
4
- * applies ADR-013: CLI-layer view module; consumes src/core/ imports only.
5
- * applies ADR-016: one syntax, one semantic — value vocabulary.
6
- * avoids PF-014: pure functions only — no process.exit(), no I/O.
7
- * avoids PF-017: generic shell in tui/terminal.ts; this module is pure logic.
4
+ * CLI-layer view module; consumes src/core/ imports only.
5
+ * One syntax, one semantic — value vocabulary.
6
+ * Pure functions only — no process.exit(), no I/O.
7
+ * Generic shell in tui/terminal.ts; this module is pure logic.
8
8
  *
9
9
  * viewMode GLUE RULE: view-mode's neutralValue ('default') maps to null in the TUI.
10
10
  * `buildFlagRows` maps record value 'default' → null via `recordToTui` (core/flags.ts);
11
11
  * `collectFlagRecord` maps null → 'default' via `tuiToRecord` (core/flags.ts).
12
12
  * Number 0 is ACTIVE — null ≠ 0. Both functions live next to neutralValueOf, their
13
- * definition dependency (PF-017 one-shared-definition corollary).
13
+ * definition dependency, so there is one shared definition rather than per-consumer copies.
14
14
  *
15
15
  * Strict number parsing: leading/trailing whitespace and leading zeros are
16
16
  * invalid ('007' → error, ' 8' → error). This rejects pathological inputs
17
- * before they reach coerceFlagValue (applies PF-023).
17
+ * before they reach coerceFlagValue.
18
18
  *
19
19
  * Buffer hard cap: BUFFER_MAX_LEN = 64 chars (paste-flood guard).
20
20
  *
@@ -198,8 +198,8 @@ function enterEdit(state) {
198
198
  * Contract:
199
199
  * - Empty buffer + allowUnset → commit null (unset)
200
200
  * - Empty buffer + !allowUnset → error "Value is required"
201
- * - For number/string flags: delegate to parseFlagValueInput (applies PF-023 —
202
- * strict grammar enforced at the core sink, not per-caller). Error messages
201
+ * - For number/string flags: delegate to parseFlagValueInput (strict grammar
202
+ * enforced at the core sink, not per-caller). Error messages
203
203
  * distinguish format failures (padded/hex/leading-zeros) from bounds failures.
204
204
  * - parseFlagValueInput returns null on invalid input → stay editing + error
205
205
  */
@@ -230,7 +230,7 @@ function commitEdit(state) {
230
230
  };
231
231
  }
232
232
  }
233
- // Number flag: parseFlagValueInput enforces strict decimal grammar (avoids PF-023).
233
+ // Number flag: parseFlagValueInput enforces strict decimal grammar.
234
234
  // Provide specific error messages to distinguish format from bounds failures.
235
235
  if (flagDef.kind === 'number') {
236
236
  if (buf !== buf.trim()) {
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Thin adapter — devflow flags TUI shell over the generic runTui driver.
3
3
  *
4
- * applies ADR-013: impure I/O shell in CLI layer; pure logic lives in state.ts/render.ts.
5
- * avoids PF-014: cleanup wired via Promise resolve — never process.exit() inside
6
- * a finally-guarded scope.
7
- * avoids PF-017: thin adapter over the generic shell (src/cli/tui/terminal.ts).
4
+ * Impure I/O shell in CLI layer; pure logic lives in state.ts/render.ts.
5
+ * Cleanup wired via Promise resolve — never process.exit() inside
6
+ * a finally-guarded scope (it would skip the finally).
7
+ * Thin adapter over the generic shell (src/cli/tui/terminal.ts).
8
8
  *
9
9
  * Public API:
10
10
  * - runFlagsTui(initialRows, io?) → Promise<FlagsTuiResult>
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Shared TUI cell helpers — shared by agents-view and flags-view.
3
3
  *
4
- * Applies PF-017: generified into a shared module rather than copy-adapted per consumer.
4
+ * Generified into a shared module rather than copy-adapted per consumer.
5
5
  * Pure functions, no I/O.
6
6
  */
7
7
  import { stripAnsi, truncate } from '../../core/ansi.js';
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Generic TUI shell — shared by agents-view and flags-view.
3
3
  *
4
- * applies ADR-013: impure I/O shell in CLI layer; pure logic lives in state + render.
5
- * avoids PF-014: cleanup wired via Promise resolve — never process.exit() inside
6
- * a finally-guarded scope.
7
- * avoids PF-017: one generic shell, thin adapters per TUI — not copy-adapted per consumer.
4
+ * Impure I/O shell in CLI layer; pure logic lives in state + render.
5
+ * Cleanup wired via Promise resolve — never process.exit() inside
6
+ * a finally-guarded scope (it would skip the finally).
7
+ * One generic shell, thin adapters per TUI — not copy-adapted per consumer.
8
8
  *
9
9
  * Bounded: MAX_KEYPRESSES = 50_000 hard limit (reliability rule — every loop bounded).
10
10
  *
@@ -268,8 +268,8 @@ export async function runTui(spec) {
268
268
  * A throw inside an EventEmitter listener does NOT reject the enclosing
269
269
  * promise — it escapes as an uncaughtException and kills the process with
270
270
  * cleanup() never having run, leaving raw mode and alt-screen set. Routing
271
- * every handler failure through here keeps the PF-014 invariant (cleanup
272
- * always runs) while still surfacing the error rather than swallowing it.
271
+ * every handler failure through here keeps the invariant that cleanup
272
+ * always runs, while still surfacing the error rather than swallowing it.
273
273
  */
274
274
  function fail(err) {
275
275
  cleanup();