devskill 2.0.8 → 2.0.10
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/meta.ts +50 -1
- package/package.json +1 -1
- package/rules/typescript-strict.md +30 -0
- package/rules/vue-style.md +35 -0
- package/scripts/cli.ts +304 -34
- package/workflows/git-flow.md +52 -0
package/meta.ts
CHANGED
|
@@ -4,6 +4,16 @@ export interface VendorSkillMeta {
|
|
|
4
4
|
skills?: Record<string, string> // sourceSkillName -> outputSkillName. If omitted, auto-discovers all subdirectories.
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
export interface RuleMeta {
|
|
8
|
+
file: string // path relative to repo root, e.g. 'rules/vue-style.md'
|
|
9
|
+
description?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface WorkflowMeta {
|
|
13
|
+
file: string // path relative to repo root, e.g. 'workflows/git-flow.md'
|
|
14
|
+
description?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
/**
|
|
8
18
|
* Repositories to clone as submodules and generate skills from their docs.
|
|
9
19
|
* After cloning, read docs and generate skills manually into skills/{name}/
|
|
@@ -134,7 +144,8 @@ export const collections: Record<string, string[]> = {
|
|
|
134
144
|
'pinia-options',
|
|
135
145
|
'builderx_spa-api',
|
|
136
146
|
'builderx_spa-permission',
|
|
137
|
-
'builderx_spa-design'
|
|
147
|
+
'builderx_spa-design',
|
|
148
|
+
'git-commit',
|
|
138
149
|
],
|
|
139
150
|
'builderx_api': [
|
|
140
151
|
'builderx_api-schemas',
|
|
@@ -144,5 +155,43 @@ export const collections: Record<string, string[]> = {
|
|
|
144
155
|
'builderx_api-redis',
|
|
145
156
|
'builderx_api-rabbitmq',
|
|
146
157
|
'builderx_api-mongodb',
|
|
158
|
+
'git-commit',
|
|
147
159
|
]
|
|
148
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Individual rule files (markdown) to install into AI tool rule directories.
|
|
164
|
+
* Format: { 'rule-name': { file: 'rules/rule-name.md', description: '...' } }
|
|
165
|
+
*/
|
|
166
|
+
export const rules: Record<string, RuleMeta> = {
|
|
167
|
+
'vue-style': {
|
|
168
|
+
file: 'rules/vue-style.md',
|
|
169
|
+
description: 'Vue 3 coding style and conventions',
|
|
170
|
+
},
|
|
171
|
+
'typescript-strict': {
|
|
172
|
+
file: 'rules/typescript-strict.md',
|
|
173
|
+
description: 'Strict TypeScript conventions',
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Individual workflow files (markdown) to install into AI tool workflow directories.
|
|
179
|
+
* Format: { 'workflow-name': { file: 'workflows/workflow-name.md', description: '...' } }
|
|
180
|
+
*/
|
|
181
|
+
export const workflows: Record<string, WorkflowMeta> = {
|
|
182
|
+
'git-flow': {
|
|
183
|
+
file: 'workflows/git-flow.md',
|
|
184
|
+
description: 'Feature branch git workflow',
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Grouped collections of rules + workflows per project.
|
|
190
|
+
* Use `npm start rules` or `npm start workflows` to install a batch.
|
|
191
|
+
*/
|
|
192
|
+
export const ruleCollections: Record<string, { rules?: string[], workflows?: string[] }> = {
|
|
193
|
+
'builderx_spa': {
|
|
194
|
+
rules: ['vue-style', 'typescript-strict'],
|
|
195
|
+
workflows: ['git-flow'],
|
|
196
|
+
},
|
|
197
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devskill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
4
4
|
"description": "Equip Cursor, Windsurf, Cline, Antigravity, Claude Code, Codex, GitHub Copilot and other AI Agents with expert programming superpowers via a single interactive prompt.",
|
|
5
5
|
"homepage": "https://vskill.vercel.app",
|
|
6
6
|
"repository": {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# TypeScript Strict Rules
|
|
2
|
+
|
|
3
|
+
## Strict Mode
|
|
4
|
+
- Always enable `"strict": true` in `tsconfig.json`
|
|
5
|
+
- No implicit `any` — always type function parameters
|
|
6
|
+
- Use `unknown` instead of `any` when type is uncertain
|
|
7
|
+
|
|
8
|
+
## Types vs Interfaces
|
|
9
|
+
- Use `interface` for object shapes that may be extended
|
|
10
|
+
- Use `type` for unions, intersections, and computed types
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// Good: interface for extendable shapes
|
|
14
|
+
interface User {
|
|
15
|
+
id: string
|
|
16
|
+
name: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Good: type for unions
|
|
20
|
+
type Status = 'active' | 'inactive' | 'pending'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Null Handling
|
|
24
|
+
- Use optional chaining `?.` and nullish coalescing `??`
|
|
25
|
+
- Avoid non-null assertion `!` — handle null explicitly
|
|
26
|
+
- Prefer explicit return types on exported functions
|
|
27
|
+
|
|
28
|
+
## Generics
|
|
29
|
+
- Name generics descriptively: `TItem`, `TKey`, `TResponse`
|
|
30
|
+
- Constrain generics when possible: `<T extends object>`
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Vue Style Rules
|
|
2
|
+
|
|
3
|
+
## Component Naming
|
|
4
|
+
- Use PascalCase for component names: `MyComponent.vue`
|
|
5
|
+
- Use kebab-case in templates: `<my-component />`
|
|
6
|
+
- Prefix base/generic components with `Base`: `BaseButton`, `BaseInput`
|
|
7
|
+
|
|
8
|
+
## Script Setup
|
|
9
|
+
- Always use `<script setup lang="ts">` for Vue 3 components
|
|
10
|
+
- Use `defineProps` and `defineEmits` with TypeScript generics
|
|
11
|
+
- Define props interface separately for reusability
|
|
12
|
+
|
|
13
|
+
```vue
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
interface Props {
|
|
16
|
+
title: string
|
|
17
|
+
count?: number
|
|
18
|
+
}
|
|
19
|
+
const props = defineProps<Props>()
|
|
20
|
+
const emit = defineEmits<{
|
|
21
|
+
change: [value: number]
|
|
22
|
+
submit: []
|
|
23
|
+
}>()
|
|
24
|
+
</script>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Template
|
|
28
|
+
- Use `v-bind` shorthand `:` and `v-on` shorthand `@`
|
|
29
|
+
- Prefer `v-show` for frequent toggles, `v-if` for conditional rendering
|
|
30
|
+
- Always provide `key` when using `v-for`
|
|
31
|
+
|
|
32
|
+
## Composables
|
|
33
|
+
- Prefix with `use`: `useAuth`, `useCart`
|
|
34
|
+
- Always return reactive refs explicitly
|
|
35
|
+
- Keep side effects inside `onMounted` / `watchEffect`
|
package/scripts/cli.ts
CHANGED
|
@@ -5,7 +5,7 @@ import process from 'node:process'
|
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
6
|
import * as p from '@clack/prompts'
|
|
7
7
|
import prompts from 'prompts'
|
|
8
|
-
import { collections, manual, submodules, vendors } from '../meta.ts'
|
|
8
|
+
import { collections, manual, ruleCollections, rules, submodules, vendors, workflows } from '../meta.ts'
|
|
9
9
|
|
|
10
10
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
11
11
|
const root = join(__dirname, '..')
|
|
@@ -389,14 +389,40 @@ async function cleanup(skipPrompt = false) {
|
|
|
389
389
|
}
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
-
|
|
393
|
-
const spinner = p.spinner()
|
|
394
|
-
|
|
392
|
+
function getAllCollections(): Record<string, string[]> {
|
|
395
393
|
const allCollections: Record<string, string[]> = { ...collections }
|
|
396
394
|
for (const [vendorName, config] of Object.entries(vendors)) {
|
|
397
395
|
const vendorConfig = config as VendorConfig
|
|
398
|
-
|
|
396
|
+
let vendorSkills: string[] = []
|
|
397
|
+
|
|
398
|
+
if (vendorConfig.skills && Object.keys(vendorConfig.skills).length > 0) {
|
|
399
|
+
vendorSkills = Object.values(vendorConfig.skills)
|
|
400
|
+
} else {
|
|
401
|
+
const vendorPath = join(root, 'vendor', vendorName)
|
|
402
|
+
const vendorSkillsPath = join(vendorPath, 'skills')
|
|
403
|
+
const searchPath = existsSync(vendorSkillsPath) ? vendorSkillsPath : (existsSync(vendorPath) ? vendorPath : null)
|
|
404
|
+
|
|
405
|
+
if (searchPath) {
|
|
406
|
+
const entries = readdirSync(searchPath, { withFileTypes: true })
|
|
407
|
+
for (const entry of entries) {
|
|
408
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
409
|
+
vendorSkills.push(entry.name)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (vendorSkills.length > 0) {
|
|
416
|
+
allCollections[vendorName] = vendorSkills
|
|
417
|
+
}
|
|
399
418
|
}
|
|
419
|
+
return allCollections
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function installSkills(targetCollectionNames: string[] = []) {
|
|
423
|
+
const spinner = p.spinner()
|
|
424
|
+
|
|
425
|
+
const allCollections = getAllCollections()
|
|
400
426
|
|
|
401
427
|
if (Object.keys(allCollections).length === 0) {
|
|
402
428
|
p.log.warn('No collections or vendors defined in meta.ts')
|
|
@@ -661,32 +687,7 @@ async function findSkills(query?: string) {
|
|
|
661
687
|
const allSkills = getExistingSkillNames()
|
|
662
688
|
p.intro(`Search results for ${query ? `"${query}"` : 'all'}`)
|
|
663
689
|
|
|
664
|
-
const allCollections
|
|
665
|
-
for (const [vendorName, config] of Object.entries(vendors)) {
|
|
666
|
-
const vendorConfig = config as VendorConfig
|
|
667
|
-
let vendorSkills: string[] = []
|
|
668
|
-
|
|
669
|
-
if (vendorConfig.skills && Object.keys(vendorConfig.skills).length > 0) {
|
|
670
|
-
vendorSkills = Object.values(vendorConfig.skills)
|
|
671
|
-
} else {
|
|
672
|
-
const vendorPath = join(root, 'vendor', vendorName)
|
|
673
|
-
const vendorSkillsPath = join(vendorPath, 'skills')
|
|
674
|
-
const searchPath = existsSync(vendorSkillsPath) ? vendorSkillsPath : (existsSync(vendorPath) ? vendorPath : null)
|
|
675
|
-
|
|
676
|
-
if (searchPath) {
|
|
677
|
-
const entries = readdirSync(searchPath, { withFileTypes: true })
|
|
678
|
-
for (const entry of entries) {
|
|
679
|
-
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
680
|
-
vendorSkills.push(entry.name)
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
if (vendorSkills.length > 0) {
|
|
687
|
-
allCollections[vendorName] = vendorSkills
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
+
const allCollections = getAllCollections()
|
|
690
691
|
|
|
691
692
|
let q = ''
|
|
692
693
|
if (!query) {
|
|
@@ -725,7 +726,242 @@ async function findSkills(query?: string) {
|
|
|
725
726
|
}
|
|
726
727
|
}
|
|
727
728
|
|
|
729
|
+
// ─── Rules & Workflows ────────────────────────────────────────────────────────
|
|
730
|
+
|
|
731
|
+
function getExistingRuleNames(): string[] {
|
|
732
|
+
return Object.keys(rules)
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function getExistingWorkflowNames(): string[] {
|
|
736
|
+
return Object.keys(workflows)
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
async function installRules(targetRuleNames: string[] = []) {
|
|
740
|
+
const spinner = p.spinner()
|
|
741
|
+
|
|
742
|
+
const allRules = getExistingRuleNames()
|
|
743
|
+
if (allRules.length === 0) {
|
|
744
|
+
p.log.warn('No rules defined in meta.ts')
|
|
745
|
+
return
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
let selectedRules: string[] = []
|
|
749
|
+
|
|
750
|
+
if (targetRuleNames.length > 0) {
|
|
751
|
+
const invalid = targetRuleNames.filter(n => !allRules.includes(n))
|
|
752
|
+
if (invalid.length > 0) {
|
|
753
|
+
p.log.error(`Rules not found: ${invalid.join(', ')}`)
|
|
754
|
+
return
|
|
755
|
+
}
|
|
756
|
+
selectedRules = targetRuleNames
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
const response = await prompts({
|
|
760
|
+
type: 'autocompleteMultiselect',
|
|
761
|
+
name: 'selected',
|
|
762
|
+
message: 'Select rules to install (Space to select, Enter to confirm)',
|
|
763
|
+
choices: allRules.map(name => ({
|
|
764
|
+
title: name,
|
|
765
|
+
value: name,
|
|
766
|
+
description: rules[name].description ?? '',
|
|
767
|
+
})),
|
|
768
|
+
})
|
|
769
|
+
if (!response.selected || response.selected.length === 0) {
|
|
770
|
+
p.log.warn('No rules selected (or canceled)')
|
|
771
|
+
return
|
|
772
|
+
}
|
|
773
|
+
selectedRules = response.selected
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const targetProject = await p.text({
|
|
777
|
+
message: 'Enter target project directory path',
|
|
778
|
+
initialValue: process.cwd(),
|
|
779
|
+
placeholder: process.cwd(),
|
|
780
|
+
validate: value => {
|
|
781
|
+
if (!value) return 'Path is required'
|
|
782
|
+
if (!existsSync(value)) return 'Directory does not exist'
|
|
783
|
+
},
|
|
784
|
+
})
|
|
785
|
+
if (p.isCancel(targetProject)) { p.cancel('Cancelled'); return }
|
|
786
|
+
|
|
787
|
+
const toolChoice = await p.select({
|
|
788
|
+
message: 'Which AI tool are you installing rules for?',
|
|
789
|
+
options: [
|
|
790
|
+
{ value: '.cursor/rules', label: 'Cursor (.cursor/rules/)' },
|
|
791
|
+
{ value: '.windsurf/rules', label: 'Windsurf (.windsurf/rules/)' },
|
|
792
|
+
{ value: '.claude', label: 'Claude Code (.claude/)' },
|
|
793
|
+
{ value: '.agents/rules', label: 'Antigravity (.agents/rules/)' },
|
|
794
|
+
{ value: 'rules', label: 'Generic (rules/)' },
|
|
795
|
+
{ value: 'custom', label: 'Custom path...' },
|
|
796
|
+
],
|
|
797
|
+
})
|
|
798
|
+
if (p.isCancel(toolChoice)) { p.cancel('Cancelled'); return }
|
|
799
|
+
|
|
800
|
+
let rulesDirName = toolChoice as string
|
|
801
|
+
if (toolChoice === 'custom') {
|
|
802
|
+
const customDir = await p.text({ message: 'Enter custom path:', initialValue: 'rules', placeholder: 'rules' })
|
|
803
|
+
if (p.isCancel(customDir)) { p.cancel('Cancelled'); return }
|
|
804
|
+
rulesDirName = customDir as string
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const targetDir = join(targetProject as string, rulesDirName)
|
|
808
|
+
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true })
|
|
809
|
+
|
|
810
|
+
spinner.start(`Installing ${selectedRules.length} rule(s) to ${targetDir}...`)
|
|
811
|
+
let successCount = 0
|
|
812
|
+
for (const ruleName of selectedRules) {
|
|
813
|
+
const meta = rules[ruleName]
|
|
814
|
+
const srcFile = join(root, meta.file)
|
|
815
|
+
if (!existsSync(srcFile)) {
|
|
816
|
+
p.log.warn(`Rule file not found: ${meta.file}`)
|
|
817
|
+
continue
|
|
818
|
+
}
|
|
819
|
+
const destFile = join(targetDir, `${ruleName}.md`)
|
|
820
|
+
cpSync(srcFile, destFile)
|
|
821
|
+
successCount++
|
|
822
|
+
}
|
|
823
|
+
spinner.stop(`Installed ${successCount}/${selectedRules.length} rule(s)`)
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
async function installWorkflows(targetWorkflowNames: string[] = []) {
|
|
827
|
+
const spinner = p.spinner()
|
|
828
|
+
|
|
829
|
+
const allWorkflows = getExistingWorkflowNames()
|
|
830
|
+
if (allWorkflows.length === 0) {
|
|
831
|
+
p.log.warn('No workflows defined in meta.ts')
|
|
832
|
+
return
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
let selectedWorkflows: string[] = []
|
|
836
|
+
|
|
837
|
+
if (targetWorkflowNames.length > 0) {
|
|
838
|
+
const invalid = targetWorkflowNames.filter(n => !allWorkflows.includes(n))
|
|
839
|
+
if (invalid.length > 0) {
|
|
840
|
+
p.log.error(`Workflows not found: ${invalid.join(', ')}`)
|
|
841
|
+
return
|
|
842
|
+
}
|
|
843
|
+
selectedWorkflows = targetWorkflowNames
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
const response = await prompts({
|
|
847
|
+
type: 'autocompleteMultiselect',
|
|
848
|
+
name: 'selected',
|
|
849
|
+
message: 'Select workflows to install (Space to select, Enter to confirm)',
|
|
850
|
+
choices: allWorkflows.map(name => ({
|
|
851
|
+
title: name,
|
|
852
|
+
value: name,
|
|
853
|
+
description: workflows[name].description ?? '',
|
|
854
|
+
})),
|
|
855
|
+
})
|
|
856
|
+
if (!response.selected || response.selected.length === 0) {
|
|
857
|
+
p.log.warn('No workflows selected (or canceled)')
|
|
858
|
+
return
|
|
859
|
+
}
|
|
860
|
+
selectedWorkflows = response.selected
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const targetProject = await p.text({
|
|
864
|
+
message: 'Enter target project directory path',
|
|
865
|
+
initialValue: process.cwd(),
|
|
866
|
+
placeholder: process.cwd(),
|
|
867
|
+
validate: value => {
|
|
868
|
+
if (!value) return 'Path is required'
|
|
869
|
+
if (!existsSync(value)) return 'Directory does not exist'
|
|
870
|
+
},
|
|
871
|
+
})
|
|
872
|
+
if (p.isCancel(targetProject)) { p.cancel('Cancelled'); return }
|
|
873
|
+
|
|
874
|
+
const toolChoice = await p.select({
|
|
875
|
+
message: 'Which AI tool are you installing workflows for?',
|
|
876
|
+
options: [
|
|
877
|
+
{ value: '.agents/workflows', label: 'Antigravity (.agents/workflows/)' },
|
|
878
|
+
{ value: '.cursor/workflows', label: 'Cursor (.cursor/workflows/)' },
|
|
879
|
+
{ value: '.windsurf/workflows', label: 'Windsurf (.windsurf/workflows/)' },
|
|
880
|
+
{ value: '.claude/workflows', label: 'Claude Code (.claude/workflows/)' },
|
|
881
|
+
{ value: 'workflows', label: 'Generic (workflows/)' },
|
|
882
|
+
{ value: 'custom', label: 'Custom path...' },
|
|
883
|
+
],
|
|
884
|
+
})
|
|
885
|
+
if (p.isCancel(toolChoice)) { p.cancel('Cancelled'); return }
|
|
886
|
+
|
|
887
|
+
let workflowsDirName = toolChoice as string
|
|
888
|
+
if (toolChoice === 'custom') {
|
|
889
|
+
const customDir = await p.text({ message: 'Enter custom path:', initialValue: 'workflows', placeholder: 'workflows' })
|
|
890
|
+
if (p.isCancel(customDir)) { p.cancel('Cancelled'); return }
|
|
891
|
+
workflowsDirName = customDir as string
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const targetDir = join(targetProject as string, workflowsDirName)
|
|
895
|
+
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true })
|
|
896
|
+
|
|
897
|
+
spinner.start(`Installing ${selectedWorkflows.length} workflow(s) to ${targetDir}...`)
|
|
898
|
+
let successCount = 0
|
|
899
|
+
for (const workflowName of selectedWorkflows) {
|
|
900
|
+
const meta = workflows[workflowName]
|
|
901
|
+
const srcFile = join(root, meta.file)
|
|
902
|
+
if (!existsSync(srcFile)) {
|
|
903
|
+
p.log.warn(`Workflow file not found: ${meta.file}`)
|
|
904
|
+
continue
|
|
905
|
+
}
|
|
906
|
+
const destFile = join(targetDir, `${workflowName}.md`)
|
|
907
|
+
cpSync(srcFile, destFile)
|
|
908
|
+
successCount++
|
|
909
|
+
}
|
|
910
|
+
spinner.stop(`Installed ${successCount}/${selectedWorkflows.length} workflow(s)`)
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
async function findRulesAndWorkflows(query?: string) {
|
|
914
|
+
p.intro(`Search results for ${query ? `"${query}"` : 'all'}`)
|
|
915
|
+
|
|
916
|
+
let q = ''
|
|
917
|
+
if (!query) {
|
|
918
|
+
const userInput = await p.text({
|
|
919
|
+
message: 'Enter keyword to search (leave empty to list all):',
|
|
920
|
+
placeholder: 'e.g. vue',
|
|
921
|
+
})
|
|
922
|
+
if (p.isCancel(userInput)) { p.cancel('Cancelled'); return }
|
|
923
|
+
q = (userInput as string).toLowerCase()
|
|
924
|
+
}
|
|
925
|
+
else {
|
|
926
|
+
q = query.toLowerCase()
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const matchedRuleCollections = Object.keys(ruleCollections).filter(c => c.toLowerCase().includes(q))
|
|
930
|
+
if (matchedRuleCollections.length > 0) {
|
|
931
|
+
p.log.success('📦 Rule Collections found:')
|
|
932
|
+
for (const c of matchedRuleCollections) {
|
|
933
|
+
const col = ruleCollections[c]
|
|
934
|
+
const rulesCount = col.rules?.length ?? 0
|
|
935
|
+
const wfCount = col.workflows?.length ?? 0
|
|
936
|
+
p.log.message(` - ${c} (${rulesCount} rules, ${wfCount} workflows)`)
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const matchedRules = Object.keys(rules).filter(r => r.toLowerCase().includes(q))
|
|
941
|
+
if (matchedRules.length > 0) {
|
|
942
|
+
p.log.success('📋 Rules found:')
|
|
943
|
+
for (const r of matchedRules) {
|
|
944
|
+
p.log.message(` - ${r}: ${rules[r].description ?? ''} -> \`npx devskill rules ${r}\``)
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
else if (q) {
|
|
948
|
+
p.log.warn('No rules found matching query')
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const matchedWorkflows = Object.keys(workflows).filter(w => w.toLowerCase().includes(q))
|
|
952
|
+
if (matchedWorkflows.length > 0) {
|
|
953
|
+
p.log.success('⚙️ Workflows found:')
|
|
954
|
+
for (const w of matchedWorkflows) {
|
|
955
|
+
p.log.message(` - ${w}: ${workflows[w].description ?? ''} -> \`npx devskill workflows ${w}\``)
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
else if (q) {
|
|
959
|
+
p.log.warn('No workflows found matching query')
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
728
963
|
async function main() {
|
|
964
|
+
|
|
729
965
|
const args = process.argv.slice(2)
|
|
730
966
|
const skipPrompt = args.includes('-y') || args.includes('--yes')
|
|
731
967
|
|
|
@@ -784,9 +1020,31 @@ async function main() {
|
|
|
784
1020
|
return
|
|
785
1021
|
}
|
|
786
1022
|
|
|
1023
|
+
if (command === 'rules') {
|
|
1024
|
+
const namesJoined = targetNames.length > 0 ? ` (${targetNames.join(', ')})` : ''
|
|
1025
|
+
p.intro(`Skills Manager - Rules${namesJoined}`)
|
|
1026
|
+
await installRules(targetNames)
|
|
1027
|
+
p.outro('Done')
|
|
1028
|
+
return
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (command === 'workflows') {
|
|
1032
|
+
const namesJoined = targetNames.length > 0 ? ` (${targetNames.join(', ')})` : ''
|
|
1033
|
+
p.intro(`Skills Manager - Workflows${namesJoined}`)
|
|
1034
|
+
await installWorkflows(targetNames)
|
|
1035
|
+
p.outro('Done')
|
|
1036
|
+
return
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
if (command === 'find-rules') {
|
|
1040
|
+
await findRulesAndWorkflows(targetName)
|
|
1041
|
+
p.outro('Done')
|
|
1042
|
+
return
|
|
1043
|
+
}
|
|
1044
|
+
|
|
787
1045
|
if (skipPrompt) {
|
|
788
1046
|
p.log.error('Command required when using -y flag')
|
|
789
|
-
p.log.info('Available commands: find, install, add, init, sync, check, cleanup')
|
|
1047
|
+
p.log.info('Available commands: find, install, add, rules, workflows, find-rules, init, sync, check, cleanup')
|
|
790
1048
|
process.exit(1)
|
|
791
1049
|
}
|
|
792
1050
|
|
|
@@ -795,10 +1053,13 @@ async function main() {
|
|
|
795
1053
|
const action = await p.select({
|
|
796
1054
|
message: 'What would you like to do?',
|
|
797
1055
|
options: [
|
|
798
|
-
{ value: 'install', label: 'Install collections', hint: 'Copy entire skill collections to a local project' },
|
|
1056
|
+
{ value: 'install', label: 'Install skill collections', hint: 'Copy entire skill collections to a local project' },
|
|
799
1057
|
{ value: 'add', label: 'Add specific skills', hint: 'Choose individual skills to add to your project' },
|
|
800
1058
|
{ value: 'find', label: 'Find a skill or collection', hint: 'Search by keyword' },
|
|
801
|
-
{ value: '
|
|
1059
|
+
{ value: 'rules', label: 'Install rules', hint: 'Copy rule files to your AI tool rules directory' },
|
|
1060
|
+
{ value: 'workflows', label: 'Install workflows', hint: 'Copy workflow files to your AI tool workflows directory' },
|
|
1061
|
+
{ value: 'find-rules', label: 'Find rules or workflows', hint: 'Search rules/workflows by keyword' },
|
|
1062
|
+
{ value: 'sync', label: 'Sync submodules', hint: 'Pull latest and sync vendor skills' },
|
|
802
1063
|
{ value: 'init', label: 'Init submodules', hint: 'Add new submodules from meta.ts' },
|
|
803
1064
|
{ value: 'check', label: 'Check updates', hint: 'See available updates' },
|
|
804
1065
|
{ value: 'cleanup', label: 'Cleanup', hint: 'Remove skills not listed in meta.ts' },
|
|
@@ -832,6 +1093,15 @@ async function main() {
|
|
|
832
1093
|
case 'cleanup':
|
|
833
1094
|
await cleanup()
|
|
834
1095
|
break
|
|
1096
|
+
case 'rules':
|
|
1097
|
+
await installRules()
|
|
1098
|
+
break
|
|
1099
|
+
case 'workflows':
|
|
1100
|
+
await installWorkflows()
|
|
1101
|
+
break
|
|
1102
|
+
case 'find-rules':
|
|
1103
|
+
await findRulesAndWorkflows()
|
|
1104
|
+
break
|
|
835
1105
|
}
|
|
836
1106
|
|
|
837
1107
|
p.outro('Done')
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Feature branch git workflow
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Creating a Feature Branch
|
|
6
|
+
|
|
7
|
+
1. Ensure you are on the latest `main` branch:
|
|
8
|
+
```bash
|
|
9
|
+
git checkout main
|
|
10
|
+
git pull origin main
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. Create and checkout a new feature branch:
|
|
14
|
+
```bash
|
|
15
|
+
git checkout -b feature/<ticket-id>-short-description
|
|
16
|
+
# Example: feature/BX-123-add-user-login
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## During Development
|
|
20
|
+
|
|
21
|
+
3. Commit frequently with meaningful messages following Conventional Commits:
|
|
22
|
+
```
|
|
23
|
+
feat: add login form validation
|
|
24
|
+
fix: handle empty email edge case
|
|
25
|
+
chore: update dependencies
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
4. Push branch to remote regularly:
|
|
29
|
+
```bash
|
|
30
|
+
git push origin feature/<branch-name>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Creating a Pull Request
|
|
34
|
+
|
|
35
|
+
5. Rebase on `main` before opening a PR to keep history clean:
|
|
36
|
+
```bash
|
|
37
|
+
git fetch origin
|
|
38
|
+
git rebase origin/main
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
6. Open a Pull Request with:
|
|
42
|
+
- Title matching the ticket: `feat: BX-123 Add user login`
|
|
43
|
+
- Description of changes and testing steps
|
|
44
|
+
- Link to the ticket/issue
|
|
45
|
+
|
|
46
|
+
## After Merge
|
|
47
|
+
|
|
48
|
+
7. Delete the feature branch locally and remotely:
|
|
49
|
+
```bash
|
|
50
|
+
git branch -d feature/<branch-name>
|
|
51
|
+
git push origin --delete feature/<branch-name>
|
|
52
|
+
```
|