devflow-kit 3.10.0 → 3.12.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 +74 -0
- package/dist/agents/architect.md +87 -0
- package/dist/agents/code.md +21 -25
- package/dist/agents/debug.md +21 -49
- package/dist/agents/design.md +9 -43
- package/dist/agents/fix.md +15 -29
- package/dist/agents/git.md +19 -22
- package/dist/agents/harden.md +14 -19
- package/dist/agents/knowledge.md +11 -19
- package/dist/agents/refactor.md +9 -14
- package/dist/agents/research.md +11 -34
- package/dist/agents/review.md +22 -52
- package/dist/agents/skim.md +7 -17
- package/dist/agents/triage.md +7 -14
- package/dist/cli/commands/backup-session.js +131 -0
- package/dist/cli/commands/compliance.js +16 -3
- package/dist/cli/commands/curation.js +12 -4
- package/dist/cli/commands/init.js +36 -55
- package/dist/cli/commands/install-report.js +95 -0
- package/dist/cli/commands/legacy-sweep.js +140 -45
- package/dist/cli/commands/rules.js +50 -13
- package/dist/cli/commands/uninstall.js +62 -24
- package/dist/commands/bug-analysis.md +31 -62
- package/dist/commands/code-review.md +38 -87
- package/dist/commands/debug.md +32 -82
- package/dist/commands/dynamic-build.md +155 -211
- package/dist/commands/dynamic-plan.md +71 -93
- package/dist/commands/dynamic-profile.md +7 -155
- package/dist/commands/dynamic-tickets.md +65 -186
- package/dist/commands/explore.md +27 -70
- package/dist/commands/implement.md +98 -186
- package/dist/commands/plan.md +60 -107
- package/dist/commands/release.md +25 -91
- package/dist/commands/research.md +34 -103
- package/dist/commands/resolve.md +64 -128
- package/dist/commands/self-review.md +35 -99
- package/dist/compliance-off/agents/git.md +15 -18
- package/dist/compliance-off/agents/triage.md +7 -14
- package/dist/compliance-off/commands/bug-analysis.md +31 -62
- package/dist/compliance-off/commands/code-review.md +34 -82
- package/dist/compliance-off/commands/dynamic-build.md +149 -205
- package/dist/compliance-off/commands/dynamic-tickets.md +65 -186
- package/dist/compliance-off/commands/implement.md +94 -180
- package/dist/compliance-off/commands/plan.md +60 -107
- package/dist/compliance-off/commands/release.md +25 -90
- package/dist/compliance-off/commands/resolve.md +54 -128
- package/dist/compliance-off/skills/git/references/tracker/github/ensure-pr-ready.md +3 -3
- package/dist/compliance-off/skills/git/references/tracker/jira/ensure-pr-ready.md +3 -3
- package/dist/compliance-off/skills/git/references/tracker/linear/ensure-pr-ready.md +3 -3
- package/dist/core/compliance.js +14 -0
- package/dist/core/fs-atomic.js +5 -1
- package/dist/core/orphan-sweep.js +20 -2
- package/dist/core/path-set.js +15 -0
- package/dist/core/plugins.js +6 -5
- 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 +7 -13
- package/dist/curation-compliance-off/commands/bug-analysis.md +21 -41
- package/dist/curation-compliance-off/commands/code-review.md +25 -62
- package/dist/curation-compliance-off/commands/dynamic-build.md +142 -194
- package/dist/curation-compliance-off/commands/dynamic-tickets.md +54 -173
- package/dist/curation-compliance-off/commands/implement.md +86 -161
- package/dist/curation-compliance-off/commands/plan.md +51 -86
- package/dist/curation-compliance-off/commands/release.md +17 -78
- package/dist/curation-compliance-off/commands/resolve.md +45 -108
- package/dist/curation-off/agents/architect.md +81 -0
- package/dist/curation-off/agents/code.md +20 -23
- package/dist/curation-off/agents/debug.md +19 -47
- package/dist/curation-off/agents/design.md +8 -41
- package/dist/curation-off/agents/fix.md +14 -27
- package/dist/curation-off/agents/harden.md +13 -18
- package/dist/curation-off/agents/knowledge.md +9 -17
- package/dist/curation-off/agents/refactor.md +8 -12
- package/dist/curation-off/agents/research.md +9 -32
- package/dist/curation-off/agents/review.md +11 -40
- package/dist/curation-off/agents/skim.md +7 -17
- package/dist/curation-off/agents/triage.md +7 -13
- package/dist/curation-off/commands/bug-analysis.md +21 -41
- package/dist/curation-off/commands/code-review.md +29 -67
- package/dist/curation-off/commands/debug.md +24 -63
- package/dist/curation-off/commands/dynamic-build.md +148 -200
- package/dist/curation-off/commands/dynamic-plan.md +60 -80
- package/dist/curation-off/commands/dynamic-tickets.md +54 -173
- package/dist/curation-off/commands/explore.md +18 -50
- package/dist/curation-off/commands/implement.md +90 -167
- package/dist/curation-off/commands/plan.md +51 -86
- package/dist/curation-off/commands/release.md +17 -79
- package/dist/curation-off/commands/research.md +23 -81
- package/dist/curation-off/commands/resolve.md +55 -108
- package/dist/curation-off/commands/self-review.md +26 -78
- package/dist/skills/git/references/pr/update-pr-evidence.md +2 -1
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +3 -3
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +3 -3
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +3 -3
- package/dist/targets/claude-code/compliance-install.js +38 -13
- package/dist/targets/claude-code/compliance-off-files.js +5 -1
- package/dist/targets/claude-code/curation-install.js +77 -32
- 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 +383 -192
- package/dist/targets/claude-code/legacy-skill-hashes.js +170 -0
- package/dist/targets/claude-code/linked-folder.js +5 -11
- package/dist/targets/claude-code/reference-converge.js +15 -2
- 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/workflow-folder-install.js +32 -12
- package/dist/variant-axes.json +5 -1
- package/package.json +1 -1
- package/src/assets/agents/_partials/_code.mds +3 -5
- package/src/assets/agents/_partials/_design.mds +86 -0
- package/src/assets/agents/architect.mds +41 -0
- package/src/assets/agents/code.mds +18 -20
- package/src/assets/agents/curate.md +26 -35
- package/src/assets/agents/debug.mds +21 -49
- package/src/assets/agents/design.mds +11 -114
- package/src/assets/agents/evaluate.md +6 -16
- package/src/assets/agents/explore.md +1 -2
- package/src/assets/agents/fix.mds +12 -24
- package/src/assets/agents/git.mds +20 -23
- package/src/assets/agents/harden.mds +14 -19
- package/src/assets/agents/knowledge.mds +10 -38
- package/src/assets/agents/polish.md +13 -39
- package/src/assets/agents/refactor.mds +6 -9
- package/src/assets/agents/research.mds +11 -34
- package/src/assets/agents/review.mds +12 -84
- package/src/assets/agents/skim.mds +7 -17
- package/src/assets/agents/synthesize.md +3 -21
- package/src/assets/agents/test.md +31 -32
- package/src/assets/agents/tracker.md +116 -229
- package/src/assets/agents/triage.mds +7 -26
- package/src/assets/agents/validate.md +1 -3
- package/src/assets/commands/_partials/_compliance.mds +2 -2
- package/src/assets/commands/_partials/_decisions.mds +17 -24
- package/src/assets/commands/_partials/_docs_root.mds +31 -10
- package/src/assets/commands/_partials/_engine.mds +34 -56
- package/src/assets/commands/_partials/_evidence_policy.mds +1 -1
- package/src/assets/commands/_partials/_factory.mds +10 -104
- package/src/assets/commands/_partials/_knowledge.mds +47 -51
- package/src/assets/commands/_partials/_plan_contract.mds +2 -2
- package/src/assets/commands/_partials/_preamble.mds +21 -21
- package/src/assets/commands/_partials/_publication.mds +3 -3
- package/src/assets/commands/_partials/_roster.mds +12 -13
- package/src/assets/commands/_partials/_settings.mds +6 -3
- package/src/assets/commands/_partials/_ticket_template.mds +3 -7
- package/src/assets/commands/_partials/_tracker.mds +10 -5
- package/src/assets/commands/_partials/_wave.mds +23 -32
- package/src/assets/commands/bug-analysis.mds +3 -7
- package/src/assets/commands/code-review.mds +12 -33
- package/src/assets/commands/debug.mds +5 -47
- package/src/assets/commands/dynamic-build.mds +48 -70
- package/src/assets/commands/dynamic-plan.mds +23 -42
- package/src/assets/commands/dynamic-profile.mds +7 -57
- package/src/assets/commands/dynamic-tickets.mds +9 -29
- package/src/assets/commands/explore.mds +5 -44
- package/src/assets/commands/implement.mds +75 -117
- package/src/assets/commands/plan.mds +27 -57
- package/src/assets/commands/release.mds +4 -62
- package/src/assets/commands/research.mds +7 -53
- package/src/assets/commands/resolve.mds +40 -87
- package/src/assets/commands/self-review.mds +5 -45
- package/src/assets/mds/git/_ops_pr.mds +1 -0
- package/src/assets/mds/git/_ops_steps.mds +4 -4
- package/src/assets/mds/git/_pr.mds +1 -1
- package/src/assets/rules/engineering.md +1 -1
- package/src/assets/rules/quality.md +2 -2
- package/src/assets/scripts/ci-wait.cjs +150 -18
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +3 -3
- package/src/assets/scripts/hooks/preamble +2 -3
- package/src/assets/scripts/pr-evidence.cjs +1 -0
- package/src/assets/scripts/resolve-evidence-policy.cjs +14 -13
- package/src/assets/scripts/resolve-root.cjs +186 -0
- package/src/assets/scripts/resolve-settings.cjs +14 -9
- package/src/assets/scripts/verify-evidence.cjs +355 -22
- package/src/assets/skills/accessibility/SKILL.md +28 -95
- package/src/assets/skills/agent-references/SKILL.md +2 -4
- package/src/assets/skills/agent-references/references/commands/implement-evidence-asks.md +1 -1
- package/src/assets/skills/agent-references/references/commands/implement-levels.md +4 -7
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +1 -1
- package/src/assets/skills/agent-references/references/commands/plan-levels.md +2 -2
- package/src/assets/skills/apply-decisions/SKILL.md +5 -6
- package/src/assets/skills/apply-feature-knowledge/SKILL.md +3 -13
- package/src/assets/skills/architecture/SKILL.md +29 -105
- package/src/assets/skills/boundary-validation/SKILL.md +27 -78
- package/src/assets/skills/complexity/SKILL.md +8 -104
- package/src/assets/skills/compliance/SKILL.md +9 -67
- package/src/assets/skills/consistency/SKILL.md +7 -107
- package/src/assets/skills/database/SKILL.md +7 -72
- package/src/assets/skills/dependencies/SKILL.md +9 -109
- package/src/assets/skills/dependencies/references/detection.md +10 -0
- package/src/assets/skills/dependency-research/SKILL.md +9 -63
- package/src/assets/skills/design-review/SKILL.md +1 -35
- package/src/assets/skills/docs-framework/SKILL.md +4 -47
- package/src/assets/skills/documentation/SKILL.md +7 -71
- package/src/assets/skills/documentation/references/patterns.md +1 -1
- package/src/assets/skills/documentation/references/violations.md +1 -1
- package/src/assets/skills/feature-knowledge/SKILL.md +19 -222
- package/src/assets/skills/feature-knowledge/references/new-kb.md +128 -0
- package/src/assets/skills/gap-analysis/SKILL.md +1 -31
- package/src/assets/skills/git/SKILL.md +2 -22
- package/src/assets/skills/go/SKILL.md +24 -113
- package/src/assets/skills/java/SKILL.md +12 -98
- package/src/assets/skills/patterns/SKILL.md +8 -93
- package/src/assets/skills/performance/SKILL.md +29 -88
- package/src/assets/skills/python/SKILL.md +22 -120
- package/src/assets/skills/qa/SKILL.md +16 -54
- package/src/assets/skills/qa/references/browser-testing.md +0 -24
- package/src/assets/skills/qa/references/sources.md +2 -2
- package/src/assets/skills/quality-gates/SKILL.md +13 -100
- package/src/assets/skills/react/SKILL.md +33 -95
- package/src/assets/skills/regression/SKILL.md +8 -73
- package/src/assets/skills/reliability/SKILL.md +11 -120
- package/src/assets/skills/research-codebase/SKILL.md +7 -49
- package/src/assets/skills/research-competitor/SKILL.md +4 -39
- package/src/assets/skills/research-external/SKILL.md +5 -43
- package/src/assets/skills/research-market/SKILL.md +5 -43
- package/src/assets/skills/research-technology/SKILL.md +8 -68
- package/src/assets/skills/review-methodology/SKILL.md +10 -56
- package/src/assets/skills/rust/SKILL.md +19 -109
- package/src/assets/skills/security/SKILL.md +39 -66
- package/src/assets/skills/software-design/SKILL.md +20 -105
- package/src/assets/skills/software-design/references/patterns.md +84 -2
- package/src/assets/skills/software-design/references/violations.md +1 -1
- package/src/assets/skills/test-driven-development/SKILL.md +32 -107
- package/src/assets/skills/test-driven-development/references/rationalization-prevention.md +33 -1
- package/src/assets/skills/testing/SKILL.md +35 -53
- package/src/assets/skills/typescript/SKILL.md +23 -102
- package/src/assets/skills/ui-design/SKILL.md +26 -74
- package/src/assets/skills/worktree-support/SKILL.md +4 -10
- package/dist/curation-off/commands/dynamic-profile.md +0 -214
|
@@ -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
|
|
@@ -39,16 +39,6 @@ export async function unlinkLinkAt(entry) {
|
|
|
39
39
|
return 'stuck';
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Remove `target` whole. A link there is unlinked instead and returned, so the caller can report it;
|
|
44
|
-
* the folder it points to is left as it is. A failure of the final `rm` is thrown.
|
|
45
|
-
*/
|
|
46
|
-
export async function removeReportingLink(target) {
|
|
47
|
-
const outcome = await unlinkLinkAt(target);
|
|
48
|
-
if (outcome === 'no-link')
|
|
49
|
-
await fs.rm(target, { recursive: true, force: true });
|
|
50
|
-
return outcome === 'unlinked' ? [target] : [];
|
|
51
|
-
}
|
|
52
42
|
/**
|
|
53
43
|
* Prove, with {@link isUnrealFolder}, that a run may write into `dir`, and record the folder in
|
|
54
44
|
* `report.refusedFolders` when it may not. Returns true for a refused folder, so the caller skips
|
|
@@ -151,6 +141,10 @@ export function warnLinkReport(report, warn) {
|
|
|
151
141
|
}
|
|
152
142
|
/** The mode of a file that replaces a link. The atomic writer would copy the mode of the link's target instead. */
|
|
153
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
|
+
}
|
|
154
148
|
/**
|
|
155
149
|
* Write `content` at `file` through a temp file beside it and one rename. A rename puts the new file
|
|
156
150
|
* in the place of whatever stands at `file`, so a link there is replaced and never followed; the link
|
|
@@ -165,7 +159,7 @@ export async function writeFileReplacingLink(file, content, links) {
|
|
|
165
159
|
await writeFileAtomicExclusive(file, content);
|
|
166
160
|
return;
|
|
167
161
|
}
|
|
168
|
-
const temp =
|
|
162
|
+
const temp = linkReplacementTempPath(file);
|
|
169
163
|
try {
|
|
170
164
|
// A stale temp file is removed first; the exclusive open then refuses anything placed there since.
|
|
171
165
|
await fs.rm(temp, { force: true });
|
|
@@ -32,8 +32,9 @@ import { getPackageRoot } from '../../core/paths.js';
|
|
|
32
32
|
import { prefixSkillName } from '../../core/plugins.js';
|
|
33
33
|
import { removeComplianceOffFiles, restoreComplianceOffFiles } from './compliance-off-files.js';
|
|
34
34
|
import { overlayGeneratedReferences, overlayUnitLabel, referencesHostState, resolveSkillSource } from './installer.js';
|
|
35
|
+
import { isUnrealFolder } from './linked-folder.js';
|
|
35
36
|
export async function convergeReferences(opts) {
|
|
36
|
-
const { claudeDir, devflowDir, compliance, warn } = opts;
|
|
37
|
+
const { claudeDir, devflowDir, compliance, warn, backups } = opts;
|
|
37
38
|
const failures = [];
|
|
38
39
|
const fail = (message) => {
|
|
39
40
|
warn(`reference converge: ${message}`);
|
|
@@ -48,9 +49,18 @@ export async function convergeReferences(opts) {
|
|
|
48
49
|
const root = opts.packageRoot ?? getPackageRoot();
|
|
49
50
|
const skillsDir = path.join(claudeDir, 'skills');
|
|
50
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
|
+
}
|
|
51
60
|
const manifest = installedReferenceManifest({ compliance });
|
|
52
61
|
let rewritten = [];
|
|
53
62
|
let unproven = false;
|
|
63
|
+
const removed = [];
|
|
54
64
|
const host = await referencesHostState(gitSkillDir);
|
|
55
65
|
if (host.kind === 'refused') {
|
|
56
66
|
fail(`${host.path} is not a real directory, so the references in it were not converged`);
|
|
@@ -63,6 +73,7 @@ export async function convergeReferences(opts) {
|
|
|
63
73
|
sourceRoot: skillRefsSourceDir(compliance, root),
|
|
64
74
|
manifest,
|
|
65
75
|
warn,
|
|
76
|
+
backups,
|
|
66
77
|
});
|
|
67
78
|
rewritten = overlay.overlaidRefs;
|
|
68
79
|
for (const failure of overlay.overlayFailures) {
|
|
@@ -73,6 +84,9 @@ export async function convergeReferences(opts) {
|
|
|
73
84
|
fail(`could not prune ${failed.name} (${String(failed.error)})`);
|
|
74
85
|
unproven = true;
|
|
75
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}`));
|
|
76
90
|
}
|
|
77
91
|
catch (err) {
|
|
78
92
|
// The overlay throws for a packaging failure only: an absent source root, or a manifest entry
|
|
@@ -81,7 +95,6 @@ export async function convergeReferences(opts) {
|
|
|
81
95
|
unproven = true;
|
|
82
96
|
}
|
|
83
97
|
}
|
|
84
|
-
const removed = [];
|
|
85
98
|
const restored = [];
|
|
86
99
|
const stillPosting = [];
|
|
87
100
|
if (!compliance) {
|
|
@@ -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
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shipped set: every file path, relative to the Claude dir, that some install of this build
|
|
3
|
+
* can write into `commands/devflow`, `agents/devflow`, `rules/devflow` and `skills/devflow:*`.
|
|
4
|
+
*
|
|
5
|
+
* The backup session moves a file out of those folders unless the set names it, so the set errs
|
|
6
|
+
* toward too large. It comes from the full registries and all four variants (compliance on and
|
|
7
|
+
* off, curation on and off), never from the selection of one run and never from what an earlier
|
|
8
|
+
* install left on disk:
|
|
9
|
+
*
|
|
10
|
+
* - commands: every plugin command, also a workflow that ships as a skill folder, since the
|
|
11
|
+
* old command file of such a workflow is removed as a shipped file and never moved;
|
|
12
|
+
* - agents and rules: every plugin entry, the feature-owned rules and the tracker agent;
|
|
13
|
+
* - skills: the files of each registry skill in the package source, the generated references
|
|
14
|
+
* of the compliance-on manifest, the compliance skill the converge writes, and the files only
|
|
15
|
+
* a compliance-on install writes;
|
|
16
|
+
* - workflow folders: the files of each registered folder under all four variant keys;
|
|
17
|
+
* - shadows: the listing of a valid skill shadow counts beside the listing it replaces.
|
|
18
|
+
*
|
|
19
|
+
* {@link buildShippedSet} is pure. {@link loadShippedSet} reads the package and the shadows from
|
|
20
|
+
* disk, within fixed bounds, and answers with a reason instead of a smaller set when it cannot.
|
|
21
|
+
*/
|
|
22
|
+
import { promises as fs } from 'fs';
|
|
23
|
+
import * as path from 'path';
|
|
24
|
+
import { skillsDir } from '../../core/assets.js';
|
|
25
|
+
import { complianceSkillFiles } from '../../core/compliance.js';
|
|
26
|
+
import { COMPLIANCE_OFF_REMOVED_SKILL_FILES } from '../../core/compliance-off-files.js';
|
|
27
|
+
import { VARIANT_KEYS } from '../../core/curation-variants.js';
|
|
28
|
+
import { SKILL_REFS_SKILL_NAME, installedReferenceManifest, workflowFolderFiles, } from '../../core/mds-variants.js';
|
|
29
|
+
import { createPathSet } from '../../core/path-set.js';
|
|
30
|
+
import { FEATURE_OWNED_RULES, getAllAgentNames, getAllCommandNames, getAllRuleNames, getAllSkillNames, prefixSkillName, } from '../../core/plugins.js';
|
|
31
|
+
import { validateSkillShadow } from './installer.js';
|
|
32
|
+
import { TRACKER_AGENT_NAME } from './tracker-install.js';
|
|
33
|
+
/** The most files one listed skill folder may hold. A folder past it is a failed listing. */
|
|
34
|
+
export const MAX_LISTED_FILES = 2_000;
|
|
35
|
+
/** The deepest level a listed folder may reach, counting the listed root as 0. */
|
|
36
|
+
export const MAX_LISTED_DEPTH = 6;
|
|
37
|
+
/** The most folders one listing may visit. */
|
|
38
|
+
export const MAX_LISTED_FOLDERS = 500;
|
|
39
|
+
const IGNORED_FILE_NAME = '.DS_Store';
|
|
40
|
+
const commandPath = (name) => `commands/devflow/${name}.md`;
|
|
41
|
+
const agentPath = (name) => `agents/devflow/${name}.md`;
|
|
42
|
+
const rulePath = (name) => `rules/devflow/${name}.md`;
|
|
43
|
+
const skillPath = (skill, file) => `skills/${prefixSkillName(skill)}/${file}`;
|
|
44
|
+
/**
|
|
45
|
+
* The file paths of the shipped set. Pure: the registries are constants, and the package and
|
|
46
|
+
* shadow listings come in as inputs.
|
|
47
|
+
*/
|
|
48
|
+
export function shippedPaths(inputs) {
|
|
49
|
+
const paths = new Set();
|
|
50
|
+
for (const name of getAllCommandNames(new Set()))
|
|
51
|
+
paths.add(commandPath(name));
|
|
52
|
+
for (const name of [...getAllAgentNames(), TRACKER_AGENT_NAME])
|
|
53
|
+
paths.add(agentPath(name));
|
|
54
|
+
for (const name of [...getAllRuleNames(), ...FEATURE_OWNED_RULES])
|
|
55
|
+
paths.add(rulePath(name));
|
|
56
|
+
for (const [skill, files] of inputs.packageSkillFiles) {
|
|
57
|
+
for (const file of files)
|
|
58
|
+
paths.add(skillPath(skill, file));
|
|
59
|
+
}
|
|
60
|
+
for (const [skill, files] of inputs.shadowListings ?? []) {
|
|
61
|
+
for (const file of files)
|
|
62
|
+
paths.add(skillPath(skill, file));
|
|
63
|
+
}
|
|
64
|
+
for (const rel of installedReferenceManifest({ compliance: true })) {
|
|
65
|
+
paths.add(skillPath(SKILL_REFS_SKILL_NAME, `references/${rel}`));
|
|
66
|
+
}
|
|
67
|
+
for (const entry of COMPLIANCE_OFF_REMOVED_SKILL_FILES)
|
|
68
|
+
paths.add(`skills/${entry}`);
|
|
69
|
+
for (const file of complianceSkillFiles())
|
|
70
|
+
paths.add(skillPath('compliance', file));
|
|
71
|
+
for (const folder of inputs.workflowFolders) {
|
|
72
|
+
for (const key of VARIANT_KEYS) {
|
|
73
|
+
for (const file of workflowFolderFiles(folder, key))
|
|
74
|
+
paths.add(skillPath(folder.name, file));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return paths;
|
|
78
|
+
}
|
|
79
|
+
/** The shipped set of {@link shippedPaths}, ready for the backup session. */
|
|
80
|
+
export function buildShippedSet(inputs) {
|
|
81
|
+
return createPathSet(shippedPaths(inputs));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The files below `root` as POSIX paths, sorted per folder. Folders are entered only as real
|
|
85
|
+
* folders (a link is listed as a file and never entered). The listing is bounded by
|
|
86
|
+
* {@link MAX_LISTED_FILES}, {@link MAX_LISTED_DEPTH} and {@link MAX_LISTED_FOLDERS}; a breach or a
|
|
87
|
+
* read error is a failed listing, since a smaller listing would be a smaller set.
|
|
88
|
+
*/
|
|
89
|
+
async function listFiles(root) {
|
|
90
|
+
const files = [];
|
|
91
|
+
const pending = [
|
|
92
|
+
{ dir: root, rel: '', depth: 0 },
|
|
93
|
+
];
|
|
94
|
+
for (let visited = 0; visited < MAX_LISTED_FOLDERS; visited++) {
|
|
95
|
+
const next = pending.pop();
|
|
96
|
+
if (next === undefined)
|
|
97
|
+
return { ok: true, files };
|
|
98
|
+
let entries;
|
|
99
|
+
try {
|
|
100
|
+
entries = await fs.readdir(next.dir, { withFileTypes: true });
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
const code = err.code;
|
|
104
|
+
return { ok: false, reason: `could not read ${next.dir} (${typeof code === 'string' ? code : 'error'})` };
|
|
105
|
+
}
|
|
106
|
+
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
const rel = next.rel === '' ? entry.name : `${next.rel}/${entry.name}`;
|
|
109
|
+
if (entry.isDirectory()) {
|
|
110
|
+
if (next.depth + 1 > MAX_LISTED_DEPTH)
|
|
111
|
+
return { ok: false, reason: `${root} is deeper than ${MAX_LISTED_DEPTH} levels` };
|
|
112
|
+
pending.push({ dir: path.join(next.dir, entry.name), rel, depth: next.depth + 1 });
|
|
113
|
+
}
|
|
114
|
+
else if ((entry.isFile() || entry.isSymbolicLink()) && entry.name !== IGNORED_FILE_NAME) {
|
|
115
|
+
if (files.length >= MAX_LISTED_FILES)
|
|
116
|
+
return { ok: false, reason: `${root} holds more than ${MAX_LISTED_FILES} files` };
|
|
117
|
+
files.push(rel);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return pending.length === 0
|
|
122
|
+
? { ok: true, files }
|
|
123
|
+
: { ok: false, reason: `${root} holds more than ${MAX_LISTED_FOLDERS} folders` };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Build the shipped set from the registries, the package skill source and the skill shadows.
|
|
127
|
+
*
|
|
128
|
+
* Every registry skill is listed from `skillsDir(packageRoot)`. A skill whose source is missing is
|
|
129
|
+
* a failed load, not a smaller set: the caller then runs no scan, because a set that lacks a
|
|
130
|
+
* shipped file would send that file to the backup folder.
|
|
131
|
+
*/
|
|
132
|
+
export async function loadShippedSet(options) {
|
|
133
|
+
const skills = getAllSkillNames(new Set());
|
|
134
|
+
const packageSkillFiles = new Map();
|
|
135
|
+
for (const skill of skills) {
|
|
136
|
+
const listed = await listFiles(path.join(skillsDir(options.packageRoot), skill));
|
|
137
|
+
if (!listed.ok)
|
|
138
|
+
return { ok: false, reason: `skill "${skill}": ${listed.reason}` };
|
|
139
|
+
packageSkillFiles.set(skill, listed.files);
|
|
140
|
+
}
|
|
141
|
+
let shadowListings = options.shadowListings;
|
|
142
|
+
if (shadowListings === undefined) {
|
|
143
|
+
const found = new Map();
|
|
144
|
+
for (const skill of skills) {
|
|
145
|
+
const shadowDir = path.join(options.devflowDir, 'skills', skill);
|
|
146
|
+
if ((await validateSkillShadow(shadowDir)) !== 'valid')
|
|
147
|
+
continue;
|
|
148
|
+
const listed = await listFiles(shadowDir);
|
|
149
|
+
if (!listed.ok)
|
|
150
|
+
return { ok: false, reason: `shadow of skill "${skill}": ${listed.reason}` };
|
|
151
|
+
found.set(skill, listed.files);
|
|
152
|
+
}
|
|
153
|
+
shadowListings = found;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
shipped: buildShippedSet({ packageSkillFiles, shadowListings, workflowFolders: options.workflowFolders }),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=shipped-set.js.map
|
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
* it was moved to and the result names that path (`recoveryPath`); nothing deletes a backup
|
|
20
20
|
* that the result names as the way back. A folder that already holds the exact files is not
|
|
21
21
|
* touched, so an install that changes nothing writes nothing.
|
|
22
|
+
* - NO LOSS of the user's files: before any rename, the backup session moves every file of the
|
|
23
|
+
* old folder that devflow did not ship (the files of all four variant keys count as shipped)
|
|
24
|
+
* to the backup folder. If one cannot move, the result is `failed` and the folder stays as it
|
|
25
|
+
* is. After the swap the displaced folder is scanned again, its shipped files are removed by the
|
|
26
|
+
* session's list, and it goes with a plain `rmdir`; no folder is removed with a recursive `rm`
|
|
27
|
+
* except the staging folder, which holds only the copies this call wrote.
|
|
22
28
|
*
|
|
23
29
|
* A link at the skills folder or at the folder, or anything there that is not a folder, is refused
|
|
24
30
|
* and recorded in `links.refusedFolders`; a link is never followed and never written through.
|
|
@@ -32,13 +38,17 @@ import { workflowFoldersSourceDir } from '../../core/assets.js';
|
|
|
32
38
|
import { describeBuildIdCheckError, describeWorkflowFolderError, validateOutputName, verifyFolderBuildId, workflowFolderFiles, workflowFolderKey, } from '../../core/mds-variants.js';
|
|
33
39
|
import { isContainedIn } from '../../core/paths.js';
|
|
34
40
|
import { prefixSkillName } from '../../core/plugins.js';
|
|
41
|
+
import { discardDisplacedFolder } from './folder-swap.js';
|
|
42
|
+
import { formatCappedList } from './install-backup.js';
|
|
35
43
|
import { directoryState, refuseUnrealFolder } from './linked-folder.js';
|
|
36
44
|
/** The deepest level of a folder that is read or compared: `references/<file>` is level 1. */
|
|
37
45
|
const MAX_FOLDER_DEPTH = 4;
|
|
38
46
|
/** The folder inside the staging folder that is moved into place. */
|
|
39
47
|
const STAGED_FOLDER = 'folder';
|
|
40
48
|
/** Where the old folder waits while the new one is moved in: beside its staging folder. */
|
|
41
|
-
const backupOf = (staging) => `${staging}.old`;
|
|
49
|
+
export const backupOf = (staging) => `${staging}.old`;
|
|
50
|
+
/** The prefix of the staging folder of one folder install; `mkdtemp` adds six random characters. */
|
|
51
|
+
export const stagingPrefixOf = (claudeDir, name) => path.join(claudeDir, `.devflow-workflow-${name}-${process.pid}-`);
|
|
42
52
|
const errorMessage = (err) => (err instanceof Error ? err.message : String(err));
|
|
43
53
|
/**
|
|
44
54
|
* Read the files one folder holds under the exact variant key of `switches`.
|
|
@@ -147,7 +157,8 @@ async function folderHolds(target, files) {
|
|
|
147
157
|
return true;
|
|
148
158
|
}
|
|
149
159
|
/**
|
|
150
|
-
* Write `skills/devflow:<name>/` as exactly `files`:
|
|
160
|
+
* Write `skills/devflow:<name>/` as exactly `files`: move the user's files aside, stage, then swap
|
|
161
|
+
* in. See the module comment.
|
|
151
162
|
*
|
|
152
163
|
* The staging folder sits under `claudeDir`, not under `skills/`, so a folder a crash leaves
|
|
153
164
|
* behind never looks like a second skill to Claude Code. It is made by `mkdtemp`, an exclusive
|
|
@@ -157,7 +168,7 @@ async function folderHolds(target, files) {
|
|
|
157
168
|
* Never throws.
|
|
158
169
|
*/
|
|
159
170
|
export async function installWorkflowFolder(opts) {
|
|
160
|
-
const { claudeDir, name, files, links } = opts;
|
|
171
|
+
const { claudeDir, name, files, links, backups } = opts;
|
|
161
172
|
if (!path.isAbsolute(claudeDir)) {
|
|
162
173
|
return { name, outcome: 'failed', reason: `claudeDir is not an absolute path ("${claudeDir}")` };
|
|
163
174
|
}
|
|
@@ -176,13 +187,23 @@ export async function installWorkflowFolder(opts) {
|
|
|
176
187
|
if (await refuseUnrealFolder(links, target))
|
|
177
188
|
return { name, outcome: 'refused' };
|
|
178
189
|
let staging = null;
|
|
179
|
-
let keepBackup = false;
|
|
180
190
|
try {
|
|
181
|
-
if ((await directoryState(target)) === 'real'
|
|
182
|
-
|
|
191
|
+
if ((await directoryState(target)) === 'real') {
|
|
192
|
+
// Before any rename: what devflow did not ship moves out of the folder, whichever key's files it holds.
|
|
193
|
+
await backups.prepass([target]);
|
|
194
|
+
const kept = backups.keptUnder(target);
|
|
195
|
+
if (kept.length > 0) {
|
|
196
|
+
return {
|
|
197
|
+
name,
|
|
198
|
+
outcome: 'failed',
|
|
199
|
+
reason: `${kept.length} file(s) in ${target} could not be moved aside, so the folder was left as it is: ${formatCappedList(kept)}`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (await folderHolds(target, files))
|
|
203
|
+
return { name, outcome: 'unchanged' };
|
|
183
204
|
}
|
|
184
205
|
await fs.mkdir(claudeDir, { recursive: true });
|
|
185
|
-
staging = await fs.mkdtemp(
|
|
206
|
+
staging = await fs.mkdtemp(stagingPrefixOf(claudeDir, name));
|
|
186
207
|
const staged = path.join(staging, STAGED_FOLDER);
|
|
187
208
|
await fs.mkdir(staged);
|
|
188
209
|
for (const { file, bytes } of files) {
|
|
@@ -205,7 +226,6 @@ export async function installWorkflowFolder(opts) {
|
|
|
205
226
|
await fs.rename(backup, target);
|
|
206
227
|
}
|
|
207
228
|
catch (restoreErr) {
|
|
208
|
-
keepBackup = true;
|
|
209
229
|
return {
|
|
210
230
|
name,
|
|
211
231
|
outcome: 'failed',
|
|
@@ -215,17 +235,17 @@ export async function installWorkflowFolder(opts) {
|
|
|
215
235
|
}
|
|
216
236
|
throw err;
|
|
217
237
|
}
|
|
238
|
+
// The displaced folder is scanned again, because a file can arrive after the first scan.
|
|
239
|
+
if (hadTarget)
|
|
240
|
+
await discardDisplacedFolder(backups, backup, target);
|
|
218
241
|
return { name, outcome: 'installed' };
|
|
219
242
|
}
|
|
220
243
|
catch (err) {
|
|
221
244
|
return { name, outcome: 'failed', reason: errorMessage(err) };
|
|
222
245
|
}
|
|
223
246
|
finally {
|
|
224
|
-
if (staging !== null)
|
|
247
|
+
if (staging !== null)
|
|
225
248
|
await fs.rm(staging, { recursive: true, force: true }).catch(() => undefined);
|
|
226
|
-
if (!keepBackup)
|
|
227
|
-
await fs.rm(backupOf(staging), { recursive: true, force: true }).catch(() => undefined);
|
|
228
|
-
}
|
|
229
249
|
}
|
|
230
250
|
}
|
|
231
251
|
//# sourceMappingURL=workflow-folder-install.js.map
|
package/dist/variant-axes.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
3
|
"hosts": {
|
|
4
|
+
"agents/architect": {
|
|
5
|
+
"curation": true,
|
|
6
|
+
"compliance": false
|
|
7
|
+
},
|
|
4
8
|
"agents/code": {
|
|
5
9
|
"curation": true,
|
|
6
10
|
"compliance": false
|
|
@@ -70,7 +74,7 @@
|
|
|
70
74
|
"compliance": false
|
|
71
75
|
},
|
|
72
76
|
"commands/dynamic-profile": {
|
|
73
|
-
"curation":
|
|
77
|
+
"curation": false,
|
|
74
78
|
"compliance": false
|
|
75
79
|
},
|
|
76
80
|
"commands/dynamic-tickets": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devflow-kit",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
4
4
|
"description": "A meta-harness for Claude Code — turns a single coding agent into an engineering team: orchestration, parallel review, persistent memory, curated decisions, and graph workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,8 +26,7 @@ inside a define is split by the build like an arm in a host: the markers sit on
|
|
|
26
26
|
- **TASK_DESCRIPTION**: What to do
|
|
27
27
|
- **PUSH** (optional): `true` (default) | `false` — when false, commit only; orchestrator owns push/CI gate
|
|
28
28
|
<!-- curation:on -->
|
|
29
|
-
- **DECISIONS_CONTEXT** (optional): Compact index of active ADR/PF entries
|
|
30
|
-
When provided, use `devflow:apply-decisions` to Read full bodies on demand.
|
|
29
|
+
- **DECISIONS_CONTEXT** (optional): Compact index of active ADR/PF entries
|
|
31
30
|
<!-- curation:end -->
|
|
32
31
|
@end
|
|
33
32
|
|
|
@@ -61,8 +60,7 @@ Run builds, typechecks, lints and tests in the foreground, each with an explicit
|
|
|
61
60
|
1. **Read once** - Never re-read an unchanged file. For a file over 400 lines, or a second look, `grep -n` first, then Read only that range (`offset`, `limit`)
|
|
62
61
|
2. **Small, focused changes** - Don't scope creep beyond the plan
|
|
63
62
|
3. **Fail honestly** - If blocked, report clearly with what was completed
|
|
64
|
-
|
|
65
|
-
- Never print a large file whole; page it with offset and limit, and cap any output you print. Before printing search matches, list or count them.
|
|
63
|
+
4. **Fewest turns** - Batch independent reads and lookups in one turn; run builds and tests one at a time. No Read-back after a successful Edit or Write unless a command changed the file. No narration-only turn, except the wait for an end notice
|
|
66
64
|
|
|
67
65
|
## Output
|
|
68
66
|
|
|
@@ -86,7 +84,7 @@ Run builds, typechecks, lints and tests in the foreground, each with an explicit
|
|
|
86
84
|
{Description of blocker or failure with recommendation}
|
|
87
85
|
```
|
|
88
86
|
|
|
89
|
-
Report cap: final message at most about 1,500 tokens; longer material goes to a
|
|
87
|
+
Report cap: final message at most about 1,500 tokens; longer material goes to a new file in a `mktemp -d` folder and the message gives its path. Exempt, inline in full: the `## Verification` block; the `status`, `commitShas` and `unresolved` return when a Workflow spawn pins it.
|
|
90
88
|
|
|
91
89
|
## Boundaries
|
|
92
90
|
|