@yemi33/minions 0.1.1726 → 0.1.1728

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1728 (2026-05-05)
4
+
5
+ ### Features
6
+ - redact repository URLs in filed issues (#2069)
7
+
8
+ ## 0.1.1727 (2026-05-05)
9
+
10
+ ### Features
11
+ - fix Brady Gaster Squad link (#2080)
12
+
3
13
  ## 0.1.1726 (2026-05-05)
4
14
 
5
15
  ### Other
package/README.md CHANGED
@@ -4,7 +4,7 @@ A multi-project AI dev team that runs from `~/.minions/`. Five autonomous agents
4
4
 
5
5
  Zero dependencies — uses only Node.js built-in modules.
6
6
 
7
- Inspired by and initially scaffolded from [Brady Gaster's Minions](https://bradygaster.github.io/minions/).
7
+ Inspired by and initially scaffolded from [Brady Gaster's Squad](https://bradygaster.github.io/squad/).
8
8
 
9
9
  ## Prerequisites
10
10
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-05T09:13:43.248Z"
4
+ "cachedAt": "2026-05-05T09:46:15.037Z"
5
5
  }
package/engine/issues.js CHANGED
@@ -5,6 +5,7 @@
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
7
  const { execFileSync: _execFileSync } = require('child_process');
8
+ const shared = require('./shared');
8
9
 
9
10
  const DEFAULT_REPO = 'yemi33/minions';
10
11
  const DEFAULT_LABELS = ['bug'];
@@ -117,6 +118,63 @@ function buildWarning(labelsSkipped, filedWithoutLabels) {
117
118
  return filedWithoutLabels ? `${base} Filed without labels.` : base;
118
119
  }
119
120
 
121
+ function _addRedactionIdentifier(entries, seen, value, replacement) {
122
+ const clean = String(value || '').trim().replace(/\/+$/, '');
123
+ if (clean.length < 4) return;
124
+ const key = `${replacement}:${clean.toLowerCase()}`;
125
+ if (seen.has(key)) return;
126
+ seen.add(key);
127
+ entries.push({ value: clean, replacement });
128
+ }
129
+
130
+ function _stripPrUrlBase(url) {
131
+ return String(url || '')
132
+ .trim()
133
+ .replace(/\/(?:pull|pullrequest)\/?$/i, '')
134
+ .replace(/\/+$/, '');
135
+ }
136
+
137
+ function _buildIssueRedactionIdentifiers({ repo, projects = [] } = {}) {
138
+ const entries = [];
139
+ const seen = new Set();
140
+ _addRedactionIdentifier(entries, seen, repo, '[REDACTED_REPO]');
141
+
142
+ for (const project of projects || []) {
143
+ if (!project || typeof project !== 'object') continue;
144
+ const owner = project.adoOrg || project.org || project.owner || '';
145
+ const repoName = project.repoName || project.repositoryName || '';
146
+ const adoProject = project.adoProject || project.project || '';
147
+
148
+ if (owner && repoName) {
149
+ _addRedactionIdentifier(entries, seen, `${owner}/${repoName}`, '[REDACTED_REPO]');
150
+ }
151
+ if (owner && adoProject && repoName) {
152
+ _addRedactionIdentifier(entries, seen, `${owner}/${adoProject}/_git/${repoName}`, '[REDACTED_REPO]');
153
+ _addRedactionIdentifier(entries, seen, `${owner}/${adoProject}/${repoName}`, '[REDACTED_REPO]');
154
+ }
155
+ const repositoryId = String(project.repositoryId || '').trim();
156
+ if (repositoryId.length >= 8) {
157
+ _addRedactionIdentifier(entries, seen, repositoryId, '[REDACTED_REPOSITORY_ID]');
158
+ }
159
+
160
+ for (const field of ['remoteUrl', 'repositoryUrl', 'cloneUrl', 'sshUrl', 'webUrl']) {
161
+ _addRedactionIdentifier(entries, seen, project[field], '[REDACTED_REPO_URL]');
162
+ }
163
+ const prBase = _stripPrUrlBase(project.prUrlBase);
164
+ _addRedactionIdentifier(entries, seen, prBase, '[REDACTED_REPO_URL]');
165
+ }
166
+
167
+ entries.sort((a, b) => b.value.length - a.value.length);
168
+ return entries;
169
+ }
170
+
171
+ function _redactIssueContent(value, { repo, projects } = {}) {
172
+ return shared.redactSecrets(String(value || ''), {
173
+ redactRepositoryUrls: true,
174
+ repositoryIdentifiers: _buildIssueRedactionIdentifiers({ repo, projects }),
175
+ });
176
+ }
177
+
120
178
  function createIssueWithLabels({ title, bodyFile, repo, labels, execFileSync }) {
121
179
  const args = ['issue', 'create', '--repo', repo, '--title', title, '--body-file', bodyFile];
122
180
  if (labels.length > 0) args.push('--label', labels.join(','));
@@ -133,6 +191,7 @@ function createGitHubIssue({
133
191
  description = '',
134
192
  labels,
135
193
  repo = DEFAULT_REPO,
194
+ projects,
136
195
  tmpDir,
137
196
  execFileSync = _execFileSync,
138
197
  } = {}) {
@@ -144,7 +203,10 @@ function createGitHubIssue({
144
203
  throw new GitHubIssueError('gh CLI not found. Install from https://cli.github.com/');
145
204
  }
146
205
 
147
- const issueBody = `${description || ''}\n\n---\n_Filed via Minions dashboard_`;
206
+ const redactionProjects = projects || shared.getProjects();
207
+ const safeTitle = _redactIssueContent(title, { repo, projects: redactionProjects });
208
+ const safeDescription = _redactIssueContent(description || '', { repo, projects: redactionProjects });
209
+ const issueBody = `${safeDescription}\n\n---\n_Filed via Minions dashboard_`;
148
210
  const dir = tmpDir || path.join(__dirname, 'tmp');
149
211
  fs.mkdirSync(dir, { recursive: true });
150
212
  const bodyFile = path.join(dir, `bug-body-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.md`);
@@ -154,7 +216,7 @@ function createGitHubIssue({
154
216
  try {
155
217
  resolved = resolveLabels({ labels, repo, execFileSync });
156
218
  const created = createIssueWithLabels({
157
- title,
219
+ title: safeTitle,
158
220
  bodyFile,
159
221
  repo,
160
222
  labels: resolved.labelsToApply,
@@ -175,7 +237,7 @@ function createGitHubIssue({
175
237
  if (isAuthError(e)) throw new GitHubIssueError('GitHub auth required. Run: gh auth login', 401);
176
238
  if (resolved && resolved.labelsToApply.length > 0 && isLabelUnavailableError(e)) {
177
239
  try {
178
- const created = createIssueWithLabels({ title, bodyFile, repo, labels: [], execFileSync });
240
+ const created = createIssueWithLabels({ title: safeTitle, bodyFile, repo, labels: [], execFileSync });
179
241
  const skipped = normalizeLabels([...resolved.labelsSkipped, ...resolved.labelsToApply], []);
180
242
  return {
181
243
  ok: true,
@@ -203,4 +265,5 @@ module.exports = {
203
265
  normalizeLabels,
204
266
  isLabelUnavailableError,
205
267
  createGitHubIssue,
268
+ _buildIssueRedactionIdentifiers,
206
269
  };
package/engine/shared.js CHANGED
@@ -42,22 +42,71 @@ function dateStamp() { return new Date().toISOString().slice(0, 10); }
42
42
  const _BEARER_RE = /Bearer\s+[A-Za-z0-9+/=._\-]{20,}/g;
43
43
  const _JWT_RE = /ey[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}(?:\.[A-Za-z0-9_\-]{10,})?/g;
44
44
  const _AZUREAUTH_RE = /"token"\s*:\s*"[A-Za-z0-9+/=._\-]{20,}"/g;
45
+ const _URL_RE = /\b(?:https?|ssh):\/\/[^\s<>"'`]+/gi;
46
+ const _GITHUB_REPO_URL_RE = /\b(?:(?:https?:\/\/|ssh:\/\/git@)github\.com[/:]|git@github\.com:)[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:\.git)?(?:\/[^\s<>"'`]*)?/gi;
47
+ const _ADO_DEV_REPO_URL_RE = /\bhttps?:\/\/dev\.azure\.com\/[^/\s<>"'`]+\/[^/\s<>"'`]+\/_git\/[^/\s<>"'`)]+(?:\/[^\s<>"'`]*)?/gi;
48
+ const _ADO_VISUALSTUDIO_REPO_URL_RE = /\bhttps?:\/\/[^/\s<>"'`]+\.visualstudio\.com\/(?:DefaultCollection\/)?[^/\s<>"'`]+\/_git\/[^/\s<>"'`)]+(?:\/[^\s<>"'`]*)?/gi;
49
+ const _ADO_SSH_REPO_URL_RE = /\b(?:ssh:\/\/)?git@ssh\.dev\.azure\.com[:/]v3\/[^/\s<>"'`]+\/[^/\s<>"'`]+\/[^/\s<>"'`)]+/gi;
50
+ const _TOKEN_URL_PARAM_RE = /[?&](?:access[_-]?token|auth[_-]?token|token|api[_-]?key|sig|signature|pat)=/i;
51
+ const _URL_CREDENTIALS_RE = /^[a-z][a-z0-9+.-]*:\/\/[^/\s@]+@/i;
45
52
 
46
- function _redactString(s) {
47
- if (typeof s !== 'string' || s.length === 0) return s;
53
+ function _escapeRegExp(s) {
54
+ return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
55
+ }
56
+
57
+ function _redactedWithTrailingPunctuation(raw, replacement) {
58
+ const match = String(raw).match(/^(.+?)([.,;:!?)]*)$/);
59
+ return replacement + (match ? match[2] : '');
60
+ }
61
+
62
+ function _redactUrlMatch(raw, replacement) {
63
+ return _redactedWithTrailingPunctuation(raw, replacement);
64
+ }
65
+
66
+ function _redactTokenBearingUrls(s) {
67
+ return s.replace(_URL_RE, url => (
68
+ _TOKEN_URL_PARAM_RE.test(url) || _URL_CREDENTIALS_RE.test(url)
69
+ ? _redactUrlMatch(url, '[REDACTED_URL]')
70
+ : url
71
+ ));
72
+ }
73
+
74
+ function _redactRepositoryUrls(s) {
48
75
  return s
76
+ .replace(_GITHUB_REPO_URL_RE, url => _redactUrlMatch(url, '[REDACTED_REPO_URL]'))
77
+ .replace(_ADO_DEV_REPO_URL_RE, url => _redactUrlMatch(url, '[REDACTED_REPO_URL]'))
78
+ .replace(_ADO_VISUALSTUDIO_REPO_URL_RE, url => _redactUrlMatch(url, '[REDACTED_REPO_URL]'))
79
+ .replace(_ADO_SSH_REPO_URL_RE, url => _redactUrlMatch(url, '[REDACTED_REPO_URL]'));
80
+ }
81
+
82
+ function _redactConfiguredRepositoryIdentifiers(s, options) {
83
+ const entries = Array.isArray(options?.repositoryIdentifiers) ? options.repositoryIdentifiers : [];
84
+ let out = s;
85
+ for (const entry of entries) {
86
+ const value = typeof entry === 'string' ? entry : entry?.value;
87
+ const replacement = typeof entry === 'string' ? '[REDACTED_REPO]' : (entry?.replacement || '[REDACTED_REPO]');
88
+ if (typeof value !== 'string' || value.length < 4) continue;
89
+ out = out.replace(new RegExp(_escapeRegExp(value), 'gi'), replacement);
90
+ }
91
+ return out;
92
+ }
93
+
94
+ function _redactString(s, options = {}) {
95
+ if (typeof s !== 'string' || s.length === 0) return s;
96
+ const repoRedacted = options.redactRepositoryUrls ? _redactRepositoryUrls(s) : s;
97
+ return _redactTokenBearingUrls(_redactConfiguredRepositoryIdentifiers(repoRedacted, options))
49
98
  .replace(_AZUREAUTH_RE, '"token":"[REDACTED_AZUREAUTH]"')
50
99
  .replace(_BEARER_RE, 'Bearer [REDACTED]')
51
100
  .replace(_JWT_RE, '[REDACTED_JWT]');
52
101
  }
53
102
 
54
- function redactSecrets(value) {
103
+ function redactSecrets(value, options = {}) {
55
104
  if (value == null) return value;
56
- if (typeof value === 'string') return _redactString(value);
57
- if (Array.isArray(value)) return value.map(redactSecrets);
105
+ if (typeof value === 'string') return _redactString(value, options);
106
+ if (Array.isArray(value)) return value.map(v => redactSecrets(v, options));
58
107
  if (typeof value === 'object') {
59
108
  const out = {};
60
- for (const k of Object.keys(value)) out[k] = redactSecrets(value[k]);
109
+ for (const k of Object.keys(value)) out[k] = redactSecrets(value[k], options);
61
110
  return out;
62
111
  }
63
112
  return value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1726",
3
+ "version": "0.1.1728",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"