prizmkit 1.0.15 → 1.0.17
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/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/lib/common.sh +13 -10
- package/bundled/skills/_metadata.json +6 -25
- package/bundled/skills/prizmkit-init/SKILL.md +28 -40
- package/bundled/templates/hooks/commit-intent-claude.json +11 -0
- package/bundled/templates/hooks/commit-intent-codebuddy.json +11 -0
- package/bundled/templates/hooks/diff-prizm-docs.sh +158 -0
- package/bundled/templates/hooks/prizm-pre-commit.sh +2 -1
- package/package.json +1 -1
- package/src/clean.js +108 -9
- package/src/external-skills.js +61 -52
- package/src/scaffold.js +29 -23
package/bundled/VERSION.json
CHANGED
|
@@ -32,9 +32,10 @@ log_success() { echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"
|
|
|
32
32
|
# PLATFORM
|
|
33
33
|
# PRIZMKIT_PLATFORM
|
|
34
34
|
prizm_detect_cli_and_platform() {
|
|
35
|
+
local _raw_cli=""
|
|
36
|
+
|
|
35
37
|
if [[ -n "${AI_CLI:-}" ]]; then
|
|
36
|
-
|
|
37
|
-
# Read from .prizmkit/config.json if present
|
|
38
|
+
_raw_cli="$AI_CLI"
|
|
38
39
|
elif [[ -f ".prizmkit/config.json" ]]; then
|
|
39
40
|
_config_ai_cli=$(python3 -c "
|
|
40
41
|
import json, sys
|
|
@@ -46,31 +47,33 @@ try:
|
|
|
46
47
|
except: pass
|
|
47
48
|
" 2>/dev/null || true)
|
|
48
49
|
if [[ -n "$_config_ai_cli" ]]; then
|
|
49
|
-
|
|
50
|
+
_raw_cli="$_config_ai_cli"
|
|
50
51
|
elif [[ -n "${CODEBUDDY_CLI:-}" ]]; then
|
|
51
|
-
|
|
52
|
+
_raw_cli="$CODEBUDDY_CLI"
|
|
52
53
|
elif command -v cbc &>/dev/null; then
|
|
53
|
-
|
|
54
|
+
_raw_cli="cbc"
|
|
54
55
|
elif command -v claude &>/dev/null; then
|
|
55
|
-
|
|
56
|
+
_raw_cli="claude"
|
|
56
57
|
else
|
|
57
58
|
echo "ERROR: No AI CLI found. Install CodeBuddy (cbc) or Claude Code (claude)." >&2
|
|
58
59
|
exit 1
|
|
59
60
|
fi
|
|
60
61
|
elif [[ -n "${CODEBUDDY_CLI:-}" ]]; then
|
|
61
|
-
|
|
62
|
+
_raw_cli="$CODEBUDDY_CLI"
|
|
62
63
|
elif command -v cbc &>/dev/null; then
|
|
63
|
-
|
|
64
|
+
_raw_cli="cbc"
|
|
64
65
|
elif command -v claude &>/dev/null; then
|
|
65
|
-
|
|
66
|
+
_raw_cli="claude"
|
|
66
67
|
else
|
|
67
68
|
echo "ERROR: No AI CLI found. Install CodeBuddy (cbc) or Claude Code (claude)." >&2
|
|
68
69
|
exit 1
|
|
69
70
|
fi
|
|
70
71
|
|
|
72
|
+
CLI_CMD="$_raw_cli"
|
|
73
|
+
|
|
71
74
|
if [[ -n "${PRIZMKIT_PLATFORM:-}" ]]; then
|
|
72
75
|
PLATFORM="$PRIZMKIT_PLATFORM"
|
|
73
|
-
elif [[ "$
|
|
76
|
+
elif [[ "$_raw_cli" == *"claude"* ]]; then
|
|
74
77
|
PLATFORM="claude"
|
|
75
78
|
else
|
|
76
79
|
PLATFORM="codebuddy"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.0.
|
|
2
|
+
"version": "1.0.17",
|
|
3
3
|
"skills": {
|
|
4
4
|
"prizm-kit": {
|
|
5
5
|
"description": "Full-lifecycle dev toolkit. Covers spec-driven development, Prizm context docs, code quality, debugging, deployment, and knowledge management.",
|
|
@@ -335,43 +335,24 @@
|
|
|
335
335
|
}
|
|
336
336
|
},
|
|
337
337
|
"external_skills": {
|
|
338
|
-
"registries": [
|
|
339
|
-
{
|
|
340
|
-
"name": "skills.sh",
|
|
341
|
-
"url": "https://skills.sh/registry.json",
|
|
342
|
-
"description": "Community skill registry"
|
|
343
|
-
}
|
|
344
|
-
],
|
|
345
338
|
"known": [
|
|
346
339
|
{
|
|
347
|
-
"name": "find-
|
|
348
|
-
"description": "
|
|
349
|
-
"
|
|
350
|
-
"registry": "skills.sh",
|
|
340
|
+
"name": "find-skills",
|
|
341
|
+
"description": "Helps discover and install agent skills by capability",
|
|
342
|
+
"repo": "https://github.com/vercel-labs/skills",
|
|
351
343
|
"tags": [
|
|
352
344
|
"search",
|
|
353
345
|
"utility"
|
|
354
346
|
]
|
|
355
347
|
},
|
|
356
348
|
{
|
|
357
|
-
"name": "
|
|
349
|
+
"name": "ui-ux-pro-max",
|
|
358
350
|
"description": "UI/UX design review and suggestions",
|
|
359
|
-
"
|
|
360
|
-
"registry": "skills.sh",
|
|
351
|
+
"repo": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill",
|
|
361
352
|
"tags": [
|
|
362
353
|
"design",
|
|
363
354
|
"frontend"
|
|
364
355
|
]
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
"name": "thinkthought",
|
|
368
|
-
"description": "Structured reasoning and problem decomposition",
|
|
369
|
-
"source": "https://skills.sh/skills/thinkthought/SKILL.md",
|
|
370
|
-
"registry": "skills.sh",
|
|
371
|
-
"tags": [
|
|
372
|
-
"reasoning",
|
|
373
|
-
"universal"
|
|
374
|
-
]
|
|
375
356
|
}
|
|
376
357
|
]
|
|
377
358
|
}
|
|
@@ -39,44 +39,32 @@ BROWNFIELD WORKFLOW (existing project):
|
|
|
39
39
|
4. Catalog dependencies (external packages)
|
|
40
40
|
5. Count source files per directory
|
|
41
41
|
|
|
42
|
-
**Step 2:
|
|
43
|
-
2a.
|
|
44
|
-
- Count outdated dependencies (if lockfile exists)
|
|
45
|
-
- Note any known vulnerability patterns
|
|
46
|
-
|
|
47
|
-
2b. Scan for technical debt indicators:
|
|
48
|
-
- Count TODO/FIXME/HACK/XXX comments
|
|
49
|
-
- Identify large files (>500 lines)
|
|
50
|
-
- Check for test directories and coverage config
|
|
51
|
-
|
|
52
|
-
2c. Generate `ASSESSMENT.md` in project root with findings
|
|
53
|
-
|
|
54
|
-
**Step 3: Prizm Documentation Generation**
|
|
55
|
-
3a. Invoke prizmkit-prizm-docs `prizmkit.doc.init` algorithm:
|
|
42
|
+
**Step 2: Prizm Documentation Generation**
|
|
43
|
+
2a. Invoke prizmkit-prizm-docs `prizmkit.doc.init` algorithm:
|
|
56
44
|
- Create `.prizm-docs/` directory structure
|
|
57
45
|
- Generate `root.prizm` (L0) with project meta and module index
|
|
58
46
|
- Generate L1 docs for all discovered modules
|
|
59
47
|
- Create `changelog.prizm`
|
|
60
48
|
- Skip L2 (lazy generation)
|
|
61
49
|
|
|
62
|
-
|
|
50
|
+
2b. If project has existing `docs/AI_CONTEXT/`: suggest running `prizmkit.doc.migrate`
|
|
63
51
|
|
|
64
|
-
**Step
|
|
65
|
-
|
|
52
|
+
**Step 3: PrizmKit Workspace Initialization**
|
|
53
|
+
3a. Create `.prizmkit/` directory:
|
|
66
54
|
- `.prizmkit/config.json` (adoption_mode, speckit_hooks_enabled, platform)
|
|
67
55
|
- `.prizmkit/specs/` (empty)
|
|
68
56
|
|
|
69
|
-
**Step
|
|
57
|
+
**Step 4: Hook & Settings Configuration (Platform-Specific)**
|
|
70
58
|
|
|
71
59
|
**If platform is CodeBuddy (or both):**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
60
|
+
4a-cb. Read or create `.codebuddy/settings.json`
|
|
61
|
+
4b-cb. Add UserPromptSubmit hook from `${SKILL_DIR}/../../../assets/hooks/prizm-commit-hook.json`
|
|
62
|
+
4c-cb. Preserve any existing hooks
|
|
75
63
|
|
|
76
64
|
**If platform is Claude Code (or both):**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
65
|
+
4a-cl. Read or create `.claude/settings.json`
|
|
66
|
+
4b-cl. Add `permissions` and `allowedTools` entries if needed
|
|
67
|
+
4c-cl. Create `.claude/rules/prizm-documentation.md` with glob-scoped rules:
|
|
80
68
|
```yaml
|
|
81
69
|
---
|
|
82
70
|
description: PrizmKit documentation rules
|
|
@@ -91,7 +79,7 @@ BROWNFIELD WORKFLOW (existing project):
|
|
|
91
79
|
2. After changes, update affected `.prizm-docs/` files
|
|
92
80
|
3. Follow Prizm doc format (KEY: value, not prose)
|
|
93
81
|
```
|
|
94
|
-
|
|
82
|
+
4d-cl. Create `.claude/rules/prizm-commit-workflow.md` with commit-scoped rules:
|
|
95
83
|
```yaml
|
|
96
84
|
---
|
|
97
85
|
description: PrizmKit commit workflow enforcement
|
|
@@ -105,33 +93,33 @@ BROWNFIELD WORKFLOW (existing project):
|
|
|
105
93
|
4. Stage .prizm-docs/ changes
|
|
106
94
|
5. Use /prizmkit-committer for the complete workflow
|
|
107
95
|
```
|
|
108
|
-
|
|
96
|
+
4e-cl. Preserve any existing Claude settings and rules
|
|
109
97
|
|
|
110
|
-
**Step
|
|
98
|
+
**Step 5: Project Memory Update (Platform-Specific)**
|
|
111
99
|
|
|
112
100
|
**If platform is CodeBuddy (or both):**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
101
|
+
5a-cb. Read existing `CODEBUDDY.md` (or create if missing)
|
|
102
|
+
5b-cb. Append PrizmKit section from `${SKILL_DIR}/../../../assets/codebuddy-md-template.md`
|
|
103
|
+
5c-cb. Do not duplicate if already present
|
|
116
104
|
|
|
117
105
|
**If platform is Claude Code (or both):**
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
106
|
+
5a-cl. Read existing `CLAUDE.md` (or create if missing)
|
|
107
|
+
5b-cl. Append PrizmKit section from `${SKILL_DIR}/../../../assets/claude-md-template.md`
|
|
108
|
+
5c-cl. Adjust command references to use `/command-name` format (not `prizmkit.xxx`)
|
|
109
|
+
5d-cl. Do not duplicate if already present
|
|
122
110
|
|
|
123
|
-
**Step
|
|
124
|
-
Output summary: platform detected, tech stack detected, modules discovered, L1 docs generated,
|
|
111
|
+
**Step 6: Report**
|
|
112
|
+
Output summary: platform detected, tech stack detected, modules discovered, L1 docs generated, platform-specific configuration applied, next recommended steps.
|
|
125
113
|
|
|
126
114
|
Include platform-specific guidance:
|
|
127
115
|
- CodeBuddy: "Use `prizmkit.specify` to start your first feature"
|
|
128
116
|
- Claude Code: "Use `/prizmkit-specify` to start your first feature"
|
|
129
117
|
|
|
130
118
|
GREENFIELD WORKFLOW (new project):
|
|
131
|
-
- Skip
|
|
132
|
-
- Step
|
|
133
|
-
- Steps
|
|
134
|
-
- Step
|
|
119
|
+
- Skip Step 1 (no code to scan)
|
|
120
|
+
- Step 2: Create minimal `.prizm-docs/` with just `root.prizm` skeleton
|
|
121
|
+
- Steps 3-5: Same as brownfield
|
|
122
|
+
- Step 6: Recommend starting with specify for first feature (platform-appropriate command format)
|
|
135
123
|
|
|
136
124
|
### Gradual Adoption Path
|
|
137
125
|
After init, PrizmKit operates in phases:
|
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
}
|
|
11
11
|
]
|
|
12
12
|
}
|
|
13
|
+
],
|
|
14
|
+
"PostToolUse": [
|
|
15
|
+
{
|
|
16
|
+
"matcher": "Bash",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "if echo \"$CLAUDE_TOOL_INPUT\" 2>/dev/null | grep -qE 'git (commit|merge|rebase)' 2>/dev/null; then DRIFT=$(sh .prizmkit/scripts/diff-prizm-docs.sh 2>/dev/null); if [ -n \"$DRIFT\" ]; then echo \"PRIZMKIT_DRIFT_DETECTED: prizm-docs structural differences found after git operation:\"; echo \"$DRIFT\"; echo \"Please update affected .prizm-docs/ files using /prizmkit-prizm-docs.\"; fi; fi"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
13
24
|
]
|
|
14
25
|
}
|
|
15
26
|
}
|
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
}
|
|
11
11
|
]
|
|
12
12
|
}
|
|
13
|
+
],
|
|
14
|
+
"PostToolUse": [
|
|
15
|
+
{
|
|
16
|
+
"matcher": "Bash",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "TOOL_INPUT=\"${CLAUDE_TOOL_INPUT:-${CODEBUDDY_TOOL_INPUT:-}}\"; if echo \"$TOOL_INPUT\" | grep -qE 'git (commit|merge|rebase)' 2>/dev/null; then DRIFT=$(sh .prizmkit/scripts/diff-prizm-docs.sh 2>/dev/null); if [ -n \"$DRIFT\" ]; then echo \"PRIZMKIT_DRIFT_DETECTED: prizm-docs structural differences found:\"; echo \"$DRIFT\"; echo \"Please update affected .prizm-docs/ files.\"; fi; fi"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
13
24
|
]
|
|
14
25
|
}
|
|
15
26
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# PrizmKit: diff-prizm-docs.sh
|
|
3
|
+
# Outputs structural differences between .prizm-docs/ and source code.
|
|
4
|
+
# Silent (exit 0) if no differences or if .prizm-docs doesn't exist.
|
|
5
|
+
# POSIX sh only — no arrays, no bashisms.
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
ROOT_PRIZM=".prizm-docs/root.prizm"
|
|
10
|
+
|
|
11
|
+
# 1. Exit silently if no root.prizm
|
|
12
|
+
[ -f "$ROOT_PRIZM" ] || exit 0
|
|
13
|
+
|
|
14
|
+
# 2. Detect language from LANG: field
|
|
15
|
+
LANG_VAL=$(grep '^LANG:' "$ROOT_PRIZM" | head -1 | sed 's/^LANG:[[:space:]]*//' | tr '[:upper:]' '[:lower:]')
|
|
16
|
+
|
|
17
|
+
case "$LANG_VAL" in
|
|
18
|
+
go)
|
|
19
|
+
FILE_PATTERN="*.go"
|
|
20
|
+
;;
|
|
21
|
+
javascript|typescript|javascript/typescript|typescript/javascript)
|
|
22
|
+
FILE_PATTERN="*.ts *.tsx *.js *.jsx"
|
|
23
|
+
;;
|
|
24
|
+
python)
|
|
25
|
+
FILE_PATTERN="*.py"
|
|
26
|
+
;;
|
|
27
|
+
rust)
|
|
28
|
+
FILE_PATTERN="*.rs"
|
|
29
|
+
;;
|
|
30
|
+
*)
|
|
31
|
+
FILE_PATTERN=""
|
|
32
|
+
;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
# Helper: count source files in a directory (maxdepth 1) by language
|
|
36
|
+
count_source_files() {
|
|
37
|
+
_dir="$1"
|
|
38
|
+
[ -d "$_dir" ] || { echo 0; return; }
|
|
39
|
+
if [ -z "$FILE_PATTERN" ]; then
|
|
40
|
+
# All non-hidden files
|
|
41
|
+
find "$_dir" -maxdepth 1 -type f ! -name '.*' 2>/dev/null | wc -l | tr -d ' '
|
|
42
|
+
else
|
|
43
|
+
_count=0
|
|
44
|
+
for _pat in $FILE_PATTERN; do
|
|
45
|
+
_c=$(find "$_dir" -maxdepth 1 -type f -name "$_pat" 2>/dev/null | wc -l | tr -d ' ')
|
|
46
|
+
_count=$((_count + _c))
|
|
47
|
+
done
|
|
48
|
+
echo "$_count"
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# 3. Parse MODULE_INDEX from root.prizm
|
|
53
|
+
# Extract lines between MODULE_INDEX: and next ALL-CAPS header or EOF
|
|
54
|
+
# Each module line: "- src/auth: ..." -> source path is text before first ":" after "- "
|
|
55
|
+
REGISTERED_PATHS=""
|
|
56
|
+
_in_index=0
|
|
57
|
+
while IFS= read -r line; do
|
|
58
|
+
case "$line" in
|
|
59
|
+
MODULE_INDEX:*)
|
|
60
|
+
_in_index=1
|
|
61
|
+
continue
|
|
62
|
+
;;
|
|
63
|
+
esac
|
|
64
|
+
if [ "$_in_index" -eq 1 ]; then
|
|
65
|
+
# Check for next ALL-CAPS section header (word followed by colon, all uppercase letters)
|
|
66
|
+
_header=$(echo "$line" | grep '^[A-Z_][A-Z_]*:' || true)
|
|
67
|
+
if [ -n "$_header" ]; then
|
|
68
|
+
break
|
|
69
|
+
fi
|
|
70
|
+
# Extract source path from "- src/auth: ..."
|
|
71
|
+
case "$line" in
|
|
72
|
+
"- "*)
|
|
73
|
+
_path=$(echo "$line" | sed 's/^- //' | sed 's/:.*//')
|
|
74
|
+
if [ -n "$_path" ]; then
|
|
75
|
+
REGISTERED_PATHS="${REGISTERED_PATHS}${_path}
|
|
76
|
+
"
|
|
77
|
+
fi
|
|
78
|
+
;;
|
|
79
|
+
esac
|
|
80
|
+
fi
|
|
81
|
+
done < "$ROOT_PRIZM"
|
|
82
|
+
|
|
83
|
+
# 4. Check each registered module for drift
|
|
84
|
+
echo "$REGISTERED_PATHS" | while IFS= read -r source_path; do
|
|
85
|
+
[ -z "$source_path" ] && continue
|
|
86
|
+
|
|
87
|
+
# Derive L1 prizm file path
|
|
88
|
+
l1_file=".prizm-docs/${source_path}.prizm"
|
|
89
|
+
|
|
90
|
+
if [ ! -f "$l1_file" ]; then
|
|
91
|
+
continue
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Read declared FILES: count
|
|
95
|
+
declared=$(grep '^FILES:' "$l1_file" | head -1 | awk '{print $2}')
|
|
96
|
+
[ -z "$declared" ] && continue
|
|
97
|
+
|
|
98
|
+
# Count actual source files
|
|
99
|
+
actual=$(count_source_files "$source_path")
|
|
100
|
+
|
|
101
|
+
if [ "$actual" -gt 0 ] && [ "$declared" != "$actual" ]; then
|
|
102
|
+
echo "MODULE_DRIFT: $source_path | declared FILES: $declared | actual: $actual"
|
|
103
|
+
fi
|
|
104
|
+
done
|
|
105
|
+
|
|
106
|
+
# 5. Find orphan docs
|
|
107
|
+
find .prizm-docs -name '*.prizm' 2>/dev/null | while IFS= read -r prizm_file; do
|
|
108
|
+
_basename=$(basename "$prizm_file")
|
|
109
|
+
case "$_basename" in
|
|
110
|
+
root.prizm|changelog.prizm|changelog-archive.prizm)
|
|
111
|
+
continue
|
|
112
|
+
;;
|
|
113
|
+
esac
|
|
114
|
+
|
|
115
|
+
# Derive source path: remove .prizm-docs/ prefix and .prizm suffix
|
|
116
|
+
source_path=$(echo "$prizm_file" | sed 's|^\.prizm-docs/||' | sed 's|\.prizm$||')
|
|
117
|
+
|
|
118
|
+
if [ ! -d "$source_path" ]; then
|
|
119
|
+
echo "ORPHAN_DOC: $prizm_file | source dir missing"
|
|
120
|
+
fi
|
|
121
|
+
done
|
|
122
|
+
|
|
123
|
+
# 6. Find unregistered directories
|
|
124
|
+
find . -mindepth 1 -maxdepth 3 -type d \
|
|
125
|
+
! -path '*/.*' \
|
|
126
|
+
! -path '*/node_modules/*' ! -path './node_modules' \
|
|
127
|
+
! -path '*/vendor/*' ! -path './vendor' \
|
|
128
|
+
! -path '*/dist/*' ! -path './dist' \
|
|
129
|
+
! -path '*/build/*' ! -path './build' \
|
|
130
|
+
! -path '*/__pycache__/*' ! -path './__pycache__' \
|
|
131
|
+
! -path '*/target/*' ! -path './target' \
|
|
132
|
+
! -path '*/tests/*' ! -path './tests' \
|
|
133
|
+
! -path '*/__tests__/*' ! -path './__tests__' \
|
|
134
|
+
! -path '*/.prizm-docs/*' ! -path './.prizm-docs' \
|
|
135
|
+
! -path '*/.claude/*' ! -path './.claude' \
|
|
136
|
+
! -path '*/.codebuddy/*' ! -path './.codebuddy' \
|
|
137
|
+
! -path '*/dev-pipeline/*' ! -path './dev-pipeline' \
|
|
138
|
+
! -path '*/coverage/*' ! -path './coverage' \
|
|
139
|
+
! -path '*/.nyc_output/*' ! -path './.nyc_output' \
|
|
140
|
+
! -path '*/out/*' ! -path './out' \
|
|
141
|
+
! -path '*/tmp/*' ! -path './tmp' \
|
|
142
|
+
2>/dev/null | while IFS= read -r candidate; do
|
|
143
|
+
# Normalize: remove leading ./
|
|
144
|
+
candidate_clean=$(echo "$candidate" | sed 's|^\./||')
|
|
145
|
+
|
|
146
|
+
# Count source files
|
|
147
|
+
file_count=$(count_source_files "$candidate_clean")
|
|
148
|
+
|
|
149
|
+
if [ "$file_count" -ge 3 ]; then
|
|
150
|
+
# Check if registered in MODULE_INDEX
|
|
151
|
+
_match=$(echo "$REGISTERED_PATHS" | grep -Fx "$candidate_clean" || true)
|
|
152
|
+
if [ -z "$_match" ]; then
|
|
153
|
+
echo "UNREGISTERED: $candidate_clean | $file_count source files | not in MODULE_INDEX"
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
done
|
|
157
|
+
|
|
158
|
+
exit 0
|
|
@@ -6,7 +6,8 @@ PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
|
|
|
6
6
|
# Only run in prizm projects
|
|
7
7
|
[ -f "$PROJECT_ROOT/.prizm-docs/root.prizm" ] || exit 0
|
|
8
8
|
|
|
9
|
-
VALIDATE_SCRIPT="$PROJECT_ROOT/
|
|
9
|
+
VALIDATE_SCRIPT="$PROJECT_ROOT/.prizmkit/scripts/validate-prizm-docs.sh"
|
|
10
|
+
[ -f "$VALIDATE_SCRIPT" ] || VALIDATE_SCRIPT="$PROJECT_ROOT/dev-pipeline/scripts/validate-prizm-docs.sh"
|
|
10
11
|
|
|
11
12
|
if [ -f "$VALIDATE_SCRIPT" ]; then
|
|
12
13
|
sh "$VALIDATE_SCRIPT" --staged
|
package/package.json
CHANGED
package/src/clean.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
|
+
import { loadMetadata, loadRulesMetadata } from './metadata.js';
|
|
4
5
|
|
|
5
6
|
async function removePath(targetPath, dryRun) {
|
|
6
7
|
if (!await fs.pathExists(targetPath)) {
|
|
@@ -13,6 +14,38 @@ async function removePath(targetPath, dryRun) {
|
|
|
13
14
|
return { path: targetPath, removed: true };
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Remove known PrizmKit files from a shared directory (skills, agents, rules, commands).
|
|
19
|
+
* Leaves user-created files untouched. Removes the parent dir if it becomes empty.
|
|
20
|
+
*/
|
|
21
|
+
async function removeKnownEntries(parentDir, entries, dryRun) {
|
|
22
|
+
const results = [];
|
|
23
|
+
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const abs = path.join(parentDir, entry);
|
|
26
|
+
if (!await fs.pathExists(abs)) continue;
|
|
27
|
+
|
|
28
|
+
if (dryRun) {
|
|
29
|
+
results.push({ path: abs, removed: false, reason: 'dry_run' });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await fs.remove(abs);
|
|
34
|
+
results.push({ path: abs, removed: true });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Remove parent dir only if it now exists and is empty
|
|
38
|
+
if (!dryRun && await fs.pathExists(parentDir)) {
|
|
39
|
+
const remaining = await fs.readdir(parentDir);
|
|
40
|
+
if (remaining.length === 0) {
|
|
41
|
+
await fs.remove(parentDir);
|
|
42
|
+
results.push({ path: parentDir, removed: true, note: '(empty dir)' });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return results;
|
|
47
|
+
}
|
|
48
|
+
|
|
16
49
|
async function cleanGitignore(projectRoot, dryRun) {
|
|
17
50
|
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
18
51
|
if (!await fs.pathExists(gitignorePath)) {
|
|
@@ -48,30 +81,78 @@ export async function runClean(directory, options = {}) {
|
|
|
48
81
|
console.log(` 预览模式: ${dryRun ? chalk.yellow('是') : chalk.green('否')}`);
|
|
49
82
|
console.log('');
|
|
50
83
|
|
|
51
|
-
|
|
84
|
+
// Load metadata to know exactly which files PrizmKit owns
|
|
85
|
+
const [metadata, rulesMeta] = await Promise.all([
|
|
86
|
+
loadMetadata().catch(() => ({ skills: {}, external_skills: { known: [] } })),
|
|
87
|
+
loadRulesMetadata().catch(() => ({ rules: {} })),
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
const skillNames = Object.keys(metadata.skills);
|
|
91
|
+
const externalSkillNames = (metadata.external_skills?.known || []).map(s => s.name);
|
|
92
|
+
const allSkillNames = [...new Set([...skillNames, ...externalSkillNames])];
|
|
93
|
+
|
|
94
|
+
// Agent file names installed by PrizmKit
|
|
95
|
+
const agentFiles = [
|
|
96
|
+
'prizm-dev-team-coordinator.md',
|
|
97
|
+
'prizm-dev-team-dev.md',
|
|
98
|
+
'prizm-dev-team-pm.md',
|
|
99
|
+
'prizm-dev-team-reviewer.md',
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
// Rule file names installed by PrizmKit (basename without category prefix)
|
|
103
|
+
const ruleFileNames = Object.keys(metadata_rules_to_filenames(rulesMeta));
|
|
104
|
+
|
|
105
|
+
const results = [];
|
|
106
|
+
|
|
107
|
+
// ── Full removes (100% PrizmKit-owned paths) ──────────────────────────────
|
|
108
|
+
const fullRemoveTargets = [
|
|
52
109
|
'.prizmkit',
|
|
53
110
|
'.dev-team',
|
|
54
111
|
'dev-pipeline',
|
|
112
|
+
'.prizm-docs', // AI-generated project context docs
|
|
55
113
|
'CODEBUDDY.md',
|
|
56
114
|
'CLAUDE.md',
|
|
57
|
-
path.join('.codebuddy', 'skills'),
|
|
58
|
-
path.join('.codebuddy', 'agents'),
|
|
59
115
|
path.join('.codebuddy', 'settings.json'),
|
|
60
|
-
path.join('.claude', 'commands'),
|
|
61
|
-
path.join('.claude', 'agents'),
|
|
62
|
-
path.join('.claude', 'rules'),
|
|
63
116
|
path.join('.claude', 'settings.json'),
|
|
64
117
|
path.join('.claude', 'team-info.json'),
|
|
65
118
|
];
|
|
66
119
|
|
|
67
|
-
const
|
|
68
|
-
for (const rel of targets) {
|
|
120
|
+
for (const rel of fullRemoveTargets) {
|
|
69
121
|
const abs = path.join(projectRoot, rel);
|
|
70
122
|
results.push(await removePath(abs, dryRun));
|
|
71
123
|
}
|
|
72
124
|
|
|
125
|
+
// ── Selective removes (shared dirs — only remove PrizmKit-known entries) ──
|
|
126
|
+
|
|
127
|
+
// .claude/commands/ — skills installed as .md files or subdirs
|
|
128
|
+
const claudeCommandsDir = path.join(projectRoot, '.claude', 'commands');
|
|
129
|
+
const claudeCommandEntries = allSkillNames.flatMap(name => [name, `${name}.md`]);
|
|
130
|
+
results.push(...await removeKnownEntries(claudeCommandsDir, claudeCommandEntries, dryRun));
|
|
131
|
+
|
|
132
|
+
// .codebuddy/skills/ — skills installed as subdirectories
|
|
133
|
+
const cbSkillsDir = path.join(projectRoot, '.codebuddy', 'skills');
|
|
134
|
+
results.push(...await removeKnownEntries(cbSkillsDir, allSkillNames, dryRun));
|
|
135
|
+
|
|
136
|
+
// .claude/agents/ — PrizmKit agent files
|
|
137
|
+
const claudeAgentsDir = path.join(projectRoot, '.claude', 'agents');
|
|
138
|
+
results.push(...await removeKnownEntries(claudeAgentsDir, agentFiles, dryRun));
|
|
139
|
+
|
|
140
|
+
// .codebuddy/agents/ — same agent files
|
|
141
|
+
const cbAgentsDir = path.join(projectRoot, '.codebuddy', 'agents');
|
|
142
|
+
results.push(...await removeKnownEntries(cbAgentsDir, agentFiles, dryRun));
|
|
143
|
+
|
|
144
|
+
// .claude/rules/ — PrizmKit rule .md files
|
|
145
|
+
const claudeRulesDir = path.join(projectRoot, '.claude', 'rules');
|
|
146
|
+
results.push(...await removeKnownEntries(claudeRulesDir, ruleFileNames.map(n => `${n}.md`), dryRun));
|
|
147
|
+
|
|
148
|
+
// .codebuddy/rules/ — PrizmKit rule .mdc files
|
|
149
|
+
const cbRulesDir = path.join(projectRoot, '.codebuddy', 'rules');
|
|
150
|
+
results.push(...await removeKnownEntries(cbRulesDir, ruleFileNames.map(n => `${n}.mdc`), dryRun));
|
|
151
|
+
|
|
152
|
+
// ── .gitignore cleanup ────────────────────────────────────────────────────
|
|
73
153
|
const gitignoreResult = await cleanGitignore(projectRoot, dryRun);
|
|
74
154
|
|
|
155
|
+
// ── Global team (optional) ────────────────────────────────────────────────
|
|
75
156
|
if (cleanGlobalTeam) {
|
|
76
157
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
77
158
|
if (homeDir) {
|
|
@@ -80,13 +161,15 @@ export async function runClean(directory, options = {}) {
|
|
|
80
161
|
}
|
|
81
162
|
}
|
|
82
163
|
|
|
164
|
+
// ── Output ────────────────────────────────────────────────────────────────
|
|
83
165
|
const removed = results.filter(r => r.removed).length;
|
|
84
166
|
const found = results.filter(r => r.reason !== 'not_found').length;
|
|
85
167
|
|
|
86
168
|
console.log(chalk.bold(' 清理结果:'));
|
|
87
169
|
for (const item of results) {
|
|
88
170
|
if (item.removed) {
|
|
89
|
-
|
|
171
|
+
const note = item.note ? chalk.gray(` ${item.note}`) : '';
|
|
172
|
+
console.log(chalk.green(` ✓ 已删除: ${item.path}`) + note);
|
|
90
173
|
} else if (item.reason === 'dry_run') {
|
|
91
174
|
console.log(chalk.gray(` [dry-run] ${item.path}`));
|
|
92
175
|
}
|
|
@@ -107,4 +190,20 @@ export async function runClean(directory, options = {}) {
|
|
|
107
190
|
console.log(chalk.green(` 清理完成:已删除 ${removed} 项。`));
|
|
108
191
|
}
|
|
109
192
|
console.log('');
|
|
193
|
+
console.log(chalk.gray(' 注意:.prizm-docs/ 为 AI 生成的项目上下文文档,已一并清理。'));
|
|
194
|
+
console.log(chalk.gray(' 用户自定义的 agent/skill/rule 文件未受影响。'));
|
|
195
|
+
console.log('');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Extract base file names from rules metadata (strip category prefix).
|
|
200
|
+
* e.g. "prizm/prizm-documentation" -> "prizm-documentation"
|
|
201
|
+
*/
|
|
202
|
+
function metadata_rules_to_filenames(rulesMeta) {
|
|
203
|
+
const names = {};
|
|
204
|
+
for (const key of Object.keys(rulesMeta.rules || {})) {
|
|
205
|
+
const basename = key.split('/').pop();
|
|
206
|
+
names[basename] = true;
|
|
207
|
+
}
|
|
208
|
+
return names;
|
|
110
209
|
}
|
package/src/external-skills.js
CHANGED
|
@@ -1,71 +1,80 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External Skills Installer
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Delegates to `npx skills add <repo> --skill <name> --yes` for installation,
|
|
5
|
+
* then removes symlinks for platforms not selected by the user.
|
|
6
|
+
*
|
|
7
|
+
* npx skills creates symlinks for ALL platforms (.claude/skills/, .codebuddy/skills/,
|
|
8
|
+
* .trae/skills/, skills/, .agent/skills/). We keep only what the user asked for.
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
|
-
import
|
|
7
|
-
import http from 'node:http';
|
|
11
|
+
import { execSync } from 'node:child_process';
|
|
8
12
|
import path from 'path';
|
|
9
13
|
import fs from 'fs-extra';
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const protocol = url.startsWith('https://') ? https : http;
|
|
20
|
-
|
|
21
|
-
const request = protocol.get(url, (res) => {
|
|
22
|
-
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
23
|
-
// Follow redirect
|
|
24
|
-
const redirectSkill = { ...skill, source: res.headers.location };
|
|
25
|
-
resolve(fetchRemoteSkill(redirectSkill));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
15
|
+
// All platform symlink dirs that `npx skills` creates (besides .agents/ canonical store)
|
|
16
|
+
const ALL_PLATFORM_DIRS = [
|
|
17
|
+
'.claude/skills',
|
|
18
|
+
'.codebuddy/skills',
|
|
19
|
+
'.trae/skills',
|
|
20
|
+
'.agent/skills',
|
|
21
|
+
'skills',
|
|
22
|
+
];
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
res.on('data', chunk => chunks.push(chunk));
|
|
36
|
-
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
37
|
-
res.on('error', reject);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
request.on('error', reject);
|
|
41
|
-
request.setTimeout(15000, () => {
|
|
42
|
-
request.destroy();
|
|
43
|
-
reject(new Error(`Timeout fetching ${url}`));
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
}
|
|
24
|
+
// Which dirs to KEEP per platform selection
|
|
25
|
+
const KEEP_DIRS = {
|
|
26
|
+
claude: ['.claude/skills'],
|
|
27
|
+
codebuddy: ['.codebuddy/skills'],
|
|
28
|
+
both: ['.claude/skills', '.codebuddy/skills'],
|
|
29
|
+
};
|
|
47
30
|
|
|
48
31
|
/**
|
|
49
|
-
* Install an external skill
|
|
50
|
-
*
|
|
51
|
-
* @param {
|
|
52
|
-
*
|
|
53
|
-
*
|
|
32
|
+
* Install an external skill via `npx skills add`, then prune unwanted platform symlinks.
|
|
33
|
+
*
|
|
34
|
+
* @param {Object} skill - Skill definition from external_skills.known
|
|
35
|
+
* skill.repo {string} - GitHub URL
|
|
36
|
+
* skill.name {string} - Skill name as listed in the repo
|
|
37
|
+
* @param {string} platform - 'claude' | 'codebuddy' | 'both'
|
|
38
|
+
* @param {string} projectRoot - Target project root
|
|
39
|
+
* @param {boolean} dryRun
|
|
54
40
|
*/
|
|
55
41
|
export async function installExternalSkill(skill, platform, projectRoot, dryRun) {
|
|
56
|
-
|
|
42
|
+
if (!skill.repo) {
|
|
43
|
+
throw new Error(`Skill "${skill.name}" has no repo URL defined`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const cmd = `npx skills add ${skill.repo} --skill ${skill.name} --yes`;
|
|
57
47
|
|
|
58
48
|
if (dryRun) {
|
|
49
|
+
const keepDirs = KEEP_DIRS[platform] || KEEP_DIRS.claude;
|
|
50
|
+
console.log(` [dry-run] ${cmd}`);
|
|
51
|
+
console.log(` [dry-run] keep symlinks: ${keepDirs.map(d => path.join(d, skill.name)).join(', ')}`);
|
|
59
52
|
return;
|
|
60
53
|
}
|
|
61
54
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
55
|
+
execSync(cmd, {
|
|
56
|
+
cwd: projectRoot,
|
|
57
|
+
stdio: 'pipe',
|
|
58
|
+
timeout: 60000,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Prune symlinks for platforms not selected
|
|
62
|
+
const keepDirs = new Set(KEEP_DIRS[platform] || KEEP_DIRS.claude);
|
|
63
|
+
|
|
64
|
+
for (const dir of ALL_PLATFORM_DIRS) {
|
|
65
|
+
const linkPath = path.join(projectRoot, dir, skill.name);
|
|
66
|
+
if (keepDirs.has(dir)) continue;
|
|
67
|
+
try {
|
|
68
|
+
const stat = await fs.lstat(linkPath);
|
|
69
|
+
if (stat.isSymbolicLink()) {
|
|
70
|
+
await fs.remove(linkPath);
|
|
71
|
+
// Remove parent dir if now empty
|
|
72
|
+
const parent = path.join(projectRoot, dir);
|
|
73
|
+
const remaining = await fs.readdir(parent).catch(() => ['_']);
|
|
74
|
+
if (remaining.length === 0) await fs.remove(parent);
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// not present, skip
|
|
78
|
+
}
|
|
70
79
|
}
|
|
71
80
|
}
|
package/src/scaffold.js
CHANGED
|
@@ -472,25 +472,34 @@ async function installGitHook(projectRoot, dryRun) {
|
|
|
472
472
|
}
|
|
473
473
|
|
|
474
474
|
/**
|
|
475
|
-
* 安装
|
|
475
|
+
* 安装 PrizmKit 脚本到 .prizmkit/scripts/(始终安装)
|
|
476
476
|
*/
|
|
477
|
-
async function
|
|
478
|
-
const
|
|
479
|
-
|
|
477
|
+
async function installPrizmkitScripts(projectRoot, dryRun) {
|
|
478
|
+
const scriptsDir = path.join(projectRoot, '.prizmkit', 'scripts');
|
|
479
|
+
const templateHooksDir = path.join(getTemplatesDir(), 'hooks');
|
|
480
480
|
|
|
481
|
-
const
|
|
482
|
-
|
|
481
|
+
const scripts = [
|
|
482
|
+
'validate-prizm-docs.sh',
|
|
483
|
+
'diff-prizm-docs.sh',
|
|
484
|
+
];
|
|
483
485
|
|
|
484
486
|
if (dryRun) {
|
|
485
|
-
|
|
487
|
+
for (const s of scripts) {
|
|
488
|
+
console.log(chalk.gray(` [dry-run] .prizmkit/scripts/${s}`));
|
|
489
|
+
}
|
|
486
490
|
return;
|
|
487
491
|
}
|
|
488
492
|
|
|
489
|
-
await fs.ensureDir(
|
|
490
|
-
await fs.copy(sourcePath, targetPath);
|
|
491
|
-
fs.chmodSync(targetPath, 0o755);
|
|
493
|
+
await fs.ensureDir(scriptsDir);
|
|
492
494
|
|
|
493
|
-
|
|
495
|
+
for (const s of scripts) {
|
|
496
|
+
const src = path.join(templateHooksDir, s);
|
|
497
|
+
if (!fs.existsSync(src)) continue;
|
|
498
|
+
const tgt = path.join(scriptsDir, s);
|
|
499
|
+
await fs.copy(src, tgt);
|
|
500
|
+
fs.chmodSync(tgt, 0o755);
|
|
501
|
+
console.log(chalk.green(` ✓ .prizmkit/scripts/${s}`));
|
|
502
|
+
}
|
|
494
503
|
}
|
|
495
504
|
|
|
496
505
|
/**
|
|
@@ -695,13 +704,12 @@ export async function scaffold(config) {
|
|
|
695
704
|
for (const name of externalSkills) {
|
|
696
705
|
const skillDef = knownSkills.find(s => s.name === name);
|
|
697
706
|
if (skillDef) {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
}
|
|
707
|
+
try {
|
|
708
|
+
// npx skills add handles installation; we pass platform to prune unwanted symlinks
|
|
709
|
+
await installExternalSkill(skillDef, platform, projectRoot, dryRun);
|
|
710
|
+
console.log(chalk.green(` ✓ ${name} (external)`));
|
|
711
|
+
} catch (e) {
|
|
712
|
+
console.log(chalk.yellow(` ⚠ ${name} 安装失败,跳过: ${e.message}`));
|
|
705
713
|
}
|
|
706
714
|
}
|
|
707
715
|
}
|
|
@@ -711,11 +719,9 @@ export async function scaffold(config) {
|
|
|
711
719
|
console.log(chalk.blue(' Git Hook:'));
|
|
712
720
|
await installGitHook(projectRoot, dryRun);
|
|
713
721
|
|
|
714
|
-
// 11.
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
await installValidateScript(projectRoot, dryRun);
|
|
718
|
-
}
|
|
722
|
+
// 11. PrizmKit scripts (always installed)
|
|
723
|
+
console.log(chalk.blue(' PrizmKit 脚本:'));
|
|
724
|
+
await installPrizmkitScripts(projectRoot, dryRun);
|
|
719
725
|
|
|
720
726
|
// === 完成 ===
|
|
721
727
|
console.log('');
|