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.
- package/CHANGELOG.md +142 -0
- package/README.md +1 -1
- package/dist/agents/code.md +6 -6
- package/dist/agents/fix.md +6 -6
- package/dist/agents/harden.md +7 -6
- package/dist/agents/refactor.md +6 -6
- package/dist/cli/commands/ambient.js +10 -2
- package/dist/cli/commands/backup-session.js +131 -0
- package/dist/cli/commands/compliance-prompts.js +9 -4
- package/dist/cli/commands/compliance.js +184 -24
- package/dist/cli/commands/curation.js +23 -10
- package/dist/cli/commands/gate-level-prompts.js +39 -25
- package/dist/cli/commands/gates.js +5 -7
- package/dist/cli/commands/init.js +169 -184
- package/dist/cli/commands/install-report.js +129 -0
- package/dist/cli/commands/legacy-sweep.js +159 -0
- package/dist/cli/commands/proxy.js +6 -1
- package/dist/cli/commands/rules.js +75 -21
- package/dist/cli/commands/skills.js +42 -9
- package/dist/cli/commands/tracker.js +9 -3
- package/dist/cli/commands/uninstall.js +339 -75
- package/dist/commands/bug-analysis.md +69 -120
- package/dist/commands/dynamic-build.md +23 -23
- package/dist/commands/implement.md +46 -32
- package/dist/commands/release.md +1 -1
- package/dist/commands/resolve.md +35 -31
- package/dist/commands/self-review.md +8 -7
- package/dist/compliance-off/agents/git.md +120 -0
- package/dist/compliance-off/agents/triage.md +180 -0
- package/dist/compliance-off/commands/bug-analysis.md +398 -0
- package/dist/compliance-off/commands/code-review.md +520 -0
- package/dist/compliance-off/commands/dynamic-build.md +1251 -0
- package/dist/compliance-off/commands/dynamic-tickets.md +676 -0
- package/dist/compliance-off/commands/implement.md +736 -0
- package/dist/compliance-off/commands/plan.md +671 -0
- package/dist/compliance-off/commands/release.md +309 -0
- package/dist/compliance-off/commands/resolve.md +742 -0
- package/dist/compliance-off/skills/git/references/decision-markers.md +17 -0
- package/dist/compliance-off/skills/git/references/learn-conventions.md +80 -0
- package/dist/compliance-off/skills/git/references/pr/check-ci-status.md +33 -0
- package/dist/compliance-off/skills/git/references/pr/check-merge-readiness.md +49 -0
- package/dist/compliance-off/skills/git/references/pr/fetch-review-threads.md +52 -0
- package/dist/compliance-off/skills/git/references/pr/validate-branch.md +47 -0
- package/dist/compliance-off/skills/git/references/tracker/_contract.md +33 -0
- package/dist/compliance-off/skills/git/references/tracker/_mcp.md +153 -0
- package/dist/compliance-off/skills/git/references/tracker/github/create-release.md +35 -0
- package/dist/compliance-off/skills/git/references/tracker/github/ensure-pr-ready.md +100 -0
- package/dist/compliance-off/skills/git/references/tracker/github/ensure-traceable-issue.md +74 -0
- package/dist/compliance-off/skills/git/references/tracker/github/fetch-issue.md +69 -0
- package/dist/compliance-off/skills/git/references/tracker/github/fetch-issues-batch.md +65 -0
- package/dist/compliance-off/skills/git/references/tracker/github/gather-release-evidence.md +48 -0
- package/dist/compliance-off/skills/git/references/tracker/github/setup-task.md +77 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/create-release.md +35 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/ensure-pr-ready.md +100 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/ensure-traceable-issue.md +61 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issue.md +51 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issues-batch.md +63 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/gather-release-evidence.md +47 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/setup-task.md +82 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/create-release.md +35 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/ensure-pr-ready.md +100 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/ensure-traceable-issue.md +61 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issue.md +51 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issues-batch.md +63 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/gather-release-evidence.md +47 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/setup-task.md +83 -0
- package/dist/compliance-off/skills/git/references/trust-rule.md +7 -0
- package/dist/core/agent-models.js +19 -4
- package/dist/core/assets.js +126 -17
- package/dist/core/compliance-off-files.js +27 -0
- package/dist/core/compliance.js +14 -0
- package/dist/core/curation-variants.js +460 -112
- package/dist/core/flags.js +2 -2
- package/dist/core/fs-atomic.js +5 -1
- package/dist/core/installed-variants.js +63 -0
- package/dist/core/manifest.js +48 -5
- package/dist/core/mds-variants.js +700 -3
- package/dist/core/orphan-sweep.js +29 -2
- package/dist/core/path-set.js +15 -0
- package/dist/core/plugins.js +68 -10
- package/dist/core/reference-sweep.js +42 -25
- package/dist/core/retired-shipped-names.js +278 -0
- package/dist/curation-compliance-off/agents/triage.md +176 -0
- package/dist/curation-compliance-off/commands/bug-analysis.md +349 -0
- package/dist/curation-compliance-off/commands/code-review.md +481 -0
- package/dist/curation-compliance-off/commands/dynamic-build.md +1211 -0
- package/dist/curation-compliance-off/commands/dynamic-tickets.md +634 -0
- package/dist/curation-compliance-off/commands/implement.md +689 -0
- package/dist/curation-compliance-off/commands/plan.md +631 -0
- package/dist/curation-compliance-off/commands/release.md +279 -0
- package/dist/curation-compliance-off/commands/resolve.md +692 -0
- package/dist/curation-off/agents/code.md +6 -6
- package/dist/curation-off/agents/fix.md +6 -6
- package/dist/curation-off/agents/harden.md +7 -6
- package/dist/curation-off/agents/refactor.md +6 -6
- package/dist/curation-off/commands/bug-analysis.md +69 -119
- package/dist/curation-off/commands/dynamic-build.md +23 -23
- package/dist/curation-off/commands/implement.md +46 -32
- package/dist/curation-off/commands/release.md +1 -1
- package/dist/curation-off/commands/resolve.md +35 -31
- package/dist/curation-off/commands/self-review.md +8 -7
- package/dist/skills/git/references/pr/resolve-review-threads.md +1 -1
- package/dist/targets/claude-code/behavior-install.js +23 -9
- package/dist/targets/claude-code/charter-render.js +374 -0
- package/dist/targets/claude-code/compliance-install.js +62 -17
- package/dist/targets/claude-code/compliance-off-files.js +249 -0
- package/dist/targets/claude-code/curation-install.js +632 -117
- package/dist/targets/claude-code/folder-swap.js +142 -0
- package/dist/targets/claude-code/install-backup.js +780 -0
- package/dist/targets/claude-code/installer.js +795 -199
- package/dist/targets/claude-code/language-stamp.js +6 -0
- package/dist/targets/claude-code/legacy-skill-hashes.js +170 -0
- package/dist/targets/claude-code/linked-folder.js +176 -0
- package/dist/targets/claude-code/reference-converge.js +131 -0
- package/dist/targets/claude-code/rules-folder.js +34 -0
- package/dist/targets/claude-code/shipped-set.js +160 -0
- package/dist/targets/claude-code/tracker-install.js +21 -8
- package/dist/targets/claude-code/tracker-servers.js +20 -6
- package/dist/targets/claude-code/workflow-folder-install.js +251 -0
- package/dist/variant-axes.json +109 -0
- package/package.json +1 -1
- package/src/assets/agents/_partials/_code.mds +6 -6
- package/src/assets/agents/evaluate.md +1 -1
- package/src/assets/agents/git.mds +32 -0
- package/src/assets/agents/harden.mds +7 -6
- package/src/assets/agents/test.md +21 -12
- package/src/assets/agents/triage.mds +2 -0
- package/src/assets/agents/validate.md +10 -10
- package/src/assets/commands/_partials/_compliance.mds +5 -3
- package/src/assets/commands/_partials/_engine.mds +9 -8
- package/src/assets/commands/_partials/_publication.mds +10 -1
- package/src/assets/commands/bug-analysis.mds +80 -126
- package/src/assets/commands/code-review.mds +43 -0
- package/src/assets/commands/dynamic-build.mds +109 -15
- package/src/assets/commands/dynamic-tickets.mds +13 -0
- package/src/assets/commands/implement.mds +145 -33
- package/src/assets/commands/plan.mds +19 -0
- package/src/assets/commands/release.mds +50 -1
- package/src/assets/commands/resolve.mds +217 -32
- package/src/assets/commands/self-review.mds +8 -7
- package/src/assets/mds/git/_ops.mds +23 -0
- package/src/assets/mds/git/_ops_steps.mds +17 -0
- package/src/assets/mds/git/_pr.mds +1 -1
- package/src/assets/mds/git/_references.mds +6 -0
- package/src/assets/mds/tracker/_common.mds +4 -0
- package/src/assets/mds/tracker/_github.mds +42 -0
- package/src/assets/mds/tracker/_jira.mds +43 -0
- package/src/assets/mds/tracker/_linear.mds +43 -0
- package/src/assets/rules/quality.md +2 -0
- package/src/assets/scripts/ci-wait.cjs +204 -126
- package/src/assets/scripts/hooks/assets/implement-installed +1 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +13 -11
- package/src/assets/scripts/hooks/preamble +18 -8
- package/src/assets/scripts/hooks/session-start-orchestrator +4 -2
- package/src/assets/scripts/pr-evidence.cjs +41 -2
- package/src/assets/scripts/verify-evidence.cjs +408 -56
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +3 -3
- package/src/assets/skills/agent-references/references/commands/plan-levels.md +1 -1
- package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +6 -2
- package/src/assets/skills/agent-references/references/validate/passed-alone.md +7 -7
- package/src/assets/skills/docs-framework/SKILL.md +5 -2
- package/src/assets/skills/git/SKILL.md +3 -3
- package/src/assets/skills/git/references/github-api.md +2 -139
- package/src/assets/skills/git/references/github-posting.md +158 -0
- package/src/assets/skills/review-methodology/SKILL.md +1 -1
- package/src/assets/skills/review-methodology/references/patterns.md +1 -1
- package/src/assets/skills/review-methodology/references/report-template.md +1 -2
- package/src/assets/skills/testing/SKILL.md +2 -4
|
@@ -44,6 +44,7 @@ import * as path from 'path';
|
|
|
44
44
|
import { writeFileAtomicExclusive } from '../../core/fs-atomic.js';
|
|
45
45
|
import { mdFileName } from '../../core/orphan-sweep.js';
|
|
46
46
|
import { installedLanguageFocuses } from '../../core/plugins.js';
|
|
47
|
+
import { isUnrealFolder } from './linked-folder.js';
|
|
47
48
|
/** The fixed prefix of the stamp line; the prompt and the anchored match share it. */
|
|
48
49
|
export const LANGUAGE_STAMP_PREFIX = 'Installed language focuses: ';
|
|
49
50
|
/** What an empty list renders as, and what the build ships. */
|
|
@@ -138,6 +139,11 @@ export async function restampInstalledCommands(opts) {
|
|
|
138
139
|
warn(`language stamp: claudeDir is not an absolute path ("${claudeDir}") — the installed commands were not stamped`);
|
|
139
140
|
return { focuses, rewritten, unchanged, absent, failed: [...LANGUAGE_STAMPED_COMMANDS] };
|
|
140
141
|
}
|
|
142
|
+
// Whatever the caller's guard says, nothing is read or written through a commands folder that is a
|
|
143
|
+
// link or not a folder; every stamped command is then left as it is, and `failed` says so.
|
|
144
|
+
if (await isUnrealFolder(path.join(claudeDir, 'commands', 'devflow'))) {
|
|
145
|
+
return { focuses, rewritten, unchanged, absent, failed: [...LANGUAGE_STAMPED_COMMANDS] };
|
|
146
|
+
}
|
|
141
147
|
for (const command of LANGUAGE_STAMPED_COMMANDS) {
|
|
142
148
|
const target = path.join(claudeDir, 'commands', 'devflow', mdFileName(command));
|
|
143
149
|
let current;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The released copies of the bare legacy skills: for each bare name of the legacy skill list that a
|
|
3
|
+
* release installed as `~/.claude/skills/<name>/`, the SHA-256 (lowercase hex) of every distinct
|
|
4
|
+
* `SKILL.md` that a release installed under that name.
|
|
5
|
+
*
|
|
6
|
+
* `devflow init` moves a bare legacy folder out of the shared skills folder only when its
|
|
7
|
+
* `SKILL.md` has one of these hashes (`sweepLegacySkillFolders`), and a full `devflow uninstall`
|
|
8
|
+
* removes one only on the same check (`judgeBareLegacySkill`). The folder name alone proves
|
|
9
|
+
* nothing: a skill of the user, or of another tool, may carry a name such as `python` or `plan`.
|
|
10
|
+
* A name with no entry here was never installed bare by a release, so no folder of that name is
|
|
11
|
+
* moved.
|
|
12
|
+
*
|
|
13
|
+
* Frozen data, made on 2026-10-30. Source: the 21 published `devflow-kit` packages 0.6.1 to 1.8.3,
|
|
14
|
+
* the releases that installed bare skill folders. (0.4.0 to 0.6.0 installed one nested folder,
|
|
15
|
+
* `skills/devflow/`; 2.0.0 and later install `devflow:`-prefixed folders.) Each package was fetched
|
|
16
|
+
* with `npm pack devflow-kit@<version> --ignore-scripts` and unpacked; no package script or code
|
|
17
|
+
* ran. Every `SKILL.md` below `src/claude/skills/devflow/<name>/` (0.6.1 to 0.9.0) or
|
|
18
|
+
* `plugins/<plugin>/skills/<name>/` (1.0.0 to 1.8.3) was hashed with `shasum -a 256`. Those
|
|
19
|
+
* installers copy a skill file byte for byte, so the installed file has the hash of the packaged
|
|
20
|
+
* file. The comment on each hash gives the first and the last release that held it.
|
|
21
|
+
*
|
|
22
|
+
* The table only grows: a release cannot be un-shipped. tests/guards/legacy-skill-hashes.test.ts
|
|
23
|
+
* proves the shape of the table and its match with tests/fixtures/released-shipped-names.ts.
|
|
24
|
+
*/
|
|
25
|
+
const hashes = (list) => Object.freeze([...list]);
|
|
26
|
+
export const BARE_LEGACY_SKILL_HASHES = Object.freeze({
|
|
27
|
+
'accessibility': hashes([
|
|
28
|
+
'06434c13344e85bf55f4fcf45e8e794ef26ad0751e7e56cac7363661e1b7b7d4', // 1.0.0 to 1.8.3
|
|
29
|
+
]),
|
|
30
|
+
'agent-teams': hashes([
|
|
31
|
+
'75591cef45adbc36e95910e08a0db681c56401e1c95db5226afe819dde36ce0c', // 1.0.0 to 1.8.3
|
|
32
|
+
]),
|
|
33
|
+
'ambient-router': hashes([
|
|
34
|
+
'b4a6bfc188d720ede3afefc3802b2a9192dd67cf1a3694a5ee221b383347cc21', // 1.1.0
|
|
35
|
+
'92a3ee58a6deb50f4dbbd50913527de78f7755ed23d492c6ffc18b42d035a867', // 1.2.0
|
|
36
|
+
'd32ef0392357eff61a47a3f758d2c1674fdf029ca6e74cf465e114723db57497', // 1.3.0 to 1.4.0
|
|
37
|
+
'2d73b902b3610b42455caf6fe0dfe5725dfe92a8590def4a125b280754a6c4f0', // 1.5.0
|
|
38
|
+
'e4221f41c567b04c2785d4127a4fc7f7fe1099afa490fc03f67e537010e8b5e8', // 1.6.0
|
|
39
|
+
'3e6ed6a711dcdf1f710266065684db637d34f15f0a3b2174d4867ea35fcf613b', // 1.6.1 to 1.8.1
|
|
40
|
+
'fb3a7025ce437ca2abc8fb8a71cae10e452f335e0d99adf979c601042e16570b', // 1.8.2 to 1.8.3
|
|
41
|
+
]),
|
|
42
|
+
'architecture-patterns': hashes([
|
|
43
|
+
'a72ea9c4dcf4083e8f3d8a70dca282a807972e70e163a3e0988a61a875db4101', // 1.0.0 to 1.2.0
|
|
44
|
+
'd5da95e223c024e31c000c0d511e845f308045ae89e5a31cfaf2a0eebf07a238', // 1.3.0 to 1.8.3
|
|
45
|
+
]),
|
|
46
|
+
'code-smell': hashes([
|
|
47
|
+
'2917880c99f1c2febb596319900c61826f58327ac933860d18eeaf98952b2bee', // 0.6.1 to 0.9.0
|
|
48
|
+
]),
|
|
49
|
+
'complexity-patterns': hashes([
|
|
50
|
+
'3a6f56e229b19d27938274dc788a02334e5d96d8c56a55a80661b62c431af50f', // 1.0.0 to 1.2.0
|
|
51
|
+
'fc068855bca532e1c1a87d59617d9b093db5fef98064b0a6b0c84add0a4c7163', // 1.3.0 to 1.8.3
|
|
52
|
+
]),
|
|
53
|
+
'consistency-patterns': hashes([
|
|
54
|
+
'ed270ef6c976d109beb3fda510cc84399a884d84e26f5a5fcf420ceb878444cb', // 1.0.0 to 1.2.0
|
|
55
|
+
'493a8ae10c759bb57a44f833251e77f1c0577e19b701f917ad6474d64bb0e96c', // 1.3.0 to 1.8.3
|
|
56
|
+
]),
|
|
57
|
+
'core-patterns': hashes([
|
|
58
|
+
'5c1c8272250c06a1e1c0b263f171b04096380385ccdda3a252b830f2179597ba', // 1.0.0 to 1.8.3
|
|
59
|
+
]),
|
|
60
|
+
'database-patterns': hashes([
|
|
61
|
+
'3cd61ce6b8b005cb72ace5af6fa3620d57f1363166621e88b6da0bb4b9566491', // 1.0.0 to 1.2.0
|
|
62
|
+
'1d28c6cdf41154f284956883c461bc56448342f8cb8d1c1118f9f85b3f4f3486', // 1.3.0 to 1.8.3
|
|
63
|
+
]),
|
|
64
|
+
'debug': hashes([
|
|
65
|
+
'7199af54865413f2dc4333e6dc83c63e633f98d0ad78bdbdded384b145249f21', // 0.6.1 to 0.9.0
|
|
66
|
+
]),
|
|
67
|
+
'debug-orchestration': hashes([
|
|
68
|
+
'eb2a5fbf55fc973dff8f5206a093248fde8626c63be967fb819fce15dda8c5cd', // 1.6.0 to 1.8.3
|
|
69
|
+
]),
|
|
70
|
+
'dependencies-patterns': hashes([
|
|
71
|
+
'7711a6225b764aea7bf4f4d14cb0fe0ec2afbb3ab106ae17595c94cb126174c0', // 1.0.0 to 1.2.0
|
|
72
|
+
'86f89ec146a451aecf320349a2ff71f1f314a43bdeab221163b60e99361e8fbf', // 1.3.0 to 1.8.3
|
|
73
|
+
]),
|
|
74
|
+
'docs-framework': hashes([
|
|
75
|
+
'7d97aa5d1ea61d5d1a9fa0f24960468c2fd365d25d6632700fdfa5efd9b180a0', // 1.0.0
|
|
76
|
+
'7ec949848e9d73249df1785d1e5e837928062a423c7ad4ee588543bea7d89950', // 1.1.0 to 1.5.0
|
|
77
|
+
'40454b337b552064a58424bf8ea505e486d0c0849a7ff14bd499a5072eb1b168', // 1.6.0
|
|
78
|
+
'e191008ac58722d14d7dadce4d828376585d26aa676b3e74882ea8c90fa8cd2d', // 1.6.1 to 1.8.3
|
|
79
|
+
]),
|
|
80
|
+
'documentation-patterns': hashes([
|
|
81
|
+
'2cc11fdae2924f862573ef61aba5c42390089e1973b374a181475548d2e6de88', // 1.0.0 to 1.2.0
|
|
82
|
+
'b5eaef09cd851054062f72ecbbd13e9392872ab2fd2867c7c6ee5be380b09904', // 1.3.0 to 1.8.3
|
|
83
|
+
]),
|
|
84
|
+
'frontend-design': hashes([
|
|
85
|
+
'015bcec670df04b8793bc8ceacf42ca579e87fb4ae9cc826f17d6067f5f39b4f', // 1.0.0 to 1.8.3
|
|
86
|
+
]),
|
|
87
|
+
'git-safety': hashes([
|
|
88
|
+
'd0af494607b6956e2a31dc27b36fe622c2b606465d012efbac5b51f3cf264efb', // 1.0.0 to 1.8.3
|
|
89
|
+
]),
|
|
90
|
+
'git-workflow': hashes([
|
|
91
|
+
'8f696c7f8758549e6f9547bdc56cf7370841411bc34f8bfba001951034d391ef', // 1.0.0 to 1.8.3
|
|
92
|
+
]),
|
|
93
|
+
'github-patterns': hashes([
|
|
94
|
+
'0a93eed85575836e42980ffcc1dcad8679eb41dd0db1eaf428c51f34c101b2fa', // 1.0.0 to 1.8.3
|
|
95
|
+
]),
|
|
96
|
+
'go': hashes([
|
|
97
|
+
'80b68a2f640b16b2d6a369cfe003ed6366a4a6c6c0a71868451a1652f2ede8d6', // 1.2.0 to 1.8.3
|
|
98
|
+
]),
|
|
99
|
+
'implementation-orchestration': hashes([
|
|
100
|
+
'023bd2b2b754945daf83b101652e8c9a7c45092d0ebc2dd56dd9f170f6ef2cf2', // 1.6.0 to 1.8.3
|
|
101
|
+
]),
|
|
102
|
+
'implementation-patterns': hashes([
|
|
103
|
+
'484816f577cb1c2d4fe6bcbe1f8adbc8c02c8a41eff19ecfa5711e2afca0cf54', // 1.0.0 to 1.8.3
|
|
104
|
+
]),
|
|
105
|
+
'input-validation': hashes([
|
|
106
|
+
'2ce71e678840855d271dc7418b207f50c1d1123de80b9b88488154c5697053dd', // 0.6.1 to 0.9.0
|
|
107
|
+
'2e08b78f98de0e6ae17d455f832672e0a3ddaff9fa13337597f54130f3bc1a6b', // 1.0.0 to 1.8.3
|
|
108
|
+
]),
|
|
109
|
+
'java': hashes([
|
|
110
|
+
'17cc4b3d476bd3ac9bbd3d367d04a88f4695d0e56f58db434aaaad80d875b6c3', // 1.2.0 to 1.8.3
|
|
111
|
+
]),
|
|
112
|
+
'knowledge-persistence': hashes([
|
|
113
|
+
'3f43892b11fb2ffefb6ff1f083872338ed5d511b6e2bf6b125fa2c610afa5c79', // 1.6.0 to 1.8.3
|
|
114
|
+
]),
|
|
115
|
+
'performance-patterns': hashes([
|
|
116
|
+
'bc025c6908ea87fab292cfe37396ee901c7acb1cccfaa27f78d9cd6f5798eea5', // 1.0.0 to 1.2.0
|
|
117
|
+
'bdd93bb51e829460d65a1ee572d0a95f7a0246b7a26bb1034a1c981e79b5ee9d', // 1.3.0 to 1.8.3
|
|
118
|
+
]),
|
|
119
|
+
'plan-orchestration': hashes([
|
|
120
|
+
'ce940a2d0f2f3d80e9200ca8c4294d6fb26076f4fe6a640a157ffedd8ee37491', // 1.6.0 to 1.8.3
|
|
121
|
+
]),
|
|
122
|
+
'python': hashes([
|
|
123
|
+
'ff03facdc25a6d6dc5d6e6979d2b3e30206b687ffc064cb0bc69591027ddcbb6', // 1.2.0 to 1.8.3
|
|
124
|
+
]),
|
|
125
|
+
'react': hashes([
|
|
126
|
+
'3df91866eec7559cbef1e48c74af9b6f23861ef703fbe2bf00822d2d2842f666', // 1.0.0 to 1.1.0
|
|
127
|
+
'671e6a38d811f1eab42a3e0e69445c8528f8222957032c166a77a2b066a1f989', // 1.2.0 to 1.8.3
|
|
128
|
+
]),
|
|
129
|
+
'regression-patterns': hashes([
|
|
130
|
+
'c4a8cfa79007c3838d5fceab272a7c6cfd808c4f64925385dff557670380b2aa', // 1.0.0 to 1.2.0
|
|
131
|
+
'd3a1a163f20a933d8fefd78e28b602ff34c6d13829fbb99eb78b29f07591f4a5', // 1.3.0 to 1.8.3
|
|
132
|
+
]),
|
|
133
|
+
'research': hashes([
|
|
134
|
+
'528c0fd4538df7b949a7d355e7daf7a6e96848053f112b437c26477ca2f456fe', // 0.6.1
|
|
135
|
+
'0a9f207dc8028961328521d9eafa2e4e51740cbed4f7560b71a562e480a08503', // 0.7.0 to 0.9.0
|
|
136
|
+
]),
|
|
137
|
+
'review-methodology': hashes([
|
|
138
|
+
'5cc67590d427510bfab6ba0daca3fd05df5aae74f7efeff4230aba4ec167e053', // 1.0.0 to 1.2.0
|
|
139
|
+
'928ddf64d96488c747552216b7079a943abcb7da2c8174d75a1281c94f61c19e', // 1.3.0 to 1.8.3
|
|
140
|
+
]),
|
|
141
|
+
'rust': hashes([
|
|
142
|
+
'efee293456d582cc38941102be4052d3a0f352253c6d913b6d1b3a92134093fa', // 1.2.0 to 1.8.3
|
|
143
|
+
]),
|
|
144
|
+
'search-first': hashes([
|
|
145
|
+
'37d60dc1fa6ab1aa690df450747c9a9e7c9b52ff8826ff3e675e612037d04794', // 1.5.0 to 1.8.3
|
|
146
|
+
]),
|
|
147
|
+
'security-patterns': hashes([
|
|
148
|
+
'6662d43fa607913db531f8445ceedf7dd17266e64551d59d7d91969082d7c9ac', // 1.0.0 to 1.2.0
|
|
149
|
+
'f52c3b5acc69b2d6764b3aebb955a51f7de417d065ed9c085aae31684a7b7af0', // 1.3.0 to 1.8.3
|
|
150
|
+
]),
|
|
151
|
+
'self-review': hashes([
|
|
152
|
+
'c7f0b6557316d2abc7cc3229c403d8abb736048a47f1e8d1d993d46351c36033', // 1.0.0 to 1.2.0
|
|
153
|
+
'f405c0f368884a714be309dcc9e85389672b22a7fd8b49bd2d5fd801cc4d9c51', // 1.3.0 to 1.8.3
|
|
154
|
+
]),
|
|
155
|
+
'test-design': hashes([
|
|
156
|
+
'9c048d74b6387143fce401f0edf271f2e02daef231ea66a6662915d7f0f25a5b', // 0.6.1 to 0.9.0
|
|
157
|
+
]),
|
|
158
|
+
'test-driven-development': hashes([
|
|
159
|
+
'54abb24907d3cf6c20c96981b4e2d20c80fbb51fb36559a3df9d4ac749af5782', // 1.1.0 to 1.2.0
|
|
160
|
+
'0457cfe9f23d2ba7f7e1faa00d027d8c3500458fcbb9957413be7ffad4c7f874', // 1.3.0 to 1.5.0
|
|
161
|
+
'21abf6a42b294bc3c6a7f51eaf981f08ab7bd6cec2c3c50312fc5d8b37ccba4e', // 1.6.0 to 1.8.3
|
|
162
|
+
]),
|
|
163
|
+
'test-patterns': hashes([
|
|
164
|
+
'8c75e34c3544824d1f8dc26c407909ec4bf177db583862b34e827bd1aea1f279', // 1.0.0 to 1.8.3
|
|
165
|
+
]),
|
|
166
|
+
'typescript': hashes([
|
|
167
|
+
'd70fc7facd2bec850c0b4cfb6eeeaa673648674024236a55ee6cbab7c2689241', // 1.0.0 to 1.8.3
|
|
168
|
+
]),
|
|
169
|
+
});
|
|
170
|
+
//# sourceMappingURL=legacy-skill-hashes.js.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import { writeFileAtomicExclusive } from '../../core/fs-atomic.js';
|
|
3
|
+
/** Classify `dir` with `lstat`, so a link is seen as a link and never as the directory it points to. */
|
|
4
|
+
export async function directoryState(dir) {
|
|
5
|
+
try {
|
|
6
|
+
const stat = await fs.lstat(dir);
|
|
7
|
+
if (stat.isSymbolicLink())
|
|
8
|
+
return 'link';
|
|
9
|
+
return stat.isDirectory() ? 'real' : 'unusable';
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
return err.code === 'ENOENT' ? 'absent' : 'unusable';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Whether `dir` is a link or not a folder, so that a run must not write into it. An absent folder is fine: the run creates it. */
|
|
16
|
+
export async function isUnrealFolder(dir) {
|
|
17
|
+
const state = await directoryState(dir);
|
|
18
|
+
return state === 'link' || state === 'unusable';
|
|
19
|
+
}
|
|
20
|
+
/** Whether a link stands at `entry`. An absent or unreadable entry is not a link. */
|
|
21
|
+
export async function entryIsLink(entry) {
|
|
22
|
+
return (await directoryState(entry)) === 'link';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Remove the link at `entry` with `unlink`: the link, never what it points to.
|
|
26
|
+
*
|
|
27
|
+
* The one place the pre-clean replaces a link. `readdir` and `mkdir` follow a link, so a pre-clean
|
|
28
|
+
* that emptied through one would empty another directory. Never throws; a link that cannot be
|
|
29
|
+
* removed is `stuck`, and the caller leaves the path alone.
|
|
30
|
+
*/
|
|
31
|
+
export async function unlinkLinkAt(entry) {
|
|
32
|
+
if (!(await entryIsLink(entry)))
|
|
33
|
+
return 'no-link';
|
|
34
|
+
try {
|
|
35
|
+
await fs.unlink(entry);
|
|
36
|
+
return 'unlinked';
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return 'stuck';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Prove, with {@link isUnrealFolder}, that a run may write into `dir`, and record the folder in
|
|
44
|
+
* `report.refusedFolders` when it may not. Returns true for a refused folder, so the caller skips
|
|
45
|
+
* every write into it.
|
|
46
|
+
*
|
|
47
|
+
* The answer for each folder a run writes into besides the `devflow:git` references, which the
|
|
48
|
+
* installer's `referencesHostState` answers: the commands, rules and agents folders and every other
|
|
49
|
+
* skill folder. An absent folder is fine, since the install creates it. A link is refused and never
|
|
50
|
+
* repaired, because the installer did not create it. A full install's pre-clean has already replaced
|
|
51
|
+
* the links it could, so this refuses only what is left. Never throws.
|
|
52
|
+
*/
|
|
53
|
+
export async function refuseUnrealFolder(report, dir) {
|
|
54
|
+
if (!(await isUnrealFolder(dir)))
|
|
55
|
+
return false;
|
|
56
|
+
report.refusedFolders.push(dir);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Make `entry` safe to write: a link there is unlinked, never followed, so the caller then creates a
|
|
61
|
+
* real file or folder in its place. The entry can be a file or a folder. Only the last component is
|
|
62
|
+
* checked, so a caller checks each folder on the way down before it checks an entry in it.
|
|
63
|
+
*
|
|
64
|
+
* Fail closed: an entry `lstat` cannot inspect is refused. Never throws.
|
|
65
|
+
*/
|
|
66
|
+
export async function clearLinkedEntry(entry) {
|
|
67
|
+
try {
|
|
68
|
+
const stat = await fs.lstat(entry);
|
|
69
|
+
if (!stat.isSymbolicLink())
|
|
70
|
+
return 'clear';
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return err.code === 'ENOENT' ? 'clear' : 'refused';
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
await fs.unlink(entry);
|
|
77
|
+
return 'replaced';
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return 'refused';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* {@link clearLinkedEntry} plus the report: a replaced link goes to `report.replacedLinks`, a refused
|
|
85
|
+
* entry to `report.refusedFolders`. Returns true when the caller may write at `entry`.
|
|
86
|
+
*
|
|
87
|
+
* The one call a write step makes before it creates or overwrites a file or folder inside a Devflow
|
|
88
|
+
* folder. Omit `report` when the step has none; the link is still never written through.
|
|
89
|
+
*/
|
|
90
|
+
export async function clearLinkForWrite(entry, report) {
|
|
91
|
+
const clearance = await clearLinkedEntry(entry);
|
|
92
|
+
if (clearance === 'replaced')
|
|
93
|
+
report?.replacedLinks.push(entry);
|
|
94
|
+
if (clearance === 'refused')
|
|
95
|
+
report?.refusedFolders.push(entry);
|
|
96
|
+
return clearance !== 'refused';
|
|
97
|
+
}
|
|
98
|
+
/** A report that holds no link yet. */
|
|
99
|
+
export function newLinkReport() {
|
|
100
|
+
return { replacedLinks: [], refusedFolders: [] };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* One report from several: the paths of every report in order, each path once. A command that gets
|
|
104
|
+
* the links of more than one step prints this, so a folder that two steps met is named once.
|
|
105
|
+
* Returns a new report; the inputs are not changed.
|
|
106
|
+
*/
|
|
107
|
+
export function mergeLinkReports(...reports) {
|
|
108
|
+
return {
|
|
109
|
+
replacedLinks: [...new Set(reports.flatMap(report => report.replacedLinks))],
|
|
110
|
+
refusedFolders: [...new Set(reports.flatMap(report => report.refusedFolders))],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The line for a link a step replaced. One wording for the install summary and for every converge
|
|
115
|
+
* that reports through a `warn` callback.
|
|
116
|
+
*/
|
|
117
|
+
export function replacedLinkMessage(linkPath) {
|
|
118
|
+
return `${linkPath} was a link. It was replaced with a real file or folder, and what it pointed to was left as it was.`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The first sentence of every refused-folder line: what the folder is. The install and the uninstall
|
|
122
|
+
* both open with it and then say what they did and what to do.
|
|
123
|
+
*/
|
|
124
|
+
export function refusedFolderStatement(folder) {
|
|
125
|
+
return `${folder} is a link or not a folder.`;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The line for a folder a step wrote nothing at, because it is a link it could not remove or not a
|
|
129
|
+
* folder. One wording for the install summary and for every converge that reports through a `warn`
|
|
130
|
+
* callback.
|
|
131
|
+
*/
|
|
132
|
+
export function refusedFolderMessage(folder) {
|
|
133
|
+
return `${refusedFolderStatement(folder)} Nothing was written there. Replace it with a real file or folder and run the install again.`;
|
|
134
|
+
}
|
|
135
|
+
/** Send one line per link in `report` to `warn`: the replaced links first, then the refused folders. */
|
|
136
|
+
export function warnLinkReport(report, warn) {
|
|
137
|
+
for (const linkPath of report.replacedLinks)
|
|
138
|
+
warn(replacedLinkMessage(linkPath));
|
|
139
|
+
for (const folder of report.refusedFolders)
|
|
140
|
+
warn(refusedFolderMessage(folder));
|
|
141
|
+
}
|
|
142
|
+
/** The mode of a file that replaces a link. The atomic writer would copy the mode of the link's target instead. */
|
|
143
|
+
const REPLACED_LINK_FILE_MODE = 0o644;
|
|
144
|
+
/** The temp file beside `file` that {@link writeFileReplacingLink} writes through: `<file>.tmp.<pid>`. */
|
|
145
|
+
export function linkReplacementTempPath(file) {
|
|
146
|
+
return `${file}.tmp.${process.pid}`;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Write `content` at `file` through a temp file beside it and one rename. A rename puts the new file
|
|
150
|
+
* in the place of whatever stands at `file`, so a link there is replaced and never followed; the link
|
|
151
|
+
* is recorded in `links.replacedLinks` once the write is done. A file that replaces a link has the
|
|
152
|
+
* fixed mode 0644, never the mode of what the link pointed to, which the atomic writer would copy
|
|
153
|
+
* because its `stat` follows the link. Any other file goes through the atomic writer and keeps its
|
|
154
|
+
* own mode. The caller has proved that the folder holding `file` is a real one. Throws what the
|
|
155
|
+
* writer throws, so the caller keeps its own catch.
|
|
156
|
+
*/
|
|
157
|
+
export async function writeFileReplacingLink(file, content, links) {
|
|
158
|
+
if (!(await entryIsLink(file))) {
|
|
159
|
+
await writeFileAtomicExclusive(file, content);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const temp = linkReplacementTempPath(file);
|
|
163
|
+
try {
|
|
164
|
+
// A stale temp file is removed first; the exclusive open then refuses anything placed there since.
|
|
165
|
+
await fs.rm(temp, { force: true });
|
|
166
|
+
await fs.writeFile(temp, content, { encoding: 'utf-8', flag: 'wx', mode: REPLACED_LINK_FILE_MODE });
|
|
167
|
+
await fs.chmod(temp, REPLACED_LINK_FILE_MODE);
|
|
168
|
+
await fs.rename(temp, file);
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
await fs.rm(temp, { force: true }).catch(() => undefined);
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
links?.replacedLinks.push(file);
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=linked-folder.js.map
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The references kind of the variant converge: make the installed `devflow:git` references
|
|
3
|
+
* match the machine's compliance switch (AC-41, AC-50).
|
|
4
|
+
*
|
|
5
|
+
* `devflow init`, `devflow compliance` and `devflow curation` reach it through
|
|
6
|
+
* convergeVariantAxes, so a compliance toggle needs no reinstall. It does, in order:
|
|
7
|
+
*
|
|
8
|
+
* 1. the overlay, with the manifest and the ONE source root of the switch
|
|
9
|
+
* (`installedReferenceManifest({ compliance })`, `skillRefsSourceDir`): it rewrites the
|
|
10
|
+
* references whose text differs between the two states and prunes the omitted
|
|
11
|
+
* operation files from `pr/` and `tracker/`. With compliance off the source root is the
|
|
12
|
+
* compliance-off tree, and an absent root is a failure that writes nothing: the converge
|
|
13
|
+
* never falls through to the compliance-on references;
|
|
14
|
+
* 2. with compliance off, the removal list ({@link removeComplianceOffFiles});
|
|
15
|
+
* with compliance on, the restore of the files the skill copy puts in place
|
|
16
|
+
* ({@link restoreComplianceOffFiles}), from the source the install would copy.
|
|
17
|
+
*
|
|
18
|
+
* It acts on a skill that is ALREADY installed. A machine whose plugin selection holds no
|
|
19
|
+
* `devflow:git` has nothing to converge, and that is a clean result. A skill directory, or a
|
|
20
|
+
* `references` directory inside it, that is a link or not a directory is a failure: the overlay
|
|
21
|
+
* would stage, prune and set modes through it. A full install empties the skill directory first and
|
|
22
|
+
* so replaces such a link; this converge and a partial install have no such step, so each refuses
|
|
23
|
+
* instead, on the same proof ({@link referencesHostState}).
|
|
24
|
+
*
|
|
25
|
+
* Never throws. Every failure is a warning and a `failures` entry, and the next step still
|
|
26
|
+
* runs. With compliance off a caller reads `failures` and `stillPosting` and fails closed.
|
|
27
|
+
*/
|
|
28
|
+
import * as path from 'path';
|
|
29
|
+
import { skillRefsSourceDir } from '../../core/assets.js';
|
|
30
|
+
import { installedReferenceManifest, SKILL_REFS_SKILL_NAME } from '../../core/mds-variants.js';
|
|
31
|
+
import { getPackageRoot } from '../../core/paths.js';
|
|
32
|
+
import { prefixSkillName } from '../../core/plugins.js';
|
|
33
|
+
import { removeComplianceOffFiles, restoreComplianceOffFiles } from './compliance-off-files.js';
|
|
34
|
+
import { overlayGeneratedReferences, overlayUnitLabel, referencesHostState, resolveSkillSource } from './installer.js';
|
|
35
|
+
import { isUnrealFolder } from './linked-folder.js';
|
|
36
|
+
export async function convergeReferences(opts) {
|
|
37
|
+
const { claudeDir, devflowDir, compliance, warn, backups } = opts;
|
|
38
|
+
const failures = [];
|
|
39
|
+
const fail = (message) => {
|
|
40
|
+
warn(`reference converge: ${message}`);
|
|
41
|
+
failures.push(message);
|
|
42
|
+
};
|
|
43
|
+
// Precondition, asserted in production code: a relative claudeDir would resolve every
|
|
44
|
+
// install target somewhere unexpected, the removals included.
|
|
45
|
+
if (!path.isAbsolute(claudeDir)) {
|
|
46
|
+
fail(`claudeDir is not an absolute path ("${claudeDir}") — skipping convergence`);
|
|
47
|
+
return { converged: false, rewritten: [], removed: [], restored: [], failures, stillPosting: [], unproven: !compliance };
|
|
48
|
+
}
|
|
49
|
+
const root = opts.packageRoot ?? getPackageRoot();
|
|
50
|
+
const skillsDir = path.join(claudeDir, 'skills');
|
|
51
|
+
const gitSkillDir = path.join(skillsDir, prefixSkillName(SKILL_REFS_SKILL_NAME));
|
|
52
|
+
// Every step below moves, removes or writes inside the skills folder. One that is a link or not a
|
|
53
|
+
// folder sends all of them into another place, and `referencesHostState` only looks at the entries
|
|
54
|
+
// below it. It is refused once, here: nothing is converged, the removal list of compliance off
|
|
55
|
+
// included, so the result is unproven and the caller fails closed.
|
|
56
|
+
if (await isUnrealFolder(skillsDir)) {
|
|
57
|
+
fail(`${skillsDir} is a link or not a folder, so nothing in it was converged`);
|
|
58
|
+
return { converged: false, rewritten: [], removed: [], restored: [], failures, stillPosting: [], unproven: !compliance };
|
|
59
|
+
}
|
|
60
|
+
const manifest = installedReferenceManifest({ compliance });
|
|
61
|
+
let rewritten = [];
|
|
62
|
+
let unproven = false;
|
|
63
|
+
const removed = [];
|
|
64
|
+
const host = await referencesHostState(gitSkillDir);
|
|
65
|
+
if (host.kind === 'refused') {
|
|
66
|
+
fail(`${host.path} is not a real directory, so the references in it were not converged`);
|
|
67
|
+
unproven = true;
|
|
68
|
+
}
|
|
69
|
+
else if (host.kind === 'ready') {
|
|
70
|
+
try {
|
|
71
|
+
const overlay = await overlayGeneratedReferences({
|
|
72
|
+
referencesTarget: path.join(gitSkillDir, 'references'),
|
|
73
|
+
sourceRoot: skillRefsSourceDir(compliance, root),
|
|
74
|
+
manifest,
|
|
75
|
+
warn,
|
|
76
|
+
backups,
|
|
77
|
+
});
|
|
78
|
+
rewritten = overlay.overlaidRefs;
|
|
79
|
+
for (const failure of overlay.overlayFailures) {
|
|
80
|
+
fail(`${overlayUnitLabel(failure.unit)} was not refreshed (${failure.error})`);
|
|
81
|
+
unproven = true;
|
|
82
|
+
}
|
|
83
|
+
for (const failed of overlay.pruned.failed) {
|
|
84
|
+
fail(`could not prune ${failed.name} (${String(failed.error)})`);
|
|
85
|
+
unproven = true;
|
|
86
|
+
}
|
|
87
|
+
// What the prune removed is named beside what the compliance-off list removes. The prune names
|
|
88
|
+
// are relative to the references folder; this list spells them from the skills folder.
|
|
89
|
+
removed.push(...overlay.pruned.removed.map(name => `${prefixSkillName(SKILL_REFS_SKILL_NAME)}/references/${name}`));
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
// The overlay throws for a packaging failure only: an absent source root, or a manifest entry
|
|
93
|
+
// the root lacks. Nothing is written in either case, and nothing falls through to another root.
|
|
94
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
95
|
+
unproven = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const restored = [];
|
|
99
|
+
const stillPosting = [];
|
|
100
|
+
if (!compliance) {
|
|
101
|
+
const removal = await removeComplianceOffFiles(skillsDir);
|
|
102
|
+
removed.push(...removal.removed);
|
|
103
|
+
for (const refusal of removal.refused) {
|
|
104
|
+
fail(`${refusal.entry} was not removed — ${refusal.reason}`);
|
|
105
|
+
stillPosting.push(path.join(skillsDir, ...refusal.entry.split('/')));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// The overlay owns every generated reference (publication-gate.md among them), so the restore
|
|
110
|
+
// writes back only the files that come from the skill source.
|
|
111
|
+
const ownedByOverlay = new Set(manifest.map(rel => `${prefixSkillName(SKILL_REFS_SKILL_NAME)}/references/${rel}`));
|
|
112
|
+
const restoration = await restoreComplianceOffFiles({
|
|
113
|
+
skillsDir,
|
|
114
|
+
sourceDirOf: async (skill) => (await resolveSkillSource(skill, devflowDir, opts.packageRoot)).dir,
|
|
115
|
+
ownedElsewhere: ownedByOverlay,
|
|
116
|
+
});
|
|
117
|
+
restored.push(...restoration.restored);
|
|
118
|
+
for (const refusal of restoration.refused)
|
|
119
|
+
fail(`${refusal.entry} was not restored — ${refusal.reason}`);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
converged: failures.length === 0,
|
|
123
|
+
rewritten,
|
|
124
|
+
removed,
|
|
125
|
+
restored,
|
|
126
|
+
failures,
|
|
127
|
+
stillPosting: compliance ? [] : stillPosting,
|
|
128
|
+
unproven: compliance ? false : unproven,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=reference-converge.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { directoryState, refusedFolderStatement, unlinkLinkAt } from './linked-folder.js';
|
|
2
|
+
/**
|
|
3
|
+
* Remove the rules folder devflow installs into, without deleting a file of the user's: the files
|
|
4
|
+
* devflow did not ship move to the backup folder, the files it shipped are removed by the list the
|
|
5
|
+
* session made, and a plain `rmdir` removes the folder only when nothing is left in it.
|
|
6
|
+
*
|
|
7
|
+
* A link is unlinked and never followed, and a path that is not a folder is left as it is. Never
|
|
8
|
+
* throws.
|
|
9
|
+
*/
|
|
10
|
+
export async function removeRulesFolder(rulesFolder, backups) {
|
|
11
|
+
const state = await directoryState(rulesFolder);
|
|
12
|
+
switch (state) {
|
|
13
|
+
case 'absent':
|
|
14
|
+
return 'absent';
|
|
15
|
+
case 'link':
|
|
16
|
+
return (await unlinkLinkAt(rulesFolder)) === 'unlinked' ? 'unlinked' : 'refused';
|
|
17
|
+
case 'unusable':
|
|
18
|
+
return 'refused';
|
|
19
|
+
case 'real': {
|
|
20
|
+
await backups.prepass([rulesFolder]);
|
|
21
|
+
const removal = await backups.removeListed(rulesFolder);
|
|
22
|
+
return removal.folderRemoved ? 'removed' : 'kept';
|
|
23
|
+
}
|
|
24
|
+
default: {
|
|
25
|
+
const unreachable = state;
|
|
26
|
+
return unreachable;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** The line for a rules folder that {@link removeRulesFolder} refused to touch. */
|
|
31
|
+
export function rulesFolderRefusedMessage(rulesFolder) {
|
|
32
|
+
return `${refusedFolderStatement(rulesFolder)} It was left as it is.`;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=rules-folder.js.map
|