skillcaddy 0.27.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 (99) hide show
  1. package/CHANGELOG.md +497 -0
  2. package/CONTEXT.md +149 -0
  3. package/LICENSE +21 -0
  4. package/README.md +499 -0
  5. package/README_CN.md +482 -0
  6. package/collection-metadata/github/mattpocock.json +16 -0
  7. package/lib/canonicalJson.js +11 -0
  8. package/lib/claudeStore.js +135 -0
  9. package/lib/collectionEnablement.js +115 -0
  10. package/lib/collectionSetup.js +199 -0
  11. package/lib/enablePlan.js +116 -0
  12. package/lib/enablementScope.js +8 -0
  13. package/lib/hermesStore.js +21 -0
  14. package/lib/libraryImage.js +1785 -0
  15. package/lib/managerInstall.js +109 -0
  16. package/lib/managerSourceWorkflow.js +234 -0
  17. package/lib/projectActions.js +24 -0
  18. package/lib/projectPath.js +51 -0
  19. package/lib/roots.js +99 -0
  20. package/lib/skillLinkStore.js +68 -0
  21. package/lib/skillMetadata.js +191 -0
  22. package/lib/skillStore.js +541 -0
  23. package/lib/sourceAcquisition.js +475 -0
  24. package/lib/sourceAcquisitionAdapter.js +47 -0
  25. package/lib/sourceAcquisitionError.js +18 -0
  26. package/lib/sourceAcquisitionPolicy.js +139 -0
  27. package/lib/sourceArchive.js +363 -0
  28. package/lib/sourceArchiveWorkspace.js +36 -0
  29. package/lib/sourceGit.js +95 -0
  30. package/lib/sourceGitAcquisitionAdapter.js +145 -0
  31. package/lib/sourceGitBatchUpdate.js +168 -0
  32. package/lib/sourceGitRepair.js +227 -0
  33. package/lib/sourceGitStaging.js +139 -0
  34. package/lib/sourceGitUpgradeAdapter.js +583 -0
  35. package/lib/sourceGitUrl.js +170 -0
  36. package/lib/sourceHttp.js +302 -0
  37. package/lib/sourceLocal.js +200 -0
  38. package/lib/sourceLocalAcquisitionAdapter.js +107 -0
  39. package/lib/sourceManager.js +242 -0
  40. package/lib/sourceMigration.js +172 -0
  41. package/lib/sourceMigrationIssue.js +6 -0
  42. package/lib/sourcePath.js +6 -0
  43. package/lib/sourcePolicy.js +53 -0
  44. package/lib/sourceRecord.js +295 -0
  45. package/lib/sourceRegistry.js +155 -0
  46. package/lib/sourceRemoteArchiveAcquisitionAdapter.js +147 -0
  47. package/lib/sourceRemoteFileAcquisitionAdapter.js +95 -0
  48. package/lib/sourceRemoteFilePolicy.js +62 -0
  49. package/lib/sourceRemoteFileWorkspace.js +55 -0
  50. package/lib/sourceSkillDiscovery.js +117 -0
  51. package/lib/sourceTree.js +185 -0
  52. package/lib/sourceUpgrade.js +596 -0
  53. package/lib/sourceUpgradeAdapter.js +41 -0
  54. package/lib/sourceUpgradePolicy.js +170 -0
  55. package/lib/sourceUpgradeTransaction.js +202 -0
  56. package/lib/sourceValidation.js +120 -0
  57. package/lib/sourceWorkspace.js +37 -0
  58. package/lib/specGate.js +235 -0
  59. package/lib/tuiActions.js +169 -0
  60. package/lib/tuiInstall.js +133 -0
  61. package/lib/tuiLayout.js +94 -0
  62. package/lib/version.js +17 -0
  63. package/lib/webManager.js +370 -0
  64. package/package.json +74 -0
  65. package/public/agentsUi.js +138 -0
  66. package/public/app.js +1431 -0
  67. package/public/claude.css +2 -0
  68. package/public/claudeUi.js +119 -0
  69. package/public/emptyState.js +12 -0
  70. package/public/enabled.css +303 -0
  71. package/public/favicon.svg +5 -0
  72. package/public/index.html +199 -0
  73. package/public/skillcaddy_CN.png +0 -0
  74. package/public/skillcaddy_EN.png +0 -0
  75. package/public/styles.css +1611 -0
  76. package/scripts/cli.js +647 -0
  77. package/scripts/cron-sync-update-git.sh +128 -0
  78. package/scripts/pull-github.sh +22 -0
  79. package/scripts/source.js +638 -0
  80. package/scripts/tui.js +1058 -0
  81. package/server.js +196 -0
  82. package/skills/skillcaddy-manager/SKILL.md +93 -0
  83. package/skills/skillcaddy-manager/agents/openai.yaml +7 -0
  84. package/skills/skillcaddy-manager/references/METADATA.md +75 -0
  85. package/skills/skillcaddy-manager/references/OPERATIONS.md +315 -0
  86. package/skills/skillcaddy-manager/references/RECOMMENDATION_GUIDE.md +51 -0
  87. package/skills/skillcaddy-manager/references/featured-skills.json +564 -0
  88. package/skills/skillcaddy-manager/references/skill-platforms.json +48 -0
  89. package/skills/skillcaddy-manager/scripts/check-conflicts.cjs +117 -0
  90. package/skills/skillcaddy-manager/scripts/check-global-skills.cjs +169 -0
  91. package/skills/skillcaddy-manager/scripts/translate-skill-notes.cjs +297 -0
  92. package/skills/skillcaddy-manager/scripts/version-manager.cjs +146 -0
  93. package/skills/skillcaddy-manager/scripts/view-recommendations.cjs +279 -0
  94. package/skills/skillcaddy-manager/skillcaddy.json +9 -0
  95. package/skills/spec-pipeline/spec-direction-gate/SKILL.md +119 -0
  96. package/skills/spec-pipeline/spec-direction-gate/check-approval.py +110 -0
  97. package/skills/spec-pipeline/spec-draft-intake/SKILL.md +116 -0
  98. package/skills/spec-pipeline/spec-role-contract/SKILL.md +113 -0
  99. package/skills/spec-pipeline/spec-role-contract/check-roles.py +108 -0
@@ -0,0 +1,191 @@
1
+ import { constants } from 'node:fs';
2
+ import { access, lstat, mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { requirePath } from './projectPath.js';
5
+ import { normalizeRoots } from './roots.js';
6
+ import {
7
+ assertInsideAllowedSkillSources,
8
+ resolveAllowedSkillSource
9
+ } from './sourcePolicy.js';
10
+
11
+ const METADATA_FILE = 'skillcaddy.json';
12
+ const METADATA_ROOT = '.skillcaddy/metadata';
13
+ const MAX_TAGS = 12;
14
+ const MAX_TAG_LENGTH = 40;
15
+ const MAX_NOTE_LENGTH = 500;
16
+
17
+ export async function readSkillMetadata(roots, skillPath) {
18
+ const sidecarMetadataPath = getSidecarMetadataPath(roots, skillPath);
19
+ try {
20
+ const content = await readFile(sidecarMetadataPath, 'utf8');
21
+ return {
22
+ ...normalizeMetadata(JSON.parse(content)),
23
+ metadataPath: sidecarMetadataPath,
24
+ hasMetadata: true,
25
+ metadataStorage: 'sidecar'
26
+ };
27
+ } catch (error) {
28
+ if (error.code !== 'ENOENT') {
29
+ return {
30
+ ...emptyMetadata(sidecarMetadataPath),
31
+ metadataError: `skillcaddy.json 读取失败:${error.message}`
32
+ };
33
+ }
34
+ }
35
+
36
+ return emptyMetadata(sidecarMetadataPath);
37
+ }
38
+
39
+ export async function updateSkillMetadata(roots, input) {
40
+ const resolvedRoots = normalizeRoots(roots);
41
+ const skillPath = path.resolve(requirePath(input.skillPath, 'skillPath'));
42
+ assertInsideAllowedSkillSources(resolvedRoots, skillPath);
43
+ await assertDirectory(skillPath, 'skillPath');
44
+
45
+ const metadata = normalizeMetadata(input);
46
+ const metadataPath = getSidecarMetadataPath(resolvedRoots, skillPath);
47
+ await mkdir(path.dirname(metadataPath), { recursive: true });
48
+ await writeFile(metadataPath, `${JSON.stringify({ note: metadata.note, tags: metadata.tags, autoEnable: metadata.autoEnable }, null, 2)}\n`, { encoding: 'utf8', flag: input.createOnly ? 'wx' : 'w' });
49
+
50
+ return {
51
+ ok: true,
52
+ skillPath,
53
+ metadataPath,
54
+ metadata: {
55
+ ...metadata,
56
+ metadataPath,
57
+ hasMetadata: true,
58
+ metadataStorage: 'sidecar'
59
+ }
60
+ };
61
+ }
62
+
63
+ export async function migrateLegacySkillMetadata(roots, skills, options = {}) {
64
+ const inspected = await Promise.all(skills.map(async (skill) => {
65
+ if (skill.hasMetadata || skill.metadataError) return null;
66
+
67
+ const legacyPath = getLegacyMetadataPath(skill.path);
68
+ try {
69
+ const metadata = normalizeMetadata(JSON.parse(await readFile(legacyPath, 'utf8')));
70
+ return {
71
+ item: {
72
+ id: skill.id,
73
+ skillPath: skill.path,
74
+ legacyPath,
75
+ sidecarPath: getSidecarMetadataPath(roots, skill.path)
76
+ },
77
+ metadata
78
+ };
79
+ } catch (error) {
80
+ if (error.code === 'ENOENT') return null;
81
+ return {
82
+ invalid: {
83
+ id: skill.id,
84
+ metadataError: `legacy skillcaddy.json 读取失败:${error.message}`
85
+ }
86
+ };
87
+ }
88
+ }));
89
+
90
+ const candidates = inspected.filter((entry) => entry?.item);
91
+ const pending = candidates.map((entry) => entry.item);
92
+ const invalid = inspected.filter((entry) => entry?.invalid).map((entry) => entry.invalid);
93
+
94
+ if (!options.write) {
95
+ return { dryRun: true, pending, migrated: 0, invalid };
96
+ }
97
+
98
+ for (const candidate of candidates) {
99
+ await updateSkillMetadata(roots, {
100
+ skillPath: candidate.item.skillPath,
101
+ ...candidate.metadata
102
+ });
103
+ }
104
+
105
+ return { dryRun: false, pending, migrated: pending.length, invalid };
106
+ }
107
+
108
+ function getLegacyMetadataPath(skillPath) {
109
+ return path.join(skillPath, METADATA_FILE);
110
+ }
111
+
112
+ /**
113
+ * sidecar 元数据属于原件库:它随 library image 一起搬运(ADR-0011),
114
+ * 因此即使 skill 本身来自代码根的仓库 skills 桶,元数据也写在原件库根下。
115
+ */
116
+ function getSidecarMetadataPath(roots, skillPath) {
117
+ const { libraryRoot } = normalizeRoots(roots);
118
+ const source = resolveAllowedSkillSource(roots, skillPath);
119
+ if (!source) {
120
+ throw new Error(`skill 不在允许的来源目录内:${skillPath}`);
121
+ }
122
+ return path.join(libraryRoot, METADATA_ROOT, source.sourceFolder, source.relativePath, METADATA_FILE);
123
+ }
124
+
125
+ function normalizeMetadata(input = {}) {
126
+ return {
127
+ note: normalizeNote(input.note),
128
+ tags: normalizeTags(input.tags),
129
+ autoEnable: normalizeAutoEnable(input.autoEnable)
130
+ };
131
+ }
132
+
133
+ function normalizeNote(value) {
134
+ if (value === undefined || value === null) return '';
135
+ if (typeof value !== 'string') throw new Error('note 必须是字符串');
136
+ const note = value.trim();
137
+ if (note.length > MAX_NOTE_LENGTH) {
138
+ throw new Error(`note 不能超过 ${MAX_NOTE_LENGTH} 个字符`);
139
+ }
140
+ return note;
141
+ }
142
+
143
+ function normalizeTags(value) {
144
+ if (value === undefined || value === null || value === '') return [];
145
+ const tags = Array.isArray(value) ? value : String(value).split(',');
146
+ const normalized = [];
147
+
148
+ for (const tag of tags) {
149
+ if (typeof tag !== 'string') throw new Error('tags 只能包含字符串');
150
+ const value = tag.trim();
151
+ if (!value) continue;
152
+ if (value.length > MAX_TAG_LENGTH) {
153
+ throw new Error(`tag 不能超过 ${MAX_TAG_LENGTH} 个字符:${value}`);
154
+ }
155
+ if (!normalized.includes(value)) normalized.push(value);
156
+ if (normalized.length > MAX_TAGS) {
157
+ throw new Error(`tags 不能超过 ${MAX_TAGS} 个`);
158
+ }
159
+ }
160
+
161
+ return normalized;
162
+ }
163
+
164
+ function normalizeAutoEnable(value) {
165
+ if (value === undefined || value === null || value === '') return true;
166
+ if (typeof value === 'boolean') return value;
167
+ if (typeof value === 'string') {
168
+ const normalized = value.trim().toLowerCase();
169
+ if (['true', '1', 'yes', 'on'].includes(normalized)) return true;
170
+ if (['false', '0', 'no', 'off'].includes(normalized)) return false;
171
+ }
172
+ throw new Error('autoEnable 必须是布尔值');
173
+ }
174
+
175
+ function emptyMetadata(metadataPath) {
176
+ return {
177
+ note: '',
178
+ tags: [],
179
+ autoEnable: true,
180
+ metadataPath,
181
+ hasMetadata: false
182
+ };
183
+ }
184
+
185
+ async function assertDirectory(targetPath, label) {
186
+ await access(targetPath, constants.F_OK);
187
+ const stat = await lstat(targetPath);
188
+ if (!stat.isDirectory()) {
189
+ throw new Error(`${label} 必须是目录:${targetPath}`);
190
+ }
191
+ }