@hegemonart/get-design-done 1.19.6 → 1.21.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 (140) hide show
  1. package/.claude-plugin/marketplace.json +11 -14
  2. package/.claude-plugin/plugin.json +9 -32
  3. package/CHANGELOG.md +138 -0
  4. package/README.md +54 -1
  5. package/agents/design-reflector.md +13 -0
  6. package/bin/gdd-sdk +55 -0
  7. package/connections/connections.md +3 -0
  8. package/connections/figma.md +2 -0
  9. package/connections/gdd-state.md +186 -0
  10. package/hooks/budget-enforcer.ts +716 -0
  11. package/hooks/context-exhaustion.ts +251 -0
  12. package/hooks/gdd-read-injection-scanner.ts +172 -0
  13. package/hooks/hooks.json +3 -3
  14. package/package.json +32 -51
  15. package/reference/codex-tools.md +53 -0
  16. package/reference/config-schema.md +2 -2
  17. package/reference/error-recovery.md +58 -0
  18. package/reference/gemini-tools.md +53 -0
  19. package/reference/registry.json +21 -0
  20. package/reference/schemas/budget.schema.json +42 -0
  21. package/reference/schemas/events.schema.json +55 -0
  22. package/reference/schemas/generated.d.ts +419 -0
  23. package/reference/schemas/iteration-budget.schema.json +36 -0
  24. package/reference/schemas/mcp-gdd-state-tools.schema.json +89 -0
  25. package/reference/schemas/rate-limits.schema.json +31 -0
  26. package/scripts/aggregate-agent-metrics.ts +282 -0
  27. package/scripts/codegen-schema-types.ts +149 -0
  28. package/scripts/e2e/run-headless.ts +514 -0
  29. package/scripts/lib/cli/commands/audit.ts +382 -0
  30. package/scripts/lib/cli/commands/init.ts +217 -0
  31. package/scripts/lib/cli/commands/query.ts +329 -0
  32. package/scripts/lib/cli/commands/run.ts +656 -0
  33. package/scripts/lib/cli/commands/stage.ts +468 -0
  34. package/scripts/lib/cli/index.ts +167 -0
  35. package/scripts/lib/cli/parse-args.ts +336 -0
  36. package/scripts/lib/context-engine/index.ts +116 -0
  37. package/scripts/lib/context-engine/manifest.ts +69 -0
  38. package/scripts/lib/context-engine/truncate.ts +282 -0
  39. package/scripts/lib/context-engine/types.ts +59 -0
  40. package/scripts/lib/discuss-parallel-runner/aggregator.ts +448 -0
  41. package/scripts/lib/discuss-parallel-runner/discussants.ts +430 -0
  42. package/scripts/lib/discuss-parallel-runner/index.ts +223 -0
  43. package/scripts/lib/discuss-parallel-runner/types.ts +184 -0
  44. package/scripts/lib/error-classifier.cjs +232 -0
  45. package/scripts/lib/error-classifier.d.cts +44 -0
  46. package/scripts/lib/event-stream/emitter.ts +88 -0
  47. package/scripts/lib/event-stream/index.ts +164 -0
  48. package/scripts/lib/event-stream/types.ts +127 -0
  49. package/scripts/lib/event-stream/writer.ts +154 -0
  50. package/scripts/lib/explore-parallel-runner/index.ts +294 -0
  51. package/scripts/lib/explore-parallel-runner/mappers.ts +290 -0
  52. package/scripts/lib/explore-parallel-runner/synthesizer.ts +295 -0
  53. package/scripts/lib/explore-parallel-runner/types.ts +139 -0
  54. package/scripts/lib/gdd-errors/classification.ts +124 -0
  55. package/scripts/lib/gdd-errors/index.ts +218 -0
  56. package/scripts/lib/gdd-state/gates.ts +216 -0
  57. package/scripts/lib/gdd-state/index.ts +167 -0
  58. package/scripts/lib/gdd-state/lockfile.ts +232 -0
  59. package/scripts/lib/gdd-state/mutator.ts +574 -0
  60. package/scripts/lib/gdd-state/parser.ts +523 -0
  61. package/scripts/lib/gdd-state/types.ts +179 -0
  62. package/scripts/lib/harness/detect.ts +90 -0
  63. package/scripts/lib/harness/index.ts +64 -0
  64. package/scripts/lib/harness/tool-map.ts +142 -0
  65. package/scripts/lib/init-runner/index.ts +396 -0
  66. package/scripts/lib/init-runner/researchers.ts +245 -0
  67. package/scripts/lib/init-runner/scaffold.ts +224 -0
  68. package/scripts/lib/init-runner/synthesizer.ts +224 -0
  69. package/scripts/lib/init-runner/types.ts +143 -0
  70. package/scripts/lib/iteration-budget.cjs +205 -0
  71. package/scripts/lib/iteration-budget.d.cts +32 -0
  72. package/scripts/lib/jittered-backoff.cjs +112 -0
  73. package/scripts/lib/jittered-backoff.d.cts +38 -0
  74. package/scripts/lib/lockfile.cjs +177 -0
  75. package/scripts/lib/lockfile.d.cts +21 -0
  76. package/scripts/lib/logger/index.ts +251 -0
  77. package/scripts/lib/logger/sinks.ts +269 -0
  78. package/scripts/lib/logger/types.ts +110 -0
  79. package/scripts/lib/pipeline-runner/human-gate.ts +134 -0
  80. package/scripts/lib/pipeline-runner/index.ts +527 -0
  81. package/scripts/lib/pipeline-runner/stage-handlers.ts +339 -0
  82. package/scripts/lib/pipeline-runner/state-machine.ts +144 -0
  83. package/scripts/lib/pipeline-runner/types.ts +183 -0
  84. package/scripts/lib/prompt-sanitizer/index.ts +435 -0
  85. package/scripts/lib/prompt-sanitizer/patterns.ts +173 -0
  86. package/scripts/lib/rate-guard.cjs +365 -0
  87. package/scripts/lib/rate-guard.d.cts +38 -0
  88. package/scripts/lib/session-runner/errors.ts +406 -0
  89. package/scripts/lib/session-runner/index.ts +715 -0
  90. package/scripts/lib/session-runner/transcript.ts +189 -0
  91. package/scripts/lib/session-runner/types.ts +144 -0
  92. package/scripts/lib/tool-scoping/index.ts +219 -0
  93. package/scripts/lib/tool-scoping/parse-agent-tools.ts +207 -0
  94. package/scripts/lib/tool-scoping/stage-scopes.ts +139 -0
  95. package/scripts/lib/tool-scoping/types.ts +77 -0
  96. package/scripts/mcp-servers/gdd-state/schemas/add_blocker.schema.json +67 -0
  97. package/scripts/mcp-servers/gdd-state/schemas/add_decision.schema.json +68 -0
  98. package/scripts/mcp-servers/gdd-state/schemas/add_must_have.schema.json +68 -0
  99. package/scripts/mcp-servers/gdd-state/schemas/checkpoint.schema.json +51 -0
  100. package/scripts/mcp-servers/gdd-state/schemas/frontmatter_update.schema.json +62 -0
  101. package/scripts/mcp-servers/gdd-state/schemas/get.schema.json +51 -0
  102. package/scripts/mcp-servers/gdd-state/schemas/probe_connections.schema.json +75 -0
  103. package/scripts/mcp-servers/gdd-state/schemas/resolve_blocker.schema.json +66 -0
  104. package/scripts/mcp-servers/gdd-state/schemas/set_status.schema.json +47 -0
  105. package/scripts/mcp-servers/gdd-state/schemas/transition_stage.schema.json +70 -0
  106. package/scripts/mcp-servers/gdd-state/schemas/update_progress.schema.json +58 -0
  107. package/scripts/mcp-servers/gdd-state/server.ts +288 -0
  108. package/scripts/mcp-servers/gdd-state/tools/add_blocker.ts +72 -0
  109. package/scripts/mcp-servers/gdd-state/tools/add_decision.ts +89 -0
  110. package/scripts/mcp-servers/gdd-state/tools/add_must_have.ts +113 -0
  111. package/scripts/mcp-servers/gdd-state/tools/checkpoint.ts +60 -0
  112. package/scripts/mcp-servers/gdd-state/tools/frontmatter_update.ts +91 -0
  113. package/scripts/mcp-servers/gdd-state/tools/get.ts +51 -0
  114. package/scripts/mcp-servers/gdd-state/tools/index.ts +51 -0
  115. package/scripts/mcp-servers/gdd-state/tools/probe_connections.ts +73 -0
  116. package/scripts/mcp-servers/gdd-state/tools/resolve_blocker.ts +84 -0
  117. package/scripts/mcp-servers/gdd-state/tools/set_status.ts +54 -0
  118. package/scripts/mcp-servers/gdd-state/tools/shared.ts +194 -0
  119. package/scripts/mcp-servers/gdd-state/tools/transition_stage.ts +80 -0
  120. package/scripts/mcp-servers/gdd-state/tools/update_progress.ts +81 -0
  121. package/scripts/validate-frontmatter.ts +114 -0
  122. package/scripts/validate-schemas.ts +401 -0
  123. package/skills/brief/SKILL.md +15 -6
  124. package/skills/design/SKILL.md +31 -13
  125. package/skills/explore/SKILL.md +41 -17
  126. package/skills/health/SKILL.md +15 -4
  127. package/skills/optimize/SKILL.md +3 -3
  128. package/skills/pause/SKILL.md +16 -10
  129. package/skills/plan/SKILL.md +33 -17
  130. package/skills/progress/SKILL.md +15 -11
  131. package/skills/resume/SKILL.md +19 -10
  132. package/skills/settings/SKILL.md +11 -3
  133. package/skills/todo/SKILL.md +12 -3
  134. package/skills/verify/SKILL.md +65 -29
  135. package/hooks/budget-enforcer.js +0 -329
  136. package/hooks/context-exhaustion.js +0 -127
  137. package/hooks/gdd-read-injection-scanner.js +0 -39
  138. package/scripts/aggregate-agent-metrics.js +0 -173
  139. package/scripts/validate-frontmatter.cjs +0 -68
  140. package/scripts/validate-schemas.cjs +0 -242
@@ -0,0 +1,81 @@
1
+ // scripts/mcp-servers/gdd-state/tools/update_progress.ts
2
+ //
3
+ // Tool: gdd_state__update_progress
4
+ // Purpose: Update <position>.task_progress and/or <position>.status.
5
+ // Emits state.mutation on success.
6
+
7
+ import { mutate } from '../../../lib/gdd-state/index.ts';
8
+ import {
9
+ emitStateMutation,
10
+ errorResponse,
11
+ okResponse,
12
+ resolveStatePath,
13
+ throwValidation,
14
+ type ToolResponse,
15
+ } from './shared.ts';
16
+
17
+ export const name = 'gdd_state__update_progress';
18
+ export const schemaPath = '../schemas/update_progress.schema.json';
19
+
20
+ export interface UpdateProgressInput {
21
+ task_progress?: string;
22
+ status?: 'initialized' | 'in_progress' | 'completed' | 'blocked';
23
+ }
24
+
25
+ /** Accept the same regex the JSON Schema enforces — double-check for
26
+ * defense in depth against a caller that somehow routes around schema
27
+ * validation (e.g. when we run the handler directly in a test). */
28
+ const TASK_PROGRESS_RE = /^[0-9]+\/[0-9]+$/;
29
+ const STATUSES = new Set([
30
+ 'initialized',
31
+ 'in_progress',
32
+ 'completed',
33
+ 'blocked',
34
+ ]);
35
+
36
+ export async function handle(input: unknown): Promise<ToolResponse> {
37
+ try {
38
+ const typed = (input ?? {}) as UpdateProgressInput;
39
+ if (typed.task_progress === undefined && typed.status === undefined) {
40
+ throwValidation(
41
+ 'MISSING_FIELD',
42
+ 'update_progress requires at least one of task_progress / status',
43
+ );
44
+ }
45
+ if (typed.task_progress !== undefined) {
46
+ if (!TASK_PROGRESS_RE.test(typed.task_progress)) {
47
+ throwValidation(
48
+ 'TASK_PROGRESS_FORMAT',
49
+ `task_progress "${typed.task_progress}" must match N/M (digits)`,
50
+ );
51
+ }
52
+ }
53
+ if (typed.status !== undefined && !STATUSES.has(typed.status)) {
54
+ throwValidation(
55
+ 'STATUS_INVALID',
56
+ `status "${typed.status}" is not one of initialized/in_progress/completed/blocked`,
57
+ );
58
+ }
59
+
60
+ const path = resolveStatePath();
61
+ const diff: Record<string, unknown> = {};
62
+ const after = await mutate(path, (s) => {
63
+ if (typed.task_progress !== undefined) {
64
+ diff['task_progress'] = {
65
+ before: s.position.task_progress,
66
+ after: typed.task_progress,
67
+ };
68
+ s.position.task_progress = typed.task_progress;
69
+ }
70
+ if (typed.status !== undefined) {
71
+ diff['status'] = { before: s.position.status, after: typed.status };
72
+ s.position.status = typed.status;
73
+ }
74
+ return s;
75
+ });
76
+ emitStateMutation(name, diff, after);
77
+ return okResponse({ position: after.position });
78
+ } catch (err) {
79
+ return errorResponse(err);
80
+ }
81
+ }
@@ -0,0 +1,114 @@
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 } from 'node:path';
18
+
19
+ import { readFrontmatter } from '../tests/helpers.ts';
20
+
21
+ // Importing a type from generated.d.ts satisfies the Plan 20-00 rule that
22
+ // every Tier-1 TS file participates in the codegen graph. We don't use
23
+ // IntelSchema at runtime; the re-export keeps it visible for static checks.
24
+ import type { IntelSchema } from '../reference/schemas/generated.js';
25
+ export type { IntelSchema };
26
+
27
+ /**
28
+ * Strict shape of the agent-frontmatter subset this validator enforces.
29
+ * Matches REQUIRED_FIELDS below. `readFrontmatter` returns a permissive
30
+ * `Record<string, string | boolean | string[]>` — we narrow per-field as
31
+ * needed rather than asserting the whole object at once.
32
+ */
33
+ export interface AgentFrontmatter {
34
+ name: string;
35
+ description: string;
36
+ tools: string;
37
+ color: string;
38
+ 'parallel-safe': boolean | string;
39
+ 'typical-duration-seconds': string | number;
40
+ 'reads-only': boolean | string;
41
+ writes: string | string[];
42
+ 'default-tier'?: 'haiku' | 'sonnet' | 'opus';
43
+ 'size_budget'?: 'S' | 'M' | 'L' | 'XL';
44
+ }
45
+
46
+ const REQUIRED_FIELDS: readonly (keyof AgentFrontmatter)[] = [
47
+ 'name',
48
+ 'description',
49
+ 'tools',
50
+ 'color',
51
+ 'parallel-safe',
52
+ 'typical-duration-seconds',
53
+ 'reads-only',
54
+ 'writes',
55
+ ];
56
+
57
+ function walkMd(dir: string): string[] {
58
+ const out: string[] = [];
59
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
60
+ const full: string = join(dir, entry.name);
61
+ if (entry.isDirectory()) out.push(...walkMd(full));
62
+ else if (entry.isFile() && entry.name.endsWith('.md')) out.push(full);
63
+ }
64
+ return out;
65
+ }
66
+
67
+ /**
68
+ * Return true when the frontmatter value is "missing" per the original
69
+ * .cjs contract: undefined / null / empty string. Preserves the semantics
70
+ * exactly so the CI gate fires on the same inputs.
71
+ */
72
+ function isMissing(v: unknown): boolean {
73
+ if (v === undefined || v === null) return true;
74
+ if (typeof v === 'string' && v === '') return true;
75
+ return false;
76
+ }
77
+
78
+ function main(): void {
79
+ const args: string[] = process.argv.slice(2).filter((a) => !a.startsWith('--'));
80
+ const targets: string[] = args.length ? args : ['agents/'];
81
+ const files: string[] = [];
82
+ for (const t of targets) {
83
+ if (!existsSync(t)) {
84
+ console.error(`${t}: path does not exist`);
85
+ process.exit(1);
86
+ }
87
+ const stat = statSync(t);
88
+ if (stat.isDirectory()) files.push(...walkMd(t));
89
+ else files.push(t);
90
+ }
91
+
92
+ let violations = 0;
93
+ for (const f of files) {
94
+ const fm = readFrontmatter(f);
95
+ if (Object.keys(fm).length === 0) {
96
+ // README.md under agents/ may have no frontmatter — skip
97
+ if (basename(f).toLowerCase() === 'readme.md') continue;
98
+ console.log(`${f}:frontmatter: missing`);
99
+ violations++;
100
+ continue;
101
+ }
102
+ for (const field of REQUIRED_FIELDS) {
103
+ if (!(field in fm) || isMissing((fm as Record<string, unknown>)[field])) {
104
+ console.log(`${f}:${field}: missing`);
105
+ violations++;
106
+ }
107
+ }
108
+ }
109
+
110
+ console.log(`summary: ${files.length} file(s) checked, ${violations} violation(s)`);
111
+ process.exit(violations === 0 ? 0 : 1);
112
+ }
113
+
114
+ main();
@@ -0,0 +1,401 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * validate-schemas.ts
4
+ *
5
+ * Runs all Draft-07 JSON schemas under reference/schemas/ against their
6
+ * corresponding subject files. Used by `npm run validate:schemas` and by the
7
+ * CI validate job.
8
+ *
9
+ * Primary path: spawn `npx --yes ajv-cli@5 validate -s <schema> -d <data>`.
10
+ *
11
+ * Fallback path: if `npx --yes` cannot fetch ajv-cli (offline/sandboxed
12
+ * environment), fall back to a structural parse check: confirm each schema is
13
+ * valid Draft-07 JSON (has $schema, type) and each data file is parseable JSON.
14
+ * The fallback exits 0 on structural validity; CI will run the real ajv pass.
15
+ *
16
+ * Usage:
17
+ * node --experimental-strip-types scripts/validate-schemas.ts
18
+ * node --experimental-strip-types scripts/validate-schemas.ts --no-npx
19
+ *
20
+ * Exit codes:
21
+ * 0 — all present (schema, data) pairs pass
22
+ * 1 — one or more pairs failed validation or a present schema is invalid
23
+ *
24
+ * Converted from scripts/validate-schemas.cjs in Plan 20-00. Behavior is
25
+ * verbatim; only typing + generated-type-import were added.
26
+ */
27
+
28
+ import { spawnSync } from 'node:child_process';
29
+ import { readFileSync, existsSync } from 'node:fs';
30
+ import { resolve, join, dirname } from 'node:path';
31
+
32
+ // Generated types from reference/schemas/generated.d.ts — importing any one
33
+ // of them satisfies Plan 20-00's requirement that every Tier-1 TS file
34
+ // consumes the codegen graph. We re-export so downstream callers can also
35
+ // pin to these types for static validation.
36
+ import type {
37
+ ConfigSchema,
38
+ PluginSchema,
39
+ MarketplaceSchema,
40
+ HooksSchema,
41
+ IntelSchema,
42
+ AuthoritySnapshotSchema,
43
+ EventsSchema,
44
+ McpGddStateToolsSchema,
45
+ BudgetSchema,
46
+ RateLimitsSchema,
47
+ IterationBudgetSchema,
48
+ } from '../reference/schemas/generated.js';
49
+
50
+ export type {
51
+ ConfigSchema,
52
+ PluginSchema,
53
+ MarketplaceSchema,
54
+ HooksSchema,
55
+ IntelSchema,
56
+ AuthoritySnapshotSchema,
57
+ EventsSchema,
58
+ McpGddStateToolsSchema,
59
+ BudgetSchema,
60
+ RateLimitsSchema,
61
+ IterationBudgetSchema,
62
+ };
63
+
64
+ /**
65
+ * Discover the repo root by walking up from cwd to find our package.json.
66
+ * Kept identical to the approach in tests/helpers.ts so both modules behave
67
+ * the same when invoked from worktrees, subdirectories, or CI.
68
+ */
69
+ function findRepoRoot(): string {
70
+ let dir: string = process.cwd();
71
+ for (let i = 0; i < 10; i++) {
72
+ try {
73
+ const pkgPath: string = join(dir, 'package.json');
74
+ const pkg: { name?: string } = JSON.parse(readFileSync(pkgPath, 'utf8')) as { name?: string };
75
+ if (pkg.name === '@hegemonart/get-design-done') return dir;
76
+ } catch {
77
+ // not this level
78
+ }
79
+ const parent: string = dirname(dir);
80
+ if (parent === dir) break;
81
+ dir = parent;
82
+ }
83
+ return resolve(process.cwd());
84
+ }
85
+
86
+ const REPO_ROOT: string = findRepoRoot();
87
+
88
+ /**
89
+ * (schema, data) pairs. `data` is relative to repo root. `required=true`
90
+ * means the data file MUST exist in the repo; `required=false` means the
91
+ * data file is generated at runtime (gitignored) and only the schema itself
92
+ * is compiled.
93
+ */
94
+ interface Pair {
95
+ name: string;
96
+ schema: string;
97
+ data: string | null;
98
+ required: boolean;
99
+ }
100
+
101
+ export const PAIRS: readonly Pair[] = [
102
+ {
103
+ name: 'plugin',
104
+ schema: 'reference/schemas/plugin.schema.json',
105
+ data: '.claude-plugin/plugin.json',
106
+ required: true,
107
+ },
108
+ {
109
+ name: 'marketplace',
110
+ schema: 'reference/schemas/marketplace.schema.json',
111
+ data: '.claude-plugin/marketplace.json',
112
+ required: true,
113
+ },
114
+ {
115
+ name: 'hooks',
116
+ schema: 'reference/schemas/hooks.schema.json',
117
+ data: 'hooks/hooks.json',
118
+ required: true,
119
+ },
120
+ {
121
+ name: 'config',
122
+ schema: 'reference/schemas/config.schema.json',
123
+ data: '.design/config.json',
124
+ required: false,
125
+ },
126
+ {
127
+ name: 'intel',
128
+ schema: 'reference/schemas/intel.schema.json',
129
+ // intel files are runtime-only (gitignored). Only schema-compile it.
130
+ data: null,
131
+ required: false,
132
+ },
133
+ {
134
+ name: 'authority-snapshot',
135
+ schema: 'reference/schemas/authority-snapshot.schema.json',
136
+ // .design/authority-snapshot.json is runtime-only (gitignored via .design/).
137
+ // Only schema-compile it.
138
+ data: null,
139
+ required: false,
140
+ },
141
+ {
142
+ name: 'events',
143
+ schema: 'reference/schemas/events.schema.json',
144
+ // .design/telemetry/events.jsonl is runtime-only (gitignored). It is
145
+ // JSONL (one JSON object per line) rather than a single JSON document,
146
+ // so we cannot point ajv-cli at it as a `data` subject — each *line*
147
+ // is the schema subject, not the file. Schema-compile only here;
148
+ // per-line structural discipline is enforced by the EventWriter at
149
+ // runtime (Plan 20-06). See scripts/lib/event-stream/writer.ts.
150
+ data: null,
151
+ required: false,
152
+ },
153
+ {
154
+ name: 'mcp-gdd-state-tools',
155
+ schema: 'reference/schemas/mcp-gdd-state-tools.schema.json',
156
+ // The combined tool manifest is a codegen artifact — individual per-tool
157
+ // schemas live under scripts/mcp-servers/gdd-state/schemas/ and are
158
+ // consumed directly by the server. Schema-compile only here so the
159
+ // Draft-07 declaration stays valid as tools evolve. See Plan 20-05.
160
+ data: null,
161
+ required: false,
162
+ },
163
+ {
164
+ name: 'budget',
165
+ schema: 'reference/schemas/budget.schema.json',
166
+ // .design/budget.json is runtime-only (created by scripts/bootstrap.sh
167
+ // on first session if absent, per D-12). It's gitignored under .design/
168
+ // so the data file isn't tracked — schema-compile only here. The
169
+ // budget-enforcer.ts hook consumes BudgetSchema from generated.d.ts at
170
+ // type-check time regardless of file presence. See Plan 20-13.
171
+ data: null,
172
+ required: false,
173
+ },
174
+ {
175
+ name: 'rate-limits',
176
+ schema: 'reference/schemas/rate-limits.schema.json',
177
+ // .design/rate-limits/<provider>.json is runtime-only, written by
178
+ // scripts/lib/rate-guard.cjs as a side-effect of every
179
+ // ingestHeaders() call. One file per provider — they're all the same
180
+ // shape — so no single subject data path exists. Schema-compile
181
+ // only here; per-file structural discipline is enforced at write
182
+ // time by rate-guard. See Plan 20-14.
183
+ data: null,
184
+ required: false,
185
+ },
186
+ {
187
+ name: 'iteration-budget',
188
+ schema: 'reference/schemas/iteration-budget.schema.json',
189
+ // .design/iteration-budget.json is runtime-only, written by
190
+ // scripts/lib/iteration-budget.cjs. Schema-compile only here.
191
+ // See Plan 20-14.
192
+ data: null,
193
+ required: false,
194
+ },
195
+ ];
196
+
197
+ const USE_NPX: boolean = !process.argv.includes('--no-npx');
198
+
199
+ /** Shape of an ajv-cli invocation result. */
200
+ interface AjvResult {
201
+ ok: boolean;
202
+ stdout: string;
203
+ stderr: string;
204
+ status: number | null;
205
+ fetchFailed: boolean;
206
+ }
207
+
208
+ /**
209
+ * Try running ajv-cli via npx. Returns { ok, stdout, stderr, status, fetchFailed }.
210
+ * fetchFailed=true if npx couldn't fetch the package (offline); caller should fall back.
211
+ *
212
+ * We pass `-c ajv-formats` so schemas declaring `format: "date-time"` (etc.) are
213
+ * validated against the standard JSON Schema formats plugin rather than being
214
+ * rejected as unknown formats under ajv's strict mode.
215
+ */
216
+ function runAjv(args: readonly string[]): AjvResult {
217
+ const injected: string[] = [...args, '-c', 'ajv-formats'];
218
+ // Windows ships `npx.cmd` (batch shim); Node's spawnSync cannot invoke .cmd
219
+ // files without `shell: true`. On POSIX `shell: true` is a no-op here since
220
+ // the argv is a fixed whitelist (no user-controlled substitution). This
221
+ // fixes the pre-existing Windows-local failure while preserving CI
222
+ // behavior on Linux runners.
223
+ const result = spawnSync(
224
+ 'npx',
225
+ ['--yes', '-p', 'ajv-cli@5', '-p', 'ajv-formats@3', 'ajv', ...injected],
226
+ { encoding: 'utf8', cwd: REPO_ROOT, shell: process.platform === 'win32' },
227
+ );
228
+ const stdout: string = result.stdout ?? '';
229
+ const stderr: string = result.stderr ?? '';
230
+ const combined: string = stdout + stderr;
231
+ // fetchFailed heuristic now also catches the "npx binary not on PATH /
232
+ // spawn ENOENT" case so offline or missing-npx sandboxes fall back cleanly.
233
+ const spawnFailed: boolean =
234
+ result.error !== undefined &&
235
+ ((result.error as NodeJS.ErrnoException).code === 'ENOENT' ||
236
+ (result.error as NodeJS.ErrnoException).code === 'ENOTDIR');
237
+ const fetchFailed: boolean =
238
+ spawnFailed ||
239
+ (result.status !== 0 &&
240
+ /(ENOTFOUND|ECONNREFUSED|ETIMEDOUT|getaddrinfo|network|unable to resolve|unable to fetch|offline|E404|registry\.npmjs\.org)/i.test(
241
+ combined,
242
+ ));
243
+ return {
244
+ ok: result.status === 0,
245
+ stdout,
246
+ stderr,
247
+ status: result.status,
248
+ fetchFailed,
249
+ };
250
+ }
251
+
252
+ /** Outcome of a structural (fallback) schema/data parse check. */
253
+ interface StructuralResult {
254
+ ok: boolean;
255
+ reason?: string;
256
+ }
257
+
258
+ /**
259
+ * Fallback: confirm schema file is a valid Draft-07 JSON Schema (has $schema
260
+ * pointing at draft-07). Confirm data file (if present) parses as JSON.
261
+ */
262
+ function structuralCheck(schemaAbs: string, dataAbs: string | null): StructuralResult {
263
+ let schema: { $schema?: string };
264
+ try {
265
+ schema = JSON.parse(readFileSync(schemaAbs, 'utf8')) as { $schema?: string };
266
+ } catch (e) {
267
+ const msg = e instanceof Error ? e.message : String(e);
268
+ return { ok: false, reason: `schema not parseable JSON: ${msg}` };
269
+ }
270
+ if (!schema.$schema || !/draft-07/.test(schema.$schema)) {
271
+ return { ok: false, reason: `schema missing Draft-07 $schema declaration` };
272
+ }
273
+ if (dataAbs) {
274
+ try {
275
+ JSON.parse(readFileSync(dataAbs, 'utf8'));
276
+ } catch (e) {
277
+ const msg = e instanceof Error ? e.message : String(e);
278
+ return { ok: false, reason: `data file not parseable JSON: ${msg}` };
279
+ }
280
+ }
281
+ return { ok: true };
282
+ }
283
+
284
+ /** Per-pair validation result. */
285
+ interface PairResult {
286
+ name: string;
287
+ ok: boolean;
288
+ via: string;
289
+ note?: string;
290
+ reason?: string;
291
+ }
292
+
293
+ function main(): void {
294
+ const results: PairResult[] = [];
295
+ let fallbackReason: string | null = null;
296
+
297
+ for (const pair of PAIRS) {
298
+ const schemaAbs: string = join(REPO_ROOT, pair.schema);
299
+ const dataAbs: string | null = pair.data ? join(REPO_ROOT, pair.data) : null;
300
+
301
+ if (!existsSync(schemaAbs)) {
302
+ results.push({
303
+ name: pair.name,
304
+ ok: false,
305
+ via: 'missing-schema',
306
+ reason: `schema not found at ${pair.schema}`,
307
+ });
308
+ continue;
309
+ }
310
+
311
+ const dataPresent: boolean = Boolean(dataAbs) && existsSync(dataAbs as string);
312
+
313
+ if (!dataPresent && pair.required) {
314
+ results.push({
315
+ name: pair.name,
316
+ ok: false,
317
+ via: 'missing-data',
318
+ reason: `required data file missing at ${pair.data}`,
319
+ });
320
+ continue;
321
+ }
322
+
323
+ if (!dataPresent) {
324
+ // Schema-only: compile to confirm the schema is structurally valid.
325
+ if (USE_NPX && !fallbackReason) {
326
+ const r = runAjv(['compile', '-s', schemaAbs]);
327
+ if (r.ok) {
328
+ results.push({
329
+ name: pair.name,
330
+ ok: true,
331
+ via: 'ajv-compile',
332
+ note: 'data not present in repo tree — schema compiled only',
333
+ });
334
+ continue;
335
+ }
336
+ if (r.fetchFailed) {
337
+ fallbackReason = `npx fetch failed: ${r.stderr.split('\n')[0] || 'unknown'}`;
338
+ } else {
339
+ results.push({
340
+ name: pair.name,
341
+ ok: false,
342
+ via: 'ajv-compile',
343
+ reason: r.stderr || r.stdout || `exit ${r.status ?? 'null'}`,
344
+ });
345
+ continue;
346
+ }
347
+ }
348
+ const s = structuralCheck(schemaAbs, null);
349
+ const reason: string = s.ok
350
+ ? 'schema is valid Draft-07 (data not in repo tree)'
351
+ : s.reason ?? 'unknown failure';
352
+ results.push({ name: pair.name, ok: s.ok, via: 'structural-compile', reason });
353
+ continue;
354
+ }
355
+
356
+ // Both schema and data present — full validation.
357
+ if (USE_NPX && !fallbackReason) {
358
+ const r = runAjv(['validate', '-s', schemaAbs, '-d', dataAbs as string]);
359
+ if (r.ok) {
360
+ results.push({ name: pair.name, ok: true, via: 'ajv-validate' });
361
+ continue;
362
+ }
363
+ if (r.fetchFailed) {
364
+ fallbackReason = `npx fetch failed: ${r.stderr.split('\n')[0] || 'unknown'}`;
365
+ } else {
366
+ results.push({
367
+ name: pair.name,
368
+ ok: false,
369
+ via: 'ajv-validate',
370
+ reason: r.stderr || r.stdout || `exit ${r.status ?? 'null'}`,
371
+ });
372
+ continue;
373
+ }
374
+ }
375
+ const s = structuralCheck(schemaAbs, dataAbs);
376
+ const reason: string = s.ok
377
+ ? 'schema is valid Draft-07 and data parses as JSON'
378
+ : s.reason ?? 'unknown failure';
379
+ results.push({ name: pair.name, ok: s.ok, via: 'structural-validate', reason });
380
+ }
381
+
382
+ // Report.
383
+ console.log('validate-schemas: results');
384
+ for (const r of results) {
385
+ const status: string = r.ok ? 'OK' : 'FAIL';
386
+ const note: string = r.note ? ` (${r.note})` : '';
387
+ const reason: string = r.reason ? ` — ${r.reason}` : '';
388
+ console.log(` [${status}] ${r.name} via ${r.via}${note}${reason}`);
389
+ }
390
+ if (fallbackReason) {
391
+ console.log(
392
+ `\nnote: ajv-cli via npx unavailable (${fallbackReason}); fell back to structural checks. CI will run the authoritative ajv pass.`,
393
+ );
394
+ }
395
+ const failed: PairResult[] = results.filter((r) => !r.ok);
396
+ console.log(`summary: ${results.length} pair(s) checked, ${failed.length} failure(s)`);
397
+
398
+ process.exit(failed.length === 0 ? 0 : 1);
399
+ }
400
+
401
+ main();
@@ -2,7 +2,7 @@
2
2
  name: gdd-brief
3
3
  description: "Design intake — captures problem statement, audience, constraints, success metrics, and scope into .design/BRIEF.md (Stage 1 of 5)"
4
4
  argument-hint: "[--re-brief to redo intake on existing project]"
5
- tools: Read, Write, AskUserQuestion
5
+ tools: Read, Write, AskUserQuestion, mcp__gdd_state__frontmatter_update, mcp__gdd_state__set_status, mcp__gdd_state__update_progress, mcp__gdd_state__get
6
6
  ---
7
7
 
8
8
  # Get Design Done — Brief
@@ -56,12 +56,21 @@ Write the brief with these sections, preserving any pre-existing answers:
56
56
  <answer>
57
57
  ```
58
58
 
59
- ## Step 4 — Update STATE.md
59
+ ## Step 4 — Bootstrap STATE.md (if missing)
60
60
 
61
- - If `.design/STATE.md` does not exist, create it from `reference/STATE-TEMPLATE.md`.
62
- - Set frontmatter `stage: explore` (next stage).
63
- - Update `last_checkpoint` to now.
64
- - Write STATE.md.
61
+ <!-- BOOTSTRAP EXCEPTION: STATE.md does not exist yet — MCP tools require it to exist. Direct Write is intentional. All subsequent mutations use MCP. -->
62
+
63
+ If `.design/STATE.md` does not exist, copy the template block from `reference/STATE-TEMPLATE.md` (between `==== BEGIN TEMPLATE ====` and `==== END TEMPLATE ====`) to `.design/STATE.md` via `Write`. Leave the `<ISO 8601 timestamp>` placeholders in-place — Step 5 stamps them via MCP. If STATE.md already exists, skip to Step 5.
64
+
65
+ ## Step 5 — Commit STATE.md initialization
66
+
67
+ With `.design/STATE.md` seeded from the template:
68
+
69
+ 1. Stamp timestamps + cycle id: call `mcp__gdd_state__frontmatter_update` with `patch: { started_at: <ISO>, last_checkpoint: <ISO>, cycle: <cycle-id> }`.
70
+ 2. Mark brief progress: call `mcp__gdd_state__update_progress` with `task_progress: "5/5"`, `status: "brief_complete"`.
71
+ 3. Set handoff status: call `mcp__gdd_state__set_status` with `status: "brief_complete"`.
72
+
73
+ Do NOT call `mcp__gdd_state__transition_stage` from brief — explore calls it on entry, keeping the transition atomic with the stage that owns the new state.
65
74
 
66
75
  ## After Writing
67
76