@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,409 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * validate-frontmatter.ts — CI-friendly frontmatter validator for agents/*.md.
4
- *
5
- * Enforces the Phase 7 agent frontmatter hygiene contract. Exits 0 on
6
- * success, 1 on any violation. One finding per stdout line.
7
- *
8
- * Converted from scripts/validate-frontmatter.cjs in Plan 20-00 (Tier-1).
9
- * Behavior preserved verbatim; strict types added for the frontmatter shape.
10
- *
11
- * Usage:
12
- * node --experimental-strip-types scripts/validate-frontmatter.ts [paths...]
13
- * # default path is `agents/` when none given.
14
- */
15
-
16
- import { existsSync, statSync, readdirSync } from 'node:fs';
17
- import { join, basename, dirname, resolve } from 'node:path';
18
- import { createRequire } from 'node:module';
19
-
20
- import { readFrontmatter } from '../tests/helpers.ts';
21
-
22
- // ── delegate_to capability matrix loader (Plan 27-06) ──────────────────────
23
- //
24
- // The `delegate_to: <peer>-<role> | none` field is validated against the
25
- // capability matrix exported by scripts/lib/peer-cli/registry.cjs (Plan
26
- // 27-05). Loading the .cjs from a .ts module under the strip-types loader
27
- // requires createRequire — and we anchor it to the repo root so the
28
- // validator survives being invoked from any cwd.
29
- //
30
- // Loading is lazy + defensive: if the registry module isn't on disk yet
31
- // (e.g. during a fresh clone before Plan 27-05 lands, or in a partial
32
- // checkout), we fall back to an inline literal mirror of the locked D-05
33
- // capability matrix so this validator never crashes a CI run on a
34
- // missing dependency. The literal mirror MUST stay in sync with
35
- // registry.cjs's CAPABILITY_MATRIX — tests assert equivalence.
36
- function _findRepoRootFromHere(): string {
37
- // process.argv[1] is the validator script path under strip-types; walk
38
- // up from its directory looking for package.json. Fall back to cwd.
39
- const start: string = (() => {
40
- const argv1 = process.argv[1];
41
- if (typeof argv1 === 'string' && argv1.length > 0) return dirname(argv1);
42
- return process.cwd();
43
- })();
44
- let dir = resolve(start);
45
- for (let i = 0; i < 8; i++) {
46
- if (existsSync(join(dir, 'package.json'))) return dir;
47
- const parent = dirname(dir);
48
- if (parent === dir) break;
49
- dir = parent;
50
- }
51
- return process.cwd();
52
- }
53
-
54
- /** Locked D-05 capability matrix mirror — fallback when registry.cjs unloadable. */
55
- const _DELEGATE_MATRIX_FALLBACK: Readonly<Record<string, readonly string[]>> = Object.freeze({
56
- codex: Object.freeze(['execute']),
57
- copilot: Object.freeze(['review', 'research']),
58
- cursor: Object.freeze(['debug', 'plan']),
59
- gemini: Object.freeze(['research', 'exploration']),
60
- qwen: Object.freeze(['write']),
61
- });
62
-
63
- let _delegateMatrixCache: Readonly<Record<string, readonly string[]>> | null = null;
64
-
65
- /**
66
- * Return the live capability matrix as a `peer -> roles[]` map. Cached
67
- * after first call. Uses registry.cjs as the source of truth; falls back
68
- * to the inline mirror only if the registry module fails to load.
69
- */
70
- export function loadDelegateMatrix(): Readonly<Record<string, readonly string[]>> {
71
- if (_delegateMatrixCache !== null) return _delegateMatrixCache;
72
- try {
73
- const root = _findRepoRootFromHere();
74
- const req = createRequire(join(root, 'package.json'));
75
- const reg = req(resolve(root, 'scripts/lib/peer-cli/registry.cjs')) as {
76
- CAPABILITY_MATRIX?: Record<string, { roles: readonly string[] }>;
77
- };
78
- if (reg && typeof reg.CAPABILITY_MATRIX === 'object' && reg.CAPABILITY_MATRIX !== null) {
79
- const out: Record<string, readonly string[]> = {};
80
- for (const [peer, cap] of Object.entries(reg.CAPABILITY_MATRIX)) {
81
- if (cap && Array.isArray(cap.roles)) {
82
- out[peer] = Object.freeze([...cap.roles]);
83
- }
84
- }
85
- _delegateMatrixCache = Object.freeze(out);
86
- return _delegateMatrixCache;
87
- }
88
- } catch {
89
- // fall through to fallback
90
- }
91
- _delegateMatrixCache = _DELEGATE_MATRIX_FALLBACK;
92
- return _delegateMatrixCache;
93
- }
94
-
95
- /**
96
- * Build the flat set of valid `<peer>-<role>` IDs from the capability
97
- * matrix. e.g. `gemini-research`, `codex-execute`, etc. The literal
98
- * string `"none"` is the explicit opt-out and is accepted separately.
99
- */
100
- export function validDelegateIds(): readonly string[] {
101
- const matrix = loadDelegateMatrix();
102
- const out: string[] = [];
103
- for (const [peer, roles] of Object.entries(matrix)) {
104
- for (const role of roles) {
105
- out.push(`${peer}-${role}`);
106
- }
107
- }
108
- return Object.freeze(out.sort());
109
- }
110
-
111
- // Importing a type from generated.d.ts satisfies the Plan 20-00 rule that
112
- // every Tier-1 TS file participates in the codegen graph. We don't use
113
- // IntelSchema at runtime; the re-export keeps it visible for static checks.
114
- import type { IntelSchema } from '../reference/schemas/generated.js';
115
- export type { IntelSchema };
116
-
117
- /**
118
- * Strict shape of the agent-frontmatter subset this validator enforces.
119
- * Matches REQUIRED_FIELDS below. `readFrontmatter` returns a permissive
120
- * `Record<string, string | boolean | string[]>` — we narrow per-field as
121
- * needed rather than asserting the whole object at once.
122
- */
123
- export interface AgentFrontmatter {
124
- name: string;
125
- description: string;
126
- tools: string;
127
- color: string;
128
- 'parallel-safe': boolean | string;
129
- 'typical-duration-seconds': string | number;
130
- 'reads-only': boolean | string;
131
- writes: string | string[];
132
- 'default-tier'?: 'haiku' | 'sonnet' | 'opus';
133
- 'reasoning-class'?: 'high' | 'medium' | 'low';
134
- 'size_budget'?: 'S' | 'M' | 'L' | 'XL';
135
- /**
136
- * Phase 27 (Plan 27-06) — peer-CLI delegation hint.
137
- *
138
- * Optional. Default unset = use local Anthropic call. Setting
139
- * `delegate_to: gemini-research` tells session-runner "try delegate
140
- * first, fall back to local on peer-absent / peer-error". Setting
141
- * `delegate_to: none` explicitly opts out (e.g. security-sensitive
142
- * agents). See agents/README.md "Peer-CLI delegation (delegate_to)"
143
- * for the additive-superset rationale (CONTEXT D-06).
144
- *
145
- * Valid values are `<peer>-<role>` IDs that the peer-CLI registry
146
- * capability matrix knows (e.g. `gemini-research`, `codex-execute`,
147
- * `cursor-debug`, `cursor-plan`, `copilot-review`, `copilot-research`,
148
- * `qwen-write`) plus the literal string `"none"`.
149
- */
150
- 'delegate_to'?: string;
151
- }
152
-
153
- const REQUIRED_FIELDS: readonly (keyof AgentFrontmatter)[] = [
154
- 'name',
155
- 'description',
156
- 'tools',
157
- 'color',
158
- 'parallel-safe',
159
- 'typical-duration-seconds',
160
- 'reads-only',
161
- 'writes',
162
- ];
163
-
164
- /**
165
- * Phase 26 (Plan 26-08) — runtime-neutral `reasoning-class` alias for
166
- * `default-tier`. Equivalence table is locked in CONTEXT D-10 / D-11:
167
- *
168
- * high <-> opus
169
- * medium <-> sonnet
170
- * low <-> haiku
171
- *
172
- * The alias is OPTIONAL (no per-agent retrofit lands in v1.26 — see
173
- * agents/README.md "Runtime-neutral reasoning class"). When both fields
174
- * appear together they MUST satisfy the equivalence; mismatched dual
175
- * annotations are a validation error.
176
- */
177
- export type DefaultTier = 'haiku' | 'sonnet' | 'opus';
178
- export type ReasoningClass = 'high' | 'medium' | 'low';
179
-
180
- export const REASONING_CLASS_VALUES: readonly ReasoningClass[] = [
181
- 'high',
182
- 'medium',
183
- 'low',
184
- ];
185
-
186
- export const DEFAULT_TIER_VALUES: readonly DefaultTier[] = [
187
- 'opus',
188
- 'sonnet',
189
- 'haiku',
190
- ];
191
-
192
- /** Equivalence map: reasoning-class -> default-tier. */
193
- export const CLASS_TO_TIER: Readonly<Record<ReasoningClass, DefaultTier>> = {
194
- high: 'opus',
195
- medium: 'sonnet',
196
- low: 'haiku',
197
- };
198
-
199
- /** Equivalence map: default-tier -> reasoning-class. */
200
- export const TIER_TO_CLASS: Readonly<Record<DefaultTier, ReasoningClass>> = {
201
- opus: 'high',
202
- sonnet: 'medium',
203
- haiku: 'low',
204
- };
205
-
206
- /** Type guard for a valid `reasoning-class` value. */
207
- export function isReasoningClass(v: unknown): v is ReasoningClass {
208
- return typeof v === 'string' && REASONING_CLASS_VALUES.includes(v as ReasoningClass);
209
- }
210
-
211
- /** Type guard for a valid `default-tier` value. */
212
- export function isDefaultTier(v: unknown): v is DefaultTier {
213
- return typeof v === 'string' && DEFAULT_TIER_VALUES.includes(v as DefaultTier);
214
- }
215
-
216
- /**
217
- * Validate the optional `reasoning-class` field and its equivalence with
218
- * `default-tier` when both are present. Returns an array of violation
219
- * messages; an empty array means the agent passes the Plan 26-08 rules.
220
- *
221
- * Rules (Plan 26-08, CONTEXT D-11):
222
- * 1. `reasoning-class` is OPTIONAL. Absence is fine.
223
- * 2. If present, it MUST be one of `high|medium|low`.
224
- * 3. If both `default-tier` and `reasoning-class` are present, the values
225
- * MUST satisfy the equivalence table (high+opus, medium+sonnet,
226
- * low+haiku). Mismatch is a validation error.
227
- *
228
- * Existing agents that carry only `default-tier` (the v1.26 baseline state
229
- * for all 26 shipped agents) are unaffected — this helper returns an empty
230
- * array for them.
231
- *
232
- * The `agentName` argument is used in error messages to surface which agent
233
- * is misconfigured when the validator runs against the full roster.
234
- */
235
- export function validateReasoningClass(
236
- fm: Record<string, unknown>,
237
- agentName: string,
238
- ): string[] {
239
- const violations: string[] = [];
240
- const hasClass = 'reasoning-class' in fm && !isMissing(fm['reasoning-class']);
241
- const hasTier = 'default-tier' in fm && !isMissing(fm['default-tier']);
242
-
243
- if (!hasClass) {
244
- // Field absent — allowed. `default-tier` is the v1.26 source of truth and
245
- // is enforced by separate Phase 10.1 contracts (not this validator).
246
- return violations;
247
- }
248
-
249
- const rawClass = fm['reasoning-class'];
250
- if (!isReasoningClass(rawClass)) {
251
- violations.push(
252
- `reasoning-class: invalid value "${String(rawClass)}" for agent "${agentName}" — must be one of ${REASONING_CLASS_VALUES.join('|')}`,
253
- );
254
- return violations;
255
- }
256
-
257
- if (hasTier) {
258
- const rawTier = fm['default-tier'];
259
- if (!isDefaultTier(rawTier)) {
260
- // default-tier shape is enforced elsewhere; we still surface a clear
261
- // message so co-validation is debuggable in one pass.
262
- violations.push(
263
- `default-tier: invalid value "${String(rawTier)}" for agent "${agentName}" — must be one of ${DEFAULT_TIER_VALUES.join('|')}`,
264
- );
265
- return violations;
266
- }
267
- const expectedTier = CLASS_TO_TIER[rawClass];
268
- if (rawTier !== expectedTier) {
269
- violations.push(
270
- `reasoning-class/default-tier: mismatch for agent "${agentName}" — reasoning-class="${rawClass}" expects default-tier="${expectedTier}", but got default-tier="${rawTier}". Equivalence table: high<->opus, medium<->sonnet, low<->haiku.`,
271
- );
272
- }
273
- }
274
-
275
- return violations;
276
- }
277
-
278
- /**
279
- * Validate the optional Phase 27 (Plan 27-06) `delegate_to` field. Returns
280
- * an array of violation messages; empty means the agent passes.
281
- *
282
- * Rules (CONTEXT D-06):
283
- * 1. `delegate_to` is OPTIONAL. Absence = use local Anthropic call.
284
- * 2. If present, value MUST be a string.
285
- * 3. The literal value `"none"` is accepted as the explicit opt-out.
286
- * 4. Any other value MUST match a `<peer>-<role>` ID drawn from the
287
- * peer-CLI capability matrix (Plan 27-05's registry.cjs is the
288
- * source of truth; loadDelegateMatrix() resolves it lazily with
289
- * a literal fallback if the registry is unloadable).
290
- *
291
- * The 26 v1.26 baseline agents do not carry this field; this helper
292
- * returns `[]` for them. The validator runs trivially clean on them.
293
- */
294
- export function validateDelegateTo(
295
- fm: Record<string, unknown>,
296
- agentName: string,
297
- ): string[] {
298
- const violations: string[] = [];
299
- const has = 'delegate_to' in fm && !isMissing(fm['delegate_to']);
300
- if (!has) return violations;
301
-
302
- const raw = fm['delegate_to'];
303
- if (typeof raw !== 'string') {
304
- violations.push(
305
- `delegate_to: invalid value "${String(raw)}" for agent "${agentName}" — must be a string ("none" or "<peer>-<role>" e.g. "gemini-research")`,
306
- );
307
- return violations;
308
- }
309
- if (raw === 'none') return violations; // explicit opt-out
310
-
311
- const valid = validDelegateIds();
312
- if (!valid.includes(raw)) {
313
- violations.push(
314
- `delegate_to: invalid value "${raw}" for agent "${agentName}" — must be "none" or one of: ${valid.join(', ')} (peer-CLI capability matrix; see scripts/lib/peer-cli/registry.cjs)`,
315
- );
316
- }
317
- return violations;
318
- }
319
-
320
- function walkMd(dir: string): string[] {
321
- const out: string[] = [];
322
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
323
- const full: string = join(dir, entry.name);
324
- if (entry.isDirectory()) out.push(...walkMd(full));
325
- else if (entry.isFile() && entry.name.endsWith('.md')) out.push(full);
326
- }
327
- return out;
328
- }
329
-
330
- /**
331
- * Return true when the frontmatter value is "missing" per the original
332
- * .cjs contract: undefined / null / empty string. Preserves the semantics
333
- * exactly so the CI gate fires on the same inputs.
334
- */
335
- function isMissing(v: unknown): boolean {
336
- if (v === undefined || v === null) return true;
337
- if (typeof v === 'string' && v === '') return true;
338
- return false;
339
- }
340
-
341
- function main(): void {
342
- const args: string[] = process.argv.slice(2).filter((a) => !a.startsWith('--'));
343
- const targets: string[] = args.length ? args : ['agents/'];
344
- const files: string[] = [];
345
- for (const t of targets) {
346
- if (!existsSync(t)) {
347
- console.error(`${t}: path does not exist`);
348
- process.exit(1);
349
- }
350
- const stat = statSync(t);
351
- if (stat.isDirectory()) files.push(...walkMd(t));
352
- else files.push(t);
353
- }
354
-
355
- let violations = 0;
356
- for (const f of files) {
357
- const fm = readFrontmatter(f);
358
- if (Object.keys(fm).length === 0) {
359
- // README.md under agents/ may have no frontmatter — skip
360
- if (basename(f).toLowerCase() === 'readme.md') continue;
361
- console.log(`${f}:frontmatter: missing`);
362
- violations++;
363
- continue;
364
- }
365
- for (const field of REQUIRED_FIELDS) {
366
- if (!(field in fm) || isMissing((fm as Record<string, unknown>)[field])) {
367
- console.log(`${f}:${field}: missing`);
368
- violations++;
369
- }
370
- }
371
-
372
- // Plan 26-08 — runtime-neutral reasoning-class alias validation.
373
- const agentName: string =
374
- typeof fm.name === 'string' && fm.name.length > 0
375
- ? fm.name
376
- : basename(f).replace(/\.md$/, '');
377
- const classViolations = validateReasoningClass(
378
- fm as Record<string, unknown>,
379
- agentName,
380
- );
381
- for (const msg of classViolations) {
382
- console.log(`${f}:${msg}`);
383
- violations++;
384
- }
385
-
386
- // Plan 27-06 — peer-CLI delegate_to validation (additive optional field).
387
- const delegateViolations = validateDelegateTo(
388
- fm as Record<string, unknown>,
389
- agentName,
390
- );
391
- for (const msg of delegateViolations) {
392
- console.log(`${f}:${msg}`);
393
- violations++;
394
- }
395
- }
396
-
397
- console.log(`summary: ${files.length} file(s) checked, ${violations} violation(s)`);
398
- process.exit(violations === 0 ? 0 : 1);
399
- }
400
-
401
- // Only run as a CLI when invoked directly (Plan 26-08: tests import the
402
- // helpers above without triggering process.exit). Node's strip-types ESM
403
- // loader sets `process.argv[1]` to the resolved entry path; a substring
404
- // match against this filename catches both direct execution and the
405
- // `node --experimental-strip-types` wrapper used by `npm run validate:frontmatter`.
406
- const entry: string = process.argv[1] ?? '';
407
- if (entry.endsWith('validate-frontmatter.ts') || entry.endsWith('validate-frontmatter.js')) {
408
- main();
409
- }
@@ -1,133 +0,0 @@
1
- #!/usr/bin/env node
2
- // scripts/validate-incubator-scope.cjs — Plan 29-05
3
- //
4
- // Phase 29 D-05: scope guard for incubator-draft promotion.
5
- //
6
- // Purpose
7
- // Enforce that a drafted incubator artifact can only resolve to one of:
8
- // * `agents/<slug>.md` (Phase 28.5 agent files)
9
- // * `skills/<slug>/SKILL.md` (Phase 28.5 skill files)
10
- // Any other path (script, hook, runtime, transport, root-escape, absolute
11
- // path outside the repo, traversal segment) is rejected with a non-zero
12
- // exit and an informative error message.
13
- //
14
- // This script is invoked BEFORE any file write inside
15
- // `scripts/lib/apply-reflections/incubator-proposals.cjs#applyAccept`, and
16
- // is the second non-bypassable line of defense after the floor enforced by
17
- // `scripts/lib/incubator-author.cjs#safeWritePath` at draft-time.
18
- //
19
- // Non-bypassable (D-05)
20
- // No flag, env var, or argument disables the check. Promotion targets that
21
- // fail the regex check throw — period. There is no opt-out flag, no
22
- // environment override, and the CLI offers no escape hatch. (The scan in
23
- // tests/apply-reflections-incubator.test.cjs grep-asserts the absence of
24
- // bypass tokens in this file's source, so even adding such an option in
25
- // future would break the build.)
26
- //
27
- // API
28
- // validateScope(targetPath, { repoRoot } = {})
29
- // → { ok: true } // accepted
30
- // → throws Error(...) // rejected; message names offending path + allowed patterns
31
- //
32
- // CLI
33
- // node scripts/validate-incubator-scope.cjs <path>
34
- // exit 0 + `[scope-guard] ok: <relPath>` on success
35
- // exit 1 + descriptive stderr on failure
36
- //
37
- // Style: zero deps beyond node:fs + node:path (matches scripts/lib/incubator-author.cjs).
38
-
39
- 'use strict';
40
-
41
- const path = require('node:path');
42
-
43
- // Allowed target patterns — slug rules match the Phase 28.5 frontmatter slug
44
- // regex (lowercase, digits, hyphens; must start with [a-z0-9]).
45
- const SLUG_RE_FRAGMENT = '[a-z0-9][a-z0-9-]*';
46
- const AGENT_RE = new RegExp(`^agents/${SLUG_RE_FRAGMENT}\\.md$`);
47
- const SKILL_RE = new RegExp(`^skills/${SLUG_RE_FRAGMENT}/SKILL\\.md$`);
48
-
49
- /**
50
- * Validate that a target path is in scope for incubator promotion.
51
- *
52
- * Algorithm:
53
- * 1. Resolve to absolute path under repoRoot.
54
- * 2. Reject if the resolved path escapes repoRoot (path traversal or
55
- * absolute path pointing outside the repository).
56
- * 3. Compute repo-relative path with forward-slash normalization.
57
- * 4. Reject if the relative path doesn't match exactly one of the two
58
- * allowed patterns.
59
- *
60
- * @param {string} targetPath - file path to validate; relative paths are
61
- * resolved against repoRoot.
62
- * @param {{repoRoot?: string}} [opts] - configuration. repoRoot defaults to
63
- * process.cwd().
64
- * @returns {{ok: true}} on success.
65
- * @throws {Error} on any rejection. Message includes the offending path and
66
- * the allowed patterns.
67
- */
68
- function validateScope(targetPath, opts) {
69
- const o = opts || {};
70
- const repoRoot = path.resolve(o.repoRoot || process.cwd());
71
-
72
- if (typeof targetPath !== 'string' || !targetPath.length) {
73
- throw new Error(
74
- `[scope-guard] invalid input: targetPath must be a non-empty string. ` +
75
- `Allowed: ${AGENT_RE.source} or ${SKILL_RE.source}`,
76
- );
77
- }
78
-
79
- // Resolve relative to repoRoot. Absolute paths bypass repoRoot prefixing;
80
- // that's fine — the prefix check below catches them anyway.
81
- const resolved = path.resolve(repoRoot, targetPath);
82
-
83
- // Step 1: confirm resolved path is inside repoRoot. We compare with a
84
- // trailing separator to avoid `repoRoot-evil/...` slipping past a startsWith
85
- // check.
86
- const rootWithSep = repoRoot + path.sep;
87
- if (!(resolved === repoRoot || resolved.startsWith(rootWithSep))) {
88
- throw new Error(
89
- `[scope-guard] path escapes repository: ${targetPath} → ${resolved} ` +
90
- `(outside ${repoRoot}). Allowed: agents/<slug>.md or skills/<slug>/SKILL.md`,
91
- );
92
- }
93
-
94
- // Step 2: compute repo-relative path and normalize separators to '/'
95
- // (Windows uses '\\' natively).
96
- const rel = path.relative(repoRoot, resolved).replace(/\\/g, '/');
97
-
98
- // Step 3: match exactly one of the allowed shapes.
99
- if (AGENT_RE.test(rel) || SKILL_RE.test(rel)) {
100
- return { ok: true };
101
- }
102
-
103
- throw new Error(
104
- `[scope-guard] path not in allowed scope: ${rel} ` +
105
- `(input: ${targetPath}). Allowed patterns: ` +
106
- `agents/<slug>.md (regex ${AGENT_RE.source}) ` +
107
- `or skills/<slug>/SKILL.md (regex ${SKILL_RE.source}). ` +
108
- `Note: scope guard is non-bypassable per Phase 29 D-05.`,
109
- );
110
- }
111
-
112
- module.exports = { validateScope };
113
-
114
- // -------------------------------------------------------------------
115
- // CLI entry
116
- // -------------------------------------------------------------------
117
-
118
- if (require.main === module) {
119
- const input = process.argv[2];
120
- if (!input) {
121
- console.error('[scope-guard] usage: node scripts/validate-incubator-scope.cjs <path>');
122
- process.exit(1);
123
- }
124
- try {
125
- validateScope(input);
126
- const rel = path.relative(process.cwd(), path.resolve(process.cwd(), input)).replace(/\\/g, '/');
127
- console.log(`[scope-guard] ok: ${rel}`);
128
- process.exit(0);
129
- } catch (err) {
130
- console.error(err.message);
131
- process.exit(1);
132
- }
133
- }