@hegemonart/get-design-done 1.13.3 → 1.14.1

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.
@@ -0,0 +1,102 @@
1
+ # AI-Native Design Tool Interface — Capability Contract
2
+
3
+ This file defines the capability-based contract that AI-native design tools must implement to integrate with the get-design-done pipeline. Two sub-categories are defined: **canvas** and **component-generator**. Future tools implement one sub-category and plug in via the same probe/read/write or probe/generate/adopt surface.
4
+
5
+ ---
6
+
7
+ ## Sub-Categories
8
+
9
+ ### Canvas Tools
10
+
11
+ Canvas tools treat the design canvas as both source AND destination. They expose a bidirectional read+write surface.
12
+
13
+ **Contract:**
14
+
15
+ ```
16
+ probe() → { available | unavailable | not_configured }
17
+
18
+ read(selection) → {
19
+ jsx: string, // React JSX of component tree
20
+ styles: object, // computed CSS styles
21
+ screenshot: base64_png, // visual snapshot
22
+ metadata: object // component name, bounds, id
23
+ }
24
+
25
+ write(proposal) → { confirmed | rejected }
26
+ ```
27
+
28
+ **Implementations:**
29
+ - `connections/paper-design.md` — MCP-based; 24-tool server; budget: 100 calls/week (free)
30
+ - `connections/pencil-dev.md` — file-based; `.pen` YAML spec files; git-tracked; no MCP
31
+
32
+ **Pipeline stages:** `explore` (read) + `verify` (screenshot) + `design` (write via writer agent)
33
+
34
+ ---
35
+
36
+ ### Component Generators
37
+
38
+ Component generators produce UI component code from a natural-language description and an optional design-system target. They expose a generative one-way (or roundtrip) surface.
39
+
40
+ **Contract:**
41
+
42
+ ```
43
+ probe() → { available | unavailable | not_configured }
44
+
45
+ generate(description: string, ds: "shadcn"|"tailwind"|"mantine"|"chakra") → {
46
+ code: string, // component source code
47
+ preview_url: string, // hosted preview URL
48
+ variants: array, // multiple generated variations
49
+ component_id: string // for adopt/annotate operations
50
+ }
51
+
52
+ adopt(variant: object) → { confirmed | rejected }
53
+ ```
54
+
55
+ **Implementations:**
56
+ - `connections/21st-dev.md` — Magic MCP; `npx @21st-dev/magic@latest init`; marketplace prior-art gate
57
+ - `connections/magic-patterns.md` — Claude connector (`mcp__magic_patterns*`) + API key fallback; DS-aware generation
58
+
59
+ **Pipeline stages:** `explore` (prior-art gate for 21st.dev) + `design` (generate + adopt)
60
+
61
+ ---
62
+
63
+ ## Shared Probe Pattern
64
+
65
+ All AI-native tools use the three-value status schema from `connections/connections.md`:
66
+
67
+ | Status | Meaning |
68
+ |--------|---------|
69
+ | `available` | Tool confirmed present and responsive |
70
+ | `unavailable` | Tool present but errored (rate-limited, auth failure) |
71
+ | `not_configured` | ToolSearch returned empty or no .pen files found |
72
+
73
+ STATE.md format: `<tool-name>: <status>` in the `<connections>` block.
74
+
75
+ ---
76
+
77
+ ## Extending with Future Tools
78
+
79
+ To add a new AI-native design tool:
80
+
81
+ 1. Determine sub-category: **canvas** (bidirectional design source) or **component-generator** (code generator).
82
+ 2. Create `connections/<tool-name>.md` following the frozen template in `connections/figma.md`.
83
+ 3. Implement `probe()` using ToolSearch or file-based check. Write status to STATE.md.
84
+ 4. For **canvas**: expose `read()` and `write()` surfaces via the corresponding agent.
85
+ 5. For **component-generator**: implement `generate()` and `adopt()` in `agents/design-component-generator.md` as a new `<!-- impl: <tool> -->` section.
86
+ 6. Add a row to `connections/connections.md` capability matrix with `canvas` or `generator` column marked.
87
+ 7. Append to `test-fixture/baselines/current/connection-list.txt` in sorted order.
88
+
89
+ ---
90
+
91
+ ## Candidate Tools (Backlog)
92
+
93
+ | Tool | Sub-category | Priority | Notes |
94
+ |------|-------------|----------|-------|
95
+ | Subframe | canvas | high | MCP-based; production-ready components; check for `mcp__subframe*` |
96
+ | v0.dev | generator | high | Vercel product; generates shadcn/tailwind; check for `mcp__v0*` |
97
+ | Galileo AI | generator | medium | Enterprise DS generation; API key required |
98
+ | Builder.io Visual Copilot | canvas + generator | medium | Figma plugin + code export; check for `mcp__builder*` |
99
+ | Locofy | generator | low | Figma→React/Next.js; Figma plugin-based |
100
+ | Anima | canvas | low | Figma→React; Figma plugin-based |
101
+ | Plasmic | generator | medium | Headless CMS + visual builder; `mcp__plasmic*` |
102
+ | TeleportHQ | generator | low | Code export + collaboration |
@@ -25,6 +25,7 @@ const os = require('os');
25
25
  const CWD = process.cwd();
26
26
  const TELEMETRY_PATH = path.join(CWD, '.design', 'telemetry', 'costs.jsonl');
27
27
  const METRICS_PATH = path.join(CWD, '.design', 'agent-metrics.json');
28
+ const PHASE_TOTALS_PATH = path.join(CWD, '.design', 'telemetry', 'phase-totals.json');
28
29
  const AGENTS_DIR = path.join(CWD, 'agents');
29
30
 
30
31
  // ---- frontmatter reader (no YAML dep) ----
@@ -124,6 +125,18 @@ function writeAtomic(filePath, content) {
124
125
  fs.renameSync(tmp, filePath);
125
126
  }
126
127
 
128
+ // ---- phase totals aggregator (WR-02: avoids full JSONL replay in budget enforcer) ----
129
+ function aggregateByPhase(rows) {
130
+ const byPhase = {};
131
+ for (const r of rows) {
132
+ const phase = r.phase || 'unknown';
133
+ byPhase[phase] = (byPhase[phase] || 0) + Number(r.est_cost_usd || 0);
134
+ }
135
+ // Round to 6dp to match per-agent precision
136
+ for (const k of Object.keys(byPhase)) byPhase[k] = Number(byPhase[k].toFixed(6));
137
+ return byPhase;
138
+ }
139
+
127
140
  // ---- main ----
128
141
  function main() {
129
142
  const rows = readTelemetryRows();
@@ -133,6 +146,13 @@ function main() {
133
146
  agents,
134
147
  };
135
148
  writeAtomic(METRICS_PATH, JSON.stringify(payload, null, 2) + '\n');
149
+ // Write lightweight phase-totals.json so budget-enforcer can read phase spend
150
+ // in O(1) without replaying the full JSONL on every agent spawn (WR-02).
151
+ const phaseTotals = {
152
+ generated_at: new Date().toISOString(),
153
+ totals: aggregateByPhase(rows),
154
+ };
155
+ writeAtomic(PHASE_TOTALS_PATH, JSON.stringify(phaseTotals, null, 2) + '\n');
136
156
  }
137
157
 
138
158
  try {
@@ -17,7 +17,7 @@
17
17
 
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
- const { execSync } = require('child_process');
20
+ const { spawnSync } = require('child_process');
21
21
 
22
22
  const ROOT = process.cwd();
23
23
  const INTEL_DIR = path.join(ROOT, '.design', 'intel');
@@ -46,15 +46,23 @@ function writeSlice(name, data) {
46
46
 
47
47
  function gitHash(filePath) {
48
48
  try {
49
- return execSync(`git log -1 --format=%h -- "${filePath}"`, { stdio: ['pipe', 'pipe', 'ignore'] })
50
- .toString().trim() || 'untracked';
49
+ const r = spawnSync('git', ['log', '-1', '--format=%h', '--', filePath], {
50
+ stdio: ['pipe', 'pipe', 'ignore'],
51
+ encoding: 'utf8',
52
+ timeout: 5000,
53
+ });
54
+ return r.stdout.trim() || 'untracked';
51
55
  } catch { return 'untracked'; }
52
56
  }
53
57
 
54
58
  function headHash() {
55
59
  try {
56
- return execSync('git rev-parse --short HEAD', { stdio: ['pipe', 'pipe', 'ignore'] })
57
- .toString().trim();
60
+ const r = spawnSync('git', ['rev-parse', '--short', 'HEAD'], {
61
+ stdio: ['pipe', 'pipe', 'ignore'],
62
+ encoding: 'utf8',
63
+ timeout: 5000,
64
+ });
65
+ return r.stdout.trim() || 'unknown';
58
66
  } catch { return 'unknown'; }
59
67
  }
60
68
 
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+ // Shared prompt-injection patterns — single source of truth for both
3
+ // hooks/gdd-read-injection-scanner.js (runtime hook) and
4
+ // scripts/run-injection-scanner-ci.cjs (CI scanner).
5
+ // Add new patterns here; both consumers pick them up automatically.
6
+
7
+ const INJECTION_PATTERNS = [
8
+ { name: 'ignore previous', re: /ignore\s+(all\s+)?(previous|prior|above)\s+instructions?/i },
9
+ { name: 'disregard previous', re: /disregard\s+(all\s+)?(previous|prior|above)\s+instructions?/i },
10
+ { name: 'you are now a different', re: /you\s+are\s+now\s+a\s+different/i },
11
+ { name: 'system: you are', re: /system\s*:\s*you\s+are/i },
12
+ { name: 'role tag injection', re: /<\s*\/?\s*(system|assistant|human)\s*>/i },
13
+ { name: '[INST] fragment', re: /\[INST\]/i },
14
+ { name: '### instruction fragment',re: /###\s*instruction/i },
15
+ ];
16
+
17
+ module.exports = { INJECTION_PATTERNS };
@@ -13,16 +13,7 @@ const path = require('path');
13
13
 
14
14
  const REPO_ROOT = path.resolve(__dirname, '..');
15
15
 
16
- // Patterns mirror hooks/gdd-read-injection-scanner.js.
17
- const INJECTION_PATTERNS = [
18
- { name: 'ignore previous', re: /ignore\s+(all\s+)?(previous|prior|above)\s+instructions?/i },
19
- { name: 'disregard previous', re: /disregard\s+(all\s+)?(previous|prior|above)\s+instructions?/i },
20
- { name: 'you are now a different', re: /you\s+are\s+now\s+a\s+different/i },
21
- { name: 'system: you are', re: /system\s*:\s*you\s+are/i },
22
- { name: 'role tag injection', re: /<\s*\/?\s*(system|assistant|human)\s*>/i },
23
- { name: '[INST] fragment', re: /\[INST\]/i },
24
- { name: '### instruction fragment', re: /###\s*instruction/i },
25
- ];
16
+ const { INJECTION_PATTERNS } = require('./injection-patterns.cjs');
26
17
 
27
18
  function walkMd(dir, out) {
28
19
  if (!fs.existsSync(dir)) return;
@@ -35,7 +35,11 @@ fi
35
35
 
36
36
  # Count actual fixture files (should be 4 frozen feeds + 1 README; we only
37
37
  # care that at least one XML/JSON fixture is present).
38
- FIXTURE_COUNT=$(find "$FIXTURE_DIR" -maxdepth 1 -type f \( -name '*.atom' -o -name '*.rss' -o -name '*.json' \) | wc -l | tr -d ' ')
38
+ # Use null-delimited find to handle filenames with spaces/newlines (WR-05).
39
+ FIXTURE_COUNT=0
40
+ while IFS= read -r -d '' _f; do
41
+ FIXTURE_COUNT=$((FIXTURE_COUNT + 1))
42
+ done < <(find "$FIXTURE_DIR" -maxdepth 1 -type f \( -name '*.atom' -o -name '*.rss' -o -name '*.json' \) -print0)
39
43
  if [ "$FIXTURE_COUNT" -lt 1 ]; then
40
44
  echo "FAIL: $FIXTURE_DIR contains no feed fixtures (.atom/.rss/.json)." >&2
41
45
  exit 1
@@ -35,6 +35,8 @@ Flags can be combined: `--refresh --prompt` is valid (re-fetch, then enrich). `-
35
35
  - Print: `No cache. Network may be unreachable or the hook has not run yet. Try /gdd:check-update --refresh.`
36
36
  - Exit.
37
37
 
38
+ <!-- markdownlint-disable MD025 -->
39
+
38
40
  4. **Dismiss path** (if `--dismiss` in flags):
39
41
  Compute new config contents and write atomically. The python heredoc receives CONFIG_PATH and LATEST_TAG via the ENVIRONMENT (env-prefix form — `KEY=VALUE python3 <<PY`), NOT via trailing argv. Passing `python3 -c '...' KEY=VALUE` makes Python treat the assignments as `sys.argv`, which the old draft did incorrectly; env-prefix form is the portable fix.
40
42
 
@@ -31,7 +31,59 @@ Empty → refero: not_configured
31
31
  Non-empty → refero: available
32
32
  ```
33
33
 
34
- Write results to STATE.md `<connections>`.
34
+ **C 21st.dev probe:**
35
+ ```
36
+ ToolSearch({ query: "mcp__21st", max_results: 5 })
37
+ Empty → 21st-dev: not_configured
38
+ Non-empty → 21st-dev: available
39
+ ```
40
+
41
+ **D — Magic Patterns probe:**
42
+ ```
43
+ ToolSearch({ query: "mcp__magic_patterns", max_results: 5 })
44
+ Empty → magic-patterns: not_configured
45
+ Non-empty → magic-patterns: available
46
+ ```
47
+
48
+ **E — paper.design probe:**
49
+ ```
50
+ ToolSearch({ query: "mcp__paper", max_results: 5 })
51
+ Empty → paper-design: not_configured
52
+ Non-empty → call mcp__paper-design__get_selection; success → available; error → unavailable
53
+ ```
54
+
55
+ **F — pencil.dev probe (file-based):**
56
+ ```bash
57
+ find . -name "*.pen" -not -path "*/node_modules/*" 2>/dev/null | head -1
58
+ Empty → pencil-dev: not_configured
59
+ Found → pencil-dev: available
60
+ ```
61
+
62
+ Write all results to STATE.md `<connections>`.
63
+
64
+ ## Step 1.5 — 21st.dev Prior-Art Check (when 21st-dev: available)
65
+
66
+ If `21st-dev: not_configured` in STATE.md: skip this step entirely.
67
+
68
+ When the explore stage identifies any greenfield component in scope (component name from BRIEF.md or user request that does not yet have an implementation file):
69
+
70
+ 1. `21st_magic_component_search(component_name, limit: 3)`
71
+ 2. Evaluate top result:
72
+ - **fit ≥ 80%**: add `<prior-art>` block to DESIGN.md:
73
+ ```xml
74
+ <prior-art source="21st.dev" component="<name>" fit="<score>%" id="<component_id>">
75
+ Recommendation: adopt — do not build custom. Confirm with design-executor.
76
+ </prior-art>
77
+ ```
78
+ - **fit < 80%**: note top candidate in DESIGN.md as a reference, proceed with custom build:
79
+ ```xml
80
+ <prior-art source="21st.dev" component="<name>" fit="<score>%" id="<component_id>">
81
+ Low fit — noted for reference. Building custom component.
82
+ </prior-art>
83
+ ```
84
+ 3. If `svgl_get_brand_logo` is available and explore scope includes brand logo assets: call `svgl_get_brand_logo(brand_name)` for each required brand asset; add SVG results to `.design/assets/` and note in DESIGN.md.
85
+
86
+ If no greenfield components in scope: skip this step.
35
87
 
36
88
  ## Step 2 — Inventory scan (unless `--skip-scan`)
37
89