devflow-kit 3.9.0 → 3.10.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 (157) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/README.md +1 -1
  3. package/dist/agents/code.md +6 -6
  4. package/dist/agents/fix.md +6 -6
  5. package/dist/agents/harden.md +7 -6
  6. package/dist/agents/refactor.md +6 -6
  7. package/dist/cli/commands/ambient.js +10 -2
  8. package/dist/cli/commands/compliance-prompts.js +9 -4
  9. package/dist/cli/commands/compliance.js +171 -24
  10. package/dist/cli/commands/curation.js +12 -7
  11. package/dist/cli/commands/gate-level-prompts.js +39 -25
  12. package/dist/cli/commands/gates.js +5 -7
  13. package/dist/cli/commands/init.js +142 -138
  14. package/dist/cli/commands/install-report.js +34 -0
  15. package/dist/cli/commands/legacy-sweep.js +64 -0
  16. package/dist/cli/commands/proxy.js +6 -1
  17. package/dist/cli/commands/rules.js +29 -12
  18. package/dist/cli/commands/skills.js +42 -9
  19. package/dist/cli/commands/tracker.js +9 -3
  20. package/dist/cli/commands/uninstall.js +277 -51
  21. package/dist/commands/bug-analysis.md +69 -120
  22. package/dist/commands/dynamic-build.md +23 -23
  23. package/dist/commands/implement.md +46 -32
  24. package/dist/commands/release.md +1 -1
  25. package/dist/commands/resolve.md +35 -31
  26. package/dist/commands/self-review.md +8 -7
  27. package/dist/compliance-off/agents/git.md +120 -0
  28. package/dist/compliance-off/agents/triage.md +180 -0
  29. package/dist/compliance-off/commands/bug-analysis.md +398 -0
  30. package/dist/compliance-off/commands/code-review.md +520 -0
  31. package/dist/compliance-off/commands/dynamic-build.md +1251 -0
  32. package/dist/compliance-off/commands/dynamic-tickets.md +676 -0
  33. package/dist/compliance-off/commands/implement.md +736 -0
  34. package/dist/compliance-off/commands/plan.md +671 -0
  35. package/dist/compliance-off/commands/release.md +309 -0
  36. package/dist/compliance-off/commands/resolve.md +742 -0
  37. package/dist/compliance-off/skills/git/references/decision-markers.md +17 -0
  38. package/dist/compliance-off/skills/git/references/learn-conventions.md +80 -0
  39. package/dist/compliance-off/skills/git/references/pr/check-ci-status.md +33 -0
  40. package/dist/compliance-off/skills/git/references/pr/check-merge-readiness.md +49 -0
  41. package/dist/compliance-off/skills/git/references/pr/fetch-review-threads.md +52 -0
  42. package/dist/compliance-off/skills/git/references/pr/validate-branch.md +47 -0
  43. package/dist/compliance-off/skills/git/references/tracker/_contract.md +33 -0
  44. package/dist/compliance-off/skills/git/references/tracker/_mcp.md +153 -0
  45. package/dist/compliance-off/skills/git/references/tracker/github/create-release.md +35 -0
  46. package/dist/compliance-off/skills/git/references/tracker/github/ensure-pr-ready.md +100 -0
  47. package/dist/compliance-off/skills/git/references/tracker/github/ensure-traceable-issue.md +74 -0
  48. package/dist/compliance-off/skills/git/references/tracker/github/fetch-issue.md +69 -0
  49. package/dist/compliance-off/skills/git/references/tracker/github/fetch-issues-batch.md +65 -0
  50. package/dist/compliance-off/skills/git/references/tracker/github/gather-release-evidence.md +48 -0
  51. package/dist/compliance-off/skills/git/references/tracker/github/setup-task.md +77 -0
  52. package/dist/compliance-off/skills/git/references/tracker/jira/create-release.md +35 -0
  53. package/dist/compliance-off/skills/git/references/tracker/jira/ensure-pr-ready.md +100 -0
  54. package/dist/compliance-off/skills/git/references/tracker/jira/ensure-traceable-issue.md +61 -0
  55. package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issue.md +51 -0
  56. package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issues-batch.md +63 -0
  57. package/dist/compliance-off/skills/git/references/tracker/jira/gather-release-evidence.md +47 -0
  58. package/dist/compliance-off/skills/git/references/tracker/jira/setup-task.md +82 -0
  59. package/dist/compliance-off/skills/git/references/tracker/linear/create-release.md +35 -0
  60. package/dist/compliance-off/skills/git/references/tracker/linear/ensure-pr-ready.md +100 -0
  61. package/dist/compliance-off/skills/git/references/tracker/linear/ensure-traceable-issue.md +61 -0
  62. package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issue.md +51 -0
  63. package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issues-batch.md +63 -0
  64. package/dist/compliance-off/skills/git/references/tracker/linear/gather-release-evidence.md +47 -0
  65. package/dist/compliance-off/skills/git/references/tracker/linear/setup-task.md +83 -0
  66. package/dist/compliance-off/skills/git/references/trust-rule.md +7 -0
  67. package/dist/core/agent-models.js +19 -4
  68. package/dist/core/assets.js +126 -17
  69. package/dist/core/compliance-off-files.js +27 -0
  70. package/dist/core/curation-variants.js +460 -112
  71. package/dist/core/flags.js +2 -2
  72. package/dist/core/installed-variants.js +63 -0
  73. package/dist/core/manifest.js +48 -5
  74. package/dist/core/mds-variants.js +700 -3
  75. package/dist/core/orphan-sweep.js +11 -2
  76. package/dist/core/plugins.js +65 -8
  77. package/dist/curation-compliance-off/agents/triage.md +176 -0
  78. package/dist/curation-compliance-off/commands/bug-analysis.md +349 -0
  79. package/dist/curation-compliance-off/commands/code-review.md +481 -0
  80. package/dist/curation-compliance-off/commands/dynamic-build.md +1211 -0
  81. package/dist/curation-compliance-off/commands/dynamic-tickets.md +634 -0
  82. package/dist/curation-compliance-off/commands/implement.md +689 -0
  83. package/dist/curation-compliance-off/commands/plan.md +631 -0
  84. package/dist/curation-compliance-off/commands/release.md +279 -0
  85. package/dist/curation-compliance-off/commands/resolve.md +692 -0
  86. package/dist/curation-off/agents/code.md +6 -6
  87. package/dist/curation-off/agents/fix.md +6 -6
  88. package/dist/curation-off/agents/harden.md +7 -6
  89. package/dist/curation-off/agents/refactor.md +6 -6
  90. package/dist/curation-off/commands/bug-analysis.md +69 -119
  91. package/dist/curation-off/commands/dynamic-build.md +23 -23
  92. package/dist/curation-off/commands/implement.md +46 -32
  93. package/dist/curation-off/commands/release.md +1 -1
  94. package/dist/curation-off/commands/resolve.md +35 -31
  95. package/dist/curation-off/commands/self-review.md +8 -7
  96. package/dist/skills/git/references/pr/resolve-review-threads.md +1 -1
  97. package/dist/targets/claude-code/behavior-install.js +23 -9
  98. package/dist/targets/claude-code/charter-render.js +374 -0
  99. package/dist/targets/claude-code/compliance-install.js +32 -12
  100. package/dist/targets/claude-code/compliance-off-files.js +245 -0
  101. package/dist/targets/claude-code/curation-install.js +570 -100
  102. package/dist/targets/claude-code/installer.js +467 -62
  103. package/dist/targets/claude-code/language-stamp.js +6 -0
  104. package/dist/targets/claude-code/linked-folder.js +182 -0
  105. package/dist/targets/claude-code/reference-converge.js +118 -0
  106. package/dist/targets/claude-code/tracker-install.js +21 -8
  107. package/dist/targets/claude-code/tracker-servers.js +20 -6
  108. package/dist/targets/claude-code/workflow-folder-install.js +231 -0
  109. package/dist/variant-axes.json +109 -0
  110. package/package.json +1 -1
  111. package/src/assets/agents/_partials/_code.mds +6 -6
  112. package/src/assets/agents/evaluate.md +1 -1
  113. package/src/assets/agents/git.mds +32 -0
  114. package/src/assets/agents/harden.mds +7 -6
  115. package/src/assets/agents/test.md +21 -12
  116. package/src/assets/agents/triage.mds +2 -0
  117. package/src/assets/agents/validate.md +10 -10
  118. package/src/assets/commands/_partials/_compliance.mds +5 -3
  119. package/src/assets/commands/_partials/_engine.mds +9 -8
  120. package/src/assets/commands/_partials/_publication.mds +10 -1
  121. package/src/assets/commands/bug-analysis.mds +80 -126
  122. package/src/assets/commands/code-review.mds +43 -0
  123. package/src/assets/commands/dynamic-build.mds +109 -15
  124. package/src/assets/commands/dynamic-tickets.mds +13 -0
  125. package/src/assets/commands/implement.mds +145 -33
  126. package/src/assets/commands/plan.mds +19 -0
  127. package/src/assets/commands/release.mds +50 -1
  128. package/src/assets/commands/resolve.mds +217 -32
  129. package/src/assets/commands/self-review.mds +8 -7
  130. package/src/assets/mds/git/_ops.mds +23 -0
  131. package/src/assets/mds/git/_ops_steps.mds +17 -0
  132. package/src/assets/mds/git/_pr.mds +1 -1
  133. package/src/assets/mds/git/_references.mds +6 -0
  134. package/src/assets/mds/tracker/_common.mds +4 -0
  135. package/src/assets/mds/tracker/_github.mds +42 -0
  136. package/src/assets/mds/tracker/_jira.mds +43 -0
  137. package/src/assets/mds/tracker/_linear.mds +43 -0
  138. package/src/assets/rules/quality.md +2 -0
  139. package/src/assets/scripts/ci-wait.cjs +204 -126
  140. package/src/assets/scripts/hooks/assets/implement-installed +1 -0
  141. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +13 -11
  142. package/src/assets/scripts/hooks/preamble +18 -8
  143. package/src/assets/scripts/hooks/session-start-orchestrator +4 -2
  144. package/src/assets/scripts/pr-evidence.cjs +41 -2
  145. package/src/assets/scripts/verify-evidence.cjs +408 -56
  146. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +3 -3
  147. package/src/assets/skills/agent-references/references/commands/plan-levels.md +1 -1
  148. package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +6 -2
  149. package/src/assets/skills/agent-references/references/validate/passed-alone.md +7 -7
  150. package/src/assets/skills/docs-framework/SKILL.md +5 -2
  151. package/src/assets/skills/git/SKILL.md +3 -3
  152. package/src/assets/skills/git/references/github-api.md +2 -139
  153. package/src/assets/skills/git/references/github-posting.md +158 -0
  154. package/src/assets/skills/review-methodology/SKILL.md +1 -1
  155. package/src/assets/skills/review-methodology/references/patterns.md +1 -1
  156. package/src/assets/skills/review-methodology/references/report-template.md +1 -2
  157. package/src/assets/skills/testing/SKILL.md +2 -4
@@ -0,0 +1,64 @@
1
+ import { promises as fs } from 'fs';
2
+ import * as path from 'path';
3
+ import { LEGACY_COMMAND_NAMES, LEGACY_RULE_NAMES } from '../../core/plugins.js';
4
+ import { isUnrealFolder } from '../../targets/claude-code/linked-folder.js';
5
+ /**
6
+ * Whether the sweep may remove files from `dir`: the install did not refuse it, and `lstat` shows a
7
+ * real folder or nothing. `readdir` and `rm` of a path below a link act on the folder it points to.
8
+ */
9
+ async function mayRemoveFrom(report, dir) {
10
+ return !report.refusedFolders.includes(dir) && !(await isUnrealFolder(dir));
11
+ }
12
+ /**
13
+ * Remove the files that earlier releases installed into `commands/devflow` and `rules/devflow` and
14
+ * that no release installs now: the legacy command and rule names, and every `*-teams.md` command.
15
+ * Runs on every install shape, since the full install's pre-clean covers only a full install.
16
+ *
17
+ * A folder the install refused (`report.refusedFolders`) or that is a link now is skipped whole: the
18
+ * sweep reads and removes nothing there, and the folder it points to is left as it is. Never throws.
19
+ *
20
+ * @param opts.legacyCommands - Legacy command names; defaults to the registry list.
21
+ * @param opts.legacyRules - Legacy rule names; defaults to the registry list.
22
+ */
23
+ export async function sweepLegacyDevflowFiles(opts) {
24
+ const { claudeDir, report } = opts;
25
+ const legacyCommands = opts.legacyCommands ?? LEGACY_COMMAND_NAMES;
26
+ const legacyRules = opts.legacyRules ?? LEGACY_RULE_NAMES;
27
+ const result = { staleCommandsRemoved: 0, staleRulesRemoved: 0 };
28
+ const commandsDir = path.join(claudeDir, 'commands', 'devflow');
29
+ if (await mayRemoveFrom(report, commandsDir)) {
30
+ for (const legacy of legacyCommands) {
31
+ try {
32
+ await fs.rm(path.join(commandsDir, `${legacy}.md`));
33
+ result.staleCommandsRemoved++;
34
+ }
35
+ catch { /* absent — expected for most entries */ }
36
+ }
37
+ // Orphaned *-teams.md workflow command variants left by the Agent Teams refactor. None is ever
38
+ // re-installed, so a blanket sweep is safe on any install type.
39
+ try {
40
+ for (const entry of await fs.readdir(commandsDir)) {
41
+ if (!entry.endsWith('-teams.md'))
42
+ continue;
43
+ try {
44
+ await fs.rm(path.join(commandsDir, entry));
45
+ result.staleCommandsRemoved++;
46
+ }
47
+ catch { /* already gone */ }
48
+ }
49
+ }
50
+ catch { /* commands folder absent — nothing to sweep */ }
51
+ }
52
+ const rulesDir = path.join(claudeDir, 'rules', 'devflow');
53
+ if (await mayRemoveFrom(report, rulesDir)) {
54
+ for (const legacy of legacyRules) {
55
+ try {
56
+ await fs.rm(path.join(rulesDir, `${legacy}.md`));
57
+ result.staleRulesRemoved++;
58
+ }
59
+ catch { /* absent — expected for most entries */ }
60
+ }
61
+ }
62
+ return result;
63
+ }
64
+ //# sourceMappingURL=legacy-sweep.js.map
@@ -31,6 +31,7 @@ import { stripAnsi } from '../../hud/colors.js';
31
31
  import { reapplyAgentMapping, revertExternalAgents, countExternalMappedAgents, readAgentMapping, } from '../../core/agent-models.js';
32
32
  import { getExternalModelsCached, discoverExternalModels, } from '../../core/model-discovery.js';
33
33
  import { modelCacheDir } from '../../core/cache.js';
34
+ import { formatLinksSummary } from './install-report.js';
34
35
  import { getClaudeDirectory, getDevFlowDirectory, getHomeDirectory, } from '../../targets/claude-code/claude-paths.js';
35
36
  import { devflowHookOwner, ensureHook, hasHook, removeHooks, runHookCommand, } from '../../targets/claude-code/hooks.js';
36
37
  function Ok(value) {
@@ -1389,6 +1390,8 @@ async function runEnable(portOption) {
1389
1390
  s.message('Warming model cache...');
1390
1391
  await discoverExternalModels(cacheDir, logPath).catch(() => { });
1391
1392
  s.stop(color.green('External model routing enabled'));
1393
+ for (const { message } of formatLinksSummary(reapplyResult))
1394
+ p.log.warn(message);
1392
1395
  // D-P4-1: applies-on-restart — env var takes effect only for new sessions
1393
1396
  p.log.info(color.dim('Context-window enforcement disabled for relay-routed models — applies to new Claude Code sessions'));
1394
1397
  if (adopted) {
@@ -1472,7 +1475,9 @@ async function runDisable() {
1472
1475
  // Step 3: Sync manifest
1473
1476
  await syncManifestFeature(devflowDir, 'proxy', false);
1474
1477
  // Step 4: Revert external agents to shipped defaults
1475
- await revertExternalAgents({ installDir, devflowDir });
1478
+ const revertResult = await revertExternalAgents({ installDir, devflowDir });
1479
+ for (const { message } of formatLinksSummary(revertResult))
1480
+ p.log.warn(message);
1476
1481
  p.log.success('External model routing disabled');
1477
1482
  // Step 5: Terminate the relay process.
1478
1483
  // Identity is confirmed via TCP + health before signalling — a recycled PID
@@ -10,6 +10,8 @@ import { rulesDir } from '../../core/assets.js';
10
10
  import { installAllRules, validateRuleShadow } from '../../targets/claude-code/installer.js';
11
11
  import { convergeFromManifest } from '../../targets/claude-code/compliance-install.js';
12
12
  import { convergeBehaviorFromManifest } from '../../targets/claude-code/behavior-install.js';
13
+ import { newLinkReport, refuseUnrealFolder, refusedFolderMessage, removeReportingLink } from '../../targets/claude-code/linked-folder.js';
14
+ import { formatLinksSummary } from './install-report.js';
13
15
  /**
14
16
  * Check whether a rule has a user shadow file at ~/.devflow/rules/{name}.md.
15
17
  */
@@ -213,17 +215,24 @@ export async function runRulesEnable(claudeDir, devflowDir) {
213
215
  }
214
216
  const installedPlugins = DEVFLOW_PLUGINS.filter(pl => manifest.plugins.includes(pl.name));
215
217
  const rulesMap = buildRulesMap(installedPlugins);
216
- // Wipe stale rules from previously uninstalled plugins before re-installing.
217
- await fs.rm(rulesTarget, { recursive: true, force: true });
218
- await fs.mkdir(rulesTarget, { recursive: true });
218
+ // Wipe stale rules from previously uninstalled plugins before re-installing. A rules folder that is
219
+ // a link is unlinked and replaced by a real folder, never emptied through, and the link is reported.
220
+ const links = newLinkReport();
221
+ links.replacedLinks.push(...await removeReportingLink(rulesTarget));
222
+ // A link that could not be removed is still there, and `mkdir` and every rule write would follow it.
223
+ // Prove the folder first; a refused folder gets no rule, no manifest write and one refusal line.
224
+ const rulesFolderRefused = await refuseUnrealFolder(links, rulesTarget);
219
225
  let outcomes = [];
220
- let installFailed = false;
221
- try {
222
- outcomes = await installAllRules(rulesMap, devflowDir, rulesTarget);
223
- }
224
- catch (err) {
225
- p.log.error(`Rules installation failed — rules directory has been cleared: ${err instanceof Error ? err.message : String(err)}`);
226
- installFailed = true;
226
+ let installFailed = rulesFolderRefused;
227
+ if (!rulesFolderRefused) {
228
+ await fs.mkdir(rulesTarget, { recursive: true });
229
+ try {
230
+ outcomes = await installAllRules(rulesMap, devflowDir, rulesTarget, links);
231
+ }
232
+ catch (err) {
233
+ p.log.error(`Rules installation failed — rules directory has been cleared: ${err instanceof Error ? err.message : String(err)}`);
234
+ installFailed = true;
235
+ }
227
236
  }
228
237
  if (!installFailed) {
229
238
  const shadowedCount = outcomes.filter(o => o.outcome === 'shadow').length;
@@ -239,6 +248,14 @@ export async function runRulesEnable(claudeDir, devflowDir) {
239
248
  await writeManifest(devflowDir, manifest);
240
249
  p.log.success(`Installed ${rulesMap.size} rule(s) to ${color.dim(rulesTarget)}${shadowSuffix}`);
241
250
  }
251
+ for (const { message } of formatLinksSummary(links))
252
+ p.log.warn(message);
253
+ // Each converge below proves the rules folder again and names a refused one itself; the line above
254
+ // has already named it, so the repeat is dropped.
255
+ const warnConverge = (msg) => {
256
+ if (!(rulesFolderRefused && msg === refusedFolderMessage(rulesTarget)))
257
+ p.log.warn(msg);
258
+ };
242
259
  // Converge compliance artifacts unconditionally — convergeFromManifest handles the
243
260
  // disabled case itself (removes stale artifacts). Running this regardless of installFailed
244
261
  // prevents a rules-install failure from leaving the compliance rule in a stale state.
@@ -249,7 +266,7 @@ export async function runRulesEnable(claudeDir, devflowDir) {
249
266
  claudeDir,
250
267
  devflowDir,
251
268
  manifest,
252
- warn: (msg) => p.log.warn(msg),
269
+ warn: warnConverge,
253
270
  rulesEnabledOverride: !installFailed,
254
271
  });
255
272
  }
@@ -265,7 +282,7 @@ export async function runRulesEnable(claudeDir, devflowDir) {
265
282
  claudeDir,
266
283
  devflowDir,
267
284
  manifest,
268
- warn: (msg) => p.log.warn(msg),
285
+ warn: warnConverge,
269
286
  rulesEnabledOverride: !installFailed,
270
287
  });
271
288
  }
@@ -4,7 +4,7 @@ import * as path from 'path';
4
4
  import * as p from '@clack/prompts';
5
5
  import color from 'picocolors';
6
6
  import { getClaudeDirectory, getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
7
- import { getAllSkillNames, prefixSkillName, unprefixSkillName, skillOwners, FEATURE_OWNED_SKILLS } from '../../core/plugins.js';
7
+ import { getAllSkillNames, prefixSkillName, unprefixSkillName, skillOwners, FEATURE_OWNED_SKILLS, WORKFLOW_FOLDER_NAMES } from '../../core/plugins.js';
8
8
  import { skillsDir } from '../../core/assets.js';
9
9
  import { copyDirectory, validateSkillShadow } from '../../targets/claude-code/installer.js';
10
10
  /**
@@ -42,6 +42,22 @@ export function shadowSeedDir(bareName, installedDir) {
42
42
  return sourceDir;
43
43
  return installedDir ?? sourceDir;
44
44
  }
45
+ /**
46
+ * D-WORKFLOW-SHADOW-REFUSAL: why `devflow skills shadow <name>` refuses a workflow that ships as a
47
+ * skill folder, or null for any other name.
48
+ *
49
+ * A folder installs from the build tree of the machine's exact variant key, with no source
50
+ * directory to copy and no shadow to overlay, so a shadow of its name could never apply. Refusing
51
+ * at the command says so before the user edits a copy that does nothing; the install ignores a
52
+ * shadow that is already there, with a warning, and keeps it.
53
+ *
54
+ * @param folderWorkflows - The target-layer set; the default is the real set.
55
+ */
56
+ export function workflowFolderShadowRefusal(bareName, folderWorkflows = WORKFLOW_FOLDER_NAMES) {
57
+ if (!folderWorkflows.has(bareName))
58
+ return null;
59
+ return `${bareName} ships as a skill folder built for the machine's variant, so a shadow cannot replace it. Nothing was shadowed.`;
60
+ }
45
61
  /**
46
62
  * Check if a skill has a shadow (personal override).
47
63
  */
@@ -85,16 +101,19 @@ function buildSkillShadowTag(shadowState) {
85
101
  }
86
102
  }
87
103
  }
88
- export const skillsCommand = new Command('skills')
89
- .description('Manage skill overrides (shadow/unshadow/list)')
90
- .argument('<action>', 'Action: shadow, unshadow, or list')
91
- .argument('[name]', 'Skill name (required for shadow/unshadow)')
92
- .action(async (action, name) => {
104
+ /**
105
+ * Run one `devflow skills` action: shadow, unshadow or list.
106
+ *
107
+ * @param folderWorkflows - The workflows that ship as skill folders; the default is the real
108
+ * set. They count as known skills, `shadow` refuses them, and `list` tags a leftover shadow
109
+ * of one as ignored. Tests pass a fixture set, because the real set is empty.
110
+ */
111
+ export async function runSkillsAction(action, name, folderWorkflows = WORKFLOW_FOLDER_NAMES) {
93
112
  const devflowDir = getDevFlowDirectory();
94
113
  const claudeDir = getClaudeDirectory();
95
114
  // Union FEATURE_OWNED_SKILLS (compliance) so shadow/unshadow/list cover feature-managed
96
115
  // skills that left the plugin registry but still live in src/assets/skills/ (step 1.5).
97
- const allSkills = [...getAllSkillNames(), ...FEATURE_OWNED_SKILLS];
116
+ const allSkills = [...getAllSkillNames(folderWorkflows), ...FEATURE_OWNED_SKILLS];
98
117
  if (action === 'shadow') {
99
118
  if (!name) {
100
119
  p.log.error('Skill name required. Usage: devflow skills shadow <name>');
@@ -103,6 +122,11 @@ export const skillsCommand = new Command('skills')
103
122
  }
104
123
  // Accept both bare and prefixed input
105
124
  const bareName = unprefixSkillName(name);
125
+ const refusal = workflowFolderShadowRefusal(bareName, folderWorkflows);
126
+ if (refusal !== null) {
127
+ p.log.error(refusal);
128
+ process.exit(1);
129
+ }
106
130
  if (!allSkills.includes(bareName)) {
107
131
  p.log.error(`Unknown skill: ${bareName}`);
108
132
  p.log.info(`Available skills: ${allSkills.join(', ')}`);
@@ -183,7 +207,11 @@ export const skillsCommand = new Command('skills')
183
207
  const provenance = installed
184
208
  ? color.dim(`installed because: ${describeOwners(skill, ownersBySkill.get(skill))}`)
185
209
  : color.dim(`not installed — provided by: ${describeOwners(skill, ownersBySkill.get(skill))}`);
186
- return ` ${color.cyan(skill.padEnd(28))} ${buildSkillShadowTag(shadowState).padEnd(20)} ${provenance}`;
210
+ // A shadow of a skill folder is kept but never applied (D-WORKFLOW-SHADOW-REFUSAL).
211
+ const tag = workflowFolderShadowRefusal(skill, folderWorkflows) !== null && shadowState !== 'none'
212
+ ? color.yellow('shadow ignored — skill folder')
213
+ : buildSkillShadowTag(shadowState);
214
+ return ` ${color.cyan(skill.padEnd(28))} ${tag.padEnd(20)} ${provenance}`;
187
215
  });
188
216
  // Orphan shadows: in ~/.devflow/skills/ but not a known skill
189
217
  for (const dirName of shadowDirNames) {
@@ -200,5 +228,10 @@ export const skillsCommand = new Command('skills')
200
228
  p.log.info('Usage: devflow skills <shadow|unshadow|list> [name]');
201
229
  process.exit(1);
202
230
  }
203
- });
231
+ }
232
+ export const skillsCommand = new Command('skills')
233
+ .description('Manage skill overrides (shadow/unshadow/list)')
234
+ .argument('<action>', 'Action: shadow, unshadow, or list')
235
+ .argument('[name]', 'Skill name (required for shadow/unshadow)')
236
+ .action((action, name) => runSkillsAction(action, name));
204
237
  //# sourceMappingURL=skills.js.map
@@ -56,12 +56,18 @@ export async function readTrackerProvenance(devflowDir, provider) {
56
56
  * every install carries them all (D-INSTALL-ALL-PROVIDERS) — rather than listing
57
57
  * the directory: the question a user asks `--status` is "can the agent load what
58
58
  * it is told to load?", and a stray file in the tree is not an answer to it.
59
+ *
60
+ * `compliance` is the machine's compliance switch, read from the install manifest by the
61
+ * caller and never from a repository file: a compliance-off machine installs the compliance-off
62
+ * manifest, which holds fewer references, so counting against the compliance-on one would read
63
+ * every such machine as damaged.
64
+ *
59
65
  * Never throws.
60
66
  */
61
- export async function readTrackerMechanics(claudeDir) {
67
+ export async function readTrackerMechanics(claudeDir, compliance) {
62
68
  const root = path.join(claudeDir, 'skills', prefixSkillName(SKILL_REFS_SKILL_NAME), 'references');
63
69
  let present = 0;
64
- for (const rel of installedReferenceManifest()) {
70
+ for (const rel of installedReferenceManifest({ compliance })) {
65
71
  try {
66
72
  await fs.access(path.join(root, ...rel.split('/')));
67
73
  present++;
@@ -292,7 +298,7 @@ export const trackerCommand = new Command('tracker')
292
298
  const selection = repoTrackerSelection(settingsModule, { dir: process.cwd() });
293
299
  const effective = selection?.provider ?? current.provider;
294
300
  const conventions = await readTrackerStatusConventions(devflowDir, effective);
295
- const mechanics = await readTrackerMechanics(getClaudeDirectory());
301
+ const mechanics = await readTrackerMechanics(getClaudeDirectory(), manifest.features.compliance.enabled);
296
302
  // [D-F] Inspecting the status re-arms the attempt counters. --status is
297
303
  // the command a capped user reaches for to find out why nothing is being
298
304
  // learned, so it is the command that has to hand back another five