@opengsd/gsd-core 1.3.1 → 1.4.0-rc.2
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/plugin.json +23 -0
- package/GEMINI.md +53 -0
- package/agents/gsd-advisor-researcher.md +1 -20
- package/agents/gsd-ai-researcher.md +2 -21
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-domain-researcher.md +2 -21
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-eval-planner.md +1 -1
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-framework-selector.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-pattern-mapper.md +1 -1
- package/agents/gsd-phase-researcher.md +92 -166
- package/agents/gsd-planner.md +9 -36
- package/agents/gsd-project-researcher.md +62 -141
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.md +3 -22
- package/agents/gsd-user-profiler.md +1 -1
- package/agents/gsd-verifier.md +8 -2
- package/bin/install.js +1977 -339
- package/commands/gsd/autonomous.md +2 -0
- package/commands/gsd/execute-phase.md +2 -0
- package/commands/gsd/graphify.md +11 -6
- package/commands/gsd/import.md +6 -2
- package/commands/gsd/plan-phase.md +4 -2
- package/commands/gsd/progress.md +1 -0
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/update.md +3 -2
- package/gemini-extension.json +6 -0
- package/gsd-core/bin/check-latest-version.cjs +61 -6
- package/gsd-core/bin/gsd-tools.cjs +238 -32
- package/gsd-core/bin/lib/check-command-router.cjs +1 -0
- package/gsd-core/bin/lib/cli-exit.cjs +42 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +1 -1
- package/gsd-core/bin/lib/commands.cjs +5 -4
- package/gsd-core/bin/lib/config.cjs +28 -4
- package/gsd-core/bin/lib/core.cjs +72 -28
- package/gsd-core/bin/lib/graphify.cjs +2 -2
- package/gsd-core/bin/lib/init-command-router.cjs +2 -2
- package/gsd-core/bin/lib/init.cjs +19 -3
- package/gsd-core/bin/lib/install-profiles.cjs +58 -0
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/000-first-time-baseline.cjs +1 -1
- package/gsd-core/bin/lib/intel.cjs +3 -20
- package/gsd-core/bin/lib/package-legitimacy.cjs +368 -0
- package/gsd-core/bin/lib/phase.cjs +3 -3
- package/gsd-core/bin/lib/research-provider.cjs +137 -0
- package/gsd-core/bin/lib/research-store.cjs +167 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +4 -19
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +67 -9
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +56 -0
- package/gsd-core/bin/lib/runtime-homes.cjs +32 -11
- package/gsd-core/bin/lib/security.cjs +73 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +9 -0
- package/gsd-core/bin/lib/surface.cjs +54 -11
- package/gsd-core/bin/lib/validate.cjs +2 -2
- package/gsd-core/bin/lib/verification-command-router.cjs +31 -0
- package/gsd-core/bin/lib/verification.cjs +193 -0
- package/gsd-core/bin/lib/verify.cjs +2 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -1
- package/gsd-core/bin/lib/worktree-base-ref.cjs +325 -0
- package/gsd-core/bin/lib/worktree-safety.cjs +31 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +8 -11
- package/gsd-core/references/planner-load-graph-context.md +36 -0
- package/gsd-core/references/planning-config.md +3 -1
- package/gsd-core/references/research-documentation-lookup.md +29 -0
- package/gsd-core/references/research-philosophy.md +29 -0
- package/gsd-core/references/research-verification-protocol.md +27 -0
- package/gsd-core/workflows/execute-phase.md +19 -8
- package/gsd-core/workflows/help/modes/full.md +4 -3
- package/gsd-core/workflows/ingest-docs.md +3 -2
- package/gsd-core/workflows/plan-phase.md +14 -10
- package/gsd-core/workflows/plan-review-convergence.md +3 -3
- package/gsd-core/workflows/review.md +24 -7
- package/gsd-core/workflows/ship.md +5 -8
- package/gsd-core/workflows/spec-phase.md +2 -1
- package/gsd-core/workflows/update.md +34 -6
- package/hooks/dist/gsd-config-reload.js +133 -0
- package/hooks/dist/gsd-context-monitor.js +1 -1
- package/hooks/dist/gsd-cursor-post-tool.js +75 -0
- package/hooks/dist/gsd-cursor-session-start.js +52 -0
- package/hooks/dist/gsd-workflow-guard.js +1 -0
- package/hooks/dist/gsd-worktree-path-guard.js +1 -1
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-config-reload.js +133 -0
- package/hooks/gsd-context-monitor.js +1 -1
- package/hooks/gsd-cursor-post-tool.js +75 -0
- package/hooks/gsd-cursor-session-start.js +52 -0
- package/hooks/gsd-workflow-guard.js +1 -0
- package/hooks/gsd-worktree-path-guard.js +1 -1
- package/hooks/hooks.json +69 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +8 -1
- package/scripts/affected-tests-lib.cjs +3 -2
- package/scripts/build-hooks.js +7 -0
- package/scripts/changeset/cli.cjs +226 -28
- package/scripts/changeset/lint.cjs +5 -4
- package/scripts/changeset/new.cjs +4 -4
- package/scripts/check-alias-drift.cjs +77 -71
- package/scripts/check-env.cjs +185 -179
- package/scripts/check-npm-integrity.cjs +115 -109
- package/scripts/ci-guard-runner.cjs +11 -5
- package/scripts/ci-prepare-test-scope.cjs +27 -22
- package/scripts/ci-rebase-check.cjs +46 -45
- package/scripts/ci-test-scope.cjs +126 -22
- package/scripts/diff-touches-shipped-paths.cjs +52 -44
- package/scripts/gen-inventory-manifest.cjs +38 -32
- package/scripts/gen-research-agents.cjs +276 -0
- package/scripts/issue-dedupe.cjs +278 -0
- package/scripts/lib/cli-exit.cjs +56 -0
- package/scripts/lint-command-contract.cjs +28 -22
- package/scripts/lint-descriptions.cjs +32 -28
- package/scripts/lint-docs-required.cjs +4 -4
- package/scripts/lint-legacy-dir-name.cjs +56 -52
- package/scripts/lint-pr-check-project-dir.cjs +3 -1
- package/scripts/lint-shell-command-projection-drift.cjs +27 -22
- package/scripts/lint-skill-deps.cjs +31 -26
- package/scripts/lint-test-file-count.allowlist.json +2 -0
- package/scripts/lint-test-file-count.cjs +5 -4
- package/scripts/mutation-matrix.cjs +6 -3
- package/scripts/prompt-injection-scan.sh +1 -1
- package/scripts/release-notes/discord-release-summary.cjs +373 -0
- package/scripts/release-notes/format-github-release-notes.cjs +8 -3
- package/scripts/release-tarball-smoke.cjs +6 -4
- package/scripts/research-profiles.cjs +149 -0
- package/scripts/run-affected-tests.cjs +2 -1
- package/scripts/run-cross-platform-tests.cjs +11 -7
- package/scripts/run-tests.cjs +8 -7
- package/scripts/strip-prose-atrefs.cjs +1 -1
- package/scripts/sync-manifest-versions.cjs +119 -0
- package/scripts/sync-runtime-launcher.cjs +0 -3
- package/scripts/verify-npm-publish.cjs +14 -26
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const cp = require('node:child_process');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { ExitError, runMain } = require('../lib/cli-exit.cjs');
|
|
8
|
+
const { classifyTitle } = require('./format-github-release-notes.cjs');
|
|
9
|
+
|
|
10
|
+
const DEFAULT_MAX_CONTENT = 1850;
|
|
11
|
+
const SECTION_LIMITS = Object.freeze([
|
|
12
|
+
['Breaking', 4],
|
|
13
|
+
['Security', 4],
|
|
14
|
+
['Feature', 4],
|
|
15
|
+
['Fix', 4],
|
|
16
|
+
['Enhancement', 3],
|
|
17
|
+
['Internal', 2],
|
|
18
|
+
['New Contributors', 3],
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
function normalizeNewlines(value) {
|
|
22
|
+
return String(value || '').replace(/\r\n/g, '\n');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function releaseTag(release) {
|
|
26
|
+
return release.tagName || release.tag_name || release.name || 'release';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function releaseName(release) {
|
|
30
|
+
return release.name || releaseTag(release);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function releaseUrl(release) {
|
|
34
|
+
return release.url || release.html_url || '';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function releaseIsPrerelease(release) {
|
|
38
|
+
return release.isPrerelease === true || release.prerelease === true || /-/.test(releaseTag(release));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function loadPackageName(repoRoot = path.resolve(__dirname, '..', '..')) {
|
|
42
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
|
|
43
|
+
return pkgJson.name || 'package';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeHeading(value) {
|
|
47
|
+
const heading = value.trim().replace(/:$/, '');
|
|
48
|
+
const lower = heading.toLowerCase();
|
|
49
|
+
|
|
50
|
+
if (lower.includes('breaking')) return 'Breaking';
|
|
51
|
+
if (lower.includes('security')) return 'Security';
|
|
52
|
+
if (lower === 'feature' || lower === 'features') return 'Feature';
|
|
53
|
+
if (lower === 'fix' || lower === 'fixes' || lower === 'bug fixes') return 'Fix';
|
|
54
|
+
if (lower === 'enhancement' || lower === 'enhancements' || lower === 'improvements') return 'Enhancement';
|
|
55
|
+
if (lower === 'internal' || lower === 'maintenance') return 'Internal';
|
|
56
|
+
if (lower === 'new contributors') return 'New Contributors';
|
|
57
|
+
if (lower === "what's changed" || lower === 'whats changed') return "What's Changed";
|
|
58
|
+
return heading;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function collectSections(body) {
|
|
62
|
+
const sections = new Map();
|
|
63
|
+
let current = null;
|
|
64
|
+
|
|
65
|
+
for (const line of normalizeNewlines(body).split('\n')) {
|
|
66
|
+
const trimmed = line.trim();
|
|
67
|
+
const heading = trimmed.match(/^#{2,3}\s+(.+)$/);
|
|
68
|
+
|
|
69
|
+
if (heading) {
|
|
70
|
+
current = normalizeHeading(heading[1]);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const bullet = trimmed.match(/^([*-])\s+(.+)$/);
|
|
75
|
+
if (!bullet || !current) continue;
|
|
76
|
+
|
|
77
|
+
let section = current;
|
|
78
|
+
if (section === "What's Changed") {
|
|
79
|
+
section = classifyTitle(trimmed);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!sections.has(section)) sections.set(section, []);
|
|
83
|
+
sections.get(section).push(cleanBullet(trimmed));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return sections;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function cleanBullet(value) {
|
|
90
|
+
let text = String(value)
|
|
91
|
+
.replace(/^[*-]\s+/, '')
|
|
92
|
+
.replace(/\s+by\s+@[A-Za-z0-9-]+\s+in\s+https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/(\d+)/g, ' (#$1)')
|
|
93
|
+
.replace(/\[([^\]]+)\]\((https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/(?:pull|issues)\/(\d+))\)/g, '$1 (#$3)')
|
|
94
|
+
.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, '$1')
|
|
95
|
+
.replace(/https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/(\d+)/g, '#$1')
|
|
96
|
+
.replace(/https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/issues\/(\d+)/g, '#$1')
|
|
97
|
+
.replace(/https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/commit\/([0-9a-f]{7})[0-9a-f]*/gi, '$1')
|
|
98
|
+
.replace(/\*\*/g, '')
|
|
99
|
+
.replace(/\s+/g, ' ')
|
|
100
|
+
.trim();
|
|
101
|
+
|
|
102
|
+
text = text
|
|
103
|
+
.replace(/^\[codex\]\s+/i, '')
|
|
104
|
+
.replace(/^(?:feat|fix|docs|chore|ci|refactor|test)(?:\([^)]+\))?!?:\s+/i, '');
|
|
105
|
+
text = text.replace(/#(\d+)\s+\(#\1\)/g, '#$1');
|
|
106
|
+
text = text.replace(/\s+\(#(\d+)\)\s+\(#\1\)$/g, ' (#$1)');
|
|
107
|
+
|
|
108
|
+
return truncateAtWord(text, 220);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function truncateAtWord(value, maxLength) {
|
|
112
|
+
if (value.length <= maxLength) return value;
|
|
113
|
+
const splitAt = value.lastIndexOf(' ', maxLength - 3);
|
|
114
|
+
if (splitAt < 80) return `${value.slice(0, maxLength - 3)}...`;
|
|
115
|
+
return `${value.slice(0, splitAt)}...`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function extractInstallCommand(body, packageName, release) {
|
|
119
|
+
const lines = normalizeNewlines(body).split('\n');
|
|
120
|
+
let inInstall = false;
|
|
121
|
+
let inFence = false;
|
|
122
|
+
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
const trimmed = line.trim();
|
|
125
|
+
|
|
126
|
+
if (/^##\s+Install\b/i.test(trimmed)) {
|
|
127
|
+
inInstall = true;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (inInstall && /^##\s+/.test(trimmed)) break;
|
|
132
|
+
if (!inInstall) continue;
|
|
133
|
+
|
|
134
|
+
if (trimmed.startsWith('```')) {
|
|
135
|
+
inFence = !inFence;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (inFence && /^npm\s+(?:i|install)\s+/.test(trimmed)) {
|
|
140
|
+
return trimmed;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const channel = releaseIsPrerelease(release) ? 'next' : 'latest';
|
|
145
|
+
return `npm i ${packageName}@${channel}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function buildDiscordReleasePayload({ release, packageName = loadPackageName(), maxContent = DEFAULT_MAX_CONTENT }) {
|
|
149
|
+
const tag = releaseTag(release);
|
|
150
|
+
const name = releaseName(release);
|
|
151
|
+
const url = releaseUrl(release);
|
|
152
|
+
const prerelease = releaseIsPrerelease(release);
|
|
153
|
+
const channel = prerelease ? 'next' : 'latest';
|
|
154
|
+
const body = release.body || '';
|
|
155
|
+
const sections = collectSections(body);
|
|
156
|
+
const footer = url ? `Full changelog: ${url}` : `Full changelog: ${tag}`;
|
|
157
|
+
const baseLines = [
|
|
158
|
+
`**${packageName} ${name}${prerelease ? ' pre-release' : ''} is out**`,
|
|
159
|
+
'',
|
|
160
|
+
'Install:',
|
|
161
|
+
`\`${extractInstallCommand(body, packageName, release)}\``,
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
const lines = baseLines.slice();
|
|
165
|
+
for (const [section, limit] of SECTION_LIMITS) {
|
|
166
|
+
appendSection(lines, section, sections.get(section) || [], limit, footer, maxContent);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (lines.length === baseLines.length) {
|
|
170
|
+
appendLineGroup(lines, ['', 'No release notes were provided.'], footer, maxContent);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (joinedLength(lines, footer) > maxContent) {
|
|
174
|
+
while (lines.length > baseLines.length && joinedLength(lines, footer) > maxContent) {
|
|
175
|
+
lines.pop();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const content = [...lines, '', footer].join('\n');
|
|
180
|
+
return {
|
|
181
|
+
username: 'GSD Releases',
|
|
182
|
+
content,
|
|
183
|
+
embeds: [
|
|
184
|
+
{
|
|
185
|
+
title: 'Release details',
|
|
186
|
+
url: url || undefined,
|
|
187
|
+
color: prerelease ? 0xd9a441 : 0x2f9e44,
|
|
188
|
+
fields: [
|
|
189
|
+
{ name: 'Package', value: `\`${packageName}\``, inline: true },
|
|
190
|
+
{ name: 'Channel', value: `\`${channel}\``, inline: true },
|
|
191
|
+
{ name: 'Version', value: `\`${tag}\``, inline: true },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
allowed_mentions: { parse: [] },
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function appendSection(lines, section, bullets, limit, footer, maxContent) {
|
|
200
|
+
if (bullets.length === 0) return;
|
|
201
|
+
|
|
202
|
+
const label = sectionLabel(section);
|
|
203
|
+
const selected = [];
|
|
204
|
+
for (const bullet of bullets.slice(0, limit)) {
|
|
205
|
+
const candidate = ['', `**${label}**`, ...selected.map((item) => `- ${item}`), `- ${bullet}`];
|
|
206
|
+
if (joinedLength([...lines, ...candidate], footer) > maxContent) break;
|
|
207
|
+
selected.push(bullet);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (selected.length === 0) return;
|
|
211
|
+
|
|
212
|
+
const group = ['', `**${label}**`, ...selected.map((item) => `- ${item}`)];
|
|
213
|
+
const remaining = bullets.length - selected.length;
|
|
214
|
+
if (remaining > 0) {
|
|
215
|
+
const moreLine = `- ...and ${remaining} more ${label.toLowerCase()}`;
|
|
216
|
+
if (joinedLength([...lines, ...group, moreLine], footer) <= maxContent) {
|
|
217
|
+
group.push(moreLine);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
appendLineGroup(lines, group, footer, maxContent);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function appendLineGroup(lines, group, footer, maxContent) {
|
|
225
|
+
if (joinedLength([...lines, ...group], footer) <= maxContent) {
|
|
226
|
+
lines.push(...group);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function joinedLength(lines, footer) {
|
|
231
|
+
return [...lines, '', footer].join('\n').length;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function sectionLabel(section) {
|
|
235
|
+
if (section === 'Feature') return 'Features';
|
|
236
|
+
if (section === 'Fix') return 'Fixes';
|
|
237
|
+
if (section === 'Enhancement') return 'Enhancements';
|
|
238
|
+
return section;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function fetchRelease({ tag, repo, latest = false }) {
|
|
242
|
+
const args = ['release', 'view'];
|
|
243
|
+
if (!latest && tag) args.push(tag);
|
|
244
|
+
args.push('--json', 'tagName,name,isPrerelease,body,url');
|
|
245
|
+
if (repo) args.push('--repo', repo);
|
|
246
|
+
const raw = cp.execFileSync('gh', args, { encoding: 'utf8' });
|
|
247
|
+
return JSON.parse(raw);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function postWebhook(webhookUrl, payload) {
|
|
251
|
+
const response = await fetch(webhookUrl, {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
headers: { 'content-type': 'application/json' },
|
|
254
|
+
body: JSON.stringify(payload),
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
if (!response.ok) {
|
|
258
|
+
throw new ExitError(1, `Discord webhook failed with ${response.status}: ${await response.text()}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function parseArgs(argv) {
|
|
263
|
+
const opts = {
|
|
264
|
+
tag: null,
|
|
265
|
+
repo: null,
|
|
266
|
+
latest: false,
|
|
267
|
+
stdin: false,
|
|
268
|
+
post: false,
|
|
269
|
+
json: false,
|
|
270
|
+
allowMissingWebhook: false,
|
|
271
|
+
packageName: null,
|
|
272
|
+
maxContent: DEFAULT_MAX_CONTENT,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const args = argv.slice();
|
|
276
|
+
while (args.length > 0) {
|
|
277
|
+
const arg = args.shift();
|
|
278
|
+
if (arg === '--tag') {
|
|
279
|
+
opts.tag = requireValue(args, arg);
|
|
280
|
+
} else if (arg === '--repo') {
|
|
281
|
+
opts.repo = requireValue(args, arg);
|
|
282
|
+
} else if (arg === '--package') {
|
|
283
|
+
opts.packageName = requireValue(args, arg);
|
|
284
|
+
} else if (arg === '--max-content') {
|
|
285
|
+
opts.maxContent = Number(requireValue(args, arg));
|
|
286
|
+
} else if (arg === '--latest') {
|
|
287
|
+
opts.latest = true;
|
|
288
|
+
} else if (arg === '--stdin') {
|
|
289
|
+
opts.stdin = true;
|
|
290
|
+
} else if (arg === '--post') {
|
|
291
|
+
opts.post = true;
|
|
292
|
+
} else if (arg === '--json') {
|
|
293
|
+
opts.json = true;
|
|
294
|
+
} else if (arg === '--allow-missing-webhook') {
|
|
295
|
+
opts.allowMissingWebhook = true;
|
|
296
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
297
|
+
process.stdout.write(
|
|
298
|
+
'Usage: node scripts/release-notes/discord-release-summary.cjs [--tag <tag>|--latest|--stdin] [options]\n' +
|
|
299
|
+
'\n' +
|
|
300
|
+
'Options:\n' +
|
|
301
|
+
' --tag <tag> GitHub release tag to announce\n' +
|
|
302
|
+
' --latest Announce the latest GitHub release\n' +
|
|
303
|
+
' --stdin Read release JSON from stdin\n' +
|
|
304
|
+
' --repo <owner/repo> Repository for gh release view\n' +
|
|
305
|
+
' --package <name> Package name override\n' +
|
|
306
|
+
' --post POST to DISCORD_WEBHOOK_URL\n' +
|
|
307
|
+
' --allow-missing-webhook Warn and skip instead of failing when posting without a webhook\n' +
|
|
308
|
+
' --json Print webhook payload JSON instead of content preview\n' +
|
|
309
|
+
' --max-content <n> Max Discord content characters before webhook footer\n'
|
|
310
|
+
);
|
|
311
|
+
throw new ExitError(0);
|
|
312
|
+
} else {
|
|
313
|
+
throw new ExitError(2, `error: unknown argument ${arg}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (!Number.isFinite(opts.maxContent) || opts.maxContent < 500 || opts.maxContent > 2000) {
|
|
318
|
+
throw new ExitError(2, 'error: --max-content must be between 500 and 2000');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return opts;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function requireValue(args, flag) {
|
|
325
|
+
const value = args.shift();
|
|
326
|
+
if (!value || value.startsWith('-')) {
|
|
327
|
+
throw new ExitError(2, `error: ${flag} requires a value`);
|
|
328
|
+
}
|
|
329
|
+
return value;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function main() {
|
|
333
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
334
|
+
let release;
|
|
335
|
+
|
|
336
|
+
if (opts.stdin) {
|
|
337
|
+
release = JSON.parse(fs.readFileSync('/dev/stdin', 'utf8'));
|
|
338
|
+
} else {
|
|
339
|
+
release = fetchRelease(opts);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const payload = buildDiscordReleasePayload({
|
|
343
|
+
release,
|
|
344
|
+
packageName: opts.packageName || loadPackageName(),
|
|
345
|
+
maxContent: opts.maxContent,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
if (opts.post) {
|
|
349
|
+
const webhookUrl = process.env.DISCORD_WEBHOOK_URL;
|
|
350
|
+
if (!webhookUrl) {
|
|
351
|
+
if (opts.allowMissingWebhook) {
|
|
352
|
+
process.stderr.write('warning: DISCORD_WEBHOOK_URL is not set; skipping Discord announcement\n');
|
|
353
|
+
} else {
|
|
354
|
+
throw new ExitError(1, 'error: DISCORD_WEBHOOK_URL is not set');
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
await postWebhook(webhookUrl, payload);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
process.stdout.write(opts.json ? `${JSON.stringify(payload, null, 2)}\n` : `${payload.content}\n`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (require.main === module) {
|
|
365
|
+
runMain(main);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
module.exports = {
|
|
369
|
+
buildDiscordReleasePayload,
|
|
370
|
+
cleanBullet,
|
|
371
|
+
collectSections,
|
|
372
|
+
extractInstallCommand,
|
|
373
|
+
};
|
|
@@ -4,6 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const { execFileSync } = require('child_process');
|
|
7
|
+
const { runMain, ExitError } = require('../lib/cli-exit.cjs');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Classify a What's-Changed bullet line into 'Feature', 'Fix', or 'Enhancement'.
|
|
@@ -155,7 +156,7 @@ function formatReleaseNotes({ generatedBody, version, prerelease, packageName })
|
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
// CLI entry point
|
|
158
|
-
|
|
159
|
+
function main() {
|
|
159
160
|
try {
|
|
160
161
|
const argv = process.argv.slice(2);
|
|
161
162
|
|
|
@@ -248,9 +249,13 @@ if (require.main === module) {
|
|
|
248
249
|
process.stdout.write(formatted + '\n');
|
|
249
250
|
}
|
|
250
251
|
} catch (err) {
|
|
251
|
-
|
|
252
|
-
|
|
252
|
+
if (err instanceof ExitError) throw err;
|
|
253
|
+
throw new ExitError(1, err && err.message ? err.message : String(err));
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
256
|
|
|
257
|
+
if (require.main === module) {
|
|
258
|
+
runMain(main);
|
|
259
|
+
}
|
|
260
|
+
|
|
256
261
|
module.exports = { formatReleaseNotes, classifyTitle };
|
|
@@ -46,6 +46,7 @@ const fs = require('fs');
|
|
|
46
46
|
const os = require('os');
|
|
47
47
|
const path = require('path');
|
|
48
48
|
const { PACKAGE_NAME } = require('../gsd-core/bin/lib/package-identity.cjs');
|
|
49
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
49
50
|
// 120 s proved too tight on Windows GitHub-hosted runners: cold-cache
|
|
50
51
|
// `npm install -g` with a 1499-file tarball took ~120 s exactly, causing
|
|
51
52
|
// spawnSync to fire SIGTERM and return { status: null, stdout: '', stderr: '' }
|
|
@@ -587,23 +588,24 @@ function cliMain() {
|
|
|
587
588
|
};
|
|
588
589
|
if (isJson) process.stdout.write(JSON.stringify(result) + '\n');
|
|
589
590
|
cleanup(packDir, installPrefix, fixtureDir);
|
|
590
|
-
|
|
591
|
+
throw new ExitError(1);
|
|
591
592
|
}
|
|
592
593
|
}
|
|
593
594
|
} catch (err) {
|
|
595
|
+
if (err instanceof ExitError) throw err;
|
|
594
596
|
const result = {
|
|
595
597
|
code: SMOKE.PACK_FAILED,
|
|
596
598
|
details: { error: err.message, stderr: err.stderr },
|
|
597
599
|
};
|
|
598
600
|
if (isJson) process.stdout.write(JSON.stringify(result) + '\n');
|
|
599
601
|
cleanup(packDir, installPrefix, fixtureDir);
|
|
600
|
-
|
|
602
|
+
throw new ExitError(1);
|
|
601
603
|
}
|
|
602
604
|
|
|
603
605
|
const result = runSmoke({ tarballPath, installPrefix, expectedVersion, fixtureDir });
|
|
604
606
|
if (isJson) process.stdout.write(JSON.stringify(result) + '\n');
|
|
605
607
|
cleanup(packDir, installPrefix, fixtureDir);
|
|
606
|
-
|
|
608
|
+
return result.code === SMOKE.OK ? 0 : 1;
|
|
607
609
|
}
|
|
608
610
|
|
|
609
611
|
function cleanup(...dirs) {
|
|
@@ -623,5 +625,5 @@ function cleanup(...dirs) {
|
|
|
623
625
|
module.exports = { SMOKE, runSmoke, binInvocation };
|
|
624
626
|
|
|
625
627
|
if (require.main === module) {
|
|
626
|
-
cliMain
|
|
628
|
+
runMain(cliMain);
|
|
627
629
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* research-profiles.cjs — hand-authored profile table for the 7 researcher agents.
|
|
6
|
+
*
|
|
7
|
+
* Each profile field is derived verbatim from the agent's current committed state so
|
|
8
|
+
* that the initial --check in gen-research-agents.cjs is green by construction.
|
|
9
|
+
*
|
|
10
|
+
* Fields:
|
|
11
|
+
* name — verbatim frontmatter `name:` value
|
|
12
|
+
* description — verbatim frontmatter `description:` value
|
|
13
|
+
* color — verbatim frontmatter `color:` value
|
|
14
|
+
* tools — verbatim frontmatter `tools:` value (single string, comma-separated)
|
|
15
|
+
* requiredIncludes — @~/.claude/gsd-core/references/<file>.md strings the body MUST contain
|
|
16
|
+
* requiredSeamCalls — `gsd-tools query <cmd>` strings the body MUST contain
|
|
17
|
+
* outputContract — strings the body MUST contain (output path, return marker, etc.)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const PROFILES = [
|
|
21
|
+
{
|
|
22
|
+
name: 'gsd-project-researcher',
|
|
23
|
+
description:
|
|
24
|
+
'Researches domain ecosystem before roadmap creation. Produces files in .planning/research/ consumed during roadmap creation. Spawned by /gsd:new-project or /gsd:new-milestone orchestrators.',
|
|
25
|
+
color: 'cyan',
|
|
26
|
+
tools:
|
|
27
|
+
'Read, Write, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*',
|
|
28
|
+
requiredIncludes: [
|
|
29
|
+
'@~/.claude/gsd-core/references/research-documentation-lookup.md',
|
|
30
|
+
'@~/.claude/gsd-core/references/research-philosophy.md',
|
|
31
|
+
'@~/.claude/gsd-core/references/research-verification-protocol.md',
|
|
32
|
+
],
|
|
33
|
+
requiredSeamCalls: [
|
|
34
|
+
'gsd-tools query research-plan',
|
|
35
|
+
'gsd-tools query research-store put',
|
|
36
|
+
'gsd-tools query classify-confidence',
|
|
37
|
+
],
|
|
38
|
+
outputContract: [
|
|
39
|
+
'.planning/research/',
|
|
40
|
+
'## RESEARCH COMPLETE',
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'gsd-phase-researcher',
|
|
45
|
+
description:
|
|
46
|
+
'Researches how to implement a phase before planning. Produces RESEARCH.md consumed by gsd-planner. Spawned by /gsd:plan-phase orchestrator.',
|
|
47
|
+
color: 'cyan',
|
|
48
|
+
tools:
|
|
49
|
+
'Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*',
|
|
50
|
+
requiredIncludes: [
|
|
51
|
+
'@~/.claude/gsd-core/references/research-documentation-lookup.md',
|
|
52
|
+
'@~/.claude/gsd-core/references/research-philosophy.md',
|
|
53
|
+
'@~/.claude/gsd-core/references/research-verification-protocol.md',
|
|
54
|
+
],
|
|
55
|
+
requiredSeamCalls: [
|
|
56
|
+
'gsd-tools query research-plan',
|
|
57
|
+
'gsd-tools query research-store put',
|
|
58
|
+
'gsd-tools query classify-confidence',
|
|
59
|
+
'gsd-tools query package-legitimacy check',
|
|
60
|
+
],
|
|
61
|
+
outputContract: [
|
|
62
|
+
'.planning/phases/XX-name/{phase_num}-RESEARCH.md',
|
|
63
|
+
'## RESEARCH COMPLETE',
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'gsd-advisor-researcher',
|
|
68
|
+
description:
|
|
69
|
+
'Researches a single gray area decision and returns a structured comparison table with rationale. Spawned by discuss-phase advisor mode.',
|
|
70
|
+
color: 'cyan',
|
|
71
|
+
tools: 'Read, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*',
|
|
72
|
+
requiredIncludes: [
|
|
73
|
+
'@~/.claude/gsd-core/references/research-documentation-lookup.md',
|
|
74
|
+
],
|
|
75
|
+
requiredSeamCalls: [],
|
|
76
|
+
outputContract: [
|
|
77
|
+
'| Option | Pros | Cons | Complexity | Recommendation |',
|
|
78
|
+
'**Rationale:**',
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'gsd-ai-researcher',
|
|
83
|
+
description:
|
|
84
|
+
'Researches a chosen AI framework\'s official docs to produce implementation-ready guidance — best practices, syntax, core patterns, and pitfalls distilled for the specific use case. Writes the Framework Quick Reference and Implementation Guidance sections of AI-SPEC.md. Spawned by /gsd:ai-integration-phase orchestrator.',
|
|
85
|
+
color: 'green',
|
|
86
|
+
tools:
|
|
87
|
+
'Read, Write, Edit, Bash, Grep, Glob, WebFetch, WebSearch, mcp__context7__*',
|
|
88
|
+
requiredIncludes: [
|
|
89
|
+
'@~/.claude/gsd-core/references/research-documentation-lookup.md',
|
|
90
|
+
],
|
|
91
|
+
requiredSeamCalls: [],
|
|
92
|
+
outputContract: [
|
|
93
|
+
'AI-SPEC.md',
|
|
94
|
+
'Section 3',
|
|
95
|
+
'Section 4',
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'gsd-domain-researcher',
|
|
100
|
+
description:
|
|
101
|
+
'Researches the business domain and real-world application context of the AI system being built. Surfaces domain expert evaluation criteria, industry-specific failure modes, regulatory context, and what "good" looks like for practitioners in this field — before the eval-planner turns it into measurable rubrics. Spawned by /gsd:ai-integration-phase orchestrator.',
|
|
102
|
+
color: 'purple',
|
|
103
|
+
tools:
|
|
104
|
+
'Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*',
|
|
105
|
+
requiredIncludes: [
|
|
106
|
+
'@~/.claude/gsd-core/references/research-documentation-lookup.md',
|
|
107
|
+
],
|
|
108
|
+
requiredSeamCalls: [],
|
|
109
|
+
outputContract: [
|
|
110
|
+
'AI-SPEC.md',
|
|
111
|
+
'Section 1b',
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'gsd-ui-researcher',
|
|
116
|
+
description:
|
|
117
|
+
'Produces UI-SPEC.md design contract for frontend phases. Reads upstream artifacts, detects design system state, asks only unanswered questions. Spawned by /gsd:ui-phase orchestrator.',
|
|
118
|
+
color: 'purple',
|
|
119
|
+
tools:
|
|
120
|
+
'Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, mcp__context7__*, mcp__firecrawl__*, mcp__exa__*, mcp__tavily__*, mcp__ref__*, mcp__jina__*',
|
|
121
|
+
requiredIncludes: [
|
|
122
|
+
'@~/.claude/gsd-core/references/research-documentation-lookup.md',
|
|
123
|
+
],
|
|
124
|
+
requiredSeamCalls: [
|
|
125
|
+
'gsd-tools query commit',
|
|
126
|
+
],
|
|
127
|
+
outputContract: [
|
|
128
|
+
'UI-SPEC.md',
|
|
129
|
+
'## UI-SPEC COMPLETE',
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'gsd-research-synthesizer',
|
|
134
|
+
description:
|
|
135
|
+
'Synthesizes research outputs from parallel researcher agents into SUMMARY.md. Spawned by /gsd:new-project after 4 researcher agents complete.',
|
|
136
|
+
color: 'purple',
|
|
137
|
+
tools: 'Read, Write, Bash',
|
|
138
|
+
requiredIncludes: [],
|
|
139
|
+
requiredSeamCalls: [
|
|
140
|
+
'gsd-tools query commit',
|
|
141
|
+
],
|
|
142
|
+
outputContract: [
|
|
143
|
+
'.planning/research/SUMMARY.md',
|
|
144
|
+
'## SYNTHESIS COMPLETE',
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
module.exports = { PROFILES };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { spawnSync } = require('child_process');
|
|
4
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
4
5
|
|
|
5
6
|
const CROSS_PLATFORM_TEST_REASON = Object.freeze({
|
|
6
7
|
PASS: 'pass',
|
|
@@ -50,14 +51,17 @@ function runCrossPlatformTests(options = {}, deps = {}) {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
if (require.main === module) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
function main() {
|
|
55
|
+
const result = runCrossPlatformTests();
|
|
56
|
+
const line = `[cross-platform-tests] reason=${result.reason} exit=${result.exitCode}`;
|
|
57
|
+
if (result.ok) {
|
|
58
|
+
process.stdout.write(`${line}\n`);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
process.stderr.write(`${line}\n`);
|
|
62
|
+
throw new ExitError(result.exitCode);
|
|
58
63
|
}
|
|
59
|
-
|
|
60
|
-
process.exit(result.exitCode);
|
|
64
|
+
runMain(main);
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
module.exports = { CROSS_PLATFORM_TEST_REASON, runCrossPlatformTests };
|