devflow-kit 3.9.0 → 3.11.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 (168) hide show
  1. package/CHANGELOG.md +142 -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/backup-session.js +131 -0
  9. package/dist/cli/commands/compliance-prompts.js +9 -4
  10. package/dist/cli/commands/compliance.js +184 -24
  11. package/dist/cli/commands/curation.js +23 -10
  12. package/dist/cli/commands/gate-level-prompts.js +39 -25
  13. package/dist/cli/commands/gates.js +5 -7
  14. package/dist/cli/commands/init.js +169 -184
  15. package/dist/cli/commands/install-report.js +129 -0
  16. package/dist/cli/commands/legacy-sweep.js +159 -0
  17. package/dist/cli/commands/proxy.js +6 -1
  18. package/dist/cli/commands/rules.js +75 -21
  19. package/dist/cli/commands/skills.js +42 -9
  20. package/dist/cli/commands/tracker.js +9 -3
  21. package/dist/cli/commands/uninstall.js +339 -75
  22. package/dist/commands/bug-analysis.md +69 -120
  23. package/dist/commands/dynamic-build.md +23 -23
  24. package/dist/commands/implement.md +46 -32
  25. package/dist/commands/release.md +1 -1
  26. package/dist/commands/resolve.md +35 -31
  27. package/dist/commands/self-review.md +8 -7
  28. package/dist/compliance-off/agents/git.md +120 -0
  29. package/dist/compliance-off/agents/triage.md +180 -0
  30. package/dist/compliance-off/commands/bug-analysis.md +398 -0
  31. package/dist/compliance-off/commands/code-review.md +520 -0
  32. package/dist/compliance-off/commands/dynamic-build.md +1251 -0
  33. package/dist/compliance-off/commands/dynamic-tickets.md +676 -0
  34. package/dist/compliance-off/commands/implement.md +736 -0
  35. package/dist/compliance-off/commands/plan.md +671 -0
  36. package/dist/compliance-off/commands/release.md +309 -0
  37. package/dist/compliance-off/commands/resolve.md +742 -0
  38. package/dist/compliance-off/skills/git/references/decision-markers.md +17 -0
  39. package/dist/compliance-off/skills/git/references/learn-conventions.md +80 -0
  40. package/dist/compliance-off/skills/git/references/pr/check-ci-status.md +33 -0
  41. package/dist/compliance-off/skills/git/references/pr/check-merge-readiness.md +49 -0
  42. package/dist/compliance-off/skills/git/references/pr/fetch-review-threads.md +52 -0
  43. package/dist/compliance-off/skills/git/references/pr/validate-branch.md +47 -0
  44. package/dist/compliance-off/skills/git/references/tracker/_contract.md +33 -0
  45. package/dist/compliance-off/skills/git/references/tracker/_mcp.md +153 -0
  46. package/dist/compliance-off/skills/git/references/tracker/github/create-release.md +35 -0
  47. package/dist/compliance-off/skills/git/references/tracker/github/ensure-pr-ready.md +100 -0
  48. package/dist/compliance-off/skills/git/references/tracker/github/ensure-traceable-issue.md +74 -0
  49. package/dist/compliance-off/skills/git/references/tracker/github/fetch-issue.md +69 -0
  50. package/dist/compliance-off/skills/git/references/tracker/github/fetch-issues-batch.md +65 -0
  51. package/dist/compliance-off/skills/git/references/tracker/github/gather-release-evidence.md +48 -0
  52. package/dist/compliance-off/skills/git/references/tracker/github/setup-task.md +77 -0
  53. package/dist/compliance-off/skills/git/references/tracker/jira/create-release.md +35 -0
  54. package/dist/compliance-off/skills/git/references/tracker/jira/ensure-pr-ready.md +100 -0
  55. package/dist/compliance-off/skills/git/references/tracker/jira/ensure-traceable-issue.md +61 -0
  56. package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issue.md +51 -0
  57. package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issues-batch.md +63 -0
  58. package/dist/compliance-off/skills/git/references/tracker/jira/gather-release-evidence.md +47 -0
  59. package/dist/compliance-off/skills/git/references/tracker/jira/setup-task.md +82 -0
  60. package/dist/compliance-off/skills/git/references/tracker/linear/create-release.md +35 -0
  61. package/dist/compliance-off/skills/git/references/tracker/linear/ensure-pr-ready.md +100 -0
  62. package/dist/compliance-off/skills/git/references/tracker/linear/ensure-traceable-issue.md +61 -0
  63. package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issue.md +51 -0
  64. package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issues-batch.md +63 -0
  65. package/dist/compliance-off/skills/git/references/tracker/linear/gather-release-evidence.md +47 -0
  66. package/dist/compliance-off/skills/git/references/tracker/linear/setup-task.md +83 -0
  67. package/dist/compliance-off/skills/git/references/trust-rule.md +7 -0
  68. package/dist/core/agent-models.js +19 -4
  69. package/dist/core/assets.js +126 -17
  70. package/dist/core/compliance-off-files.js +27 -0
  71. package/dist/core/compliance.js +14 -0
  72. package/dist/core/curation-variants.js +460 -112
  73. package/dist/core/flags.js +2 -2
  74. package/dist/core/fs-atomic.js +5 -1
  75. package/dist/core/installed-variants.js +63 -0
  76. package/dist/core/manifest.js +48 -5
  77. package/dist/core/mds-variants.js +700 -3
  78. package/dist/core/orphan-sweep.js +29 -2
  79. package/dist/core/path-set.js +15 -0
  80. package/dist/core/plugins.js +68 -10
  81. package/dist/core/reference-sweep.js +42 -25
  82. package/dist/core/retired-shipped-names.js +278 -0
  83. package/dist/curation-compliance-off/agents/triage.md +176 -0
  84. package/dist/curation-compliance-off/commands/bug-analysis.md +349 -0
  85. package/dist/curation-compliance-off/commands/code-review.md +481 -0
  86. package/dist/curation-compliance-off/commands/dynamic-build.md +1211 -0
  87. package/dist/curation-compliance-off/commands/dynamic-tickets.md +634 -0
  88. package/dist/curation-compliance-off/commands/implement.md +689 -0
  89. package/dist/curation-compliance-off/commands/plan.md +631 -0
  90. package/dist/curation-compliance-off/commands/release.md +279 -0
  91. package/dist/curation-compliance-off/commands/resolve.md +692 -0
  92. package/dist/curation-off/agents/code.md +6 -6
  93. package/dist/curation-off/agents/fix.md +6 -6
  94. package/dist/curation-off/agents/harden.md +7 -6
  95. package/dist/curation-off/agents/refactor.md +6 -6
  96. package/dist/curation-off/commands/bug-analysis.md +69 -119
  97. package/dist/curation-off/commands/dynamic-build.md +23 -23
  98. package/dist/curation-off/commands/implement.md +46 -32
  99. package/dist/curation-off/commands/release.md +1 -1
  100. package/dist/curation-off/commands/resolve.md +35 -31
  101. package/dist/curation-off/commands/self-review.md +8 -7
  102. package/dist/skills/git/references/pr/resolve-review-threads.md +1 -1
  103. package/dist/targets/claude-code/behavior-install.js +23 -9
  104. package/dist/targets/claude-code/charter-render.js +374 -0
  105. package/dist/targets/claude-code/compliance-install.js +62 -17
  106. package/dist/targets/claude-code/compliance-off-files.js +249 -0
  107. package/dist/targets/claude-code/curation-install.js +632 -117
  108. package/dist/targets/claude-code/folder-swap.js +142 -0
  109. package/dist/targets/claude-code/install-backup.js +780 -0
  110. package/dist/targets/claude-code/installer.js +795 -199
  111. package/dist/targets/claude-code/language-stamp.js +6 -0
  112. package/dist/targets/claude-code/legacy-skill-hashes.js +170 -0
  113. package/dist/targets/claude-code/linked-folder.js +176 -0
  114. package/dist/targets/claude-code/reference-converge.js +131 -0
  115. package/dist/targets/claude-code/rules-folder.js +34 -0
  116. package/dist/targets/claude-code/shipped-set.js +160 -0
  117. package/dist/targets/claude-code/tracker-install.js +21 -8
  118. package/dist/targets/claude-code/tracker-servers.js +20 -6
  119. package/dist/targets/claude-code/workflow-folder-install.js +251 -0
  120. package/dist/variant-axes.json +109 -0
  121. package/package.json +1 -1
  122. package/src/assets/agents/_partials/_code.mds +6 -6
  123. package/src/assets/agents/evaluate.md +1 -1
  124. package/src/assets/agents/git.mds +32 -0
  125. package/src/assets/agents/harden.mds +7 -6
  126. package/src/assets/agents/test.md +21 -12
  127. package/src/assets/agents/triage.mds +2 -0
  128. package/src/assets/agents/validate.md +10 -10
  129. package/src/assets/commands/_partials/_compliance.mds +5 -3
  130. package/src/assets/commands/_partials/_engine.mds +9 -8
  131. package/src/assets/commands/_partials/_publication.mds +10 -1
  132. package/src/assets/commands/bug-analysis.mds +80 -126
  133. package/src/assets/commands/code-review.mds +43 -0
  134. package/src/assets/commands/dynamic-build.mds +109 -15
  135. package/src/assets/commands/dynamic-tickets.mds +13 -0
  136. package/src/assets/commands/implement.mds +145 -33
  137. package/src/assets/commands/plan.mds +19 -0
  138. package/src/assets/commands/release.mds +50 -1
  139. package/src/assets/commands/resolve.mds +217 -32
  140. package/src/assets/commands/self-review.mds +8 -7
  141. package/src/assets/mds/git/_ops.mds +23 -0
  142. package/src/assets/mds/git/_ops_steps.mds +17 -0
  143. package/src/assets/mds/git/_pr.mds +1 -1
  144. package/src/assets/mds/git/_references.mds +6 -0
  145. package/src/assets/mds/tracker/_common.mds +4 -0
  146. package/src/assets/mds/tracker/_github.mds +42 -0
  147. package/src/assets/mds/tracker/_jira.mds +43 -0
  148. package/src/assets/mds/tracker/_linear.mds +43 -0
  149. package/src/assets/rules/quality.md +2 -0
  150. package/src/assets/scripts/ci-wait.cjs +204 -126
  151. package/src/assets/scripts/hooks/assets/implement-installed +1 -0
  152. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +13 -11
  153. package/src/assets/scripts/hooks/preamble +18 -8
  154. package/src/assets/scripts/hooks/session-start-orchestrator +4 -2
  155. package/src/assets/scripts/pr-evidence.cjs +41 -2
  156. package/src/assets/scripts/verify-evidence.cjs +408 -56
  157. package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +3 -3
  158. package/src/assets/skills/agent-references/references/commands/plan-levels.md +1 -1
  159. package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +6 -2
  160. package/src/assets/skills/agent-references/references/validate/passed-alone.md +7 -7
  161. package/src/assets/skills/docs-framework/SKILL.md +5 -2
  162. package/src/assets/skills/git/SKILL.md +3 -3
  163. package/src/assets/skills/git/references/github-api.md +2 -139
  164. package/src/assets/skills/git/references/github-posting.md +158 -0
  165. package/src/assets/skills/review-methodology/SKILL.md +1 -1
  166. package/src/assets/skills/review-methodology/references/patterns.md +1 -1
  167. package/src/assets/skills/review-methodology/references/report-template.md +1 -2
  168. package/src/assets/skills/testing/SKILL.md +2 -4
@@ -53,6 +53,16 @@ export function mdEntryName(entry) {
53
53
  * For agents and commands: use {@link mdEntryName}.
54
54
  * For skills: strip the devflow: prefix.
55
55
  *
56
+ * @param mayRemove - Optional check made on the full path of each orphan before
57
+ * it is removed. An orphan it refuses stays where it is and is neither
58
+ * removed nor failed; the check reports the refusal itself. The caller proves
59
+ * that `dir` is a real folder before the call, because the sweep reads and
60
+ * removes inside whatever `dir` names.
61
+ *
62
+ * @param removal - How an admitted orphan is removed ({@link SweepRemoval}). Defaults to
63
+ * `recursive`. With `unlink`, an entry that has become a folder since the check stays and is
64
+ * recorded in `failed`; an entry that has vanished since the check counts as removed.
65
+ *
56
66
  * @returns A {@link SweepResult} reporting how many entries were scanned, which
57
67
  * registry names were removed, and which removals failed.
58
68
  * `scanned` is the count matched by the predicate (not the removed count) —
@@ -64,7 +74,7 @@ export function mdEntryName(entry) {
64
74
  * individual removal is recorded in `failed` without aborting the sweep.
65
75
  * Never writes, only removes — no delete-then-write window.
66
76
  */
67
- export async function sweepOrphanedAssets(dir, knownNames, extractRegistryName) {
77
+ export async function sweepOrphanedAssets(dir, knownNames, extractRegistryName, mayRemove, removal = 'recursive') {
68
78
  const removed = [];
69
79
  const failed = [];
70
80
  let scanned = 0;
@@ -76,8 +86,11 @@ export async function sweepOrphanedAssets(dir, knownNames, extractRegistryName)
76
86
  continue;
77
87
  scanned++;
78
88
  if (!knownNames.has(registryName)) {
89
+ const entryPath = path.join(dir, entry);
79
90
  try {
80
- await fs.rm(path.join(dir, entry), { recursive: true, force: true });
91
+ if (mayRemove !== undefined && !(await mayRemove(entryPath)))
92
+ continue;
93
+ await removeEntry(entryPath, removal);
81
94
  removed.push(registryName);
82
95
  }
83
96
  catch (err) {
@@ -89,4 +102,18 @@ export async function sweepOrphanedAssets(dir, knownNames, extractRegistryName)
89
102
  catch { /* directory absent or unreadable — not an error */ }
90
103
  return { scanned, removed, failed };
91
104
  }
105
+ /** Remove one orphan as `removal` says. An absent entry is already removed, as `fs.rm` with `force` treats it. */
106
+ async function removeEntry(entryPath, removal) {
107
+ if (removal === 'recursive') {
108
+ await fs.rm(entryPath, { recursive: true, force: true });
109
+ return;
110
+ }
111
+ try {
112
+ await fs.unlink(entryPath);
113
+ }
114
+ catch (err) {
115
+ if (err.code !== 'ENOENT')
116
+ throw err;
117
+ }
118
+ }
92
119
  //# sourceMappingURL=orphan-sweep.js.map
@@ -0,0 +1,15 @@
1
+ /** Build a {@link PathSet} from file paths: the folder prefixes are computed once. */
2
+ export function createPathSet(files) {
3
+ const all = new Set(files);
4
+ const folders = new Set();
5
+ for (const file of all) {
6
+ for (let cut = file.indexOf('/'); cut !== -1; cut = file.indexOf('/', cut + 1)) {
7
+ folders.add(file.slice(0, cut));
8
+ }
9
+ }
10
+ return Object.freeze({
11
+ files: all,
12
+ holds: (folder) => all.has(folder) || folders.has(folder),
13
+ });
14
+ }
15
+ //# sourceMappingURL=path-set.js.map
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Shared plugin registry — single source of truth for all CLI commands.
3
3
  */
4
+ import { WORKFLOW_FOLDERS } from './mds-variants.js';
4
5
  /**
5
6
  * Namespace prefix for Devflow skills installed to ~/.claude/skills/.
6
7
  * Skills are installed as `devflow:{skill-name}` to avoid collisions with
@@ -431,6 +432,18 @@ export const LEGACY_PLUGIN_NAMES = {
431
432
  'devflow-frontend-design': 'devflow-ui-design',
432
433
  'devflow-specify': 'devflow-plan',
433
434
  };
435
+ /**
436
+ * The registered plugins that `names` select: each legacy name mapped to its current plugin, and every name
437
+ * the registry does not have dropped. Order and repeats are kept.
438
+ *
439
+ * `names` is often file content (a manifest record), so only a name that LEGACY_PLUGIN_NAMES owns maps; a
440
+ * key the table inherits from Object, such as `constructor`, is an unknown name like any other.
441
+ */
442
+ export function registeredPluginNames(names) {
443
+ return names
444
+ .map(name => (Object.hasOwn(LEGACY_PLUGIN_NAMES, name) ? LEGACY_PLUGIN_NAMES[name] : name))
445
+ .filter(name => DEVFLOW_PLUGINS.some(plugin => plugin.name === name));
446
+ }
434
447
  /**
435
448
  * Plugin names that have been deleted from the registry.
436
449
  * Used during init to prune stale entries from users' manifests on partial
@@ -489,7 +502,7 @@ export const FEATURE_OWNED_RULES = ['compliance', 'behavior'];
489
502
  * stays OWNED and REQUIRED by its plugins in the registry — the closure guard
490
503
  * reasons over the curation-on variant, the superset — and the install drops it
491
504
  * on top of the selection: `installViaFileCopy` skips it when `curation` is false
492
- * and `convergeCurationVariants` installs or removes it when the switch flips.
505
+ * and `convergeVariantAxes` installs or removes it when the switch flips.
493
506
  *
494
507
  * This is a machine-switch condition, not a deselection: the plan
495
508
  * ({@link resolveSkillInstallPlan}) is untouched, so a curation-off machine never
@@ -498,7 +511,7 @@ export const FEATURE_OWNED_RULES = ['compliance', 'behavior'];
498
511
  *
499
512
  * Used by:
500
513
  * - installer.ts installViaFileCopy: omits these from the install loop when curation is off
501
- * - curation-install.ts convergeCurationVariants: installs or removes the directory
514
+ * - curation-install.ts convergeVariantAxes: installs or removes the directory
502
515
  * - tests: independent literal ['apply-decisions'] (avoids the EXCLUDED-as-oracle trap)
503
516
  */
504
517
  export const CURATION_GATED_SKILLS = ['apply-decisions'];
@@ -627,35 +640,71 @@ export function parsePluginSelection(input, validPlugins) {
627
640
  /**
628
641
  * Deprecated command names from old installations.
629
642
  * Used during init to clean up stale command files on upgrade.
643
+ *
644
+ * Each name is a command file that a release installed. The cleanup removes such a file with no
645
+ * backup, so a name that no release shipped does not belong here.
630
646
  */
631
647
  export const LEGACY_COMMAND_NAMES = [
632
- 'review',
633
648
  'specify',
634
- 'specify-teams',
635
649
  ];
650
+ /** A command entry of a plugin without its leading `/`. */
651
+ function commandName(cmd) {
652
+ return cmd.startsWith('/') ? cmd.slice(1) : cmd;
653
+ }
654
+ /** The set of the folders in a registry; tests pass a fixture registry to get a fixture set. */
655
+ export function workflowFolderNames(folders) {
656
+ return new Set(folders.map(folder => folder.name));
657
+ }
658
+ /** The real set: empty until a workflow is registered as a folder. */
659
+ export const WORKFLOW_FOLDER_NAMES = workflowFolderNames(WORKFLOW_FOLDERS);
660
+ /**
661
+ * The folder workflows that some plugin of `plugins` owns as a command. A plugin that
662
+ * declares `/name` for a name in the set owns the folder `devflow:name`.
663
+ */
664
+ export function selectedWorkflowFolders(plugins, folderWorkflows = WORKFLOW_FOLDER_NAMES) {
665
+ const selected = new Set();
666
+ for (const plugin of plugins) {
667
+ for (const cmd of plugin.commands) {
668
+ const name = commandName(cmd);
669
+ if (folderWorkflows.has(name))
670
+ selected.add(name);
671
+ }
672
+ }
673
+ return selected;
674
+ }
636
675
  /**
637
676
  * Derive unique command names (without leading /) from all plugins.
638
677
  * Returns one entry per distinct command, preserving DEVFLOW_PLUGINS declaration order.
678
+ *
679
+ * A workflow in `folderWorkflows` is not a command file, so it is left out: the
680
+ * command sweeps then treat a stale command file of a folder workflow as an orphan.
639
681
  */
640
- export function getAllCommandNames() {
682
+ export function getAllCommandNames(folderWorkflows = WORKFLOW_FOLDER_NAMES) {
641
683
  const commands = new Set();
642
684
  for (const plugin of DEVFLOW_PLUGINS) {
643
685
  for (const cmd of plugin.commands) {
644
- commands.add(cmd.startsWith('/') ? cmd.slice(1) : cmd);
686
+ const name = commandName(cmd);
687
+ if (!folderWorkflows.has(name))
688
+ commands.add(name);
645
689
  }
646
690
  }
647
691
  return [...commands];
648
692
  }
649
693
  /**
650
694
  * Derive unique skill names from all plugins.
695
+ *
696
+ * The folders of `folderWorkflows` install as `devflow:<name>`, so they join the names
697
+ * the skill sweeps treat as known.
651
698
  */
652
- export function getAllSkillNames() {
699
+ export function getAllSkillNames(folderWorkflows = WORKFLOW_FOLDER_NAMES) {
653
700
  const skills = new Set();
654
701
  for (const plugin of DEVFLOW_PLUGINS) {
655
702
  for (const skill of plugin.skills) {
656
703
  skills.add(skill);
657
704
  }
658
705
  }
706
+ for (const name of folderWorkflows)
707
+ skills.add(name);
659
708
  return [...skills];
660
709
  }
661
710
  /**
@@ -804,8 +853,10 @@ export function buildFullSkillsMap() {
804
853
  * nothing, and is reported so the user can tell "inert" from "ignored".
805
854
  */
806
855
  export function resolveSkillInstallPlan(input) {
856
+ const folderWorkflows = input.folderWorkflows ?? WORKFLOW_FOLDER_NAMES;
807
857
  const install = skillsOf(input.effectivePlugins);
808
858
  const remove = new Set();
859
+ const removeFolders = new Set();
809
860
  if (!input.isPartialInstall) {
810
861
  for (const skill of skillsOf(DEVFLOW_PLUGINS)) {
811
862
  if (install.has(skill))
@@ -814,11 +865,18 @@ export function resolveSkillInstallPlan(input) {
814
865
  continue;
815
866
  remove.add(skill);
816
867
  }
868
+ const ownedFolders = selectedWorkflowFolders(input.effectivePlugins, folderWorkflows);
869
+ for (const name of folderWorkflows) {
870
+ if (!ownedFolders.has(name))
871
+ removeFolders.add(name);
872
+ }
817
873
  }
818
- const dormantShadows = [...new Set(input.shadowedSkills)]
819
- .filter(skill => !install.has(skill))
874
+ const shadowed = [...new Set(input.shadowedSkills)];
875
+ const dormantShadows = shadowed
876
+ .filter(skill => !install.has(skill) && !folderWorkflows.has(skill))
820
877
  .sort();
821
- return { install, remove, dormantShadows };
878
+ const ignoredFolderShadows = shadowed.filter(skill => folderWorkflows.has(skill)).sort();
879
+ return { install, remove, dormantShadows, removeFolders, ignoredFolderShadows };
822
880
  }
823
881
  /**
824
882
  * Derive unique rule names from all plugins.
@@ -14,7 +14,9 @@ import * as path from 'path';
14
14
  * two providers may legitimately both carry a `comment.md`, so the registry name has to
15
15
  * be the relative PATH, and the walk has to descend.
16
16
  *
17
- * Never writes, only removes — no delete-then-write window.
17
+ * Never writes. The sweep names what stays and what goes; a {@link ReferenceSweepDelegate}
18
+ * acts on every entry the manifest does not name, and the sweep itself never removes a
19
+ * folder recursively. No delete-then-write window.
18
20
  */
19
21
  /**
20
22
  * Descent bound for every walk over the generated reference tree — this sweep and the
@@ -42,24 +44,27 @@ export const MAX_REFERENCE_SWEEP_DEPTH = 8;
42
44
  * @param root - Directory to converge (the installed `references/tracker/` tree).
43
45
  * @param knownRelPaths - POSIX paths relative to `root` that must survive. Derived from
44
46
  * the build's own module registries by the caller — never hand-listed.
47
+ * @param delegate - Acts on every entry the manifest does not name. Required: a sweep with
48
+ * no delegate would have to delete what it cannot name.
45
49
  *
46
50
  * @returns A {@link SweepResult} whose `removed` entries are POSIX relative paths. A
47
- * directory into which no manifest path descends is removed WHOLE and reported by its
48
- * own relative path — leaving it empty would be a convergence that stops one step
49
- * short, and an empty provider directory is indistinguishable from a provider whose
50
- * references failed to install. A subtree left unswept because it breached
51
+ * directory into which no manifest path descends is handed to the delegate WHOLE and
52
+ * reported by its own relative path once the delegate removed it — leaving it empty
53
+ * would be a convergence that stops one step short, and an empty provider directory is
54
+ * indistinguishable from a provider whose references failed to install. A subtree left
55
+ * unswept because it breached
51
56
  * {@link MAX_REFERENCE_SWEEP_DEPTH} is reported in `failed` under its own relative
52
57
  * path, for the same reason: everything this sweep did not converge is named.
53
58
  *
54
59
  * A missing or unreadable `root` is a no-op, not an error: the overlay creates the tree
55
60
  * it converges, so an absent one simply means there is nothing to prune yet.
56
61
  */
57
- export async function sweepOrphanedReferences(root, knownRelPaths) {
62
+ export async function sweepOrphanedReferences(root, knownRelPaths, delegate) {
58
63
  const acc = { scanned: 0, removed: [], failed: [] };
59
- await sweepDirectory(root, '', 0, knownRelPaths, directoryPrefixes(knownRelPaths), acc);
64
+ await sweepDirectory(root, '', 0, knownRelPaths, directoryPrefixes(knownRelPaths), delegate, acc);
60
65
  return { scanned: acc.scanned, removed: acc.removed, failed: acc.failed };
61
66
  }
62
- async function sweepDirectory(dir, prefix, depth, known, knownDirPrefixes, acc) {
67
+ async function sweepDirectory(dir, prefix, depth, known, knownDirPrefixes, delegate, acc) {
63
68
  if (depth > MAX_REFERENCE_SWEEP_DEPTH) {
64
69
  // A breached bound means this subtree is never visited, so any orphan inside it
65
70
  // survives. Report it through the same channel as a failed removal: a silent return
@@ -82,35 +87,47 @@ async function sweepDirectory(dir, prefix, depth, known, knownDirPrefixes, acc)
82
87
  const relPath = prefix === '' ? entry.name : `${prefix}/${entry.name}`;
83
88
  const fullPath = path.join(dir, entry.name);
84
89
  // A real subdirectory is either an ancestor of something the manifest names — in
85
- // which case descend — or dead weight, in which case take the whole subtree.
86
- // isDirectory() is false for a symlink-to-dir, so a planted link is treated as a
87
- // leaf and removed rather than followed.
90
+ // which case descend — or dead weight, in which case the delegate takes the whole
91
+ // subtree. isDirectory() is false for a symlink-to-dir, so a planted link is treated
92
+ // as a leaf and handed over rather than followed.
88
93
  if (entry.isDirectory()) {
89
94
  if (knownDirPrefixes.has(`${relPath}/`)) {
90
- await sweepDirectory(fullPath, relPath, depth + 1, known, knownDirPrefixes, acc);
95
+ await sweepDirectory(fullPath, relPath, depth + 1, known, knownDirPrefixes, delegate, acc);
91
96
  continue;
92
97
  }
93
98
  acc.scanned++;
94
- try {
95
- await fs.rm(fullPath, { recursive: true, force: true });
96
- acc.removed.push(relPath);
97
- }
98
- catch (err) {
99
- acc.failed.push({ name: relPath, error: err }); /* per-item isolation */
100
- }
99
+ await dispose(fullPath, relPath, true, delegate, acc);
101
100
  continue;
102
101
  }
103
102
  acc.scanned++;
104
103
  if (known.has(relPath))
105
104
  continue;
106
- try {
107
- await fs.rm(fullPath, { force: true });
108
- acc.removed.push(relPath);
109
- }
110
- catch (err) {
111
- acc.failed.push({ name: relPath, error: err }); /* per-item isolation */
105
+ await dispose(fullPath, relPath, false, delegate, acc);
106
+ }
107
+ }
108
+ /** Hand one entry to the delegate and record the outcome; a throw from the delegate is a per-item failure. */
109
+ async function dispose(fullPath, relPath, isDirectory, delegate, acc) {
110
+ try {
111
+ const disposition = await delegate.dispose(fullPath, isDirectory);
112
+ switch (disposition.outcome) {
113
+ case 'removed':
114
+ acc.removed.push(relPath);
115
+ break;
116
+ case 'failed':
117
+ acc.failed.push({ name: relPath, error: disposition.error });
118
+ break;
119
+ case 'moved':
120
+ case 'kept':
121
+ break;
122
+ default: {
123
+ const _exhaustive = disposition;
124
+ void _exhaustive;
125
+ }
112
126
  }
113
127
  }
128
+ catch (err) {
129
+ acc.failed.push({ name: relPath, error: err }); /* per-item isolation */
130
+ }
114
131
  }
115
132
  /**
116
133
  * Every directory prefix some manifest path sits under, trailing slash included:
@@ -0,0 +1,278 @@
1
+ import { createPathSet } from './path-set.js';
2
+ const names = (list) => Object.freeze([...list]);
3
+ export const RETIRED_SHIPPED = Object.freeze({
4
+ commands: names([
5
+ 'ambient',
6
+ 'audit-claude',
7
+ 'brainstorm',
8
+ 'breakdown',
9
+ 'catch-up',
10
+ 'commit',
11
+ 'design',
12
+ 'devlog',
13
+ 'get-issue',
14
+ 'plan-next-steps',
15
+ 'pre-commit',
16
+ 'pre-pr',
17
+ 'pull-request',
18
+ 'resolve-comments',
19
+ 'specify',
20
+ ]),
21
+ agents: names([
22
+ 'audit-architecture',
23
+ 'audit-complexity',
24
+ 'audit-database',
25
+ 'audit-dependencies',
26
+ 'audit-documentation',
27
+ 'audit-performance',
28
+ 'audit-security',
29
+ 'audit-tests',
30
+ 'audit-typescript',
31
+ 'brainstorm',
32
+ 'catch-up',
33
+ 'claude-md-auditor',
34
+ 'code-edit',
35
+ 'code-fix',
36
+ 'code-review',
37
+ 'coder',
38
+ 'commit',
39
+ 'diagnose',
40
+ 'get-issue',
41
+ 'learning',
42
+ 'pr-comments',
43
+ 'project-state',
44
+ 'pull-request',
45
+ 'release',
46
+ 'resolver',
47
+ 'reviewer',
48
+ 'scrutinize',
49
+ 'scrutinizer',
50
+ 'shepherd',
51
+ 'simplifier',
52
+ 'simplify',
53
+ 'skimmer',
54
+ 'synthesizer',
55
+ 'tech-debt',
56
+ 'validator',
57
+ ]),
58
+ rules: names([]),
59
+ skills: names([
60
+ 'accessibility',
61
+ 'agent-teams',
62
+ 'ambient-router',
63
+ 'apply-feature-kb',
64
+ 'apply-knowledge',
65
+ 'architecture-patterns',
66
+ 'code-smell',
67
+ 'codebase-navigation',
68
+ 'commit',
69
+ 'complexity-patterns',
70
+ 'consistency-patterns',
71
+ 'core-patterns',
72
+ 'database-patterns',
73
+ 'debug',
74
+ 'debug-orchestration',
75
+ 'debug:guided',
76
+ 'debug:orch',
77
+ 'debug:triage',
78
+ 'decisions-format',
79
+ 'dependencies-patterns',
80
+ 'devflow',
81
+ 'devflow-accessibility',
82
+ 'devflow-agent-teams',
83
+ 'devflow-architecture-patterns',
84
+ 'devflow-code-smell',
85
+ 'devflow-codebase-navigation',
86
+ 'devflow-commit',
87
+ 'devflow-complexity-patterns',
88
+ 'devflow-consistency-patterns',
89
+ 'devflow-core-patterns',
90
+ 'devflow-database-patterns',
91
+ 'devflow-debug',
92
+ 'devflow-dependencies-patterns',
93
+ 'devflow-docs-framework',
94
+ 'devflow-documentation-patterns',
95
+ 'devflow-error-handling',
96
+ 'devflow-frontend-design',
97
+ 'devflow-git-safety',
98
+ 'devflow-git-workflow',
99
+ 'devflow-github-patterns',
100
+ 'devflow-implementation-patterns',
101
+ 'devflow-input-validation',
102
+ 'devflow-pattern-check',
103
+ 'devflow-performance-patterns',
104
+ 'devflow-pull-request',
105
+ 'devflow-react',
106
+ 'devflow-regression-patterns',
107
+ 'devflow-review-methodology',
108
+ 'devflow-security-patterns',
109
+ 'devflow-self-review',
110
+ 'devflow-test-design',
111
+ 'devflow-tests-patterns',
112
+ 'devflow-typescript',
113
+ 'devflow:agent-teams',
114
+ 'devflow:ambient-router',
115
+ 'devflow:apply-feature-kb',
116
+ 'devflow:apply-knowledge',
117
+ 'devflow:architecture-patterns',
118
+ 'devflow:complexity-patterns',
119
+ 'devflow:consistency-patterns',
120
+ 'devflow:core-patterns',
121
+ 'devflow:database-patterns',
122
+ 'devflow:debug',
123
+ 'devflow:debug-orchestration',
124
+ 'devflow:debug:guided',
125
+ 'devflow:debug:orch',
126
+ 'devflow:debug:triage',
127
+ 'devflow:dependencies-patterns',
128
+ 'devflow:documentation-patterns',
129
+ 'devflow:dream-curation',
130
+ 'devflow:dream-decisions',
131
+ 'devflow:dream-knowledge',
132
+ 'devflow:dream-memory',
133
+ 'devflow:explore',
134
+ 'devflow:explore:guided',
135
+ 'devflow:explore:orch',
136
+ 'devflow:explore:triage',
137
+ 'devflow:feature-kb',
138
+ 'devflow:frontend-design',
139
+ 'devflow:git-safety',
140
+ 'devflow:git-workflow',
141
+ 'devflow:github-patterns',
142
+ 'devflow:implement',
143
+ 'devflow:implement:guided',
144
+ 'devflow:implement:orch',
145
+ 'devflow:implement:triage',
146
+ 'devflow:implementation-orchestration',
147
+ 'devflow:implementation-patterns',
148
+ 'devflow:input-validation',
149
+ 'devflow:knowledge-persistence',
150
+ 'devflow:performance-patterns',
151
+ 'devflow:pipeline',
152
+ 'devflow:pipeline-orchestration',
153
+ 'devflow:pipeline:orch',
154
+ 'devflow:plan',
155
+ 'devflow:plan-orchestration',
156
+ 'devflow:plan:guided',
157
+ 'devflow:plan:orch',
158
+ 'devflow:plan:triage',
159
+ 'devflow:regression-patterns',
160
+ 'devflow:release:guided',
161
+ 'devflow:release:orch',
162
+ 'devflow:release:triage',
163
+ 'devflow:research',
164
+ 'devflow:research:guided',
165
+ 'devflow:research:orch',
166
+ 'devflow:research:triage',
167
+ 'devflow:resolve',
168
+ 'devflow:resolve-orchestration',
169
+ 'devflow:resolve:orch',
170
+ 'devflow:review',
171
+ 'devflow:review-orchestration',
172
+ 'devflow:review:guided',
173
+ 'devflow:review:orch',
174
+ 'devflow:review:triage',
175
+ 'devflow:router',
176
+ 'devflow:search-first',
177
+ 'devflow:security-patterns',
178
+ 'devflow:self-review',
179
+ 'devflow:sidecar',
180
+ 'devflow:test-patterns',
181
+ 'docs-framework',
182
+ 'documentation-patterns',
183
+ 'dream-curation',
184
+ 'dream-decisions',
185
+ 'dream-knowledge',
186
+ 'dream-memory',
187
+ 'error-handling',
188
+ 'explore',
189
+ 'explore:guided',
190
+ 'explore:orch',
191
+ 'explore:triage',
192
+ 'feature-kb',
193
+ 'frontend-design',
194
+ 'git-safety',
195
+ 'git-workflow',
196
+ 'github-patterns',
197
+ 'go',
198
+ 'implement',
199
+ 'implement:guided',
200
+ 'implement:orch',
201
+ 'implement:triage',
202
+ 'implementation-orchestration',
203
+ 'implementation-patterns',
204
+ 'input-validation',
205
+ 'java',
206
+ 'knowledge-persistence',
207
+ 'pattern-check',
208
+ 'performance-patterns',
209
+ 'pipeline',
210
+ 'pipeline-orchestration',
211
+ 'pipeline:orch',
212
+ 'plan',
213
+ 'plan-orchestration',
214
+ 'plan:guided',
215
+ 'plan:orch',
216
+ 'plan:triage',
217
+ 'pull-request',
218
+ 'python',
219
+ 'react',
220
+ 'regression-patterns',
221
+ 'release:guided',
222
+ 'release:orch',
223
+ 'release:triage',
224
+ 'research',
225
+ 'research:guided',
226
+ 'research:orch',
227
+ 'research:triage',
228
+ 'resolve',
229
+ 'resolve-orchestration',
230
+ 'resolve:orch',
231
+ 'review',
232
+ 'review-methodology',
233
+ 'review-orchestration',
234
+ 'review:guided',
235
+ 'review:orch',
236
+ 'review:triage',
237
+ 'router',
238
+ 'rust',
239
+ 'search-first',
240
+ 'security-patterns',
241
+ 'self-review',
242
+ 'sidecar',
243
+ 'test-design',
244
+ 'test-driven-development',
245
+ 'test-patterns',
246
+ 'tests-patterns',
247
+ 'typescript',
248
+ 'worktree-support',
249
+ ]),
250
+ skillFiles: Object.freeze({
251
+ 'devflow:agent-references': names([
252
+ 'references/learning/maintain.md',
253
+ ]),
254
+ 'devflow:git': names([
255
+ 'references/pr/ensure-pr-ready.md',
256
+ ]),
257
+ }),
258
+ });
259
+ /**
260
+ * The files of {@link RETIRED_SHIPPED} as paths relative to the Claude dir: the command, agent and
261
+ * rule files, and the listed files of a skill folder. A skill folder of `skills` is not a path
262
+ * here, since the install moves it whole.
263
+ */
264
+ export function retiredPathSet() {
265
+ const paths = [];
266
+ for (const name of RETIRED_SHIPPED.commands)
267
+ paths.push(`commands/devflow/${name}.md`);
268
+ for (const name of RETIRED_SHIPPED.agents)
269
+ paths.push(`agents/devflow/${name}.md`);
270
+ for (const name of RETIRED_SHIPPED.rules)
271
+ paths.push(`rules/devflow/${name}.md`);
272
+ for (const [skill, files] of Object.entries(RETIRED_SHIPPED.skillFiles)) {
273
+ for (const file of files)
274
+ paths.push(`skills/${skill}/${file}`);
275
+ }
276
+ return createPathSet(paths);
277
+ }
278
+ //# sourceMappingURL=retired-shipped-names.js.map