bmad-method 6.5.1-next.11 → 6.5.1-next.12

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "bmad-method",
4
- "version": "6.5.1-next.11",
4
+ "version": "6.5.1-next.12",
5
5
  "description": "Breakthrough Method of Agile AI-driven Development",
6
6
  "keywords": [
7
7
  "agile",
@@ -39,12 +39,13 @@
39
39
  "lint:fix": "eslint . --ext .js,.cjs,.mjs,.yaml --fix",
40
40
  "lint:md": "markdownlint-cli2 \"**/*.md\"",
41
41
  "prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0",
42
- "quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run validate:refs && npm run validate:skills",
42
+ "quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run test:urls && npm run validate:refs && npm run validate:skills",
43
43
  "rebundle": "node tools/installer/bundlers/bundle-web.js rebundle",
44
- "test": "npm run test:refs && npm run test:install && npm run test:channels && npm run lint && npm run lint:md && npm run format:check",
44
+ "test": "npm run test:refs && npm run test:install && npm run test:urls && npm run test:channels && npm run lint && npm run lint:md && npm run format:check",
45
45
  "test:channels": "node test/test-installer-channels.js",
46
46
  "test:install": "node test/test-installation-components.js",
47
47
  "test:refs": "node test/test-file-refs-csv.js",
48
+ "test:urls": "node test/test-parse-source-urls.js",
48
49
  "validate:refs": "node tools/validate-file-refs.js --strict",
49
50
  "validate:skills": "node tools/validate-skills.js --strict"
50
51
  },
@@ -128,58 +128,102 @@ class CustomModuleManager {
128
128
  };
129
129
  }
130
130
 
131
- // HTTPS/HTTP URL: https://host/owner/repo[/tree/branch/subdir][.git]
132
- const httpsMatch = trimmed.match(/^(https?):\/\/([^/]+)\/([^/]+)\/([^/.]+?)(?:\.git)?(\/.*)?$/);
133
- if (httpsMatch) {
134
- const [, protocol, host, owner, repo, remainder] = httpsMatch;
135
- const cloneUrl = `${protocol}://${host}/${owner}/${repo}`;
136
- let subdir = null;
137
- let urlRef = null; // branch/tag extracted from /tree/<ref>/subdir
138
-
139
- if (remainder) {
140
- // Extract subdir from deep path patterns used by various Git hosts
131
+ // HTTPS/HTTP URL: generic handling for any Git host.
132
+ // We avoid host-specific parsing — `git clone` will accept whatever URL the
133
+ // user provides. We only need to (a) separate an optional browser-style
134
+ // subdir suffix from the clone URL, (b) extract any embedded ref
135
+ // (branch/tag) from deep-path URLs, and (c) derive a cache key / display
136
+ // name from the path. The original protocol (http or https) is preserved.
137
+ if (/^https?:\/\//i.test(trimmed)) {
138
+ let url;
139
+ try {
140
+ url = new URL(trimmed);
141
+ } catch {
142
+ url = null;
143
+ }
144
+
145
+ if (url && url.host) {
146
+ const host = url.host;
147
+ let repoPath = url.pathname.replace(/^\/+/, '').replace(/\/+$/, '');
148
+ let subdir = null;
149
+ let urlRef = null; // branch/tag/commit extracted from deep-path URLs
150
+
151
+ // Detect browser-style deep-path patterns that embed a ref
152
+ // (branch/tag/commit) and optional subdirectory. These appear
153
+ // across many hosts:
154
+ // GitHub /<repo>/tree|blob/<ref>[/<subdir>]
155
+ // GitLab /<repo>/-/tree|blob/<ref>[/<subdir>]
156
+ // Gitea /<repo>/src/<ref>[/<subdir>]
157
+ // Gitea /<repo>/src/(branch|commit|tag)/<ref>[/<subdir>]
158
+ // Group 1 = repo path prefix, Group 2 = ref, Group 3 = subdir (optional).
141
159
  const deepPathPatterns = [
142
- { regex: /^\/(?:-\/)?tree\/([^/]+)\/(.+)$/, refIdx: 1, pathIdx: 2 }, // GitHub, GitLab
143
- { regex: /^\/(?:-\/)?blob\/([^/]+)\/(.+)$/, refIdx: 1, pathIdx: 2 },
144
- { regex: /^\/src\/([^/]+)\/(.+)$/, refIdx: 1, pathIdx: 2 }, // Gitea/Forgejo
160
+ /^(.+?)\/(?:-\/)?(?:tree|blob)\/([^/]+)(?:\/(.+))?$/,
161
+ /^(.+?)\/src\/(?:branch\/|commit\/|tag\/)?([^/]+)(?:\/(.+))?$/,
145
162
  ];
146
- // Also match `/tree/<ref>` with no subdir
147
- const refOnlyPatterns = [/^\/(?:-\/)?tree\/([^/]+?)\/?$/, /^\/(?:-\/)?blob\/([^/]+?)\/?$/, /^\/src\/([^/]+?)\/?$/];
148
-
149
- for (const p of deepPathPatterns) {
150
- const match = remainder.match(p.regex);
163
+ for (const pattern of deepPathPatterns) {
164
+ const match = repoPath.match(pattern);
151
165
  if (match) {
152
- urlRef = match[p.refIdx];
153
- subdir = match[p.pathIdx].replace(/\/$/, '');
166
+ repoPath = match[1];
167
+ if (match[2]) urlRef = match[2];
168
+ if (match[3]) {
169
+ const cleaned = match[3].replace(/\/+$/, '');
170
+ if (cleaned) subdir = cleaned;
171
+ }
154
172
  break;
155
173
  }
156
174
  }
175
+
176
+ // Some hosts use ?path=/subdir on browse links to point at a file or
177
+ // directory. Honor it when no deep-path marker matched above.
157
178
  if (!subdir) {
158
- for (const r of refOnlyPatterns) {
159
- const match = remainder.match(r);
160
- if (match) {
161
- urlRef = match[1];
162
- break;
163
- }
179
+ const pathParam = url.searchParams.get('path');
180
+ if (pathParam) {
181
+ const cleaned = pathParam.replace(/^\/+/, '').replace(/\/+$/, '');
182
+ if (cleaned) subdir = cleaned;
164
183
  }
165
184
  }
166
- }
167
185
 
168
- // Precedence: explicit @version suffix > URL /tree/<ref> path segment.
169
- const version = versionSuffix || urlRef || null;
186
+ // Strip a single trailing .git for a stable cacheKey/displayName.
187
+ const repoPathClean = repoPath.replace(/\.git$/i, '');
188
+ if (!repoPathClean) {
189
+ return {
190
+ type: null,
191
+ cloneUrl: null,
192
+ subdir: null,
193
+ localPath: null,
194
+ cacheKey: null,
195
+ displayName: null,
196
+ isValid: false,
197
+ error: 'Not a valid Git URL or local path',
198
+ };
199
+ }
170
200
 
171
- return {
172
- type: 'url',
173
- cloneUrl,
174
- subdir,
175
- localPath: null,
176
- version,
177
- rawInput: trimmedRaw,
178
- cacheKey: `${host}/${owner}/${repo}`,
179
- displayName: `${owner}/${repo}`,
180
- isValid: true,
181
- error: null,
182
- };
201
+ const cloneUrl = `${url.protocol}//${host}/${repoPathClean}`;
202
+ const cacheKey = `${host}/${repoPathClean}`;
203
+
204
+ // Display name: prefer "<owner>/<repo>" using the last two meaningful
205
+ // path segments.
206
+ const segments = repoPathClean.split('/').filter(Boolean);
207
+ const repoSeg = segments.at(-1);
208
+ const ownerSeg = segments.at(-2);
209
+ const displayName = ownerSeg ? `${ownerSeg}/${repoSeg}` : repoSeg;
210
+
211
+ // Precedence: explicit @version suffix > URL /tree/<ref> path segment.
212
+ const version = versionSuffix || urlRef || null;
213
+
214
+ return {
215
+ type: 'url',
216
+ cloneUrl,
217
+ subdir,
218
+ localPath: null,
219
+ version,
220
+ rawInput: trimmedRaw,
221
+ cacheKey,
222
+ displayName,
223
+ isValid: true,
224
+ error: null,
225
+ };
226
+ }
183
227
  }
184
228
 
185
229
  return {