@hegemonart/get-design-done 1.14.4 → 1.14.7

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 (45) hide show
  1. package/.claude-plugin/marketplace.json +7 -2
  2. package/.claude-plugin/plugin.json +6 -1
  3. package/CHANGELOG.md +106 -0
  4. package/README.md +17 -0
  5. package/agents/design-executor.md +41 -0
  6. package/agents/design-figma-writer.md +61 -1
  7. package/agents/design-verifier.md +2 -2
  8. package/connections/connections.md +2 -0
  9. package/connections/figma.md +10 -0
  10. package/connections/preview.md +9 -5
  11. package/hooks/budget-enforcer.js +2 -2
  12. package/hooks/gdd-bash-guard.js +49 -0
  13. package/hooks/gdd-decision-injector.js +196 -0
  14. package/hooks/gdd-mcp-circuit-breaker.js +140 -0
  15. package/hooks/gdd-protected-paths.js +114 -0
  16. package/hooks/hooks.json +36 -0
  17. package/package.json +1 -1
  18. package/reference/STATE-TEMPLATE.md +10 -1
  19. package/reference/config-schema.md +29 -0
  20. package/reference/cycle-handoff-preamble.md +22 -0
  21. package/reference/figma-sandbox.md +19 -0
  22. package/reference/mcp-budget.default.json +13 -0
  23. package/reference/meta-rules.md +66 -0
  24. package/reference/protected-paths.default.json +18 -0
  25. package/reference/registry.json +34 -0
  26. package/reference/registry.schema.json +52 -0
  27. package/reference/retrieval-contract.md +30 -0
  28. package/reference/schemas/mcp-budget.schema.json +21 -0
  29. package/reference/schemas/protected-paths.schema.json +19 -0
  30. package/reference/shared-preamble.md +6 -57
  31. package/scripts/aggregate-agent-metrics.js +9 -0
  32. package/scripts/build-intel.cjs +20 -0
  33. package/scripts/injection-patterns.cjs +42 -1
  34. package/scripts/lib/blast-radius.cjs +97 -0
  35. package/scripts/lib/dangerous-patterns.cjs +118 -0
  36. package/scripts/lib/glob-match.cjs +57 -0
  37. package/scripts/lib/reference-registry.cjs +101 -0
  38. package/skills/connections/SKILL.md +477 -0
  39. package/skills/new-project/SKILL.md +1 -1
  40. package/skills/pause/SKILL.md +3 -0
  41. package/skills/progress/SKILL.md +12 -0
  42. package/skills/reflect/SKILL.md +2 -0
  43. package/skills/resume/SKILL.md +3 -0
  44. package/skills/scan/SKILL.md +8 -0
  45. package/skills/verify/SKILL.md +4 -3
@@ -0,0 +1,118 @@
1
+ 'use strict';
2
+ /**
3
+ * scripts/lib/dangerous-patterns.cjs — canonical dangerous-shell pattern list
4
+ * + Unicode NFKC + ANSI-strip + zero-width/bidi normalization used by
5
+ * hooks/gdd-bash-guard.js and any downstream audit tooling.
6
+ *
7
+ * Contract: exports
8
+ * - normalize(s): string — NFKC + ANSI strip + zero-width/bidi strip
9
+ * - patterns: Array<{name, regex, description, severity}>
10
+ * - match(command): { matched: boolean, pattern?, description?, severity? }
11
+ *
12
+ * Severity levels: 'critical' (system-destroying), 'high' (data-destroying / credential),
13
+ * 'medium' (destructive but scoped).
14
+ */
15
+
16
+ const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
17
+ const INVISIBLE_RE = /[\u200B-\u200D\u2060\uFEFF\u202A-\u202E]/g;
18
+
19
+ function normalize(s) {
20
+ if (typeof s !== 'string') return '';
21
+ return s.normalize('NFKC').replace(ANSI_RE, '').replace(INVISIBLE_RE, '');
22
+ }
23
+
24
+ // Hex-decode helper so obfuscated `\x72\x6d` attacks land in the same pattern space.
25
+ function hexDecodedVariant(s) {
26
+ return s.replace(/\\x([0-9a-fA-F]{2})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));
27
+ }
28
+
29
+ // Ordered: critical first so the first match is always the worst.
30
+ const patterns = [
31
+ // ── filesystem destruction ────────────────────────────────────────────
32
+ { name: 'rm-rf-root', regex: /\brm\s+-[rRfF]+\s+\/(\s|$)/, description: 'rm -rf / — root filesystem deletion', severity: 'critical' },
33
+ { name: 'rm-rf-no-preserve-root', regex: /\brm\s+-[rRfF]+\s+(--no-preserve-root)/, description: 'rm -rf with --no-preserve-root', severity: 'critical' },
34
+ { name: 'rm-rf-home', regex: /\brm\s+-[rRfF]+\s+(~|\/home(\s|\/|$))/, description: 'rm -rf of home directory', severity: 'critical' },
35
+ { name: 'rm-rf-etc', regex: /\brm\s+-[rRfF]+\s+\/etc(\s|\/|$)/, description: 'rm -rf of /etc', severity: 'critical' },
36
+ { name: 'rm-rf-wildcard-root', regex: /\brm\s+-[rRfF]+\s+\/\*/, description: 'rm -rf /*', severity: 'critical' },
37
+ { name: 'rm-rf-usr', regex: /\brm\s+-[rRfF]+\s+\/usr(\s|\/|$)/, description: 'rm -rf /usr', severity: 'critical' },
38
+ { name: 'rm-rf-var', regex: /\brm\s+-[rRfF]+\s+\/var(\s|\/|$)/, description: 'rm -rf /var', severity: 'critical' },
39
+ { name: 'fork-bomb', regex: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/, description: 'classic :(){ :|:& };: fork bomb', severity: 'critical' },
40
+
41
+ // ── disk / device mutation ───────────────────────────────────────────
42
+ { name: 'dd-zero-to-device', regex: /\bdd\s+if=\/dev\/(zero|random|urandom)\s+of=\/dev\/(sd|hd|nvme)/, description: 'dd overwrite of block device', severity: 'critical' },
43
+ { name: 'mkfs-device', regex: /\bmkfs(\.[a-z0-9]+)?\s+\/dev\//, description: 'mkfs on a block device', severity: 'critical' },
44
+ { name: 'redirect-to-device', regex: />\s*\/dev\/(sd|hd|nvme)[a-z]\d*/, description: 'shell redirect overwrites a block device', severity: 'critical' },
45
+
46
+ // ── permission escalation ────────────────────────────────────────────
47
+ { name: 'chmod-777', regex: /\bchmod\s+(-[A-Za-z]+\s+)?777\b/, description: 'chmod 777 — world-writable permissions', severity: 'high' },
48
+ { name: 'chmod-recursive-world', regex: /\bchmod\s+-R\s+[0-7]*[0-7]7[0-7]\b/, description: 'chmod -R with world-writable bits', severity: 'high' },
49
+ { name: 'chown-root-recursive', regex: /\bchown\s+-R\s+root(:root)?\s+\//, description: 'chown -R root on absolute path', severity: 'high' },
50
+
51
+ // ── pipe to shell ─────────────────────────────────────────────────────
52
+ { name: 'curl-pipe-sh', regex: /\bcurl\s[^|;&\n]*\|\s*(sh|bash|zsh|sudo\s+sh|sudo\s+bash)\b/i, description: 'curl … | sh (remote-code execution)', severity: 'critical' },
53
+ { name: 'wget-pipe-sh', regex: /\bwget\b[^|;&\n]*\|\s*(sh|bash|zsh|sudo\s+sh|sudo\s+bash)\b/i, description: 'wget … | sh (remote-code execution)', severity: 'critical' },
54
+ { name: 'fetch-pipe-sh', regex: /\bfetch\s[^|;&\n]*\|\s*(sh|bash)\b/i, description: 'fetch … | sh', severity: 'critical' },
55
+ { name: 'eval-curl', regex: /\beval\s*"?\s*\$\s*\(\s*(curl|wget)/i, description: 'eval $(curl|wget …) — remote-code execution via eval', severity: 'critical' },
56
+
57
+ // ── git destruction ─────────────────────────────────────────────────
58
+ { name: 'git-reset-hard-HEAD', regex: /\bgit\s+reset\s+--hard\s*(HEAD)?(\s|$)/, description: 'git reset --hard (unscoped) — discards uncommitted work', severity: 'high' },
59
+ { name: 'git-clean-fd', regex: /\bgit\s+clean\s+-[a-z]*[fF][a-z]*[dD][a-z]*\b/, description: 'git clean -fd — untracked file wipe', severity: 'high' },
60
+ { name: 'git-push-force-main', regex: /\bgit\s+push\s+(--force|-f)(\s+\S+)?\s+(main|master|trunk)/i, description: 'git push --force to a protected branch', severity: 'high' },
61
+ { name: 'git-branch-delete-main', regex: /\bgit\s+branch\s+-D\s+(main|master|trunk)\b/i, description: 'git branch -D on the main branch', severity: 'high' },
62
+ { name: 'git-filter-repo', regex: /\bgit\s+filter-(branch|repo)\s+/, description: 'git history rewrite', severity: 'high' },
63
+ { name: 'git-checkout-all', regex: /\bgit\s+checkout\s+\.\s*$/, description: 'git checkout . — overwrites all uncommitted edits', severity: 'medium' },
64
+
65
+ // ── system mutation / config ────────────────────────────────────────
66
+ { name: 'sed-inplace-etc', regex: /\bsed\s+-i\s+[^\n]*\/etc\//, description: 'sed -i on /etc/* config', severity: 'high' },
67
+ { name: 'shutdown-now', regex: /\b(shutdown|halt|poweroff|reboot)\s+(-[a-z]\s+)*(now|0|\+0)\b/, description: 'shutdown/halt/reboot now', severity: 'high' },
68
+ { name: 'init-0-6', regex: /\binit\s+[06]\b/, description: 'init 0/6 — system halt or reboot', severity: 'high' },
69
+
70
+ // ── process nuking ────────────────────────────────────────────────
71
+ { name: 'kill-all-pgrep', regex: /\bkill\s+-9\s+\$\(\s*pgrep/, description: 'kill -9 $(pgrep …) — broad process kill', severity: 'medium' },
72
+ { name: 'killall-9', regex: /\bkillall\s+-9\b/, description: 'killall -9', severity: 'medium' },
73
+ { name: 'pkill-9-dotall', regex: /\bpkill\s+-9\s+-f\s+['"]?\.\*['"]?/, description: 'pkill -9 -f .* — process kill everything', severity: 'high' },
74
+
75
+ // ── credential exfil ────────────────────────────────────────────────
76
+ { name: 'env-to-curl', regex: /\b(cat\s+)?\.env(\.[a-z]+)?\s*\|\s*curl/i, description: 'exfil .env via curl', severity: 'critical' },
77
+ { name: 'ssh-key-to-curl', regex: /\bcat\s+~?\/?\.ssh\/id_(rsa|ed25519|ecdsa|dsa)\b[^\n]*\|\s*(curl|nc|ssh)/, description: 'ssh private-key exfiltration', severity: 'critical' },
78
+ { name: 'printenv-to-curl', regex: /\bprintenv\b[^\n]*\|\s*(curl|wget|nc)/, description: 'printenv | curl — environment exfiltration', severity: 'critical' },
79
+ { name: 'tar-home-to-netcat', regex: /\btar\s+c[fzvj]+\s+-\s+~[^\n]*\|\s*(nc|ssh|curl)/, description: 'tar ~ | nc|ssh — home-directory exfil', severity: 'critical' },
80
+ { name: 'aws-credentials-read', regex: /\bcat\s+~\/\.aws\/credentials\b/, description: 'reading AWS credentials file', severity: 'high' },
81
+
82
+ // ── shell mutation / obfuscation ────────────────────────────────────
83
+ { name: 'bash-decode-base64', regex: /\becho\s+[A-Za-z0-9+\/=]{40,}\s*\|\s*base64\s+-d\s*\|\s*(sh|bash)/, description: 'base64 decode | shell — obfuscated exec', severity: 'critical' },
84
+ { name: 'exec-python-c', regex: /\bpython[23]?\s+-c\s+["']import\s+os[^"']*os\.(system|popen|exec)\b/i, description: 'python -c inline os.system shell-out', severity: 'high' },
85
+ { name: 'bash-c-remote', regex: /\bbash\s+-c\s+["'][^"']*(curl|wget)\s+[^"']*\|/, description: 'bash -c with embedded curl|wget pipe', severity: 'critical' },
86
+
87
+ // ── path traversal ────────────────────────────────────────────────
88
+ { name: 'path-traversal-deep', regex: /(\.\.\/){5,}/, description: '5+ chained ../../../../../ traversal', severity: 'medium' },
89
+
90
+ // ── npm / package registry abuse ────────────────────────────────────
91
+ { name: 'npm-install-remote-tgz', regex: /\bnpm\s+(install|i)\s+https?:\/\/[^\s]+\.tgz/, description: 'npm install from an arbitrary HTTP tarball URL', severity: 'high' },
92
+ { name: 'npm-publish-force', regex: /\bnpm\s+publish\s+(--force|-f)\b/, description: 'npm publish --force (bypasses version checks)', severity: 'medium' },
93
+ { name: 'npm-run-in-quotes-eval', regex: /\bnpm\s+run\s+\S+\s+--\s+--?eval=/, description: 'npm run … --eval= (code injection through script runner)', severity: 'medium' },
94
+
95
+ // ── docker / container escape ───────────────────────────────────────
96
+ { name: 'docker-run-privileged', regex: /\bdocker\s+run\b[^\n]*--privileged\b[^\n]*(--|[^\n]*(-v|--volume)\s+\/:\/host|\/var\/run\/docker\.sock)/, description: 'docker run --privileged mounting host fs / docker socket', severity: 'critical' },
97
+ { name: 'docker-socket-mount', regex: /-v\s+\/var\/run\/docker\.sock:\/var\/run\/docker\.sock/, description: 'host docker socket mount (escape vector)', severity: 'high' },
98
+
99
+ // ── firewall / networking flip ──────────────────────────────────────
100
+ { name: 'iptables-flush', regex: /\biptables\s+-F(\s|$)/, description: 'iptables -F — flush all firewall rules', severity: 'high' },
101
+ { name: 'ufw-disable', regex: /\bufw\s+disable\b/, description: 'ufw disable — firewall off', severity: 'high' },
102
+
103
+ // ── sudo bypass ────────────────────────────────────────────────────
104
+ { name: 'sudo-nopasswd-write', regex: /\becho\s+[^\n]*NOPASSWD[^\n]*\|\s*sudo\s+tee\s+\/etc\/sudoers/, description: 'sudo NOPASSWD injection via tee', severity: 'critical' },
105
+ ];
106
+
107
+ function match(command) {
108
+ const normalized = normalize(command);
109
+ const hexVariant = hexDecodedVariant(normalized);
110
+ for (const p of patterns) {
111
+ if (p.regex.test(normalized) || p.regex.test(hexVariant)) {
112
+ return { matched: true, pattern: p.name, description: p.description, severity: p.severity };
113
+ }
114
+ }
115
+ return { matched: false };
116
+ }
117
+
118
+ module.exports = { normalize, patterns, match, _INVISIBLE_RE: INVISIBLE_RE, _ANSI_RE: ANSI_RE };
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+ /**
3
+ * scripts/lib/glob-match.cjs — tiny dependency-free glob matcher.
4
+ * Supports: **, *, ?, and literal segments. Not a full minimatch implementation,
5
+ * but covers the patterns used in reference/protected-paths.default.json.
6
+ */
7
+
8
+ function globToRegex(glob) {
9
+ // Normalize separators
10
+ const g = glob.replace(/\\/g, '/');
11
+ let re = '^';
12
+ let i = 0;
13
+ while (i < g.length) {
14
+ const c = g[i];
15
+ if (c === '*' && g[i + 1] === '*') {
16
+ // `**` — match zero or more of ANY characters (including path separators).
17
+ // Consume a trailing `/` so `reference/**/foo` becomes `reference/.*foo`
18
+ // and also matches `reference/foo` (the empty-match case).
19
+ let j = i + 2;
20
+ if (g[j] === '/') j++;
21
+ re += '.*';
22
+ i = j;
23
+ continue;
24
+ }
25
+ if (c === '*') {
26
+ // single-segment wildcard
27
+ re += '[^/]*';
28
+ i++;
29
+ continue;
30
+ }
31
+ if (c === '?') {
32
+ re += '[^/]';
33
+ i++;
34
+ continue;
35
+ }
36
+ if ('.+^$(){}[]|\\'.includes(c)) {
37
+ re += '\\' + c;
38
+ i++;
39
+ continue;
40
+ }
41
+ re += c;
42
+ i++;
43
+ }
44
+ re += '$';
45
+ return new RegExp(re);
46
+ }
47
+
48
+ function matches(filepath, globList) {
49
+ const norm = String(filepath).replace(/\\/g, '/').replace(/^\.\//, '');
50
+ for (const g of globList) {
51
+ const re = globToRegex(g);
52
+ if (re.test(norm)) return { matched: true, pattern: g };
53
+ }
54
+ return { matched: false };
55
+ }
56
+
57
+ module.exports = { matches, globToRegex };
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+ /**
3
+ * scripts/lib/reference-registry.cjs — typed index over reference/*.md.
4
+ *
5
+ * Exports:
6
+ * list({type}) → Entry[] (filter by type; omit to return all)
7
+ * find(name) → Entry | null (exact-name lookup)
8
+ * validateRegistry({cwd}) → { ok, missingInRegistry, danglingInRegistry, duplicates }
9
+ * loadRegistry({cwd}) → Registry (read-through; caches at module scope)
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
16
+ const DEFAULT_REGISTRY_PATH = path.join(REPO_ROOT, 'reference', 'registry.json');
17
+
18
+ let _cache = null;
19
+ let _cachePath = null;
20
+
21
+ function loadRegistry({ cwd } = {}) {
22
+ const p = cwd ? path.join(cwd, 'reference', 'registry.json') : DEFAULT_REGISTRY_PATH;
23
+ if (_cache && _cachePath === p) return _cache;
24
+ _cachePath = p;
25
+ _cache = JSON.parse(fs.readFileSync(p, 'utf8'));
26
+ return _cache;
27
+ }
28
+
29
+ function list({ type, cwd } = {}) {
30
+ const reg = loadRegistry({ cwd });
31
+ if (!type) return reg.entries.slice();
32
+ return reg.entries.filter(e => e.type === type);
33
+ }
34
+
35
+ function find(name, { cwd } = {}) {
36
+ const reg = loadRegistry({ cwd });
37
+ return reg.entries.find(e => e.name === name) || null;
38
+ }
39
+
40
+ /**
41
+ * Walk reference/*.md + reference/*.json (excluding schemas/**, data/**) and
42
+ * compare to the registry. Returns:
43
+ * - missingInRegistry : files on disk not referenced by any entry
44
+ * - danglingInRegistry: entries whose paths do not exist on disk
45
+ * - duplicates : entries sharing the same `name` OR the same `path`
46
+ */
47
+ function validateRegistry({ cwd } = {}) {
48
+ const root = cwd || REPO_ROOT;
49
+ const refDir = path.join(root, 'reference');
50
+ const reg = (() => {
51
+ try { return JSON.parse(fs.readFileSync(path.join(refDir, 'registry.json'), 'utf8')); }
52
+ catch { return { entries: [] }; }
53
+ })();
54
+
55
+ const onDisk = new Set();
56
+ for (const leaf of walk(refDir)) {
57
+ const rel = path.relative(root, leaf).replace(/\\/g, '/');
58
+ // Exclude registry itself, all .schema.json files (non-registerable),
59
+ // schemas tree, data tree, and non-md/non-json files.
60
+ if (rel === 'reference/registry.json') continue;
61
+ if (rel.endsWith('.schema.json')) continue;
62
+ if (rel.startsWith('reference/schemas/')) continue;
63
+ if (rel.startsWith('reference/data/')) continue;
64
+ if (!/\.(md|json)$/.test(rel)) continue;
65
+ onDisk.add(rel);
66
+ }
67
+
68
+ const registryPaths = new Set(reg.entries.map(e => e.path));
69
+ const missingInRegistry = [...onDisk].filter(p => !registryPaths.has(p)).sort();
70
+ const danglingInRegistry = reg.entries
71
+ .filter(e => !fs.existsSync(path.join(root, e.path)))
72
+ .map(e => ({ name: e.name, path: e.path }));
73
+
74
+ const nameCount = {}, pathCount = {};
75
+ for (const e of reg.entries) {
76
+ nameCount[e.name] = (nameCount[e.name] || 0) + 1;
77
+ pathCount[e.path] = (pathCount[e.path] || 0) + 1;
78
+ }
79
+ const duplicates = [];
80
+ for (const [k, v] of Object.entries(nameCount)) if (v > 1) duplicates.push({ kind: 'name', key: k, count: v });
81
+ for (const [k, v] of Object.entries(pathCount)) if (v > 1) duplicates.push({ kind: 'path', key: k, count: v });
82
+
83
+ return {
84
+ ok: missingInRegistry.length === 0 && danglingInRegistry.length === 0 && duplicates.length === 0,
85
+ missingInRegistry,
86
+ danglingInRegistry,
87
+ duplicates,
88
+ };
89
+ }
90
+
91
+ function* walk(dir) {
92
+ let entries;
93
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
94
+ for (const e of entries) {
95
+ const full = path.join(dir, e.name);
96
+ if (e.isDirectory()) yield* walk(full);
97
+ else if (e.isFile()) yield full;
98
+ }
99
+ }
100
+
101
+ module.exports = { list, find, validateRegistry, loadRegistry };