@hegemonart/get-design-done 1.31.0 → 1.32.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.
Files changed (180) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +75 -0
  4. package/NOTICE +262 -0
  5. package/README.md +13 -1
  6. package/SKILL.md +4 -0
  7. package/agents/design-authority-watcher.md +1 -1
  8. package/agents/perf-analyzer.md +2 -2
  9. package/bin/gdd-mcp +78 -0
  10. package/bin/gdd-sdk +34 -24
  11. package/bin/gdd-state-mcp +78 -0
  12. package/{README.de.md → docs/i18n/README.de.md} +1 -1
  13. package/{README.fr.md → docs/i18n/README.fr.md} +1 -1
  14. package/{README.it.md → docs/i18n/README.it.md} +1 -1
  15. package/{README.ja.md → docs/i18n/README.ja.md} +1 -1
  16. package/{README.ko.md → docs/i18n/README.ko.md} +1 -1
  17. package/{README.zh-CN.md → docs/i18n/README.zh-CN.md} +1 -1
  18. package/hooks/_hook-emit.js +1 -1
  19. package/hooks/budget-enforcer.ts +5 -5
  20. package/hooks/context-exhaustion.ts +2 -2
  21. package/hooks/gdd-precompact-snapshot.js +3 -3
  22. package/hooks/gdd-read-injection-scanner.ts +2 -2
  23. package/hooks/gdd-sessionstart-recap.js +1 -1
  24. package/hooks/gdd-turn-closeout.js +1 -1
  25. package/hooks/hooks.json +9 -0
  26. package/hooks/inject-using-gdd.sh +72 -0
  27. package/hooks/run-hook.cmd +35 -0
  28. package/package.json +20 -9
  29. package/recipes/.gitkeep +0 -0
  30. package/reference/schemas/events.schema.json +63 -1
  31. package/reference/schemas/recipe.schema.json +33 -0
  32. package/scripts/cli/gdd-events.mjs +5 -5
  33. package/scripts/lib/cache/gdd-cache-manager.cjs +1 -1
  34. package/scripts/lib/cli/index.ts +22 -160
  35. package/scripts/lib/connection-probe/index.cjs +1 -1
  36. package/scripts/lib/discuss-parallel-runner/aggregator.ts +1 -1
  37. package/scripts/lib/discuss-parallel-runner/index.ts +1 -1
  38. package/scripts/lib/error-classifier.cjs +24 -227
  39. package/scripts/lib/event-stream/index.ts +25 -193
  40. package/scripts/lib/gdd-errors/index.ts +24 -213
  41. package/scripts/lib/gdd-state/index.ts +23 -161
  42. package/scripts/lib/health-mirror/index.cjs +79 -1
  43. package/scripts/lib/iteration-budget.cjs +23 -199
  44. package/scripts/lib/jittered-backoff.cjs +24 -107
  45. package/scripts/lib/lockfile.cjs +23 -195
  46. package/scripts/lib/logger/index.ts +1 -1
  47. package/scripts/lib/parallelism-engine/concurrency-tuner.cjs +1 -1
  48. package/scripts/lib/perf-analyzer/index.cjs +1 -1
  49. package/scripts/lib/pipeline-runner/index.ts +4 -4
  50. package/scripts/lib/pipeline-runner/state-machine.ts +1 -1
  51. package/scripts/lib/prompt-dedup/index.cjs +1 -1
  52. package/scripts/lib/rate-guard.cjs +2 -2
  53. package/scripts/lib/recipe-loader.cjs +142 -0
  54. package/scripts/lib/session-runner/errors.ts +3 -3
  55. package/scripts/lib/session-runner/index.ts +3 -3
  56. package/scripts/lib/session-runner/transcript.ts +1 -1
  57. package/scripts/lib/tool-scoping/index.ts +1 -1
  58. package/scripts/mcp-servers/gdd-mcp/server.ts +29 -311
  59. package/scripts/mcp-servers/gdd-state/server.ts +28 -282
  60. package/sdk/README.md +45 -0
  61. package/{scripts/lib → sdk}/cli/commands/audit.ts +3 -3
  62. package/{scripts/lib → sdk}/cli/commands/init.ts +3 -3
  63. package/{scripts/lib → sdk}/cli/commands/query.ts +4 -4
  64. package/{scripts/lib → sdk}/cli/commands/run.ts +5 -5
  65. package/{scripts/lib → sdk}/cli/commands/stage.ts +5 -5
  66. package/sdk/cli/index.js +8091 -0
  67. package/sdk/cli/index.ts +172 -0
  68. package/{scripts/lib → sdk}/cli/parse-args.ts +2 -2
  69. package/{scripts/lib/gdd-errors → sdk/errors}/classification.ts +1 -1
  70. package/sdk/errors/index.ts +218 -0
  71. package/{scripts/lib → sdk}/event-stream/emitter.ts +1 -1
  72. package/sdk/event-stream/index.ts +197 -0
  73. package/{scripts/lib → sdk}/event-stream/reader.ts +1 -1
  74. package/{scripts/lib → sdk}/event-stream/types.ts +2 -2
  75. package/{scripts/lib → sdk}/event-stream/writer.ts +1 -1
  76. package/sdk/index.ts +19 -0
  77. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/README.md +3 -3
  78. package/sdk/mcp/gdd-mcp/server.js +1966 -0
  79. package/sdk/mcp/gdd-mcp/server.ts +325 -0
  80. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_cycle_recap.ts +3 -3
  81. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_decisions_list.ts +2 -2
  82. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_events_tail.ts +3 -3
  83. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_health.ts +2 -2
  84. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_intel_get.ts +2 -2
  85. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_learnings_digest.ts +2 -2
  86. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phase_current.ts +2 -2
  87. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phases_list.ts +2 -2
  88. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_plans_list.ts +2 -2
  89. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_reflections_latest.ts +2 -2
  90. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_status.ts +3 -3
  91. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_telemetry_query.ts +3 -3
  92. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/index.ts +2 -2
  93. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/shared.ts +3 -3
  94. package/sdk/mcp/gdd-state/server.js +2790 -0
  95. package/sdk/mcp/gdd-state/server.ts +294 -0
  96. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_blocker.ts +3 -3
  97. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_decision.ts +3 -3
  98. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_must_have.ts +3 -3
  99. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/checkpoint.ts +2 -2
  100. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/frontmatter_update.ts +2 -2
  101. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/get.ts +3 -3
  102. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/index.ts +1 -1
  103. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/probe_connections.ts +3 -3
  104. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/resolve_blocker.ts +3 -3
  105. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/set_status.ts +2 -2
  106. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/shared.ts +8 -8
  107. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/transition_stage.ts +4 -4
  108. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/update_progress.ts +2 -2
  109. package/sdk/primitives/error-classifier.cjs +232 -0
  110. package/sdk/primitives/iteration-budget.cjs +205 -0
  111. package/sdk/primitives/jittered-backoff.cjs +112 -0
  112. package/sdk/primitives/lockfile.cjs +201 -0
  113. package/{scripts/lib/gdd-state → sdk/state}/gates.ts +1 -1
  114. package/sdk/state/index.ts +167 -0
  115. package/{scripts/lib/gdd-state → sdk/state}/lockfile.ts +1 -1
  116. package/{scripts/lib/gdd-state → sdk/state}/mutator.ts +1 -1
  117. package/{scripts/lib/gdd-state → sdk/state}/parser.ts +1 -1
  118. package/{scripts/lib/gdd-state → sdk/state}/types.ts +4 -4
  119. package/skills/audit/SKILL.md +13 -0
  120. package/skills/brief/SKILL.md +25 -0
  121. package/skills/design/SKILL.md +17 -0
  122. package/skills/discuss/SKILL.md +13 -0
  123. package/skills/explore/SKILL.md +17 -0
  124. package/skills/health/SKILL.md +6 -0
  125. package/skills/plan/SKILL.md +25 -0
  126. package/skills/quality-gate/SKILL.md +2 -2
  127. package/skills/router/SKILL.md +4 -0
  128. package/skills/router/router-pick-emitter.md +78 -0
  129. package/skills/using-gdd/SKILL.md +78 -0
  130. package/skills/verify/SKILL.md +17 -0
  131. package/scripts/aggregate-agent-metrics.ts +0 -282
  132. package/scripts/bootstrap-manifest.txt +0 -3
  133. package/scripts/bootstrap.sh +0 -80
  134. package/scripts/build-distribution-bundles.cjs +0 -549
  135. package/scripts/build-intel.cjs +0 -486
  136. package/scripts/codegen-schema-types.ts +0 -149
  137. package/scripts/detect-stale-refs.cjs +0 -107
  138. package/scripts/e2e/run-headless.ts +0 -514
  139. package/scripts/extract-changelog-section.cjs +0 -58
  140. package/scripts/gsd-cleanup-incubator.cjs +0 -367
  141. package/scripts/injection-patterns.cjs +0 -58
  142. package/scripts/lint-agentskills-spec.cjs +0 -457
  143. package/scripts/release-smoke-test.cjs +0 -200
  144. package/scripts/rollback-release.sh +0 -42
  145. package/scripts/run-injection-scanner-ci.cjs +0 -83
  146. package/scripts/tests/test-authority-rejected-kinds.sh +0 -58
  147. package/scripts/tests/test-authority-watcher-diff.sh +0 -113
  148. package/scripts/tests/test-motion-provenance.sh +0 -64
  149. package/scripts/validate-frontmatter.ts +0 -409
  150. package/scripts/validate-incubator-scope.cjs +0 -133
  151. package/scripts/validate-schemas.ts +0 -401
  152. package/scripts/validate-skill-length.cjs +0 -283
  153. package/scripts/verify-version-sync.cjs +0 -30
  154. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_cycle_recap.schema.json +0 -0
  155. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_decisions_list.schema.json +0 -0
  156. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_events_tail.schema.json +0 -0
  157. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_health.schema.json +0 -0
  158. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_intel_get.schema.json +0 -0
  159. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_learnings_digest.schema.json +0 -0
  160. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phase_current.schema.json +0 -0
  161. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phases_list.schema.json +0 -0
  162. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_plans_list.schema.json +0 -0
  163. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_reflections_latest.schema.json +0 -0
  164. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_status.schema.json +0 -0
  165. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_telemetry_query.schema.json +0 -0
  166. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_blocker.schema.json +0 -0
  167. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_decision.schema.json +0 -0
  168. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_must_have.schema.json +0 -0
  169. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/checkpoint.schema.json +0 -0
  170. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/frontmatter_update.schema.json +0 -0
  171. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/get.schema.json +0 -0
  172. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/probe_connections.schema.json +0 -0
  173. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/resolve_blocker.schema.json +0 -0
  174. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/set_status.schema.json +0 -0
  175. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/transition_stage.schema.json +0 -0
  176. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/update_progress.schema.json +0 -0
  177. /package/{scripts/lib → sdk/primitives}/error-classifier.d.cts +0 -0
  178. /package/{scripts/lib → sdk/primitives}/iteration-budget.d.cts +0 -0
  179. /package/{scripts/lib → sdk/primitives}/jittered-backoff.d.cts +0 -0
  180. /package/{scripts/lib → sdk/primitives}/lockfile.d.cts +0 -0
@@ -1,549 +0,0 @@
1
- 'use strict';
2
- /**
3
- * scripts/build-distribution-bundles.cjs — Phase 28.8 (Plan 28-8-X1).
4
- *
5
- * Shared-source / multi-channel distribution bundler.
6
- *
7
- * Per CONTEXT D-06: skills are shared source / per-channel converters at
8
- * distribution-build time. This script fans out canonical `skills/` into
9
- * three channel-specific bundles under `dist/`:
10
- *
11
- * - dist/cursor-marketplace/ (via scripts/lib/install/converters/cursor-marketplace.cjs)
12
- * - dist/codex-plugin/ (via scripts/lib/install/converters/codex-plugin.cjs)
13
- * - dist/agentskills-io/ (passthrough per D-13 lint-only)
14
- *
15
- * Tier-2 channels are discovered by inspecting `runtimes.cjs` entries with
16
- * `kind: 'cursor-marketplace'` or `kind: 'codex-plugin'`. `agentskills-io`
17
- * is hardcoded as a passthrough (it's a spec, not a runtime — D-02/D-13).
18
- *
19
- * Determinism: two consecutive runs produce byte-identical output.
20
- * Tier-1 unaffected: only writes under `dist/`.
21
- *
22
- * --- ADAPTER NOTE (Plan 28-8-X1 implementation): the Wave-B converters
23
- * (B1: cursor-marketplace.cjs, C1: codex-plugin.cjs) actually export
24
- * `{ buildManifest, convert, CURATED_KEYWORDS }` — NOT the
25
- * `{ convertSkill, buildManifest, MANIFEST_PATH }` shape that the plan's
26
- * `<interfaces>` block hypothesized. Per the plan's "Adapter divergence
27
- * handling" clause, this bundler adapts to the actual converter shape
28
- * rather than modifying the converters (which are already shipped and
29
- * out of scope per D-05).
30
- *
31
- * Actual converter contract used here:
32
- * - converter.buildManifest({ packageJson, claudePlugin, claudePluginJson,
33
- * marketplaceJson, readmeFirstPara })
34
- * → returns a manifest OBJECT (not a string).
35
- * (cursor-marketplace looks for `claudePluginJson`; codex-plugin
36
- * looks for `claudePlugin`. We pass BOTH keys to be compatible
37
- * with either accessor.)
38
- * - converter.convert({ skillsDir, outDir, manifest })
39
- * → writes manifest + copies skills/ tree under outDir. Owns its
40
- * own manifest path (.cursor-plugin/plugin.json or .codex-plugin/
41
- * plugin.json) — the bundler doesn't need to know.
42
- *
43
- * CLI:
44
- * node scripts/build-distribution-bundles.cjs # all channels
45
- * node scripts/build-distribution-bundles.cjs --channel cursor-marketplace
46
- * node scripts/build-distribution-bundles.cjs --help
47
- *
48
- * Exit codes: 0 ok / 1 converter error / 2 missing dependency.
49
- */
50
-
51
- const fs = require('fs');
52
- const path = require('path');
53
-
54
- const EXIT_CODES = Object.freeze({
55
- OK: 0,
56
- CONVERTER_ERROR: 1,
57
- MISSING_DEPENDENCY: 2,
58
- });
59
-
60
- // agentskills-io is hardcoded — it is a spec, not a runtime entry
61
- // (per CONTEXT D-02 / D-13). No converter file, no manifest file.
62
- const PASSTHROUGH_CHANNEL = Object.freeze({
63
- id: 'agentskills-io',
64
- kind: 'passthrough',
65
- converterPath: null,
66
- });
67
-
68
- // Set of runtime `kind` values that the bundler dispatches to Wave-B
69
- // converters. Hardcoded to two kinds — adding a third Tier-2 channel in
70
- // a future phase requires (a) adding the runtime entry with a new kind,
71
- // (b) shipping a converter at scripts/lib/install/converters/<kind>.cjs,
72
- // (c) extending this set. The channel-ID discovery itself is data-driven.
73
- const TIER2_KINDS = Object.freeze(new Set(['cursor-marketplace', 'codex-plugin']));
74
-
75
- // ---------------------------------------------------------------
76
- // Channel discovery
77
- // ---------------------------------------------------------------
78
-
79
- /**
80
- * Discover Tier-2 channels from the runtimes registry + add the hardcoded
81
- * passthrough channel. Returns Array<{id, kind, converterPath}>.
82
- *
83
- * `runtimesModule` is dependency-injected so tests can supply a fixture.
84
- * Production callers pass `require('./lib/install/runtimes.cjs')`.
85
- *
86
- * Determinism: runtime list sorted lexicographically by id before iteration.
87
- * The hardcoded PASSTHROUGH_CHANNEL is appended last; callers that want a
88
- * fully lexicographic ordering should re-sort the returned array.
89
- */
90
- function discoverTier2Channels(runtimesModule) {
91
- const channels = [];
92
- const runtimes = (runtimesModule && typeof runtimesModule.listRuntimes === 'function')
93
- ? runtimesModule.listRuntimes()
94
- : [];
95
- const sorted = runtimes.slice().sort((a, b) => a.id.localeCompare(b.id));
96
- for (const rt of sorted) {
97
- if (!TIER2_KINDS.has(rt.kind)) continue;
98
- channels.push({
99
- id: rt.id,
100
- kind: rt.kind,
101
- // Converter file lives at scripts/lib/install/converters/<kind>.cjs.
102
- // T-28.8-X1-01 (Tampering / require()): `kind` originates in the
103
- // version-controlled runtimes.cjs file — an attacker would already
104
- // need write access to introduce a malicious value. Acceptable.
105
- converterPath: path.join(
106
- __dirname,
107
- 'lib', 'install', 'converters',
108
- rt.kind + '.cjs',
109
- ),
110
- });
111
- }
112
- channels.push(PASSTHROUGH_CHANNEL);
113
- return channels;
114
- }
115
-
116
- // ---------------------------------------------------------------
117
- // Skill enumeration (canonical source)
118
- // ---------------------------------------------------------------
119
-
120
- /**
121
- * Enumerate child directories of `<sourceRoot>/skills/` that contain a
122
- * `SKILL.md`. Returns { skillsRoot, skillNames } where skillNames is
123
- * sorted lexicographically (determinism).
124
- *
125
- * Throws Error with code MISSING_SKILLS_ROOT if skills/ is absent.
126
- */
127
- function enumerateSkills(sourceRoot) {
128
- const skillsRoot = path.join(sourceRoot, 'skills');
129
- if (!fs.existsSync(skillsRoot)) {
130
- const err = new Error('Canonical skills/ tree not found at ' + skillsRoot);
131
- err.code = 'MISSING_SKILLS_ROOT';
132
- throw err;
133
- }
134
- const names = fs.readdirSync(skillsRoot)
135
- .filter((name) => {
136
- const skillDir = path.join(skillsRoot, name);
137
- try {
138
- return fs.statSync(skillDir).isDirectory()
139
- && fs.existsSync(path.join(skillDir, 'SKILL.md'));
140
- } catch {
141
- return false;
142
- }
143
- })
144
- .sort();
145
- return { skillsRoot, skillNames: names };
146
- }
147
-
148
- // ---------------------------------------------------------------
149
- // Filesystem helpers
150
- // ---------------------------------------------------------------
151
-
152
- function ensureCleanDir(dirPath) {
153
- if (fs.existsSync(dirPath)) {
154
- fs.rmSync(dirPath, { recursive: true, force: true });
155
- }
156
- fs.mkdirSync(dirPath, { recursive: true });
157
- }
158
-
159
- /**
160
- * Deterministic file write: 0o644, no timestamp metadata leaked into content.
161
- */
162
- function writeFile(dest, content) {
163
- fs.mkdirSync(path.dirname(dest), { recursive: true });
164
- fs.writeFileSync(dest, content, { mode: 0o644 });
165
- }
166
-
167
- /**
168
- * Recursive byte-for-byte copy of `srcDir` into `destDir`. Used by the
169
- * passthrough channel. Deterministic: lexicographic readdir + 0o644.
170
- *
171
- * T-28.8-X1-03 (Tampering / symlinks): only entry.isFile() and
172
- * entry.isDirectory() are propagated. Symlinks and other types are
173
- * silently skipped — the canonical skills/ tree is expected to be
174
- * regular files only.
175
- */
176
- function copyDirRecursive(srcDir, destDir) {
177
- fs.mkdirSync(destDir, { recursive: true });
178
- const entries = fs.readdirSync(srcDir, { withFileTypes: true })
179
- .slice()
180
- .sort((a, b) => a.name.localeCompare(b.name));
181
- for (const entry of entries) {
182
- const srcPath = path.join(srcDir, entry.name);
183
- const destPath = path.join(destDir, entry.name);
184
- if (entry.isDirectory()) {
185
- copyDirRecursive(srcPath, destPath);
186
- } else if (entry.isFile()) {
187
- const content = fs.readFileSync(srcPath);
188
- writeFile(destPath, content);
189
- }
190
- // Symlinks / other entry types: skip.
191
- }
192
- }
193
-
194
- function countFiles(dir) {
195
- let count = 0;
196
- if (!fs.existsSync(dir)) return 0;
197
- const stack = [dir];
198
- while (stack.length) {
199
- const d = stack.pop();
200
- for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
201
- const p = path.join(d, entry.name);
202
- if (entry.isDirectory()) stack.push(p);
203
- else if (entry.isFile()) count++;
204
- }
205
- }
206
- return count;
207
- }
208
-
209
- // ---------------------------------------------------------------
210
- // Optional ancillary sources (loaded best-effort from repo root)
211
- // ---------------------------------------------------------------
212
-
213
- /**
214
- * Best-effort loader for ancillary inputs the converters may consult:
215
- * - .claude-plugin/plugin.json → claudePlugin / claudePluginJson
216
- * - .claude-plugin/marketplace.json → marketplaceJson
217
- * - README.md (first paragraph) → readmeFirstPara
218
- *
219
- * Returns an object with each key present only if the corresponding source
220
- * exists and parses cleanly. Never throws — converters are tolerant of
221
- * absent optional sources, and tmpdir test fixtures typically omit them.
222
- */
223
- function loadAncillarySources(sourceRoot) {
224
- const sources = {};
225
- const pluginJsonPath = path.join(sourceRoot, '.claude-plugin', 'plugin.json');
226
- if (fs.existsSync(pluginJsonPath)) {
227
- try {
228
- const parsed = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf8'));
229
- sources.claudePlugin = parsed;
230
- sources.claudePluginJson = parsed;
231
- } catch {
232
- // Best-effort: malformed plugin.json is the converter's problem,
233
- // not the bundler's. Continue without it.
234
- }
235
- }
236
- const marketplaceJsonPath = path.join(sourceRoot, '.claude-plugin', 'marketplace.json');
237
- if (fs.existsSync(marketplaceJsonPath)) {
238
- try {
239
- sources.marketplaceJson = JSON.parse(fs.readFileSync(marketplaceJsonPath, 'utf8'));
240
- } catch {
241
- // skip
242
- }
243
- }
244
- const readmePath = path.join(sourceRoot, 'README.md');
245
- if (fs.existsSync(readmePath)) {
246
- try {
247
- const raw = fs.readFileSync(readmePath, 'utf8');
248
- // First non-empty, non-heading paragraph.
249
- const paragraphs = raw.split(/\n\s*\n/);
250
- for (const p of paragraphs) {
251
- const trimmed = p.trim();
252
- if (!trimmed || trimmed.startsWith('#')) continue;
253
- sources.readmeFirstPara = trimmed.replace(/\s+/g, ' ');
254
- break;
255
- }
256
- } catch {
257
- // skip
258
- }
259
- }
260
- return sources;
261
- }
262
-
263
- // ---------------------------------------------------------------
264
- // Channel build dispatch
265
- // ---------------------------------------------------------------
266
-
267
- /**
268
- * Build a single channel into `outRoot/<channelId>/`.
269
- * Returns { channel, fileCount }.
270
- *
271
- * Throws on converter error or missing dependency — caller maps to exit code.
272
- *
273
- * Errors set `err.code` to one of:
274
- * - 'MISSING_CONVERTER' → exit 2
275
- * - 'CONVERTER_LOAD_FAILED' → exit 2 (require() failed — broken module)
276
- * - 'CONVERTER_EXEC_FAILED' → exit 1 (converter ran and threw)
277
- * - 'MANIFEST_BUILD_FAILED' → exit 1 (buildManifest threw)
278
- * - 'MISSING_SKILLS_ROOT' → exit 2 (no skills/ dir to read)
279
- */
280
- function buildChannel(channel, opts) {
281
- const { sourceRoot, outRoot, packageJson } = opts || {};
282
- if (!channel || typeof channel !== 'object') {
283
- throw new Error('buildChannel: channel is required');
284
- }
285
- if (typeof sourceRoot !== 'string' || sourceRoot.length === 0) {
286
- throw new Error('buildChannel: opts.sourceRoot is required');
287
- }
288
- if (typeof outRoot !== 'string' || outRoot.length === 0) {
289
- throw new Error('buildChannel: opts.outRoot is required');
290
- }
291
-
292
- const bundleRoot = path.join(outRoot, channel.id);
293
- ensureCleanDir(bundleRoot);
294
-
295
- if (channel.kind === 'passthrough') {
296
- // agentskills-io: passthrough copy of skills/ (D-13).
297
- const skillsSrc = path.join(sourceRoot, 'skills');
298
- if (!fs.existsSync(skillsSrc)) {
299
- const err = new Error('Canonical skills/ tree not found at ' + skillsSrc);
300
- err.code = 'MISSING_SKILLS_ROOT';
301
- err.channelId = channel.id;
302
- throw err;
303
- }
304
- copyDirRecursive(skillsSrc, path.join(bundleRoot, 'skills'));
305
- return { channel: channel.id, fileCount: countFiles(bundleRoot) };
306
- }
307
-
308
- // Tier-2 converter-backed channels (cursor-marketplace, codex-plugin).
309
- if (!fs.existsSync(channel.converterPath)) {
310
- const err = new Error(
311
- 'Missing converter for channel "' + channel.id + '": expected at ' + channel.converterPath
312
- );
313
- err.code = 'MISSING_CONVERTER';
314
- err.channelId = channel.id;
315
- throw err;
316
- }
317
-
318
- let converter;
319
- try {
320
- converter = require(channel.converterPath);
321
- } catch (e) {
322
- const err = new Error(
323
- 'Failed to load converter for channel "' + channel.id + '": ' + e.message
324
- );
325
- err.code = 'CONVERTER_LOAD_FAILED';
326
- err.channelId = channel.id;
327
- err.cause = e;
328
- throw err;
329
- }
330
-
331
- if (typeof converter.buildManifest !== 'function' || typeof converter.convert !== 'function') {
332
- const err = new Error(
333
- 'Converter for channel "' + channel.id + '" missing required exports: ' +
334
- 'expected { buildManifest, convert }, got ' + Object.keys(converter).join(', ')
335
- );
336
- err.code = 'CONVERTER_LOAD_FAILED';
337
- err.channelId = channel.id;
338
- throw err;
339
- }
340
-
341
- // Enumerate skills BEFORE invoking convert(). This both validates that
342
- // skills/ exists and surfaces a deterministic name list for error
343
- // messages — convert() will re-walk the directory itself per its own
344
- // semantics, which is fine (idempotent) but we want a stable list for
345
- // the CONVERTER_EXEC_FAILED error message.
346
- const { skillsRoot, skillNames } = enumerateSkills(sourceRoot);
347
-
348
- // Build manifest via the converter.
349
- // Pass BOTH `claudePlugin` and `claudePluginJson` accessor keys so the
350
- // adapter works regardless of which key the specific converter consults.
351
- const ancillary = loadAncillarySources(sourceRoot);
352
- const sources = Object.assign({}, ancillary, { packageJson });
353
-
354
- let manifest;
355
- try {
356
- manifest = converter.buildManifest(sources);
357
- } catch (e) {
358
- const err = new Error(
359
- 'Converter "' + channel.id + '" failed building manifest: ' + e.message
360
- );
361
- err.code = 'MANIFEST_BUILD_FAILED';
362
- err.channelId = channel.id;
363
- err.cause = e;
364
- throw err;
365
- }
366
-
367
- // Invoke convert() — converter writes manifest + copies skills/ under outDir.
368
- try {
369
- converter.convert({
370
- skillsDir: skillsRoot,
371
- outDir: bundleRoot,
372
- manifest,
373
- });
374
- } catch (e) {
375
- // The converter walked skills/ internally so we don't know which
376
- // individual skill triggered the throw — surface the full list to
377
- // aid debugging.
378
- const skillsHint = skillNames.length > 0
379
- ? ' (skills: ' + skillNames.join(', ') + ')'
380
- : '';
381
- const err = new Error(
382
- 'Converter "' + channel.id + '" failed during convert()' + skillsHint + ': ' + e.message
383
- );
384
- err.code = 'CONVERTER_EXEC_FAILED';
385
- err.channelId = channel.id;
386
- err.skillName = skillNames[0] || null;
387
- err.cause = e;
388
- throw err;
389
- }
390
-
391
- return { channel: channel.id, fileCount: countFiles(bundleRoot) };
392
- }
393
-
394
- /**
395
- * Build all (or one filtered) channel(s) into `outRoot`.
396
- *
397
- * Options:
398
- * sourceRoot — repo root containing skills/ + ancillary sources
399
- * outRoot — destination root (e.g., repo/dist)
400
- * runtimesModule — dependency-injected runtimes registry (test seam)
401
- * packageJson — parsed package.json object passed to converters
402
- * channelFilter — optional channel id to scope the build to one channel
403
- *
404
- * Returns Array<{ channel, fileCount }> in lexicographic channel order.
405
- */
406
- function buildAllChannels(opts) {
407
- const { sourceRoot, outRoot, runtimesModule, packageJson, channelFilter } = opts || {};
408
- const channels = discoverTier2Channels(runtimesModule);
409
- const targets = channelFilter
410
- ? channels.filter((c) => c.id === channelFilter)
411
- : channels;
412
- if (channelFilter && targets.length === 0) {
413
- const err = new Error(
414
- 'Unknown channel: "' + channelFilter + '". Available: ' +
415
- channels.map((c) => c.id).join(', ')
416
- );
417
- err.code = 'UNKNOWN_CHANNEL';
418
- throw err;
419
- }
420
- // Lexicographic order for deterministic stdout + filesystem traversal.
421
- targets.sort((a, b) => a.id.localeCompare(b.id));
422
- const results = [];
423
- for (const channel of targets) {
424
- results.push(buildChannel(channel, { sourceRoot, outRoot, packageJson }));
425
- }
426
- return results;
427
- }
428
-
429
- // ---------------------------------------------------------------
430
- // CLI entrypoint
431
- // ---------------------------------------------------------------
432
-
433
- function parseArgs(argv) {
434
- const args = { help: false, channel: null };
435
- for (let i = 0; i < argv.length; i++) {
436
- const a = argv[i];
437
- if (a === '--help' || a === '-h') {
438
- args.help = true;
439
- } else if (a === '--channel') {
440
- if (i + 1 >= argv.length) {
441
- throw new Error('--channel requires a value');
442
- }
443
- args.channel = argv[++i];
444
- } else {
445
- throw new Error('Unknown argument: ' + a);
446
- }
447
- }
448
- return args;
449
- }
450
-
451
- function printUsage(out) {
452
- out.write([
453
- 'Usage: node scripts/build-distribution-bundles.cjs [--channel <id>]',
454
- '',
455
- 'Builds Tier-2 distribution bundles from canonical skills/ into dist/.',
456
- '',
457
- 'Options:',
458
- ' --channel <id> Build only the named channel (e.g., cursor-marketplace,',
459
- ' codex-plugin, agentskills-io). Default: all channels.',
460
- ' --help, -h Print this message.',
461
- '',
462
- 'Exit codes:',
463
- ' 0 success',
464
- ' 1 converter error (converter ran and threw)',
465
- ' 2 missing dependency (converter file, runtimes.cjs entry, skills/, or bad arg)',
466
- '',
467
- ].join('\n'));
468
- }
469
-
470
- function main(argv, ioOpts) {
471
- const stdout = (ioOpts && ioOpts.stdout) || process.stdout;
472
- const stderr = (ioOpts && ioOpts.stderr) || process.stderr;
473
-
474
- let args;
475
- try {
476
- args = parseArgs(argv || []);
477
- } catch (e) {
478
- stderr.write('Error: ' + e.message + '\n');
479
- printUsage(stderr);
480
- return EXIT_CODES.MISSING_DEPENDENCY;
481
- }
482
- if (args.help) {
483
- printUsage(stdout);
484
- return EXIT_CODES.OK;
485
- }
486
-
487
- const repoRoot = path.resolve(__dirname, '..');
488
- const sourceRoot = repoRoot;
489
- const outRoot = path.join(repoRoot, 'dist');
490
-
491
- let runtimesModule;
492
- try {
493
- runtimesModule = require('./lib/install/runtimes.cjs');
494
- } catch (e) {
495
- stderr.write('Error: failed to load runtimes.cjs: ' + e.message + '\n');
496
- return EXIT_CODES.MISSING_DEPENDENCY;
497
- }
498
-
499
- let packageJson;
500
- try {
501
- packageJson = require(path.join(repoRoot, 'package.json'));
502
- } catch (e) {
503
- stderr.write('Error: failed to load package.json: ' + e.message + '\n');
504
- return EXIT_CODES.MISSING_DEPENDENCY;
505
- }
506
-
507
- try {
508
- const results = buildAllChannels({
509
- sourceRoot,
510
- outRoot,
511
- runtimesModule,
512
- packageJson,
513
- channelFilter: args.channel,
514
- });
515
- for (const r of results) {
516
- stdout.write('[bundles] ' + r.channel + ': ' + r.fileCount + ' file(s)\n');
517
- }
518
- return EXIT_CODES.OK;
519
- } catch (e) {
520
- stderr.write('Error: ' + e.message + '\n');
521
- if (
522
- e.code === 'MISSING_CONVERTER' ||
523
- e.code === 'MISSING_SKILLS_ROOT' ||
524
- e.code === 'UNKNOWN_CHANNEL' ||
525
- e.code === 'CONVERTER_LOAD_FAILED'
526
- ) {
527
- return EXIT_CODES.MISSING_DEPENDENCY;
528
- }
529
- // CONVERTER_EXEC_FAILED, MANIFEST_BUILD_FAILED, or anything else.
530
- return EXIT_CODES.CONVERTER_ERROR;
531
- }
532
- }
533
-
534
- module.exports = {
535
- buildAllChannels,
536
- buildChannel,
537
- discoverTier2Channels,
538
- enumerateSkills,
539
- loadAncillarySources,
540
- main,
541
- parseArgs,
542
- EXIT_CODES,
543
- PASSTHROUGH_CHANNEL,
544
- TIER2_KINDS,
545
- };
546
-
547
- if (require.main === module) {
548
- process.exitCode = main(process.argv.slice(2));
549
- }