@hegemonart/get-design-done 1.0.7 → 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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +129 -2
- package/README.md +186 -53
- package/SKILL.md +6 -4
- package/agents/design-authority-watcher.md +208 -0
- package/agents/design-component-generator.md +221 -0
- package/agents/design-context-builder.md +83 -6
- package/agents/design-discussant.md +1 -1
- package/agents/design-figma-writer.md +8 -8
- package/agents/design-paper-writer.md +131 -0
- package/agents/design-pencil-writer.md +99 -0
- package/agents/design-research-synthesizer.md +13 -1
- package/agents/design-update-checker.md +117 -0
- package/agents/design-verifier.md +51 -0
- package/agents/token-mapper.md +1 -1
- package/connections/21st-dev.md +98 -0
- package/connections/claude-design.md +0 -1
- package/connections/connections.md +50 -33
- package/connections/figma.md +81 -39
- package/connections/magic-patterns.md +105 -0
- package/connections/paper-design.md +137 -0
- package/connections/pencil-dev.md +88 -0
- package/connections/pinterest.md +0 -1
- package/hooks/budget-enforcer.js +13 -2
- package/hooks/gdd-read-injection-scanner.js +4 -9
- package/hooks/hooks.json +8 -0
- package/hooks/update-check.sh +251 -0
- package/package.json +1 -1
- package/reference/ai-native-tool-interface.md +102 -0
- package/reference/authority-feeds.md +72 -0
- package/reference/schemas/authority-snapshot.schema.json +42 -0
- package/reference/schemas/config.schema.json +4 -0
- package/reference/schemas/marketplace.schema.json +2 -2
- package/reference/schemas/plugin.schema.json +1 -1
- package/scripts/aggregate-agent-metrics.js +20 -0
- package/scripts/build-intel.cjs +13 -5
- package/scripts/injection-patterns.cjs +17 -0
- package/scripts/run-injection-scanner-ci.cjs +1 -10
- package/scripts/tests/test-authority-rejected-kinds.sh +58 -0
- package/scripts/tests/test-authority-watcher-diff.sh +113 -0
- package/scripts/validate-schemas.cjs +18 -1
- package/skills/audit/SKILL.md +11 -1
- package/skills/check-update/SKILL.md +135 -0
- package/skills/complete-cycle/SKILL.md +10 -0
- package/skills/design/SKILL.md +5 -5
- package/skills/discover/SKILL.md +2 -2
- package/skills/explore/SKILL.md +55 -3
- package/skills/health/SKILL.md +10 -0
- package/skills/help/SKILL.md +10 -0
- package/skills/progress/SKILL.md +10 -0
- package/skills/reflect/SKILL.md +1 -0
- package/skills/scan/SKILL.md +9 -19
- package/skills/ship/SKILL.md +10 -0
- package/skills/watch-authorities/SKILL.md +82 -0
- package/connections/figma-writer.md +0 -139
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# pencil.dev — Connection Specification
|
|
2
|
+
|
|
3
|
+
This file is the connection specification for pencil.dev within the get-design-done pipeline. pencil.dev uses git-tracked `.pen` files as its source of truth — no MCP server is required. See `connections/connections.md` for the full connection index and capability matrix.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
### Prerequisites
|
|
10
|
+
- pencil.dev VS Code or Cursor extension installed from the marketplace
|
|
11
|
+
- One or more `.pen` files in the project (typically at project root or in `src/`)
|
|
12
|
+
|
|
13
|
+
### Verification
|
|
14
|
+
|
|
15
|
+
Run in the project directory:
|
|
16
|
+
```bash
|
|
17
|
+
find . -name "*.pen" -not -path "*/node_modules/*" | head -5
|
|
18
|
+
```
|
|
19
|
+
One or more results = pencil.dev is in use.
|
|
20
|
+
|
|
21
|
+
No MCP server install needed — the pipeline reads and writes `.pen` files directly via standard file tools.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Probe Pattern
|
|
26
|
+
|
|
27
|
+
pencil.dev does not use MCP tools. Probe is file-based.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
PEN_FILES=$(find . -name "*.pen" -not -path "*/node_modules/*" 2>/dev/null)
|
|
31
|
+
if [ -n "$PEN_FILES" ]; then
|
|
32
|
+
echo "pencil-dev: available"
|
|
33
|
+
else
|
|
34
|
+
echo "pencil-dev: not_configured"
|
|
35
|
+
fi
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Write result to STATE.md `<connections>`: `pencil-dev: available` or `pencil-dev: not_configured`.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## .pen File Format
|
|
43
|
+
|
|
44
|
+
`.pen` files are YAML-front-matter component specs:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
---
|
|
48
|
+
component: Button
|
|
49
|
+
variant: primary
|
|
50
|
+
state: default
|
|
51
|
+
design-tokens:
|
|
52
|
+
bg: brand-primary-500
|
|
53
|
+
text: white
|
|
54
|
+
radius: 6px
|
|
55
|
+
padding: "8px 16px"
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
Notes: Primary CTA button. Use for the main action in any modal or form.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `component` — component name (must match the implementation filename)
|
|
62
|
+
- `variant` — variant label (matches Storybook story name where applicable)
|
|
63
|
+
- `state` — `default | hover | focus | disabled | error`
|
|
64
|
+
- `design-tokens` — key/value map of token names to values (CSS variables or literal values)
|
|
65
|
+
- Body prose — optional implementation notes
|
|
66
|
+
|
|
67
|
+
`.pen` files are **git-tracked source of truth**. Commits to `.pen` files must be atomic — spec and implementation changes committed together when possible.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Pipeline Integration
|
|
72
|
+
|
|
73
|
+
| Stage | What pencil.dev provides |
|
|
74
|
+
|-------|--------------------------|
|
|
75
|
+
| explore | `.pen` file discovery; synthesizer merges `.pen` declarations with code grep results |
|
|
76
|
+
| verify | Spec-vs-implementation diff: `.pen` declared token values vs. actual token values in code |
|
|
77
|
+
| design | `design-pencil-writer` agent: annotate + roundtrip modes; atomic git commits on `.pen` writes |
|
|
78
|
+
|
|
79
|
+
**Architectural advantage:** Both the design spec (`.pen` file) and implementation (source code) are version-controlled. Pre-merge diff verification is uniquely strong compared to tools where the design lives outside git.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Fallback Behavior
|
|
84
|
+
|
|
85
|
+
When `pencil-dev: not_configured`:
|
|
86
|
+
- All pencil.dev steps are skipped silently.
|
|
87
|
+
- A one-line diagnostic is printed: `pencil.dev not configured — no .pen files found.`
|
|
88
|
+
- Pipeline continues normally.
|
package/connections/pinterest.md
CHANGED
package/hooks/budget-enforcer.js
CHANGED
|
@@ -32,6 +32,7 @@ const { spawn } = require('child_process');
|
|
|
32
32
|
const BUDGET_PATH = path.join(process.cwd(), '.design', 'budget.json');
|
|
33
33
|
const MANIFEST_PATH = path.join(process.cwd(), '.design', 'cache-manifest.json');
|
|
34
34
|
const TELEMETRY_PATH = path.join(process.cwd(), '.design', 'telemetry', 'costs.jsonl');
|
|
35
|
+
const PHASE_TOTALS_PATH = path.join(process.cwd(), '.design', 'telemetry', 'phase-totals.json');
|
|
35
36
|
const STATE_PATH = path.join(process.cwd(), '.design', 'STATE.md');
|
|
36
37
|
|
|
37
38
|
// ---- budget.json loader with defaults per D-12 ----
|
|
@@ -49,8 +50,18 @@ function loadBudget() {
|
|
|
49
50
|
catch { return defaults; }
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
// ---- cumulative phase spend
|
|
53
|
+
// ---- cumulative phase spend (WR-02) ----
|
|
54
|
+
// Reads from the lightweight phase-totals.json written by aggregate-agent-metrics.js
|
|
55
|
+
// instead of replaying the full costs.jsonl on every hook invocation.
|
|
56
|
+
// Falls back to 0 when the file doesn't exist yet (early in a session).
|
|
53
57
|
function currentPhaseSpend(phase) {
|
|
58
|
+
if (fs.existsSync(PHASE_TOTALS_PATH)) {
|
|
59
|
+
try {
|
|
60
|
+
const data = JSON.parse(fs.readFileSync(PHASE_TOTALS_PATH, 'utf8'));
|
|
61
|
+
return Number(data.totals?.[phase] || 0);
|
|
62
|
+
} catch { /* fall through */ }
|
|
63
|
+
}
|
|
64
|
+
// Fallback: replay JSONL when phase-totals.json not yet written (first spawn of session).
|
|
54
65
|
if (!fs.existsSync(TELEMETRY_PATH)) return 0;
|
|
55
66
|
const lines = fs.readFileSync(TELEMETRY_PATH, 'utf8').split(/\r?\n/).filter(Boolean);
|
|
56
67
|
let sum = 0;
|
|
@@ -118,7 +129,7 @@ function spawnAggregator() {
|
|
|
118
129
|
cwd: process.cwd(),
|
|
119
130
|
detached: true,
|
|
120
131
|
stdio: 'ignore',
|
|
121
|
-
env: process.env,
|
|
132
|
+
env: { PATH: process.env.PATH }, // IN-02: minimal env; aggregator needs no secrets
|
|
122
133
|
});
|
|
123
134
|
child.unref();
|
|
124
135
|
} catch {
|
|
@@ -6,16 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const readline = require('readline');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { INJECTION_PATTERNS: RAW_PATTERNS } = require(path.join(__dirname, '..', 'scripts', 'injection-patterns.cjs'));
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/disregard\s+(all\s+)?(previous|prior|above)\s+instructions?/i,
|
|
13
|
-
/you\s+are\s+now\s+a\s+different/i,
|
|
14
|
-
/system\s*:\s*you\s+are/i,
|
|
15
|
-
/<\s*\/?\s*(system|assistant|human)\s*>/i,
|
|
16
|
-
/\[INST\]/i,
|
|
17
|
-
/###\s*instruction/i,
|
|
18
|
-
];
|
|
12
|
+
// The hook needs bare RegExp objects; extract them from the shared {name,re} entries.
|
|
13
|
+
const INJECTION_PATTERNS = RAW_PATTERNS.map(p => p.re);
|
|
19
14
|
|
|
20
15
|
async function main() {
|
|
21
16
|
const rl = readline.createInterface({ input: process.stdin });
|
package/hooks/hooks.json
CHANGED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# get-design-done — update check (Phase 13.3)
|
|
3
|
+
# SessionStart hook. Silent-on-failure by policy (D-04): exits 0 on every error path.
|
|
4
|
+
# 24h-cached unauthenticated GET of /releases/latest. Renders .design/update-available.md
|
|
5
|
+
# only when a newer version exists AND it is not dismissed AND stage-guard allows.
|
|
6
|
+
|
|
7
|
+
set -u # intentionally no -e: we want to fall through to exit 0
|
|
8
|
+
|
|
9
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
10
|
+
PLUGIN_ROOT="${PLUGIN_ROOT//\\//}" # Windows → POSIX slashes
|
|
11
|
+
|
|
12
|
+
DESIGN_DIR="$(pwd)/.design"
|
|
13
|
+
CACHE="${DESIGN_DIR}/update-cache.json"
|
|
14
|
+
BANNER="${DESIGN_DIR}/update-available.md"
|
|
15
|
+
CONFIG="${DESIGN_DIR}/config.json"
|
|
16
|
+
STATE="${DESIGN_DIR}/STATE.md"
|
|
17
|
+
CACHE_TTL_SECONDS=86400 # 24h
|
|
18
|
+
|
|
19
|
+
# Silent logger — writes nothing by default. Set GDD_UPDATE_DEBUG=1 to enable stderr.
|
|
20
|
+
log() {
|
|
21
|
+
if [ "${GDD_UPDATE_DEBUG:-0}" = "1" ]; then
|
|
22
|
+
printf '[gdd update-check] %s\n' "$*" >&2
|
|
23
|
+
fi
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Ensure .design/ exists (bootstrap normally creates it; belt+suspenders).
|
|
27
|
+
mkdir -p "${DESIGN_DIR}" 2>/dev/null || exit 0
|
|
28
|
+
|
|
29
|
+
# ---- Read current plugin version (no jq) ----
|
|
30
|
+
PLUGIN_JSON="${PLUGIN_ROOT}/.claude-plugin/plugin.json"
|
|
31
|
+
|
|
32
|
+
read_current_tag() {
|
|
33
|
+
[ -f "${PLUGIN_JSON}" ] || return 1
|
|
34
|
+
grep -E '^[[:space:]]*"version"[[:space:]]*:' "${PLUGIN_JSON}" | head -n1 | \
|
|
35
|
+
sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# ---- Semver normalizer: "v1.0.7" -> "1 0 7 0"; "v1.0.7.3" -> "1 0 7 3" ----
|
|
39
|
+
normalize_semver() {
|
|
40
|
+
local t="${1#v}"
|
|
41
|
+
# strip any -pre/-beta suffix after first hyphen (unauth'd API rarely surfaces these, best-effort)
|
|
42
|
+
t="${t%%-*}"
|
|
43
|
+
# Replace dots with spaces; pad to 4 segments
|
|
44
|
+
# shellcheck disable=SC2086
|
|
45
|
+
set -- $(printf '%s' "${t}" | tr '.' ' ')
|
|
46
|
+
local a="${1:-0}" b="${2:-0}" c="${3:-0}" d="${4:-0}"
|
|
47
|
+
# Sanitize to digits only (POSIX: tr -cd 0-9 — BSD+GNU safe)
|
|
48
|
+
a="$(printf '%s' "$a" | tr -cd '0-9')"; a="${a:-0}"
|
|
49
|
+
b="$(printf '%s' "$b" | tr -cd '0-9')"; b="${b:-0}"
|
|
50
|
+
c="$(printf '%s' "$c" | tr -cd '0-9')"; c="${c:-0}"
|
|
51
|
+
d="$(printf '%s' "$d" | tr -cd '0-9')"; d="${d:-0}"
|
|
52
|
+
printf '%s %s %s %s' "$a" "$b" "$c" "$d"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# ---- Classify delta: compare 4-segment tuples ----
|
|
56
|
+
# Args: current_tag latest_tag
|
|
57
|
+
# Prints: "newer|same|older|invalid" + "major|minor|patch|off-cadence|none"
|
|
58
|
+
classify_delta() {
|
|
59
|
+
local cur lat
|
|
60
|
+
cur="$(normalize_semver "$1")" || { printf 'invalid none'; return; }
|
|
61
|
+
lat="$(normalize_semver "$2")" || { printf 'invalid none'; return; }
|
|
62
|
+
# shellcheck disable=SC2086
|
|
63
|
+
set -- $cur; local ca="$1" cb="$2" cc="$3" cd="$4"
|
|
64
|
+
# shellcheck disable=SC2086
|
|
65
|
+
set -- $lat; local la="$1" lb="$2" lc="$3" ld="$4"
|
|
66
|
+
|
|
67
|
+
# Per-segment integer compare (lexicographic per segment by numeric value)
|
|
68
|
+
if [ "$la" -gt "$ca" ]; then printf 'newer major'; return
|
|
69
|
+
elif [ "$la" -lt "$ca" ]; then printf 'older major'; return
|
|
70
|
+
fi
|
|
71
|
+
if [ "$lb" -gt "$cb" ]; then printf 'newer minor'; return
|
|
72
|
+
elif [ "$lb" -lt "$cb" ]; then printf 'older minor'; return
|
|
73
|
+
fi
|
|
74
|
+
if [ "$lc" -gt "$cc" ]; then printf 'newer patch'; return
|
|
75
|
+
elif [ "$lc" -lt "$cc" ]; then printf 'older patch'; return
|
|
76
|
+
fi
|
|
77
|
+
if [ "$ld" -gt "$cd" ]; then printf 'newer off-cadence'; return
|
|
78
|
+
elif [ "$ld" -lt "$cd" ]; then printf 'older off-cadence'; return
|
|
79
|
+
fi
|
|
80
|
+
printf 'same none'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# ---- Cache freshness check: returns 0 if fresh (<24h old), 1 if stale or missing ----
|
|
84
|
+
is_cache_fresh() {
|
|
85
|
+
[ -f "${CACHE}" ] || return 1
|
|
86
|
+
local now mtime age
|
|
87
|
+
now="$(date +%s)"
|
|
88
|
+
# BSD date -r on macOS; GNU stat -c on Linux; fall back to perl then python.
|
|
89
|
+
if mtime="$(date -r "${CACHE}" +%s 2>/dev/null)"; then :
|
|
90
|
+
elif mtime="$(stat -c %Y "${CACHE}" 2>/dev/null)"; then :
|
|
91
|
+
elif mtime="$(perl -e 'print((stat shift)[9])' "${CACHE}" 2>/dev/null)"; then :
|
|
92
|
+
else return 1
|
|
93
|
+
fi
|
|
94
|
+
[ -n "${mtime:-}" ] || return 1
|
|
95
|
+
age=$((now - mtime))
|
|
96
|
+
[ "${age}" -lt "${CACHE_TTL_SECONDS}" ]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# ---- Fetch latest release. Writes raw body to stdout on success, nothing on failure. ----
|
|
100
|
+
fetch_latest() {
|
|
101
|
+
command -v curl >/dev/null 2>&1 || { log "no curl"; return 1; }
|
|
102
|
+
local url="https://api.github.com/repos/hegemonart/get-design-done/releases/latest"
|
|
103
|
+
curl -sf --max-time 3 -H 'Accept: application/vnd.github+json' "${url}" 2>/dev/null || return 1
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# ---- Extract fields from the release JSON (no jq). Robust to whitespace; fails soft. ----
|
|
107
|
+
extract_tag() {
|
|
108
|
+
grep -E '"tag_name"[[:space:]]*:' | head -n1 | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/'
|
|
109
|
+
}
|
|
110
|
+
# Body extraction: python3-only. If python3 is absent, we intentionally return empty
|
|
111
|
+
# (D-04 silent-on-failure posture). No awk/sed fallback — JSON string decoding in pure
|
|
112
|
+
# bash is fragile and untested; empty excerpt is the correct degraded state.
|
|
113
|
+
extract_body() {
|
|
114
|
+
command -v python3 >/dev/null 2>&1 || return 0
|
|
115
|
+
python3 -c 'import json,sys
|
|
116
|
+
try:
|
|
117
|
+
d=json.load(sys.stdin)
|
|
118
|
+
b=d.get("body","") or ""
|
|
119
|
+
print(b[:500])
|
|
120
|
+
except Exception:
|
|
121
|
+
pass' 2>/dev/null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# ---- Read .design/STATE.md stage field. Returns "brief"|"explore"|"plan"|"design"|"verify"|"" ----
|
|
125
|
+
# Schema source: reference/STATE-TEMPLATE.md — `stage:` lives in both the frontmatter
|
|
126
|
+
# and the <position> block with identical values per the write contract. We take the
|
|
127
|
+
# first occurrence (head -n1), which is the frontmatter line.
|
|
128
|
+
read_state_stage() {
|
|
129
|
+
[ -f "${STATE}" ] || { printf ''; return; }
|
|
130
|
+
grep -E '^stage:' "${STATE}" 2>/dev/null | head -n1 | sed -E 's/^stage:[[:space:]]*"?([^"[:space:]]+)"?.*/\1/'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# ---- Read .design/config.json for update_dismissed. Returns tag or empty. ----
|
|
134
|
+
read_dismissed() {
|
|
135
|
+
[ -f "${CONFIG}" ] || { printf ''; return; }
|
|
136
|
+
grep -E '"update_dismissed"[[:space:]]*:' "${CONFIG}" 2>/dev/null | head -n1 | \
|
|
137
|
+
sed -E 's/.*"update_dismissed"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/'
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# ---- Main control flow ----
|
|
141
|
+
# MANDATORY sourcing guard: wrap the entire main flow so that `source update-check.sh`
|
|
142
|
+
# (used by unit tests and interactive debugging) loads the function definitions without
|
|
143
|
+
# executing steps 1-6 and exiting the sourcing shell. This is non-negotiable — the
|
|
144
|
+
# semver self-test acceptance criterion sources this script.
|
|
145
|
+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
146
|
+
|
|
147
|
+
CURRENT_TAG="$(read_current_tag)" || { log "no plugin.json"; exit 0; }
|
|
148
|
+
[ -n "${CURRENT_TAG:-}" ] || { log "no current version parsed"; exit 0; }
|
|
149
|
+
# Normalize to "vX.Y.Z" shape for display (plugin.json stores bare "1.0.7")
|
|
150
|
+
DISPLAY_CURRENT="v${CURRENT_TAG#v}"
|
|
151
|
+
|
|
152
|
+
# Optional --refresh forces a fresh fetch (called by plan 13.3-04's /gdd:check-update --refresh).
|
|
153
|
+
FORCE_REFRESH=0
|
|
154
|
+
for arg in "$@"; do
|
|
155
|
+
case "$arg" in
|
|
156
|
+
--refresh) FORCE_REFRESH=1 ;;
|
|
157
|
+
esac
|
|
158
|
+
done
|
|
159
|
+
|
|
160
|
+
# 1. Populate cache if missing/stale or forced.
|
|
161
|
+
if [ "${FORCE_REFRESH}" -eq 1 ] || ! is_cache_fresh; then
|
|
162
|
+
RAW="$(fetch_latest)" || RAW=""
|
|
163
|
+
if [ -n "${RAW}" ]; then
|
|
164
|
+
LATEST_TAG="$(printf '%s' "${RAW}" | extract_tag)"
|
|
165
|
+
BODY_EXCERPT="$(printf '%s' "${RAW}" | extract_body)"
|
|
166
|
+
# Strip control chars defensively (T-13.3-03)
|
|
167
|
+
BODY_EXCERPT="$(printf '%s' "${BODY_EXCERPT}" | tr -d '\000-\010\013\014\016-\037')"
|
|
168
|
+
# Strip double-quotes so the JSON round-trip sed read-back cannot be injected via a
|
|
169
|
+
# crafted release body. Body is display-only — losing quotes is acceptable.
|
|
170
|
+
BODY_EXCERPT="$(printf '%s' "${BODY_EXCERPT}" | tr -d '"')"
|
|
171
|
+
# Validate LATEST_TAG is a safe semver string before trusting it (CR-02).
|
|
172
|
+
if ! printf '%s' "${LATEST_TAG}" | grep -qE '^v?[0-9]+\.[0-9]+(\.[0-9]+)*$'; then
|
|
173
|
+
log "LATEST_TAG '${LATEST_TAG}' failed semver safety check — aborting cache write"
|
|
174
|
+
LATEST_TAG=""
|
|
175
|
+
fi
|
|
176
|
+
if [ -n "${LATEST_TAG}" ]; then
|
|
177
|
+
read -r DELTA_STATE DELTA_KIND <<EOF
|
|
178
|
+
$(classify_delta "${DISPLAY_CURRENT}" "${LATEST_TAG}")
|
|
179
|
+
EOF
|
|
180
|
+
IS_NEWER=false
|
|
181
|
+
[ "${DELTA_STATE}" = "newer" ] && IS_NEWER=true
|
|
182
|
+
CHECKED_AT="$(date +%s)"
|
|
183
|
+
# Write cache atomically (write-to-tmp + rename) — T-13.3-04 mitigation
|
|
184
|
+
TMP="${CACHE}.tmp.$$"
|
|
185
|
+
{
|
|
186
|
+
printf '{\n'
|
|
187
|
+
printf ' "checked_at": %s,\n' "${CHECKED_AT}"
|
|
188
|
+
printf ' "current_tag": "%s",\n' "${DISPLAY_CURRENT}"
|
|
189
|
+
printf ' "latest_tag": "%s",\n' "${LATEST_TAG}"
|
|
190
|
+
printf ' "delta": "%s",\n' "${DELTA_KIND}"
|
|
191
|
+
printf ' "is_newer": %s,\n' "${IS_NEWER}"
|
|
192
|
+
# Escape the body for JSON — backslashes first, then quotes, then newlines.
|
|
193
|
+
ESC="$(printf '%s' "${BODY_EXCERPT}" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' | awk '{printf "%s\\n", $0}')"
|
|
194
|
+
printf ' "changelog_excerpt": "%s"\n' "${ESC}"
|
|
195
|
+
printf '}\n'
|
|
196
|
+
} > "${TMP}" 2>/dev/null && mv "${TMP}" "${CACHE}" 2>/dev/null || rm -f "${TMP}" 2>/dev/null
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
# 2. Read cache (whether freshly written or still valid).
|
|
202
|
+
[ -f "${CACHE}" ] || exit 0 # no cache, nothing to do — silent exit
|
|
203
|
+
|
|
204
|
+
C_LATEST="$(grep -E '"latest_tag"' "${CACHE}" 2>/dev/null | head -n1 | sed -E 's/.*"latest_tag"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')"
|
|
205
|
+
C_DELTA="$(grep -E '"delta"' "${CACHE}" 2>/dev/null | head -n1 | sed -E 's/.*"delta"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')"
|
|
206
|
+
# Allowlist-gate C_DELTA before it reaches any shell context (WR-04).
|
|
207
|
+
case "${C_DELTA:-}" in
|
|
208
|
+
major|minor|patch|off-cadence|none) : ;;
|
|
209
|
+
*) C_DELTA="unknown" ;;
|
|
210
|
+
esac
|
|
211
|
+
C_NEWER="$(grep -E '"is_newer"' "${CACHE}" 2>/dev/null | head -n1 | sed -E 's/.*"is_newer"[[:space:]]*:[[:space:]]*(true|false).*/\1/')"
|
|
212
|
+
C_BODY="$(grep -E '"changelog_excerpt"' "${CACHE}" 2>/dev/null | head -n1 | sed -E 's/.*"changelog_excerpt"[[:space:]]*:[[:space:]]*"(.*)".*/\1/' | sed -E 's/\\n/\n/g')"
|
|
213
|
+
|
|
214
|
+
# 3. Gate: if cache says not newer, remove any stale banner and exit.
|
|
215
|
+
if [ "${C_NEWER:-false}" != "true" ]; then
|
|
216
|
+
rm -f "${BANNER}" 2>/dev/null
|
|
217
|
+
exit 0
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
# 4. Dismissal gate (D-13): if user already dismissed this exact tag, suppress.
|
|
221
|
+
DISMISSED="$(read_dismissed)"
|
|
222
|
+
if [ -n "${DISMISSED}" ] && [ "${DISMISSED}" = "${C_LATEST}" ]; then
|
|
223
|
+
rm -f "${BANNER}" 2>/dev/null
|
|
224
|
+
exit 0
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
# 5. State-machine guard (D-11/D-12): suppress during plan|design|verify.
|
|
228
|
+
STAGE="$(read_state_stage)"
|
|
229
|
+
case "${STAGE}" in
|
|
230
|
+
plan|design|verify)
|
|
231
|
+
rm -f "${BANNER}" 2>/dev/null
|
|
232
|
+
exit 0
|
|
233
|
+
;;
|
|
234
|
+
esac
|
|
235
|
+
|
|
236
|
+
# 6. All gates passed — render the banner atomically.
|
|
237
|
+
TMP="${BANNER}.tmp.$$"
|
|
238
|
+
{
|
|
239
|
+
printf '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'
|
|
240
|
+
printf ' 📦 Plugin update: %s → %s (%s)\n' "${DISPLAY_CURRENT}" "${C_LATEST}" "${C_DELTA}"
|
|
241
|
+
if [ -n "${C_BODY}" ]; then
|
|
242
|
+
printf '%s\n' "${C_BODY}"
|
|
243
|
+
fi
|
|
244
|
+
printf ' Install: /gdd:update Dismiss: /gdd:check-update --dismiss\n'
|
|
245
|
+
printf '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'
|
|
246
|
+
} > "${TMP}" 2>/dev/null && mv "${TMP}" "${BANNER}" 2>/dev/null || rm -f "${TMP}" 2>/dev/null
|
|
247
|
+
|
|
248
|
+
exit 0
|
|
249
|
+
fi
|
|
250
|
+
# When sourced (BASH_SOURCE != $0), fall through with function definitions loaded
|
|
251
|
+
# and without side effects. Sourcing callers must invoke functions explicitly.
|
package/package.json
CHANGED
|
@@ -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 |
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Authority Feeds — Whitelist
|
|
2
|
+
|
|
3
|
+
> **Scope:** Curated whitelist of **design authorities** — sources that ship specs, guidelines, or named-practitioner curation. Consumed by `agents/design-authority-watcher.md` at runtime. Rejected kinds are listed explicitly below and enforced by `scripts/tests/test-authority-rejected-kinds.sh`.
|
|
4
|
+
>
|
|
5
|
+
> **Anti-slop thesis:** No Dribbble. No Behance. No LinkedIn. No generic trending aggregators. See `.planning/PROJECT.md` and `.planning/phases/13.2-external-authority-watcher/13.2-CONTEXT.md` §D-08.
|
|
6
|
+
|
|
7
|
+
**Last reviewed:** 2026-04-19
|
|
8
|
+
**Feed count:** 26 (updated each time a feed is added or removed)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Spec sources (4-5 feeds)
|
|
13
|
+
|
|
14
|
+
- **[WAI-ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/)** — `kind: spec-source` · `url: https://github.com/w3c/aria-practices/releases.atom` · `cadence-hint: monthly` · *Normative accessibility patterns from the W3C ARIA working group; release-tagged on each APG update.*
|
|
15
|
+
- **[Material Design 3](https://m3.material.io/)** — `kind: spec-source` · `url: https://github.com/material-components/material-web/releases.atom` · `cadence-hint: weekly` · *Google's design system release notes; new tokens and components land here first.*
|
|
16
|
+
- **[Apple Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/)** — `kind: spec-source` · `url: https://developer.apple.com/news/releases/rss/releases.rss` · `cadence-hint: irregular` · *Apple developer release feed — HIG updates ship alongside SDK announcements.*
|
|
17
|
+
- **[Fluent 2 Design System](https://fluent2.microsoft.design/)** — `kind: spec-source` · `url: https://github.com/microsoft/fluentui/releases.atom` · `cadence-hint: weekly` · *Microsoft's cross-platform design system; normative component API and token changes.*
|
|
18
|
+
- **[W3C Design Tokens Community Group](https://www.w3.org/community/design-tokens/)** — `kind: spec-source` · `url: https://github.com/design-tokens/community-group/commits/main.atom` · `cadence-hint: monthly` · *Draft tokens format spec; commit feed surfaces spec edits before formal publication.*
|
|
19
|
+
|
|
20
|
+
## Component systems (6-8 feeds)
|
|
21
|
+
|
|
22
|
+
- **[Radix UI](https://www.radix-ui.com/)** — `kind: component-system` · `url: https://github.com/radix-ui/primitives/releases.atom` · `cadence-hint: weekly` · *Unstyled accessible primitives; release notes document ARIA behavior changes.*
|
|
23
|
+
- **[shadcn/ui](https://ui.shadcn.com/)** — `kind: component-system` · `url: https://github.com/shadcn-ui/ui/releases.atom` · `cadence-hint: weekly` · *Copy-paste component library built on Radix + Tailwind; release notes map to new patterns.*
|
|
24
|
+
- **[Shopify Polaris](https://polaris.shopify.com/)** — `kind: component-system` · `url: https://github.com/Shopify/polaris/releases.atom` · `cadence-hint: weekly` · *Shopify admin design system; commerce-tuned component patterns.*
|
|
25
|
+
- **[IBM Carbon](https://carbondesignsystem.com/)** — `kind: component-system` · `url: https://github.com/carbon-design-system/carbon/releases.atom` · `cadence-hint: weekly` · *IBM's enterprise design system; strong on data-dense patterns and accessibility.*
|
|
26
|
+
- **[GitHub Primer](https://primer.style/)** — `kind: component-system` · `url: https://github.com/primer/react/releases.atom` · `cadence-hint: weekly` · *GitHub's design system; opinionated developer-tool patterns.*
|
|
27
|
+
- **[Atlassian Design System](https://atlassian.design/)** — `kind: component-system` · `url: https://github.com/atlassian/design-system/releases.atom` · `cadence-hint: weekly` · *Jira/Confluence design system; strong on collaboration and editor patterns.*
|
|
28
|
+
- **[Ant Design](https://ant.design/)** — `kind: component-system` · `url: https://github.com/ant-design/ant-design/releases.atom` · `cadence-hint: weekly` · *Enterprise React component library with deep form and table patterns.*
|
|
29
|
+
- **[Mantine](https://mantine.dev/)** — `kind: component-system` · `url: https://github.com/mantinedev/mantine/releases.atom` · `cadence-hint: weekly` · *React components with hooks-first architecture; strong accessibility defaults.*
|
|
30
|
+
|
|
31
|
+
## Research institutions (2-3 feeds)
|
|
32
|
+
|
|
33
|
+
- **[Nielsen Norman Group Articles](https://www.nngroup.com/articles/)** — `kind: research` · `url: https://www.nngroup.com/feed/rss/` · `cadence-hint: weekly` · *UX research articles from the Nielsen Norman Group; heuristic updates and usability findings ship here.*
|
|
34
|
+
- **[Laws of UX](https://lawsofux.com/)** — `kind: research` · `url: https://github.com/jonyablonski/laws-of-ux/releases.atom` · `cadence-hint: monthly` · *Jon Yablonski's curated catalogue of psychology-rooted UX principles; release feed tracks new laws and revisions.*
|
|
35
|
+
- **[Baymard Institute](https://baymard.com/)** — `kind: research` · `url: https://baymard.com/blog/rss` · `cadence-hint: monthly` · *E-commerce UX research with empirical benchmarks; public surface of their large-scale usability studies.*
|
|
36
|
+
|
|
37
|
+
## Named practitioners (10-12 feeds)
|
|
38
|
+
|
|
39
|
+
- **[Adam Wathan](https://adamwathan.me/)** — `kind: named-practitioner` · `url: https://adamwathan.me/feed.xml` · `cadence-hint: monthly` · *Tailwind creator; utility-first CSS, component API design, refactoring patterns.*
|
|
40
|
+
- **[Ryan Mulligan](https://ryanmulligan.dev/)** — `kind: named-practitioner` · `url: https://ryanmulligan.dev/feed.xml` · `cadence-hint: monthly` · *CSS craft at spec-adjacent depth; cascade layers, container queries, color functions.*
|
|
41
|
+
- **[Rachel Andrew](https://rachelandrew.co.uk/)** — `kind: named-practitioner` · `url: https://rachelandrew.co.uk/feed/atom` · `cadence-hint: monthly` · *CSS Working Group member; grid, layout, and evolving layout-engine features.*
|
|
42
|
+
- **[Josh W. Comeau](https://www.joshwcomeau.com/)** — `kind: named-practitioner` · `url: https://www.joshwcomeau.com/rss.xml` · `cadence-hint: monthly` · *Interactive explainers on CSS, animation, and React rendering; durable reference-quality deep dives.*
|
|
43
|
+
- **[Ahmad Shadeed](https://ishadeed.com/)** — `kind: named-practitioner` · `url: https://ishadeed.com/rss.xml` · `cadence-hint: monthly` · *CSS layout and component articles grounded in real interface patterns.*
|
|
44
|
+
- **[Sara Soueidan](https://www.sarasoueidan.com/)** — `kind: named-practitioner` · `url: https://www.sarasoueidan.com/feed.xml` · `cadence-hint: quarterly` · *SVG, accessibility, and inclusive design with spec-level rigor.*
|
|
45
|
+
- **[Lea Verou](https://lea.verou.me/)** — `kind: named-practitioner` · `url: https://lea.verou.me/feed/atom` · `cadence-hint: quarterly` · *CSS Working Group invited expert; writes about the spec surface before it ships.*
|
|
46
|
+
- **[Scott Jehl](https://scottjehl.com/)** — `kind: named-practitioner` · `url: https://scottjehl.com/feed/` · `cadence-hint: monthly` · *Progressive enhancement and web performance; long-form durable analysis.*
|
|
47
|
+
- **[Heydon Pickering](https://heydonworks.com/)** — `kind: named-practitioner` · `url: https://heydonworks.com/feed.xml` · `cadence-hint: irregular` · *Accessibility-first component design; Inclusive Components author.*
|
|
48
|
+
- **[Una Kravets](https://una.im/)** — `kind: named-practitioner` · `url: https://una.im/feed.xml` · `cadence-hint: monthly` · *Chrome DevRel on CSS; surfaces and explains new platform capabilities.*
|
|
49
|
+
|
|
50
|
+
## User-added Are.na channels (user-extensible)
|
|
51
|
+
|
|
52
|
+
Are.na channels are user-curated reference collections. Add your own channel by appending an entry to this section following the schema shown in the commented template below. The watcher fetches `https://api.are.na/v2/channels/<slug>/contents` and treats each block addition as a new entry.
|
|
53
|
+
|
|
54
|
+
<!--
|
|
55
|
+
- **[My Design Refs](https://are.na/my-handle/my-design-refs)** — `kind: arena` · `url: https://api.are.na/v2/channels/my-design-refs/contents` · `cadence-hint: irregular` · *Personal channel of pattern references.*
|
|
56
|
+
-->
|
|
57
|
+
|
|
58
|
+
## Rejected kinds
|
|
59
|
+
|
|
60
|
+
The following hosts and feed kinds are **explicitly rejected** from this whitelist. This list is enforced by `scripts/tests/test-authority-rejected-kinds.sh` — any merge that adds a matching URL fails CI.
|
|
61
|
+
|
|
62
|
+
- **dribbble.com** — visual-trend aggregator; no normative content, no named-practitioner curation.
|
|
63
|
+
- **behance.net** — portfolio aggregator; same category.
|
|
64
|
+
- **linkedin.com** — social feed; signal-to-noise ratio incompatible with the plugin's anti-slop thesis.
|
|
65
|
+
- **medium.com/topic/\*** — generic topic feeds; named Medium authors may appear as `named-practitioner` entries, but topic-level feeds are rejected wholesale.
|
|
66
|
+
- **"trending"-style aggregators** (e.g., product-hunt daily digests, "top 10 UI" roundups) — curatorial output indistinguishable from advertising.
|
|
67
|
+
|
|
68
|
+
Rationale: the whitelist is restricted to sources that ship specs, guidelines, or named-practitioner curation (PROJECT.md, ROADMAP.md SC 8, CONTEXT.md D-08).
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
**How to propose a new feed:** open a PR that adds an entry to the appropriate `## <kind>` section. Are.na channel additions go in the Are.na section and do not require approval beyond CI-green. All other additions are reviewed against the anti-slop thesis.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/hegemonart/get-design-done/main/reference/schemas/authority-snapshot.schema.json",
|
|
4
|
+
"title": "AuthoritySnapshot",
|
|
5
|
+
"description": "Structure of .design/authority-snapshot.json produced by agents/design-authority-watcher.md. See .planning/phases/13.2-external-authority-watcher/13.2-CONTEXT.md §D-12.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["version", "generated_at", "feeds"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"version": { "const": 1 },
|
|
11
|
+
"generated_at": { "type": "string", "format": "date-time" },
|
|
12
|
+
"feeds": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"additionalProperties": { "$ref": "#/definitions/feedState" }
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"definitions": {
|
|
18
|
+
"feedState": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"additionalProperties": false,
|
|
21
|
+
"required": ["last_fetched_at", "entries"],
|
|
22
|
+
"properties": {
|
|
23
|
+
"last_fetched_at": { "type": "string", "format": "date-time" },
|
|
24
|
+
"etag": { "type": "string" },
|
|
25
|
+
"entries": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"maxItems": 200,
|
|
28
|
+
"items": { "$ref": "#/definitions/entry" }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"entry": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"additionalProperties": false,
|
|
35
|
+
"required": ["id", "hash"],
|
|
36
|
+
"properties": {
|
|
37
|
+
"id": { "type": "string", "minLength": 1 },
|
|
38
|
+
"hash": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|