@hegemonart/get-design-done 1.30.6 → 1.31.5

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 (175) hide show
  1. package/.claude-plugin/marketplace.json +6 -3
  2. package/.claude-plugin/plugin.json +5 -2
  3. package/CHANGELOG.md +105 -0
  4. package/NOTICE +224 -0
  5. package/README.md +22 -1
  6. package/SKILL.md +1 -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/package.json +24 -10
  26. package/recipes/.gitkeep +0 -0
  27. package/reference/schemas/recipe.schema.json +33 -0
  28. package/scripts/cli/gdd-events.mjs +5 -5
  29. package/scripts/lib/cache/gdd-cache-manager.cjs +1 -1
  30. package/scripts/lib/cli/index.ts +22 -160
  31. package/scripts/lib/connection-probe/index.cjs +1 -1
  32. package/scripts/lib/discuss-parallel-runner/aggregator.ts +1 -1
  33. package/scripts/lib/discuss-parallel-runner/index.ts +1 -1
  34. package/scripts/lib/error-classifier.cjs +24 -227
  35. package/scripts/lib/event-stream/index.ts +25 -193
  36. package/scripts/lib/figma-extract/digest.cjs +430 -0
  37. package/scripts/lib/figma-extract/parse-url.cjs +87 -0
  38. package/scripts/lib/figma-extract/payload-schema.json +108 -0
  39. package/scripts/lib/figma-extract/pull.cjs +394 -0
  40. package/scripts/lib/figma-extract/receiver.cjs +273 -0
  41. package/scripts/lib/figma-extract/render-md.cjs +143 -0
  42. package/scripts/lib/figma-extract/styles-resolver.cjs +147 -0
  43. package/scripts/lib/figma-extract/walk.cjs +100 -0
  44. package/scripts/lib/gdd-errors/index.ts +24 -213
  45. package/scripts/lib/gdd-state/index.ts +23 -161
  46. package/scripts/lib/health-mirror/index.cjs +88 -1
  47. package/scripts/lib/iteration-budget.cjs +23 -199
  48. package/scripts/lib/jittered-backoff.cjs +24 -107
  49. package/scripts/lib/lockfile.cjs +23 -195
  50. package/scripts/lib/logger/index.ts +1 -1
  51. package/scripts/lib/parallelism-engine/concurrency-tuner.cjs +1 -1
  52. package/scripts/lib/perf-analyzer/index.cjs +1 -1
  53. package/scripts/lib/pipeline-runner/index.ts +4 -4
  54. package/scripts/lib/pipeline-runner/state-machine.ts +1 -1
  55. package/scripts/lib/prompt-dedup/index.cjs +1 -1
  56. package/scripts/lib/rate-guard.cjs +2 -2
  57. package/scripts/lib/recipe-loader.cjs +142 -0
  58. package/scripts/lib/session-runner/errors.ts +3 -3
  59. package/scripts/lib/session-runner/index.ts +3 -3
  60. package/scripts/lib/session-runner/transcript.ts +1 -1
  61. package/scripts/lib/tool-scoping/index.ts +1 -1
  62. package/scripts/mcp-servers/gdd-mcp/server.ts +29 -311
  63. package/scripts/mcp-servers/gdd-state/server.ts +28 -282
  64. package/sdk/README.md +45 -0
  65. package/{scripts/lib → sdk}/cli/commands/audit.ts +3 -3
  66. package/{scripts/lib → sdk}/cli/commands/init.ts +3 -3
  67. package/{scripts/lib → sdk}/cli/commands/query.ts +4 -4
  68. package/{scripts/lib → sdk}/cli/commands/run.ts +5 -5
  69. package/{scripts/lib → sdk}/cli/commands/stage.ts +5 -5
  70. package/sdk/cli/index.js +8091 -0
  71. package/sdk/cli/index.ts +172 -0
  72. package/{scripts/lib → sdk}/cli/parse-args.ts +2 -2
  73. package/{scripts/lib/gdd-errors → sdk/errors}/classification.ts +1 -1
  74. package/sdk/errors/index.ts +218 -0
  75. package/{scripts/lib → sdk}/event-stream/emitter.ts +1 -1
  76. package/sdk/event-stream/index.ts +197 -0
  77. package/{scripts/lib → sdk}/event-stream/reader.ts +1 -1
  78. package/{scripts/lib → sdk}/event-stream/types.ts +2 -2
  79. package/{scripts/lib → sdk}/event-stream/writer.ts +1 -1
  80. package/sdk/index.ts +19 -0
  81. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/README.md +3 -3
  82. package/sdk/mcp/gdd-mcp/server.js +1924 -0
  83. package/sdk/mcp/gdd-mcp/server.ts +325 -0
  84. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_cycle_recap.ts +3 -3
  85. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_decisions_list.ts +2 -2
  86. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_events_tail.ts +3 -3
  87. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_health.ts +2 -2
  88. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_intel_get.ts +2 -2
  89. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_learnings_digest.ts +2 -2
  90. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phase_current.ts +2 -2
  91. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phases_list.ts +2 -2
  92. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_plans_list.ts +2 -2
  93. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_reflections_latest.ts +2 -2
  94. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_status.ts +3 -3
  95. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_telemetry_query.ts +3 -3
  96. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/index.ts +2 -2
  97. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/shared.ts +3 -3
  98. package/sdk/mcp/gdd-state/server.js +2790 -0
  99. package/sdk/mcp/gdd-state/server.ts +294 -0
  100. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_blocker.ts +3 -3
  101. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_decision.ts +3 -3
  102. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_must_have.ts +3 -3
  103. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/checkpoint.ts +2 -2
  104. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/frontmatter_update.ts +2 -2
  105. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/get.ts +3 -3
  106. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/index.ts +1 -1
  107. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/probe_connections.ts +3 -3
  108. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/resolve_blocker.ts +3 -3
  109. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/set_status.ts +2 -2
  110. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/shared.ts +8 -8
  111. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/transition_stage.ts +4 -4
  112. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/update_progress.ts +2 -2
  113. package/sdk/primitives/error-classifier.cjs +232 -0
  114. package/sdk/primitives/iteration-budget.cjs +205 -0
  115. package/sdk/primitives/jittered-backoff.cjs +112 -0
  116. package/sdk/primitives/lockfile.cjs +201 -0
  117. package/{scripts/lib/gdd-state → sdk/state}/gates.ts +1 -1
  118. package/sdk/state/index.ts +167 -0
  119. package/{scripts/lib/gdd-state → sdk/state}/lockfile.ts +1 -1
  120. package/{scripts/lib/gdd-state → sdk/state}/mutator.ts +1 -1
  121. package/{scripts/lib/gdd-state → sdk/state}/parser.ts +1 -1
  122. package/{scripts/lib/gdd-state → sdk/state}/types.ts +4 -4
  123. package/skills/figma-extract/SKILL.md +64 -0
  124. package/skills/health/SKILL.md +10 -0
  125. package/skills/quality-gate/SKILL.md +2 -2
  126. package/scripts/aggregate-agent-metrics.ts +0 -282
  127. package/scripts/bootstrap-manifest.txt +0 -3
  128. package/scripts/bootstrap.sh +0 -80
  129. package/scripts/build-distribution-bundles.cjs +0 -549
  130. package/scripts/build-intel.cjs +0 -486
  131. package/scripts/codegen-schema-types.ts +0 -149
  132. package/scripts/detect-stale-refs.cjs +0 -107
  133. package/scripts/e2e/run-headless.ts +0 -514
  134. package/scripts/extract-changelog-section.cjs +0 -58
  135. package/scripts/gsd-cleanup-incubator.cjs +0 -367
  136. package/scripts/injection-patterns.cjs +0 -58
  137. package/scripts/lint-agentskills-spec.cjs +0 -457
  138. package/scripts/release-smoke-test.cjs +0 -200
  139. package/scripts/rollback-release.sh +0 -42
  140. package/scripts/run-injection-scanner-ci.cjs +0 -83
  141. package/scripts/tests/test-authority-rejected-kinds.sh +0 -58
  142. package/scripts/tests/test-authority-watcher-diff.sh +0 -113
  143. package/scripts/tests/test-motion-provenance.sh +0 -64
  144. package/scripts/validate-frontmatter.ts +0 -409
  145. package/scripts/validate-incubator-scope.cjs +0 -133
  146. package/scripts/validate-schemas.ts +0 -401
  147. package/scripts/validate-skill-length.cjs +0 -283
  148. package/scripts/verify-version-sync.cjs +0 -30
  149. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_cycle_recap.schema.json +0 -0
  150. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_decisions_list.schema.json +0 -0
  151. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_events_tail.schema.json +0 -0
  152. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_health.schema.json +0 -0
  153. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_intel_get.schema.json +0 -0
  154. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_learnings_digest.schema.json +0 -0
  155. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phase_current.schema.json +0 -0
  156. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phases_list.schema.json +0 -0
  157. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_plans_list.schema.json +0 -0
  158. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_reflections_latest.schema.json +0 -0
  159. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_status.schema.json +0 -0
  160. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_telemetry_query.schema.json +0 -0
  161. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_blocker.schema.json +0 -0
  162. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_decision.schema.json +0 -0
  163. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_must_have.schema.json +0 -0
  164. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/checkpoint.schema.json +0 -0
  165. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/frontmatter_update.schema.json +0 -0
  166. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/get.schema.json +0 -0
  167. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/probe_connections.schema.json +0 -0
  168. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/resolve_blocker.schema.json +0 -0
  169. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/set_status.schema.json +0 -0
  170. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/transition_stage.schema.json +0 -0
  171. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/update_progress.schema.json +0 -0
  172. /package/{scripts/lib → sdk/primitives}/error-classifier.d.cts +0 -0
  173. /package/{scripts/lib → sdk/primitives}/iteration-budget.d.cts +0 -0
  174. /package/{scripts/lib → sdk/primitives}/jittered-backoff.d.cts +0 -0
  175. /package/{scripts/lib → sdk/primitives}/lockfile.d.cts +0 -0
@@ -1,457 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
- /**
4
- * scripts/lint-agentskills-spec.cjs — agentskills.io spec lint.
5
- *
6
- * Phase 28.8 Plan 28-8-A1 (D-13 `lint-only` outcome).
7
- * See .planning/research/agentskills-io-2026-05-19.md § Implementation Implications
8
- * → Plan 28-8-A1 — what to ship — for the source-of-truth rule list.
9
- *
10
- * Walks `skills/<name>/SKILL.md` and applies the following rules per skill:
11
- *
12
- * R1 (FAIL) — frontmatter contains a non-empty `name`.
13
- * R2 (FAIL) — `name` matches /^[a-z0-9]+(-[a-z0-9]+)*$/ AND length ≤ 64.
14
- * R3 (FAIL) — `name` matches the parent directory (allow bare slug OR `gdd-`-prefixed
15
- * slug, because source-tree uses bare and install-tree uses prefixed per
16
- * Phase 28.7 D-05).
17
- * R4 (FAIL) — `description` is non-empty AND ≤ 1024 chars (spec hard cap).
18
- * R5 (FAIL) — SKILL.md body line count ≤ 500 (spec readability guidance).
19
- *
20
- * W1 (WARN) — both `tools` and `allowed-tools` are present. `allowed-tools` is marked
21
- * Experimental in the spec; pick one form to avoid drift.
22
- * W2 (WARN) — description length > 200 chars (Phase 28.5 D-01 advisory; distinct
23
- * from R4's 1024-char hard cap).
24
- * W3 — reserved slot (covered today by R2). No emission.
25
- *
26
- * CLI:
27
- * node scripts/lint-agentskills-spec.cjs # default: lint ./skills
28
- * node scripts/lint-agentskills-spec.cjs <dir> # lint <dir>/<name>/SKILL.md
29
- * node scripts/lint-agentskills-spec.cjs --json # emit JSON instead of table
30
- * node scripts/lint-agentskills-spec.cjs --summary # one-line PASS/WARN/FAIL counts
31
- * node scripts/lint-agentskills-spec.cjs --summary --json
32
- * # JSON {pass,warn,fail} counts
33
- *
34
- * Exit codes:
35
- * 0 — no FAIL rows (WARN rows do NOT fail the run)
36
- * 1 — at least one FAIL row
37
- * 2 — internal error (I/O failure, parse exception, bad CLI arg)
38
- *
39
- * Empty / missing skills directory:
40
- * Prints `Lint: no skills found at <dir> — nothing to lint.` and exits 0.
41
- * Under `--summary`, prints `PASS=0 WARN=0 FAIL=0` (or `{"pass":0,"warn":0,"fail":0}`
42
- * with `--summary --json`) and exits 0 — empty dirs never fail the run.
43
- *
44
- * Exports (for tests + Plan 28-8-X2 in-process consumption):
45
- * lint(skillsDir, opts?) → { rows, summary, emptyDir }
46
- * lintSummary({sourceRoot}) → { pass, warn, fail } // Plan 28-8-X2 doctor seam
47
- * main(argv) → number (exit code; pure — does NOT call process.exit)
48
- * parseFrontmatter(content) → { frontmatter, body, hasFrontmatter }
49
- * lintSkill(skillDir, skillName) → Array<{status, skill, rule, detail}>
50
- *
51
- * Plan 28-8-X2 wiring:
52
- * `lintSummary` is consumed in-process by `scripts/lib/install/doctor-tier2.cjs`
53
- * (Tier-2 doctor aggregator). It returns `{pass, warn, fail}` counts only — no
54
- * table, no JSON, no IO beyond fs.readFileSync of SKILL.md files. The doctor wraps
55
- * it as the agentskills.io channel state per D-13 (lint-only adoption).
56
- */
57
-
58
- const fs = require('fs');
59
- const path = require('path');
60
-
61
- const NAME_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
62
- const NAME_MAX = 64;
63
- const DESC_MAX = 1024;
64
- const DESC_ADVISORY = 200;
65
- const BODY_MAX_LINES = 500;
66
- const DETAIL_TRUNCATE = 100;
67
-
68
- /**
69
- * Parse YAML-ish frontmatter at the top of a markdown document.
70
- *
71
- * Zero-dep: handles only what our Phase 28.5 frontmatter contract emits:
72
- * - leading `---\n` block delimited by `\n---\n`
73
- * - scalar `key: value` lines (no nested maps, no arrays)
74
- * - surrounding single or double quotes stripped from value
75
- * - for values containing colons (URLs in description), the substring after the FIRST `:`
76
- * is taken — so `description: "https://example.com"` works.
77
- *
78
- * Returns:
79
- * { frontmatter: object, body: string, hasFrontmatter: boolean }
80
- *
81
- * If the opening delimiter is missing or the closing delimiter is not found, returns
82
- * `hasFrontmatter: false` with `frontmatter: {}` and `body` set to the original content.
83
- */
84
- function parseFrontmatter(content) {
85
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
86
- if (!match) {
87
- return { frontmatter: {}, body: content, hasFrontmatter: false };
88
- }
89
- const block = match[1];
90
- const body = match[2];
91
- const frontmatter = {};
92
- const lines = block.split(/\r?\n/);
93
- for (const line of lines) {
94
- if (!line.trim()) continue;
95
- const colonIdx = line.indexOf(':');
96
- if (colonIdx < 0) continue;
97
- const key = line.slice(0, colonIdx).trim();
98
- let value = line.slice(colonIdx + 1).trim();
99
- if (
100
- value.length >= 2 &&
101
- ((value.startsWith('"') && value.endsWith('"')) ||
102
- (value.startsWith("'") && value.endsWith("'")))
103
- ) {
104
- value = value.slice(1, -1);
105
- }
106
- frontmatter[key] = value;
107
- }
108
- return { frontmatter, body, hasFrontmatter: true };
109
- }
110
-
111
- /**
112
- * Lint a single skill directory. Returns one or more rows.
113
- *
114
- * Row shape: { status: 'PASS'|'WARN'|'FAIL', skill: string, rule: string, detail: string }
115
- *
116
- * If no rule fires, returns a single PASS row with rule='-' and detail='-'.
117
- */
118
- function lintSkill(skillDir, skillName) {
119
- const skillPath = path.join(skillDir, 'SKILL.md');
120
- let content;
121
- try {
122
- content = fs.readFileSync(skillPath, 'utf8');
123
- } catch (err) {
124
- if (err && err.code === 'ENOENT') {
125
- return [
126
- {
127
- status: 'FAIL',
128
- skill: skillName,
129
- rule: 'IO',
130
- detail: 'SKILL.md not found',
131
- },
132
- ];
133
- }
134
- throw err;
135
- }
136
-
137
- const { frontmatter, body } = parseFrontmatter(content);
138
- const rows = [];
139
-
140
- const name = (frontmatter.name || '').trim();
141
- const description = (frontmatter.description || '').trim();
142
- const hasTools = Object.prototype.hasOwnProperty.call(frontmatter, 'tools');
143
- const hasAllowedTools = Object.prototype.hasOwnProperty.call(frontmatter, 'allowed-tools');
144
-
145
- // R1
146
- if (!name) {
147
- rows.push({
148
- status: 'FAIL',
149
- skill: skillName,
150
- rule: 'R1',
151
- detail: 'frontmatter missing or empty `name`',
152
- });
153
- } else {
154
- // R2
155
- if (!NAME_REGEX.test(name) || name.length > NAME_MAX) {
156
- rows.push({
157
- status: 'FAIL',
158
- skill: skillName,
159
- rule: 'R2',
160
- detail: `name "${name}" fails slug regex /^[a-z0-9]+(-[a-z0-9]+)*$/ or exceeds ${NAME_MAX} chars`,
161
- });
162
- }
163
- // R3 — name must match parent dir (bare or gdd-prefixed)
164
- if (
165
- name !== skillName &&
166
- name !== `gdd-${skillName}` &&
167
- skillName !== `gdd-${name}`
168
- ) {
169
- rows.push({
170
- status: 'FAIL',
171
- skill: skillName,
172
- rule: 'R3',
173
- detail: `name "${name}" does not match parent dir "${skillName}" (allowed: bare slug or gdd-prefixed slug per Phase 28.7 D-05)`,
174
- });
175
- }
176
- }
177
-
178
- // R4
179
- if (!description) {
180
- rows.push({
181
- status: 'FAIL',
182
- skill: skillName,
183
- rule: 'R4',
184
- detail: 'description missing or empty',
185
- });
186
- } else if (description.length > DESC_MAX) {
187
- rows.push({
188
- status: 'FAIL',
189
- skill: skillName,
190
- rule: 'R4',
191
- detail: `description: ${description.length} chars (>${DESC_MAX} hard cap)`,
192
- });
193
- }
194
-
195
- // R5 — body line count
196
- const bodyLines = body ? body.split(/\r?\n/).length : 0;
197
- if (bodyLines > BODY_MAX_LINES) {
198
- rows.push({
199
- status: 'FAIL',
200
- skill: skillName,
201
- rule: 'R5',
202
- detail: `body ${bodyLines} lines (>${BODY_MAX_LINES} spec guidance)`,
203
- });
204
- }
205
-
206
- // W1 — both tools and allowed-tools present
207
- if (hasTools && hasAllowedTools) {
208
- rows.push({
209
- status: 'WARN',
210
- skill: skillName,
211
- rule: 'W1',
212
- detail: '`tools` and `allowed-tools` both present; spec marks `allowed-tools` Experimental — pick one to avoid drift',
213
- });
214
- }
215
-
216
- // W2 — description over advisory cap but under hard cap
217
- if (
218
- description &&
219
- description.length > DESC_ADVISORY &&
220
- description.length <= DESC_MAX
221
- ) {
222
- rows.push({
223
- status: 'WARN',
224
- skill: skillName,
225
- rule: 'W2',
226
- detail: `description: ${description.length} chars (>${DESC_ADVISORY} advisory cap, Phase 28.5 D-01)`,
227
- });
228
- }
229
-
230
- // W3 — reserved (covered by R2).
231
-
232
- if (rows.length === 0) {
233
- rows.push({
234
- status: 'PASS',
235
- skill: skillName,
236
- rule: '-',
237
- detail: '-',
238
- });
239
- }
240
- return rows;
241
- }
242
-
243
- /**
244
- * Walk a skills directory and lint each child subdirectory containing SKILL.md.
245
- *
246
- * Returns:
247
- * { rows: Array, summary: {total, pass, warn, fail}, emptyDir: boolean }
248
- */
249
- function lint(skillsDir, opts) {
250
- const _opts = opts || {};
251
- if (!fs.existsSync(skillsDir)) {
252
- return {
253
- rows: [],
254
- summary: { total: 0, pass: 0, warn: 0, fail: 0 },
255
- emptyDir: true,
256
- };
257
- }
258
- let stat;
259
- try {
260
- stat = fs.statSync(skillsDir);
261
- } catch (err) {
262
- return {
263
- rows: [],
264
- summary: { total: 0, pass: 0, warn: 0, fail: 0 },
265
- emptyDir: true,
266
- };
267
- }
268
- if (!stat.isDirectory()) {
269
- return {
270
- rows: [],
271
- summary: { total: 0, pass: 0, warn: 0, fail: 0 },
272
- emptyDir: true,
273
- };
274
- }
275
-
276
- const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
277
- const skillDirs = entries
278
- .filter((e) => e.isDirectory())
279
- .map((e) => e.name)
280
- .filter((name) => fs.existsSync(path.join(skillsDir, name, 'SKILL.md')))
281
- .sort();
282
-
283
- if (skillDirs.length === 0) {
284
- return {
285
- rows: [],
286
- summary: { total: 0, pass: 0, warn: 0, fail: 0 },
287
- emptyDir: true,
288
- };
289
- }
290
-
291
- const rows = [];
292
- for (const skillName of skillDirs) {
293
- const skillDir = path.join(skillsDir, skillName);
294
- const skillRows = lintSkill(skillDir, skillName);
295
- for (const row of skillRows) rows.push(row);
296
- }
297
-
298
- const summary = {
299
- total: skillDirs.length,
300
- pass: rows.filter((r) => r.status === 'PASS').length,
301
- warn: rows.filter((r) => r.status === 'WARN').length,
302
- fail: rows.filter((r) => r.status === 'FAIL').length,
303
- };
304
-
305
- return { rows, summary, emptyDir: false };
306
- }
307
-
308
- /**
309
- * Plan 28-8-X2 seam — return only the PASS/WARN/FAIL counts as a flat object.
310
- *
311
- * Consumed in-process by `scripts/lib/install/doctor-tier2.cjs` (Tier-2 doctor
312
- * aggregator). Wraps `lint()` and projects its `summary` onto a 3-field shape
313
- * matching the X2 interface contract: `{ pass, warn, fail }`. Empty dirs
314
- * yield `{ pass: 0, warn: 0, fail: 0 }`. The `total` field is dropped — callers
315
- * compute it from `pass + warn + fail` if needed, matching D-13 lint-only contract.
316
- *
317
- * Pure: no IO beyond what `lint()` already does. Never calls process.exit.
318
- *
319
- * @param {{ sourceRoot?: string }} [opts] Optional. `sourceRoot` is the directory
320
- * containing `skills/` (NOT the skills/
321
- * directory itself — matches Phase 28.7
322
- * `findInstallSourceRoot()` return contract).
323
- * Defaults to `process.cwd()` when omitted.
324
- * @returns {{ pass: number, warn: number, fail: number }}
325
- */
326
- function lintSummary(opts) {
327
- const _opts = opts || {};
328
- const sourceRoot = _opts.sourceRoot || process.cwd();
329
- const skillsDir = path.join(sourceRoot, 'skills');
330
- const result = lint(skillsDir);
331
- return {
332
- pass: result.summary.pass,
333
- warn: result.summary.warn,
334
- fail: result.summary.fail,
335
- };
336
- }
337
-
338
- /**
339
- * Format the row set as an aligned plain-text table.
340
- *
341
- * Columns: STATUS SKILL RULE DETAIL
342
- * DETAIL is truncated to DETAIL_TRUNCATE chars with a `…` suffix for terminal sanity.
343
- */
344
- function formatTable(rows) {
345
- const headers = ['STATUS', 'SKILL', 'RULE', 'DETAIL'];
346
- const display = rows.map((r) => {
347
- let detail = String(r.detail);
348
- if (detail.length > DETAIL_TRUNCATE) {
349
- detail = detail.slice(0, DETAIL_TRUNCATE) + '…';
350
- }
351
- return [r.status, r.skill, r.rule, detail];
352
- });
353
- const widths = headers.map((h, i) =>
354
- Math.max(h.length, ...display.map((row) => row[i].length))
355
- );
356
- const pad = (cells) =>
357
- cells.map((c, i) => c.padEnd(widths[i])).join(' ');
358
- const sep = widths.map((w) => '-'.repeat(w)).join(' ');
359
- const out = [pad(headers), sep];
360
- for (const row of display) out.push(pad(row));
361
- return out.join('\n');
362
- }
363
-
364
- /**
365
- * Main CLI entry. Pure — returns exit code rather than calling process.exit.
366
- *
367
- * argv is the argv slice AFTER node + script (i.e. process.argv.slice(2)).
368
- */
369
- function main(argv) {
370
- try {
371
- let skillsDir = './skills';
372
- let jsonMode = false;
373
- let summaryMode = false;
374
- for (const arg of argv) {
375
- if (arg === '--json') {
376
- jsonMode = true;
377
- } else if (arg === '--summary') {
378
- summaryMode = true;
379
- } else if (arg === '--help' || arg === '-h') {
380
- process.stdout.write(
381
- 'lint-agentskills-spec.cjs — agentskills.io spec lint over skills/<name>/SKILL.md\n' +
382
- '\n' +
383
- 'Usage:\n' +
384
- ' node scripts/lint-agentskills-spec.cjs [<dir>] [--json]\n' +
385
- ' node scripts/lint-agentskills-spec.cjs [<dir>] --summary [--json]\n' +
386
- '\n' +
387
- 'Modes:\n' +
388
- ' default Aligned table of all rows + final summary line\n' +
389
- ' --json JSON {rows, summary} object\n' +
390
- ' --summary One-line `PASS=N WARN=N FAIL=N`\n' +
391
- ' --summary --json JSON {pass, warn, fail} (Plan 28-8-X2 seam)\n' +
392
- '\n' +
393
- 'Exit codes:\n' +
394
- ' 0 no FAIL rows (WARN rows do NOT fail the run)\n' +
395
- ' 1 at least one FAIL row\n' +
396
- ' 2 internal error\n'
397
- );
398
- return 0;
399
- } else if (arg.startsWith('--')) {
400
- process.stderr.write(`lint-agentskills-spec: unknown flag: ${arg}\n`);
401
- return 2;
402
- } else {
403
- skillsDir = arg;
404
- }
405
- }
406
-
407
- const result = lint(skillsDir);
408
-
409
- // Plan 28-8-X2 — --summary mode short-circuits the table renderer for
410
- // doctor-tier2 callers. Empty dir is treated as 0-everything (exits 0)
411
- // matching the default-mode "no skills found" exit-0 contract.
412
- if (summaryMode) {
413
- const pass = result.summary.pass || 0;
414
- const warn = result.summary.warn || 0;
415
- const fail = result.summary.fail || 0;
416
- if (jsonMode) {
417
- process.stdout.write(JSON.stringify({ pass, warn, fail }) + '\n');
418
- } else {
419
- process.stdout.write(`PASS=${pass} WARN=${warn} FAIL=${fail}\n`);
420
- }
421
- return fail > 0 ? 1 : 0;
422
- }
423
-
424
- if (result.emptyDir) {
425
- process.stdout.write(
426
- `Lint: no skills found at ${skillsDir} — nothing to lint.\n`
427
- );
428
- return 0;
429
- }
430
-
431
- if (jsonMode) {
432
- process.stdout.write(
433
- JSON.stringify({ rows: result.rows, summary: result.summary }, null, 2) +
434
- '\n'
435
- );
436
- } else {
437
- process.stdout.write(formatTable(result.rows) + '\n');
438
- const { total, pass, warn, fail } = result.summary;
439
- process.stdout.write(
440
- `\nLint summary: ${total} skills, ${pass} PASS, ${warn} WARN, ${fail} FAIL\n`
441
- );
442
- }
443
-
444
- return result.summary.fail > 0 ? 1 : 0;
445
- } catch (err) {
446
- process.stderr.write(
447
- `lint-agentskills-spec: internal error: ${err && err.message ? err.message : err}\n`
448
- );
449
- return 2;
450
- }
451
- }
452
-
453
- if (require.main === module) {
454
- process.exit(main(process.argv.slice(2)));
455
- }
456
-
457
- module.exports = { lint, lintSummary, main, parseFrontmatter, lintSkill };
@@ -1,200 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
- // release-smoke-test.cjs — release-time smoke test per D-22.
4
- //
5
- // Validates that the freshly-checked-out tag produces a consistent plugin
6
- // surface. Runs deterministic (non-LLM) portions of /gdd:explore against
7
- // test-fixture/src/ in an isolated temp dir and diffs resulting artifacts
8
- // against the provided baseline directory. Exit code:
9
- // 0 — zero diffs, zero missing artifacts
10
- // 1 — one or more diffs or missing artifacts
11
- // 2 — baseline not found / argument error
12
- //
13
- // Does NOT invoke the `claude` CLI (unavailable on stock GitHub runners).
14
- // The LLM-dependent portions of /gdd:explore are intentionally out of scope;
15
- // this smoke test covers the intel builder + static analysis surface.
16
- //
17
- // Usage:
18
- // node scripts/release-smoke-test.cjs --baseline <path>
19
- // node scripts/release-smoke-test.cjs --baseline test-fixture/baselines/phase-13
20
- // node scripts/release-smoke-test.cjs --baseline <path> --keep # keep tmp dir
21
-
22
- const fs = require('fs');
23
- const path = require('path');
24
- const os = require('os');
25
- const { spawnSync } = require('child_process');
26
-
27
- const args = process.argv.slice(2);
28
- const baselineIdx = args.indexOf('--baseline');
29
- const KEEP = args.includes('--keep');
30
-
31
- if (baselineIdx < 0 || !args[baselineIdx + 1]) {
32
- console.error('Usage: node scripts/release-smoke-test.cjs --baseline <path> [--keep]');
33
- process.exit(2);
34
- }
35
-
36
- const baselineDir = path.resolve(args[baselineIdx + 1]);
37
- if (!fs.existsSync(baselineDir) || !fs.statSync(baselineDir).isDirectory()) {
38
- console.error(`baseline not found: ${baselineDir}`);
39
- process.exit(2);
40
- }
41
-
42
- const REPO_ROOT = path.resolve(__dirname, '..');
43
- const FIXTURE_SRC = path.join(REPO_ROOT, 'test-fixture', 'src');
44
-
45
- if (!fs.existsSync(FIXTURE_SRC)) {
46
- console.error(`fixture not found: ${FIXTURE_SRC}`);
47
- process.exit(2);
48
- }
49
-
50
- const tmpDir = path.join(os.tmpdir(), `gdd-smoke-${Date.now()}`);
51
- fs.mkdirSync(tmpDir, { recursive: true });
52
-
53
- // Snapshot REPO_ROOT/.design/ contents BEFORE the smoke test runs. This lets
54
- // the post-test pollution assertion (below) detect actual pollution (new
55
- // files created during the run) rather than tripping on legitimately-tracked
56
- // `.design/` files that exist in a fresh checkout — e.g.
57
- // `.design/config.example.json` shipped by Plan 29-02 for discoverability.
58
- function snapshotDesignDir() {
59
- const designDir = path.join(REPO_ROOT, '.design');
60
- if (!fs.existsSync(designDir)) return '<absent>';
61
- const entries = [];
62
- function walk(d) {
63
- for (const ent of fs.readdirSync(d, { withFileTypes: true })) {
64
- const full = path.join(d, ent.name);
65
- if (ent.isDirectory()) walk(full);
66
- else entries.push(path.relative(designDir, full).replace(/\\/g, '/'));
67
- }
68
- }
69
- walk(designDir);
70
- return entries.sort().join('\n');
71
- }
72
- const designSnapshotBefore = snapshotDesignDir();
73
-
74
- function copyRecursive(src, dst) {
75
- fs.mkdirSync(dst, { recursive: true });
76
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
77
- const s = path.join(src, entry.name);
78
- const d = path.join(dst, entry.name);
79
- if (entry.isDirectory()) copyRecursive(s, d);
80
- else fs.copyFileSync(s, d);
81
- }
82
- }
83
-
84
- function cleanup() {
85
- if (KEEP) {
86
- console.log(`(kept) ${tmpDir}`);
87
- return;
88
- }
89
- try {
90
- fs.rmSync(tmpDir, { recursive: true, force: true });
91
- } catch (_) {
92
- /* best effort */
93
- }
94
- }
95
-
96
- process.on('exit', cleanup);
97
- process.on('SIGINT', () => {
98
- cleanup();
99
- process.exit(130);
100
- });
101
-
102
- // ── Step 1: Copy fixture into isolated temp dir.
103
- const workDir = path.join(tmpDir, 'work');
104
- copyRecursive(FIXTURE_SRC, path.join(workDir, 'src'));
105
-
106
- // ── Step 2: Run deterministic intel builder against the temp dir.
107
- // build-intel.cjs uses process.cwd(); invoke it with cwd=workDir.
108
- const intelResult = spawnSync(
109
- 'node',
110
- [path.join(REPO_ROOT, 'scripts', 'build-intel.cjs')],
111
- { cwd: workDir, encoding: 'utf8' }
112
- );
113
-
114
- // Non-zero intel run is OK (fixture may lack some scan inputs) — log but don't fail.
115
- if (intelResult.status !== 0) {
116
- console.log(`(note) build-intel exited ${intelResult.status} on fixture — continuing to baseline diff`);
117
- }
118
-
119
- // ── Step 3: Compare baseline files against the fresh run.
120
- const diffs = [];
121
- const missing = [];
122
-
123
- function walk(dir, base, out) {
124
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
125
- const full = path.join(dir, entry.name);
126
- const rel = path.relative(base, full);
127
- if (entry.isDirectory()) walk(full, base, out);
128
- else out.push(rel);
129
- }
130
- }
131
-
132
- const baselineFiles = [];
133
- walk(baselineDir, baselineDir, baselineFiles);
134
-
135
- // Artifact resolution: for each baseline file, look for a sibling file in the
136
- // fresh run workDir (best-effort: baseline may contain reference manifests
137
- // that aren't produced by the deterministic pipeline — those count as
138
- // "present in baseline, not in run" and are recorded as missing_artifacts but
139
- // not as diffs).
140
- for (const rel of baselineFiles) {
141
- const baselineFile = path.join(baselineDir, rel);
142
- const freshCandidate1 = path.join(workDir, rel);
143
- const freshCandidate2 = path.join(workDir, '.design', 'intel', path.basename(rel));
144
-
145
- let freshFile = null;
146
- if (fs.existsSync(freshCandidate1)) freshFile = freshCandidate1;
147
- else if (fs.existsSync(freshCandidate2)) freshFile = freshCandidate2;
148
-
149
- if (!freshFile) {
150
- missing.push(rel);
151
- continue;
152
- }
153
-
154
- const expected = fs.readFileSync(baselineFile, 'utf8').replace(/\r\n/g, '\n');
155
- const actual = fs.readFileSync(freshFile, 'utf8').replace(/\r\n/g, '\n');
156
-
157
- if (expected !== actual) {
158
- diffs.push({
159
- rel,
160
- expected: expected.slice(0, 80),
161
- actual: actual.slice(0, 80),
162
- });
163
- }
164
- }
165
-
166
- // ── Report.
167
- for (const d of diffs) {
168
- console.log(`DIFF: ${d.rel}`);
169
- console.log(` Expected: ${d.expected.replace(/\n/g, '\\n')}`);
170
- console.log(` Actual: ${d.actual.replace(/\n/g, '\\n')}`);
171
- }
172
-
173
- // Missing baseline artifacts are reported informationally. Baselines ship
174
- // reference material (like BASELINE.md) that the deterministic pipeline does
175
- // not regenerate — those are expected "missing in run" hits and are NOT treated
176
- // as failures. Only actual byte-level diffs fail the build.
177
- if (missing.length) {
178
- console.log(`note: ${missing.length} baseline artifact(s) not regenerated by deterministic run:`);
179
- for (const m of missing) console.log(` - ${m}`);
180
- }
181
-
182
- console.log(`smoke-test: ${diffs.length} diffs, ${missing.length} baseline artifacts not in fresh run`);
183
-
184
- // Ensure .design/ was not created in the real repo root.
185
- // Pollution check: only fail if NEW files appeared in REPO_ROOT/.design/ during
186
- // the smoke test. Tracked files (like `.design/config.example.json` shipped by
187
- // Plan 29-02) are present in the fresh checkout and are NOT pollution. We
188
- // compare the directory snapshot taken before the test (top of file) against
189
- // the post-test snapshot.
190
- const designSnapshotAfter = snapshotDesignDir();
191
- if (designSnapshotBefore !== designSnapshotAfter) {
192
- console.error('ERROR: .design/ contents changed during smoke test — pipeline wrote to REPO_ROOT instead of temp dir');
193
- console.error('Before:');
194
- for (const line of designSnapshotBefore.split('\n')) console.error(` ${line}`);
195
- console.error('After:');
196
- for (const line of designSnapshotAfter.split('\n')) console.error(` ${line}`);
197
- process.exit(1);
198
- }
199
-
200
- process.exit(diffs.length > 0 ? 1 : 0);
@@ -1,42 +0,0 @@
1
- #!/usr/bin/env bash
2
- # rollback-release.sh — manually delete a tag + GitHub Release. NOT called from CI.
3
- # Usage: bash scripts/rollback-release.sh <version>
4
- # Example: bash scripts/rollback-release.sh 1.0.7
5
-
6
- set -euo pipefail
7
-
8
- VERSION="${1:-}"
9
- if [ -z "$VERSION" ]; then
10
- echo "Usage: $0 <version>"
11
- echo "Example: $0 1.0.7"
12
- exit 1
13
- fi
14
-
15
- TAG="v${VERSION}"
16
- REPO="${GITHUB_REPOSITORY:-hegemonart/get-design-done}"
17
-
18
- if ! command -v gh >/dev/null 2>&1; then
19
- echo "ERROR: gh CLI not found."
20
- exit 1
21
- fi
22
-
23
- echo "About to delete release + tag $TAG on $REPO."
24
- echo "This is a DESTRUCTIVE operation. Confirm (y/N): "
25
- read -r CONFIRM
26
- if [ "$CONFIRM" != "y" ]; then
27
- echo "Aborted."
28
- exit 0
29
- fi
30
-
31
- # Delete the GitHub Release (this also deletes the tag ref on github.com).
32
- gh release delete "$TAG" --repo "$REPO" --yes --cleanup-tag || {
33
- echo "Release delete failed or no release existed; attempting tag-only delete."
34
- gh api -X DELETE "repos/${REPO}/git/refs/tags/${TAG}" || true
35
- }
36
-
37
- # Also delete locally if the user has the tag.
38
- git tag -d "$TAG" 2>/dev/null || true
39
-
40
- echo "Rollback complete for $TAG."
41
- echo "Note: clones that already pulled the tag retain it. Consider posting"
42
- echo " a deprecation note in the next CHANGELOG entry."