devflow-kit 2.5.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +44 -19
  3. package/dist/agents/git.md +13 -15
  4. package/dist/cli/commands/ambient.js +160 -145
  5. package/dist/cli/commands/capture.js +29 -55
  6. package/dist/cli/commands/compliance.js +32 -61
  7. package/dist/cli/commands/context.js +17 -32
  8. package/dist/cli/commands/debug.js +65 -26
  9. package/dist/cli/commands/flags.js +3 -3
  10. package/dist/cli/commands/hud.js +34 -10
  11. package/dist/cli/commands/init-seed.js +40 -4
  12. package/dist/cli/commands/init.js +249 -271
  13. package/dist/cli/commands/install-report.js +10 -15
  14. package/dist/cli/commands/knowledge/index.js +1 -1
  15. package/dist/cli/commands/knowledge/toggle.js +11 -3
  16. package/dist/cli/commands/learning.js +52 -37
  17. package/dist/cli/commands/legacy-hooks.js +11 -14
  18. package/dist/cli/commands/memory.js +67 -78
  19. package/dist/cli/commands/proxy.js +23 -41
  20. package/dist/cli/commands/security.js +5 -13
  21. package/dist/cli/commands/skills.js +21 -3
  22. package/dist/cli/commands/tracker.js +100 -228
  23. package/dist/cli/commands/uninstall.js +343 -138
  24. package/dist/commands/bug-analysis.md +38 -12
  25. package/dist/commands/code-review.md +70 -21
  26. package/dist/commands/debug.md +37 -7
  27. package/dist/commands/dynamic-build.md +66 -17
  28. package/dist/commands/dynamic-plan.md +19 -8
  29. package/dist/commands/dynamic-profile.md +24 -10
  30. package/dist/commands/dynamic-tickets.md +22 -11
  31. package/dist/commands/explore.md +37 -7
  32. package/dist/commands/implement.md +96 -32
  33. package/dist/commands/plan.md +62 -19
  34. package/dist/commands/release.md +2 -2
  35. package/dist/commands/research.md +34 -8
  36. package/dist/commands/resolve.md +65 -17
  37. package/dist/commands/self-review.md +45 -9
  38. package/dist/core/compliance-compose.js +27 -27
  39. package/dist/core/evidence-policy.js +240 -24
  40. package/dist/core/feature-config.js +94 -25
  41. package/dist/core/feature-switch.js +1 -1
  42. package/dist/core/flags.js +30 -2
  43. package/dist/core/fs-atomic.js +27 -0
  44. package/dist/core/hook-log-dirs.js +104 -0
  45. package/dist/core/learning-tuning-config.js +5 -3
  46. package/dist/core/ledger-root.js +102 -0
  47. package/dist/core/manifest.js +6 -4
  48. package/dist/core/mds-variants.js +34 -97
  49. package/dist/core/migrations.js +49 -23
  50. package/dist/core/plugins.js +5 -4
  51. package/dist/core/project-paths.js +0 -17
  52. package/dist/core/same-location.js +25 -0
  53. package/dist/core/tracker.js +226 -139
  54. package/dist/hud/components/config-counts.js +15 -4
  55. package/dist/hud/components/learning-counts.js +14 -0
  56. package/dist/hud/config.js +2 -1
  57. package/dist/hud/cost-history.js +2 -4
  58. package/dist/hud/git.js +52 -7
  59. package/dist/hud/index.js +7 -9
  60. package/dist/skills/git/references/pr/check-merge-readiness.md +1 -1
  61. package/dist/skills/git/references/pr/ensure-pr-ready.md +1 -1
  62. package/dist/skills/git/references/pr/update-pr-evidence.md +1 -1
  63. package/dist/skills/git/references/tracker/_mcp.md +1 -1
  64. package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +1 -1
  65. package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +1 -1
  66. package/dist/skills/git/references/tracker/github/gather-release-evidence.md +2 -2
  67. package/dist/skills/git/references/tracker/github/manage-debt.md +3 -3
  68. package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +1 -1
  69. package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +1 -1
  70. package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +2 -2
  71. package/dist/skills/git/references/tracker/jira/manage-debt.md +1 -1
  72. package/dist/skills/git/references/tracker/jira/post-wave-report.md +1 -1
  73. package/dist/skills/git/references/tracker/jira/setup-task.md +1 -1
  74. package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +1 -1
  75. package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +1 -1
  76. package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +2 -2
  77. package/dist/skills/git/references/tracker/linear/manage-debt.md +1 -1
  78. package/dist/skills/git/references/tracker/linear/post-wave-report.md +1 -1
  79. package/dist/skills/git/references/tracker/linear/setup-task.md +1 -1
  80. package/dist/targets/claude-code/claude-paths.js +59 -57
  81. package/dist/targets/claude-code/compliance-install.js +49 -65
  82. package/dist/targets/claude-code/hooks.js +108 -3
  83. package/dist/targets/claude-code/installer.js +30 -57
  84. package/dist/targets/claude-code/post-install.js +232 -139
  85. package/dist/targets/claude-code/tracker-install.js +38 -65
  86. package/package.json +5 -4
  87. package/src/assets/agents/code.md +4 -3
  88. package/src/assets/agents/design.md +1 -0
  89. package/src/assets/agents/git.mds +55 -57
  90. package/src/assets/agents/knowledge.md +2 -2
  91. package/src/assets/agents/review.md +3 -1
  92. package/src/assets/agents/tracker.md +37 -30
  93. package/src/assets/commands/_partials/_compliance.mds +19 -1
  94. package/src/assets/commands/_partials/_decisions.mds +15 -3
  95. package/src/assets/commands/_partials/_docs_root.mds +35 -0
  96. package/src/assets/commands/_partials/_engine.mds +2 -2
  97. package/src/assets/commands/_partials/_evidence_policy.mds +3 -3
  98. package/src/assets/commands/_partials/_factory.mds +1 -1
  99. package/src/assets/commands/_partials/_knowledge.mds +27 -9
  100. package/src/assets/commands/_partials/_plan_contract.mds +2 -2
  101. package/src/assets/commands/_partials/_preamble.mds +1 -1
  102. package/src/assets/commands/_partials/_publication.mds +6 -2
  103. package/src/assets/commands/_partials/_settings.mds +28 -0
  104. package/src/assets/commands/_partials/_ticket_template.mds +3 -3
  105. package/src/assets/commands/_partials/_tracker.mds +4 -4
  106. package/src/assets/commands/_partials/_wave.mds +4 -4
  107. package/src/assets/commands/bug-analysis.mds +19 -17
  108. package/src/assets/commands/code-review.mds +39 -33
  109. package/src/assets/commands/debug.mds +4 -5
  110. package/src/assets/commands/dynamic-build.mds +75 -53
  111. package/src/assets/commands/dynamic-plan.mds +20 -15
  112. package/src/assets/commands/dynamic-profile.mds +24 -11
  113. package/src/assets/commands/dynamic-tickets.mds +25 -20
  114. package/src/assets/commands/explore.mds +4 -5
  115. package/src/assets/commands/implement.mds +58 -45
  116. package/src/assets/commands/plan.mds +34 -29
  117. package/src/assets/commands/release.md +2 -2
  118. package/src/assets/commands/research.mds +11 -9
  119. package/src/assets/commands/resolve.mds +41 -39
  120. package/src/assets/commands/self-review.mds +24 -25
  121. package/src/assets/mds/git/_pr.mds +61 -61
  122. package/src/assets/mds/git/_references.mds +19 -19
  123. package/src/assets/mds/tracker/_common.mds +8 -8
  124. package/src/assets/mds/tracker/_github.mds +71 -71
  125. package/src/assets/mds/tracker/_jira.mds +74 -74
  126. package/src/assets/mds/tracker/_linear.mds +75 -75
  127. package/src/assets/mds/tracker/_mcp.mds +23 -17
  128. package/src/assets/scripts/hooks/background-memory-update +35 -19
  129. package/src/assets/scripts/hooks/capture-prompt +18 -12
  130. package/src/assets/scripts/hooks/capture-question +18 -12
  131. package/src/assets/scripts/hooks/capture-turn +27 -17
  132. package/src/assets/scripts/hooks/debug-trace +11 -6
  133. package/src/assets/scripts/hooks/ensure-devflow-init +33 -6
  134. package/src/assets/scripts/hooks/ensure-proxy +9 -8
  135. package/src/assets/scripts/hooks/ensure-root-gitignore +111 -36
  136. package/src/assets/scripts/hooks/git-marker +48 -0
  137. package/src/assets/scripts/hooks/json-helper.cjs +6 -1
  138. package/src/assets/scripts/hooks/lib/project-paths.cjs +0 -19
  139. package/src/assets/scripts/hooks/log-paths +80 -0
  140. package/src/assets/scripts/hooks/memory-worker +17 -15
  141. package/src/assets/scripts/hooks/pre-compact-memory +41 -16
  142. package/src/assets/scripts/hooks/queue-append +104 -30
  143. package/src/assets/scripts/hooks/resolve-project-root +101 -7
  144. package/src/assets/scripts/hooks/session-start-context +289 -122
  145. package/src/assets/scripts/hooks/session-start-memory +35 -16
  146. package/src/assets/scripts/lib/project-config.cjs +633 -0
  147. package/src/assets/scripts/resolve-evidence-policy.cjs +300 -220
  148. package/src/assets/scripts/resolve-settings.cjs +1054 -0
  149. package/src/assets/scripts/verify-evidence.cjs +1 -1
  150. package/src/assets/skills/compliance/SKILL.md +2 -2
  151. package/src/assets/skills/docs-framework/SKILL.md +6 -7
  152. package/src/assets/skills/docs-framework/references/patterns.md +10 -17
  153. package/src/assets/skills/gap-analysis/SKILL.md +2 -2
  154. package/src/assets/skills/git/references/github-api.md +9 -9
  155. package/src/assets/skills/git/references/patterns.md +1 -1
  156. package/src/assets/skills/worktree-support/SKILL.md +1 -1
  157. package/src/assets/skills/worktree-support/references/roots.md +29 -0
  158. package/src/targets/claude-code/templates/managed-settings.json +25 -9
@@ -24,7 +24,7 @@ import * as p from '@clack/prompts';
24
24
  import color from 'picocolors';
25
25
  import { readProxyState, writeProxyState, buildProxyState, buildRoutingConfigJson, proxyBaseUrl, proxyJsonExists, resolveProxyBin, DEFAULT_PROXY_PORT, } from '../../core/proxy-state.js';
26
26
  import { syncManifestFeature, readManifest } from '../../core/manifest.js';
27
- import { writeFileAtomicExclusive } from '../../core/fs-atomic.js';
27
+ import { writeSettingsFileAtomic } from '../../core/fs-atomic.js';
28
28
  import { scrubChildEnv, openProxyLog, rotateProxyLogIfLarge } from '../../core/proxy-log.js';
29
29
  import { inspectCodexAuth, classifyCodexAuthReadError } from '../../core/codex-auth-inspect.js';
30
30
  import { stripAnsi } from '../../hud/colors.js';
@@ -32,6 +32,7 @@ import { reapplyAgentMapping, revertExternalAgents, countExternalMappedAgents, r
32
32
  import { getExternalModelsCached, discoverExternalModels, } from '../../core/model-discovery.js';
33
33
  import { modelCacheDir } from '../../core/cache.js';
34
34
  import { getClaudeDirectory, getDevFlowDirectory, getHomeDirectory, } from '../../targets/claude-code/claude-paths.js';
35
+ import { devflowHookOwner, ensureHook, hasHook, removeHooks, runHookCommand, } from '../../targets/claude-code/hooks.js';
35
36
  function Ok(value) {
36
37
  return { ok: true, value };
37
38
  }
@@ -39,8 +40,17 @@ function Err(error) {
39
40
  return { ok: false, error };
40
41
  }
41
42
  // ─── Constants ────────────────────────────────────────────────────────────────
42
- /** Marker used to identify ensure-proxy hook entries. */
43
+ /** The run-hook marker of the ensure-proxy hook. */
43
44
  const PROXY_HOOK_MARKER = 'ensure-proxy';
45
+ /**
46
+ * D-EXACT-HOOK-OWNER: the ensure-proxy hook is devflow's only when its command ends
47
+ * in `/scripts/hooks/run-hook ensure-proxy` (hooks.ts), under any directory — the one
48
+ * form it has ever been registered in. A user's hook that merely mentions the word
49
+ * is theirs.
50
+ */
51
+ const isProxyHook = devflowHookOwner([PROXY_HOOK_MARKER]);
52
+ /** The events the ensure-proxy hook is registered on. */
53
+ const PROXY_HOOK_EVENTS = ['SessionStart', 'UserPromptSubmit'];
44
54
  /** Pattern matching our relay's ANTHROPIC_BASE_URL value. */
45
55
  const OUR_BASE_URL_PATTERN = /^http:\/\/127\.0\.0\.1:\d+$/;
46
56
  /** Timeout for individual TCP connect probes and HTTP health checks (ms). */
@@ -166,34 +176,6 @@ function _stripProxyEnvFromObject(settings, managedPort) {
166
176
  delete s.env;
167
177
  return removedUrl || hadWindowVar; // OR the locals — never compose with || inline (PF-015)
168
178
  }
169
- /** Internal: add ensure-proxy hook to one event. Returns true when added. */
170
- function _ensureProxyHook(settings, eventName, hookCmd) {
171
- const existing = settings.hooks?.[eventName];
172
- if (existing?.some((m) => m.hooks.some((h) => h.command.includes(PROXY_HOOK_MARKER)))) {
173
- return false;
174
- }
175
- settings.hooks ??= {};
176
- settings.hooks[eventName] ??= [];
177
- const entry = {
178
- hooks: [{ type: 'command', command: hookCmd, timeout: 15 }],
179
- };
180
- settings.hooks[eventName].push(entry);
181
- return true;
182
- }
183
- /** Internal: remove ensure-proxy hooks from one event. Returns true when removed. */
184
- function _filterProxyHooks(settings, eventName) {
185
- if (!settings.hooks?.[eventName])
186
- return false;
187
- const before = settings.hooks[eventName].length;
188
- settings.hooks[eventName] = settings.hooks[eventName].filter((m) => !m.hooks.some((h) => h.command.includes(PROXY_HOOK_MARKER)));
189
- if (settings.hooks[eventName].length === before)
190
- return false;
191
- if (settings.hooks[eventName].length === 0)
192
- delete settings.hooks[eventName];
193
- if (Object.keys(settings.hooks).length === 0)
194
- delete settings.hooks;
195
- return true;
196
- }
197
179
  // ─── Pure env functions (exported for testing and cross-module reuse) ─────────
198
180
  /**
199
181
  * Apply ANTHROPIC_BASE_URL=http://127.0.0.1:<port> to settings JSON.
@@ -251,20 +233,21 @@ export function readProxyEnvState(settingsJson, port) {
251
233
  * Mutates settings in place. Returns true when any hook was added.
252
234
  */
253
235
  export function addProxyHooks(settings, devflowDir) {
254
- const hookCmd = path.join(devflowDir, 'scripts', 'hooks', 'run-hook') + ' ' + PROXY_HOOK_MARKER;
255
- const addedSession = _ensureProxyHook(settings, 'SessionStart', hookCmd);
256
- const addedPrompt = _ensureProxyHook(settings, 'UserPromptSubmit', hookCmd);
236
+ const command = runHookCommand(devflowDir, PROXY_HOOK_MARKER);
237
+ // Evaluate each event into its own local — never short-circuit (PF-015).
238
+ const [addedSession, addedPrompt] = PROXY_HOOK_EVENTS.map((event) => ensureHook(settings, event, isProxyHook, { hooks: [{ type: 'command', command, timeout: 15 }] }));
257
239
  return addedSession || addedPrompt;
258
240
  }
259
241
  /**
260
242
  * Remove ensure-proxy hooks from all events.
261
243
  * Idempotent — no-op when hooks are not present.
262
- * Preserves other hooks. Cleans empty arrays/objects.
244
+ * Removes single hooks, so the other hooks of a shared matcher group stay in place
245
+ * (D-EXACT-HOOK-OWNER). Cleans empty arrays/objects.
263
246
  * Mutates settings in place. Returns true when any hook was removed.
264
247
  */
265
248
  export function removeProxyHooks(settings) {
266
- const removedSession = _filterProxyHooks(settings, 'SessionStart');
267
- const removedPrompt = _filterProxyHooks(settings, 'UserPromptSubmit');
249
+ // Evaluate each event into its own local — never short-circuit (PF-015).
250
+ const [removedSession, removedPrompt] = PROXY_HOOK_EVENTS.map((event) => removeHooks(settings, event, isProxyHook));
268
251
  return removedSession || removedPrompt;
269
252
  }
270
253
  /**
@@ -319,8 +302,7 @@ export function applyProxyTeardownToSettings(settings, managedPort) {
319
302
  */
320
303
  export function hasProxyHooks(input) {
321
304
  const settings = typeof input === 'string' ? JSON.parse(input) : input;
322
- const check = (eventName) => settings.hooks?.[eventName]?.some((m) => m.hooks.some((h) => h.command.includes(PROXY_HOOK_MARKER))) === true;
323
- return check('SessionStart') || check('UserPromptSubmit');
305
+ return PROXY_HOOK_EVENTS.some((event) => hasHook(settings, event, isProxyHook));
324
306
  }
325
307
  // ─── Health-check identity helper ────────────────────────────────────────────
326
308
  /**
@@ -747,7 +729,7 @@ export async function runPostSpawnVerification(binPath, configPath, logPath, spa
747
729
  * Perform the single atomic settings.json pass for enable:
748
730
  * strip old hooks + env, then apply new hooks + env in one write.
749
731
  *
750
- * The writeFileAtomicExclusive call is guarded — ENOSPC/EACCES returns Err
732
+ * The writeSettingsFileAtomic call is guarded — ENOSPC/EACCES returns Err
751
733
  * instead of crashing with an unhandled rejection.
752
734
  *
753
735
  * Returns Ok(undefined) on success, Err(reason) on hard failure.
@@ -777,7 +759,7 @@ async function applyEnableSettingsPass(settingsPath, devflowDir, port) {
777
759
  addProxyHooks(parsedSettings, devflowDir);
778
760
  _applyProxyEnvToObject(parsedSettings, port);
779
761
  try {
780
- await writeFileAtomicExclusive(settingsPath, JSON.stringify(parsedSettings, null, 2) + '\n');
762
+ await writeSettingsFileAtomic(settingsPath, JSON.stringify(parsedSettings, null, 2) + '\n');
781
763
  }
782
764
  catch (err) {
783
765
  return Err(`Could not write settings.json: ${err instanceof Error ? err.message : String(err)}`);
@@ -1465,7 +1447,7 @@ async function runDisable() {
1465
1447
  if (changed) {
1466
1448
  // Guard ENOSPC/EACCES — unhandled rejection leaves proxy in partial state
1467
1449
  try {
1468
- await writeFileAtomicExclusive(settingsPath, JSON.stringify(parsedSettings, null, 2) + '\n');
1450
+ await writeSettingsFileAtomic(settingsPath, JSON.stringify(parsedSettings, null, 2) + '\n');
1469
1451
  }
1470
1452
  catch (err) {
1471
1453
  p.log.error(`Could not write settings.json: ${err instanceof Error ? err.message : String(err)}`);
@@ -4,8 +4,8 @@ import * as p from '@clack/prompts';
4
4
  import color from 'picocolors';
5
5
  import { getClaudeDirectory, getDevFlowDirectory, getManagedSettingsPath } from '../../targets/claude-code/claude-paths.js';
6
6
  import { readManifest, syncManifestFeature } from '../../core/manifest.js';
7
- import { mergeDenyList, stripUserDenyList, detectDenyState, DEVFLOW_HISTORICAL_DENY, removeManagedSettings, installManagedSettings, loadTemplateDenyEntries, stripUserSecurityDenyList, } from '../../targets/claude-code/post-install.js';
8
- import { writeFileAtomicExclusive } from '../../core/fs-atomic.js';
7
+ import { applyUserSecurityDenyList, stripUserDenyList, detectDenyState, DEVFLOW_HISTORICAL_DENY, removeManagedSettings, installManagedSettings, loadTemplateDenyEntries, stripUserSecurityDenyList, } from '../../targets/claude-code/post-install.js';
8
+ import { writeSettingsFileAtomic } from '../../core/fs-atomic.js';
9
9
  import { promises as fs } from 'fs';
10
10
  import { getPackageRoot } from '../../core/paths.js';
11
11
  // ─── Pure helpers ─────────────────────────────────────────────────────────────
@@ -183,16 +183,8 @@ export const securityCommand = new Command('security')
183
183
  }
184
184
  }
185
185
  else {
186
- // User mode: merge into ~/.claude/settings.json
187
- let existing;
188
- try {
189
- existing = await fs.readFile(userSettingsPath, 'utf-8');
190
- }
191
- catch {
192
- existing = '{}';
193
- }
194
- const merged = mergeDenyList(existing, templateDeny);
195
- await writeFileAtomicExclusive(userSettingsPath, merged);
186
+ // User mode: merge into ~/.claude/settings.json, converging retired entries
187
+ const merged = await applyUserSecurityDenyList(userSettingsPath, templateDeny);
196
188
  const count = countDenyEntries(merged);
197
189
  p.log.success(`Security deny list applied to user settings (${count} entries)`);
198
190
  p.log.info(` Location: ${color.dim(userSettingsPath)}`);
@@ -214,7 +206,7 @@ export const securityCommand = new Command('security')
214
206
  }
215
207
  const { json: stripped, removed } = stripUserDenyList(userSettingsJson, DEVFLOW_HISTORICAL_DENY);
216
208
  if (removed.length > 0) {
217
- await writeFileAtomicExclusive(userSettingsPath, stripped);
209
+ await writeSettingsFileAtomic(userSettingsPath, stripped);
218
210
  p.log.success(`Removed ${removed.length} entries from user settings:`);
219
211
  for (const entry of removed) {
220
212
  p.log.info(` ${color.dim(entry)}`);
@@ -25,6 +25,23 @@ async function dirExists(dirPath) {
25
25
  function getShadowDir(devflowDir, skillName) {
26
26
  return path.join(devflowDir, 'skills', unprefixSkillName(skillName));
27
27
  }
28
+ /**
29
+ * The directory a new shadow of `bareName` is copied from.
30
+ *
31
+ * D-COMPLIANCE-SHADOW-SOURCE (mirrors seedRuleShadow's Tier-1 skip in rules.ts):
32
+ * a FEATURE_OWNED skill always seeds from the shipped SOURCE. Its installed copy
33
+ * is composed — every `${DEVFLOW_COMPLIANCE_*}` token already replaced with the
34
+ * frameworks of that moment — and a token-free shadow passes through composition
35
+ * byte-identical (C1), so seeding from it would freeze that stamp: a later
36
+ * `devflow compliance --set` could never re-stamp the skill. Every other skill
37
+ * seeds from its installed copy when there is one, else from source.
38
+ */
39
+ export function shadowSeedDir(bareName, installedDir) {
40
+ const sourceDir = path.join(skillsDir(), bareName);
41
+ if (FEATURE_OWNED_SKILLS.includes(bareName))
42
+ return sourceDir;
43
+ return installedDir ?? sourceDir;
44
+ }
28
45
  /**
29
46
  * Check if a skill has a shadow (personal override).
30
47
  */
@@ -40,13 +57,14 @@ export async function hasShadow(skillName, devflowDir) {
40
57
  * names one plugin out of several that would each do (D-ALL-OWNERS).
41
58
  * FEATURE_OWNED skills have no plugin owner at all and say so, because telling
42
59
  * a user to select a plugin for `compliance` would send them looking for one
43
- * that does not exist.
60
+ * that does not exist. Compliance is installed on every machine
61
+ * (D-COMPLIANCE-INSTALL-ALWAYS), so its feature is the whole answer.
44
62
  */
45
63
  function describeOwners(bareName, owners) {
46
64
  const declarers = owners ?? skillOwners(bareName);
47
65
  if (declarers.length === 0) {
48
66
  return FEATURE_OWNED_SKILLS.includes(bareName)
49
- ? 'its feature (devflow compliance --enable)'
67
+ ? 'the compliance feature (every install)'
50
68
  : 'no plugin';
51
69
  }
52
70
  return declarers.join(' or ');
@@ -99,7 +117,7 @@ export const skillsCommand = new Command('skills')
99
117
  // The shadow is seeded from the shipped source instead and reported as
100
118
  // DORMANT — it exists, it is preserved by every future install, and it
101
119
  // applies to nothing until the plugin that uses it is selected.
102
- const seedDir = installed ? installedSkillDir : path.join(skillsDir(), bareName);
120
+ const seedDir = shadowSeedDir(bareName, installed ? installedSkillDir : null);
103
121
  if (!await dirExists(seedDir)) {
104
122
  p.log.error(`No source for ${bareName} — reinstall devflow, then try again.`);
105
123
  process.exit(1);