bmad-method-test-architecture-enterprise 1.16.0 → 1.17.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/.claude-plugin/marketplace.json +1 -1
- package/.github/workflows/docs.yaml +3 -3
- package/.github/workflows/publish.yaml +25 -4
- package/.github/workflows/quality.yaml +10 -13
- package/AGENTS.md +31 -0
- package/CHANGELOG.md +34 -1
- package/README.md +4 -24
- package/package.json +2 -3
- package/tools/validate-marketplace.js +0 -186
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"name": "bmad-method-test-architecture-enterprise",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"description": "Master Test Architect module for quality strategy, test automation, CI/CD quality gates, and structured testing education. Part of the BMad Method ecosystem.",
|
|
15
|
-
"version": "1.
|
|
15
|
+
"version": "1.17.0",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "Murat K Ozcan (TEA Creator) & Brian (BMad) Madison"
|
|
18
18
|
},
|
|
@@ -25,14 +25,14 @@ jobs:
|
|
|
25
25
|
runs-on: ubuntu-latest
|
|
26
26
|
steps:
|
|
27
27
|
- name: Checkout repository
|
|
28
|
-
uses: actions/checkout@
|
|
28
|
+
uses: actions/checkout@v5
|
|
29
29
|
with:
|
|
30
30
|
fetch-depth: 0
|
|
31
31
|
|
|
32
32
|
- name: Setup Node.js
|
|
33
|
-
uses: actions/setup-node@
|
|
33
|
+
uses: actions/setup-node@v6
|
|
34
34
|
with:
|
|
35
|
-
node-version: "
|
|
35
|
+
node-version-file: ".nvmrc"
|
|
36
36
|
cache: "npm"
|
|
37
37
|
|
|
38
38
|
- name: Install root dependencies
|
|
@@ -43,19 +43,19 @@ jobs:
|
|
|
43
43
|
- name: Generate GitHub App token
|
|
44
44
|
id: app-token
|
|
45
45
|
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'latest'
|
|
46
|
-
uses: actions/create-github-app-token@
|
|
46
|
+
uses: actions/create-github-app-token@v3
|
|
47
47
|
with:
|
|
48
48
|
app-id: ${{ secrets.RELEASE_APP_ID }}
|
|
49
49
|
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
|
50
50
|
|
|
51
51
|
- name: Checkout
|
|
52
|
-
uses: actions/checkout@
|
|
52
|
+
uses: actions/checkout@v5
|
|
53
53
|
with:
|
|
54
54
|
fetch-depth: 0
|
|
55
55
|
token: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }}
|
|
56
56
|
|
|
57
57
|
- name: Setup Node
|
|
58
|
-
uses: actions/setup-node@
|
|
58
|
+
uses: actions/setup-node@v6
|
|
59
59
|
with:
|
|
60
60
|
node-version-file: ".nvmrc"
|
|
61
61
|
cache: "npm"
|
|
@@ -150,6 +150,14 @@ jobs:
|
|
|
150
150
|
run: |
|
|
151
151
|
TAG="v$(node -p 'require("./package.json").version')"
|
|
152
152
|
VERSION="${TAG#v}"
|
|
153
|
+
has_release_notes() {
|
|
154
|
+
awk '
|
|
155
|
+
/^[[:space:]]*$/ { next }
|
|
156
|
+
/^[[:space:]]*-{3,}[[:space:]]*$/ { next }
|
|
157
|
+
{ found = 1 }
|
|
158
|
+
END { exit found ? 0 : 1 }
|
|
159
|
+
' <<< "$1"
|
|
160
|
+
}
|
|
153
161
|
BODY=$(awk -v ver="$VERSION" '
|
|
154
162
|
/^## / {
|
|
155
163
|
if (found) exit
|
|
@@ -158,7 +166,20 @@ jobs:
|
|
|
158
166
|
}
|
|
159
167
|
found { print }
|
|
160
168
|
' CHANGELOG.md)
|
|
161
|
-
if
|
|
169
|
+
if ! has_release_notes "$BODY"; then
|
|
170
|
+
BODY=$(awk '
|
|
171
|
+
/^##[[:space:]]*\[Unreleased\]/ { found=1; next }
|
|
172
|
+
/^## / {
|
|
173
|
+
if (found) exit
|
|
174
|
+
next
|
|
175
|
+
}
|
|
176
|
+
found { print }
|
|
177
|
+
' CHANGELOG.md)
|
|
178
|
+
if has_release_notes "$BODY"; then
|
|
179
|
+
echo "::notice::No CHANGELOG.md entry for $TAG — using [Unreleased] notes"
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
if ! has_release_notes "$BODY"; then
|
|
162
183
|
echo "::warning::No CHANGELOG.md entry for $TAG — falling back to auto-generated notes"
|
|
163
184
|
gh release create "$TAG" --generate-notes
|
|
164
185
|
else
|
|
@@ -18,10 +18,10 @@ jobs:
|
|
|
18
18
|
runs-on: ubuntu-latest
|
|
19
19
|
steps:
|
|
20
20
|
- name: Checkout
|
|
21
|
-
uses: actions/checkout@
|
|
21
|
+
uses: actions/checkout@v5
|
|
22
22
|
|
|
23
23
|
- name: Setup Node
|
|
24
|
-
uses: actions/setup-node@
|
|
24
|
+
uses: actions/setup-node@v6
|
|
25
25
|
with:
|
|
26
26
|
node-version-file: ".nvmrc"
|
|
27
27
|
cache: "npm"
|
|
@@ -36,10 +36,10 @@ jobs:
|
|
|
36
36
|
runs-on: ubuntu-latest
|
|
37
37
|
steps:
|
|
38
38
|
- name: Checkout
|
|
39
|
-
uses: actions/checkout@
|
|
39
|
+
uses: actions/checkout@v5
|
|
40
40
|
|
|
41
41
|
- name: Setup Node
|
|
42
|
-
uses: actions/setup-node@
|
|
42
|
+
uses: actions/setup-node@v6
|
|
43
43
|
with:
|
|
44
44
|
node-version-file: ".nvmrc"
|
|
45
45
|
cache: "npm"
|
|
@@ -54,10 +54,10 @@ jobs:
|
|
|
54
54
|
runs-on: ubuntu-latest
|
|
55
55
|
steps:
|
|
56
56
|
- name: Checkout
|
|
57
|
-
uses: actions/checkout@
|
|
57
|
+
uses: actions/checkout@v5
|
|
58
58
|
|
|
59
59
|
- name: Setup Node
|
|
60
|
-
uses: actions/setup-node@
|
|
60
|
+
uses: actions/setup-node@v6
|
|
61
61
|
with:
|
|
62
62
|
node-version-file: ".nvmrc"
|
|
63
63
|
cache: "npm"
|
|
@@ -72,10 +72,10 @@ jobs:
|
|
|
72
72
|
runs-on: ubuntu-latest
|
|
73
73
|
steps:
|
|
74
74
|
- name: Checkout
|
|
75
|
-
uses: actions/checkout@
|
|
75
|
+
uses: actions/checkout@v5
|
|
76
76
|
|
|
77
77
|
- name: Setup Node
|
|
78
|
-
uses: actions/setup-node@
|
|
78
|
+
uses: actions/setup-node@v6
|
|
79
79
|
with:
|
|
80
80
|
node-version-file: ".nvmrc"
|
|
81
81
|
cache: "npm"
|
|
@@ -93,10 +93,10 @@ jobs:
|
|
|
93
93
|
runs-on: ubuntu-latest
|
|
94
94
|
steps:
|
|
95
95
|
- name: Checkout
|
|
96
|
-
uses: actions/checkout@
|
|
96
|
+
uses: actions/checkout@v5
|
|
97
97
|
|
|
98
98
|
- name: Setup Node
|
|
99
|
-
uses: actions/setup-node@
|
|
99
|
+
uses: actions/setup-node@v6
|
|
100
100
|
with:
|
|
101
101
|
node-version-file: ".nvmrc"
|
|
102
102
|
cache: "npm"
|
|
@@ -115,6 +115,3 @@ jobs:
|
|
|
115
115
|
|
|
116
116
|
- name: Test agent compilation components
|
|
117
117
|
run: npm run test:install
|
|
118
|
-
|
|
119
|
-
- name: Validate marketplace manifest
|
|
120
|
-
run: npm run validate:marketplace
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Repository Guidelines
|
|
2
|
+
|
|
3
|
+
## Project Shape
|
|
4
|
+
|
|
5
|
+
- Core TEA module content lives in `src/`.
|
|
6
|
+
- Public documentation lives in `docs/`; the Starlight site consumes it through `website/`.
|
|
7
|
+
- GitHub Actions workflows live in `.github/workflows/`.
|
|
8
|
+
- Release metadata must stay synchronized across `package.json`, `package-lock.json`, and `.claude-plugin/marketplace.json`.
|
|
9
|
+
|
|
10
|
+
## Common Commands
|
|
11
|
+
|
|
12
|
+
- `npm test`: full quality gate used before release.
|
|
13
|
+
- `npm run format:check`: Prettier check.
|
|
14
|
+
- `npm run lint`: ESLint check.
|
|
15
|
+
- `npm run lint:md`: markdownlint check.
|
|
16
|
+
- `npm run docs:validate-links`: docs link validation.
|
|
17
|
+
- `npm run docs:build`: full docs and site build.
|
|
18
|
+
- `npm run test:release-metadata`: package/lockfile/marketplace version sync check.
|
|
19
|
+
|
|
20
|
+
## Changelog Discipline
|
|
21
|
+
|
|
22
|
+
- For any user-facing change, documentation change, workflow/CI change, release behavior change, or bug fix, update `CHANGELOG.md` in the same PR.
|
|
23
|
+
- Put unreleased work under the top `## [Unreleased]` section using Keep a Changelog headings such as `Added`, `Changed`, `Fixed`, `Deprecated`, or `Removed`.
|
|
24
|
+
- When preparing or repairing a stable release, convert the relevant `[Unreleased]` notes into an exact version section like `## [1.16.0] - YYYY-MM-DD`.
|
|
25
|
+
- Do not leave release-worthy changes only in GitHub auto-generated notes. The publish workflow can fall back to `[Unreleased]`, but an exact version entry is preferred for stable releases.
|
|
26
|
+
|
|
27
|
+
## PR Notes
|
|
28
|
+
|
|
29
|
+
- Mention validation commands actually run.
|
|
30
|
+
- For docs-only changes, at minimum run `npm run docs:validate-links`, `npm run lint:md`, and `npm run format:check`.
|
|
31
|
+
- For workflow or release changes, run `npm run format:check`, `npm run lint:md`, `npm run lint`, and `npm run test:release-metadata`.
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ All notable changes to the Test Architect (TEA) module will be documented in thi
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- GitHub Actions workflow dependencies upgraded to Node 24-compatible major versions:
|
|
13
|
+
- `actions/checkout@v5`
|
|
14
|
+
- `actions/setup-node@v6`
|
|
15
|
+
- `actions/create-github-app-token@v3`
|
|
16
|
+
- Publish releases now use `[Unreleased]` changelog notes before falling back to generated GitHub release notes when an exact version section is missing.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [1.16.0] - 2026-05-08
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Claude Cowork marketplace plugin support.
|
|
25
|
+
- TEA Phase 3 command examples in the overview docs, including direct slash commands and Codex skill invocations.
|
|
26
|
+
- System-level and per-epic `test-design` usage examples in the TEA overview docs.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Catalog dependency metadata now uses `preceded-by` and `followed-by` column names.
|
|
31
|
+
- TEA overview command guidance now distinguishes workflow names, TEA menu codes, slash commands, and Codex skills.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- Normalized `module-help.csv` to the documented 13-column schema.
|
|
36
|
+
- Clarified the exact `/bmad:tea:ci` command path for Phase 3 CI setup.
|
|
37
|
+
- Clarified the difference between Phase 3 system-level `test-design` and Phase 4 per-epic `test-design`.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
8
41
|
## [1.2.4] - 2026-02-22
|
|
9
42
|
|
|
10
43
|
### Changed
|
|
@@ -15,7 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
15
48
|
- Affected workflows: `atdd`, `automate`, `ci`, `framework`, `nfr-assess`, `teach-me-testing`, `test-design`, `test-review`, `trace`
|
|
16
49
|
- Removed redundant `web_bundle: false` from workflow.yaml files
|
|
17
50
|
|
|
18
|
-
## [Unreleased]
|
|
51
|
+
## [Historical Unreleased Notes]
|
|
19
52
|
|
|
20
53
|
### Added
|
|
21
54
|
|
package/README.md
CHANGED
|
@@ -108,32 +108,12 @@ npx bmad-method install
|
|
|
108
108
|
|
|
109
109
|
**Note:** TEA is automatically added to party mode after installation. Use `/party` to collaborate with TEA alongside other BMad agents.
|
|
110
110
|
|
|
111
|
-
### Claude Cowork
|
|
112
|
-
|
|
113
|
-
Claude.ai web and the Claude desktop chat have no access to your project files, so the `npx` installer's writes can't reach them. [Claude Cowork](https://www.claude.com/product/claude-code) does — it sandboxes your project in a VM and exposes a plugin manager. The `npx` installer still can't write into that sandbox, but Cowork accepts plugins via its marketplace API, which TEA's `.claude-plugin/marketplace.json` ships with.
|
|
114
|
-
|
|
115
|
-
**Install** (two steps — register the marketplace, then install the plugin):
|
|
116
|
-
|
|
117
|
-
```
|
|
118
|
-
/plugin marketplace add bmad-code-org/bmad-method-test-architecture-enterprise
|
|
119
|
-
/plugin install bmad-method-test-architecture-enterprise@bmad-method-test-architecture-enterprise
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
Restart the Cowork session, then `/bmad-method-test-architecture-enterprise:*` slash commands appear.
|
|
123
|
-
|
|
124
|
-
**Update**: `/plugin marketplace update bmad-method-test-architecture-enterprise`
|
|
125
|
-
|
|
126
|
-
**Uninstall**: `/plugin uninstall bmad-method-test-architecture-enterprise@bmad-method-test-architecture-enterprise`
|
|
127
|
-
|
|
128
|
-
**Known issue**: Cowork's plugin reconciler currently has open bugs ([anthropics/claude-code#38429](https://github.com/anthropics/claude-code/issues/38429), [#39274](https://github.com/anthropics/claude-code/issues/39274)) that can purge third-party marketplace plugins on session sync. If your slash commands disappear, re-run the `/plugin install` line.
|
|
129
|
-
|
|
130
111
|
### Tool-specific invocation
|
|
131
112
|
|
|
132
|
-
| Tool | Invocation style
|
|
133
|
-
| ------------------------------- |
|
|
134
|
-
| Claude Code / Cursor / Windsurf | Slash command
|
|
135
|
-
| Codex | `$` skill from `.agents/skills`
|
|
136
|
-
| Claude Cowork | Marketplace plugin slash command | `/bmad-method-test-architecture-enterprise:bmad-testarch-automate` |
|
|
113
|
+
| Tool | Invocation style | Example |
|
|
114
|
+
| ------------------------------- | ------------------------------- | -------------------------------------------- |
|
|
115
|
+
| Claude Code / Cursor / Windsurf | Slash command | `/bmad:tea:automate` |
|
|
116
|
+
| Codex | `$` skill from `.agents/skills` | `$bmad-tea` or `$bmad-tea-testarch-automate` |
|
|
137
117
|
|
|
138
118
|
## Quickstart
|
|
139
119
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bmad-method-test-architecture-enterprise",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.17.0",
|
|
5
5
|
"description": "Master Test Architect for quality strategy, test automation, and release gates",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"bmad",
|
|
@@ -37,14 +37,13 @@
|
|
|
37
37
|
"release:minor": "gh workflow run publish.yaml -f channel=latest -f bump=minor",
|
|
38
38
|
"release:next": "gh workflow run publish.yaml -f channel=next",
|
|
39
39
|
"release:patch": "gh workflow run publish.yaml -f channel=latest -f bump=patch",
|
|
40
|
-
"test": "npm run test:schemas && npm run test:install && npm run test:knowledge && npm run test:release-metadata && npm run test:tea-workflow-descriptions && npm run validate:schemas && npm run
|
|
40
|
+
"test": "npm run test:schemas && npm run test:install && npm run test:knowledge && npm run test:release-metadata && npm run test:tea-workflow-descriptions && npm run validate:schemas && npm run lint && npm run lint:md && npm run format:check",
|
|
41
41
|
"test:coverage": "c8 npm test",
|
|
42
42
|
"test:install": "node test/test-installation-components.js",
|
|
43
43
|
"test:knowledge": "node test/test-knowledge-base.js",
|
|
44
44
|
"test:release-metadata": "node test/test-release-metadata.js",
|
|
45
45
|
"test:schemas": "node test/test-agent-schema.js",
|
|
46
46
|
"test:tea-workflow-descriptions": "node tools/validate-tea-workflow-descriptions.js",
|
|
47
|
-
"validate:marketplace": "node tools/validate-marketplace.js --strict",
|
|
48
47
|
"validate:schemas": "node tools/validate-agent-schema.js",
|
|
49
48
|
"validate:tea-workflow-descriptions": "node tools/validate-tea-workflow-descriptions.js"
|
|
50
49
|
},
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Marketplace Drift Validator
|
|
5
|
-
*
|
|
6
|
-
* Verifies .claude-plugin/marketplace.json stays in sync with src/**\/SKILL.md.
|
|
7
|
-
* The marketplace.json is what Claude Code (and Claude Cowork) consume when a
|
|
8
|
-
* user runs `/plugin marketplace add bmad-code-org/bmad-method-test-architecture-enterprise` — every skill
|
|
9
|
-
* shipped to other IDEs through the regular installer must also be reachable
|
|
10
|
-
* through the marketplace, or Cowork users silently miss skills.
|
|
11
|
-
*
|
|
12
|
-
* Checks:
|
|
13
|
-
* - Every src/**\/SKILL.md path is declared in some plugin's `skills` array.
|
|
14
|
-
* - Every declared skill path resolves to an existing src/.../SKILL.md.
|
|
15
|
-
* - No skill path is declared by more than one plugin.
|
|
16
|
-
*
|
|
17
|
-
* Usage:
|
|
18
|
-
* node tools/validate-marketplace.js human-readable report
|
|
19
|
-
* node tools/validate-marketplace.js --strict exit 1 on any drift (CI)
|
|
20
|
-
* node tools/validate-marketplace.js --json JSON output
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const fs = require('node:fs');
|
|
24
|
-
const path = require('node:path');
|
|
25
|
-
|
|
26
|
-
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
27
|
-
const SRC_DIR = path.join(PROJECT_ROOT, 'src');
|
|
28
|
-
const MARKETPLACE_PATH = path.join(PROJECT_ROOT, '.claude-plugin', 'marketplace.json');
|
|
29
|
-
|
|
30
|
-
const args = new Set(process.argv.slice(2));
|
|
31
|
-
const STRICT = args.has('--strict');
|
|
32
|
-
const JSON_OUTPUT = args.has('--json');
|
|
33
|
-
|
|
34
|
-
function findSkillPaths(dir, acc = []) {
|
|
35
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
36
|
-
const full = path.join(dir, entry.name);
|
|
37
|
-
if (entry.isDirectory()) {
|
|
38
|
-
findSkillPaths(full, acc);
|
|
39
|
-
} else if (entry.name === 'SKILL.md') {
|
|
40
|
-
// Normalize to forward slashes so the string-equal comparison against
|
|
41
|
-
// marketplace.json paths works on Windows (where path.relative uses '\').
|
|
42
|
-
const rel = path.relative(PROJECT_ROOT, path.dirname(full)).split(path.sep).join('/');
|
|
43
|
-
acc.push('./' + rel);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return acc;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function suggestPlugin(skillPath, plugins) {
|
|
50
|
-
// Score by shared path-segment depth (not raw character prefix), so a new
|
|
51
|
-
// module like ./src/cis-skills/foo doesn't get falsely suggested under
|
|
52
|
-
// bmad-pro-skills just because both share './src/'.
|
|
53
|
-
// Suggest only when the match goes beyond the './src/<family>/' boundary.
|
|
54
|
-
const skillSegments = skillPath.split('/');
|
|
55
|
-
let best = null;
|
|
56
|
-
let bestScore = 0;
|
|
57
|
-
for (const plugin of plugins) {
|
|
58
|
-
for (const declared of plugin.skills || []) {
|
|
59
|
-
const declaredSegments = declared.split('/');
|
|
60
|
-
let i = 0;
|
|
61
|
-
while (i < skillSegments.length && i < declaredSegments.length && skillSegments[i] === declaredSegments[i]) {
|
|
62
|
-
i++;
|
|
63
|
-
}
|
|
64
|
-
if (i > bestScore) {
|
|
65
|
-
bestScore = i;
|
|
66
|
-
best = plugin.name;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
// Two skills in the same module family (e.g., both under ./src/core-skills/)
|
|
71
|
-
// share exactly 3 segments: '.', 'src', '<family>'. A skill in a brand-new
|
|
72
|
-
// family (e.g., ./src/cis-skills/) only shares 2 segments. Require >= 3
|
|
73
|
-
// so suggestions stay within the same family and don't leak across modules.
|
|
74
|
-
return bestScore >= 3 ? best : null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function validate() {
|
|
78
|
-
if (!fs.existsSync(MARKETPLACE_PATH)) {
|
|
79
|
-
return { ok: false, fatal: `marketplace.json not found at ${MARKETPLACE_PATH}` };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
let marketplace;
|
|
83
|
-
try {
|
|
84
|
-
marketplace = JSON.parse(fs.readFileSync(MARKETPLACE_PATH, 'utf8'));
|
|
85
|
-
} catch (error) {
|
|
86
|
-
return { ok: false, fatal: `marketplace.json is not valid JSON: ${error.message}` };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!fs.existsSync(SRC_DIR)) {
|
|
90
|
-
return { ok: false, fatal: `src directory not found at ${SRC_DIR}` };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const plugins = Array.isArray(marketplace.plugins) ? marketplace.plugins : [];
|
|
94
|
-
const declaredBy = new Map(); // skillPath -> [pluginName]
|
|
95
|
-
for (const plugin of plugins) {
|
|
96
|
-
const skills = Array.isArray(plugin.skills) ? plugin.skills : [];
|
|
97
|
-
for (const skillPath of skills) {
|
|
98
|
-
if (!declaredBy.has(skillPath)) declaredBy.set(skillPath, []);
|
|
99
|
-
declaredBy.get(skillPath).push(plugin.name);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const onDisk = new Set(findSkillPaths(SRC_DIR));
|
|
104
|
-
|
|
105
|
-
const missing = []; // SKILL.md exists in src/ but no plugin declares it
|
|
106
|
-
for (const skillPath of [...onDisk].sort()) {
|
|
107
|
-
if (!declaredBy.has(skillPath)) {
|
|
108
|
-
missing.push({ path: skillPath, suggestedPlugin: suggestPlugin(skillPath, plugins) });
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const orphans = []; // plugin declares a path that has no SKILL.md
|
|
113
|
-
for (const skillPath of declaredBy.keys()) {
|
|
114
|
-
if (!onDisk.has(skillPath)) {
|
|
115
|
-
orphans.push({ path: skillPath, declaredBy: declaredBy.get(skillPath) });
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const duplicates = []; // same path declared more than once (within or across plugins)
|
|
120
|
-
for (const [skillPath, names] of declaredBy) {
|
|
121
|
-
if (names.length > 1) {
|
|
122
|
-
const uniquePlugins = [...new Set(names)];
|
|
123
|
-
duplicates.push({
|
|
124
|
-
path: skillPath,
|
|
125
|
-
declaredBy: uniquePlugins,
|
|
126
|
-
withinSamePlugin: uniquePlugins.length === 1,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
ok: missing.length === 0 && orphans.length === 0 && duplicates.length === 0,
|
|
133
|
-
totals: { onDisk: onDisk.size, declared: declaredBy.size, plugins: plugins.length },
|
|
134
|
-
missing,
|
|
135
|
-
orphans,
|
|
136
|
-
duplicates,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function reportHuman(result) {
|
|
141
|
-
if (result.fatal) {
|
|
142
|
-
console.error(`✗ ${result.fatal}`);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const { totals, missing, orphans, duplicates, ok } = result;
|
|
146
|
-
console.log(`Marketplace coverage: ${totals.declared} declared / ${totals.onDisk} on disk across ${totals.plugins} plugin(s)`);
|
|
147
|
-
|
|
148
|
-
if (missing.length > 0) {
|
|
149
|
-
console.log(`\n✗ ${missing.length} skill(s) on disk are not declared in marketplace.json:`);
|
|
150
|
-
for (const m of missing) {
|
|
151
|
-
const hint = m.suggestedPlugin ? ` → likely belongs in "${m.suggestedPlugin}"` : '';
|
|
152
|
-
console.log(` ${m.path}${hint}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (orphans.length > 0) {
|
|
157
|
-
console.log(`\n✗ ${orphans.length} declared skill path(s) do not exist on disk:`);
|
|
158
|
-
for (const o of orphans) {
|
|
159
|
-
console.log(` ${o.path} (declared by: ${o.declaredBy.join(', ')})`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (duplicates.length > 0) {
|
|
164
|
-
console.log(`\n✗ ${duplicates.length} skill path(s) declared more than once:`);
|
|
165
|
-
for (const d of duplicates) {
|
|
166
|
-
const where = d.withinSamePlugin
|
|
167
|
-
? `listed multiple times in "${d.declaredBy[0]}"`
|
|
168
|
-
: `in multiple plugins: ${d.declaredBy.join(', ')}`;
|
|
169
|
-
console.log(` ${d.path} (${where})`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (ok) console.log('\n✓ marketplace.json is in sync with src/');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const result = validate();
|
|
177
|
-
|
|
178
|
-
if (JSON_OUTPUT) {
|
|
179
|
-
console.log(JSON.stringify(result, null, 2));
|
|
180
|
-
} else {
|
|
181
|
-
reportHuman(result);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (!result.ok && (STRICT || result.fatal)) {
|
|
185
|
-
process.exit(1);
|
|
186
|
-
}
|