@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
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
},
|
|
40
|
+
"update_dismissed": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "Latest plugin tag (e.g. \"v1.0.7.3\") whose update nudge the user has dismissed. Set by /gdd:check-update --dismiss and by hooks/update-check.sh on the --dismiss code path. When a newer tag ships, the nudge reappears."
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"required": ["description", "version"],
|
|
26
26
|
"properties": {
|
|
27
27
|
"description": { "type": "string", "minLength": 1 },
|
|
28
|
-
"version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d
|
|
28
|
+
"version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(\\.\\d+)?$" }
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"plugins": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"name": { "type": "string", "minLength": 1 },
|
|
49
49
|
"source": { "type": "string", "minLength": 1 },
|
|
50
50
|
"description": { "type": "string", "minLength": 1 },
|
|
51
|
-
"version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d
|
|
51
|
+
"version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(\\.\\d+)?$" },
|
|
52
52
|
"author": {
|
|
53
53
|
"type": "object",
|
|
54
54
|
"additionalProperties": true,
|
|
@@ -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 {
|
package/scripts/build-intel.cjs
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
const fs = require('fs');
|
|
19
19
|
const path = require('path');
|
|
20
|
-
const {
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test-authority-rejected-kinds.sh
|
|
3
|
+
#
|
|
4
|
+
# Asserts that reference/authority-feeds.md does NOT contain trend-aggregator
|
|
5
|
+
# hosts OUTSIDE the explicit "## Rejected kinds" section. Enforces the anti-slop
|
|
6
|
+
# thesis structurally (CONTEXT.md D-08, D-28).
|
|
7
|
+
#
|
|
8
|
+
# Exit 0 = clean. Exit 1 = at least one rejected host appeared in the active
|
|
9
|
+
# whitelist, or the rejected-kinds section itself was removed.
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
WHITELIST="${WHITELIST:-reference/authority-feeds.md}"
|
|
14
|
+
|
|
15
|
+
if [ ! -f "$WHITELIST" ]; then
|
|
16
|
+
echo "FAIL: $WHITELIST not found." >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Split the file at the "## Rejected kinds" heading. Everything BEFORE it is
|
|
21
|
+
# the active whitelist; everything AFTER is the rejection manifest (which is
|
|
22
|
+
# allowed to mention these hosts — that's the whole point).
|
|
23
|
+
ACTIVE_SECTION="$(awk '/^## Rejected kinds/{exit} {print}' "$WHITELIST")"
|
|
24
|
+
|
|
25
|
+
REJECTED_PATTERNS=(
|
|
26
|
+
'dribbble\.com'
|
|
27
|
+
'behance\.net'
|
|
28
|
+
'linkedin\.com'
|
|
29
|
+
'medium\.com/topic'
|
|
30
|
+
'producthunt\.com/posts'
|
|
31
|
+
'top[[:space:]]*10[[:space:]]*ui'
|
|
32
|
+
'trending-ui'
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
FAIL=0
|
|
36
|
+
for pat in "${REJECTED_PATTERNS[@]}"; do
|
|
37
|
+
if echo "$ACTIVE_SECTION" | grep -iEq "$pat"; then
|
|
38
|
+
echo "FAIL: rejected pattern '$pat' matched in active whitelist section of $WHITELIST." >&2
|
|
39
|
+
FAIL=1
|
|
40
|
+
fi
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
if [ "$FAIL" -ne 0 ]; then
|
|
44
|
+
echo "" >&2
|
|
45
|
+
echo "The whitelist must not contain trend-aggregator hosts outside the '## Rejected kinds' block." >&2
|
|
46
|
+
echo "See .planning/phases/13.2-external-authority-watcher/13.2-CONTEXT.md §D-08." >&2
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Also assert the rejected-kinds section itself is present — regression against
|
|
51
|
+
# someone deleting the section entirely.
|
|
52
|
+
if ! grep -q '^## Rejected kinds$' "$WHITELIST"; then
|
|
53
|
+
echo "FAIL: '## Rejected kinds' section is missing from $WHITELIST." >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
echo "OK: $WHITELIST passes rejected-kinds check."
|
|
58
|
+
exit 0
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test-authority-watcher-diff.sh
|
|
3
|
+
#
|
|
4
|
+
# Structural-only v1: validates that the frozen authority-report baseline at
|
|
5
|
+
# test-fixture/baselines/phase-13.2/authority-report.expected.md preserves the
|
|
6
|
+
# shape the watcher-diff test depends on. Full end-to-end byte-diff against a
|
|
7
|
+
# live watcher run is deferred — CI cannot spawn Claude Code agents.
|
|
8
|
+
#
|
|
9
|
+
# What this test asserts (structural-only v1):
|
|
10
|
+
# 1. The fixture directory exists and is non-empty.
|
|
11
|
+
# 2. The baseline file exists, is non-empty, and starts with an Authority
|
|
12
|
+
# Report header.
|
|
13
|
+
# 3. The baseline contains the canonical classification section headings in
|
|
14
|
+
# the D-21 weighted order (spec > heuristic > pattern > craft).
|
|
15
|
+
# 4. The baseline declares a total-entries line consistent with the count of
|
|
16
|
+
# bulleted entries under its classification sections.
|
|
17
|
+
# 5. The baseline ends with a `**Skipped:**` footer.
|
|
18
|
+
#
|
|
19
|
+
# Exit 0 = structural shape preserved. Exit 1 = shape drift detected.
|
|
20
|
+
#
|
|
21
|
+
# Full end-to-end byte-diff (running the watcher against the fixtures and
|
|
22
|
+
# diffing stdout against the baseline) is a follow-up tracked in STATE.md
|
|
23
|
+
# "Open follow-ups". When the agent runtime becomes available in CI this
|
|
24
|
+
# script graduates from structural-only v1 to the full diff check.
|
|
25
|
+
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
|
|
28
|
+
FIXTURE_DIR="test-fixture/authority-feeds"
|
|
29
|
+
BASELINE="test-fixture/baselines/phase-13.2/authority-report.expected.md"
|
|
30
|
+
|
|
31
|
+
if [ ! -d "$FIXTURE_DIR" ]; then
|
|
32
|
+
echo "FAIL: fixture dir $FIXTURE_DIR missing." >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Count actual fixture files (should be 4 frozen feeds + 1 README; we only
|
|
37
|
+
# care that at least one XML/JSON fixture is present).
|
|
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)
|
|
43
|
+
if [ "$FIXTURE_COUNT" -lt 1 ]; then
|
|
44
|
+
echo "FAIL: $FIXTURE_DIR contains no feed fixtures (.atom/.rss/.json)." >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if [ ! -f "$BASELINE" ]; then
|
|
49
|
+
echo "FAIL: baseline $BASELINE missing." >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
if [ ! -s "$BASELINE" ]; then
|
|
54
|
+
echo "FAIL: baseline $BASELINE is empty." >&2
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Header: must start with "# Authority Report"
|
|
59
|
+
if ! head -1 "$BASELINE" | grep -q '^# Authority Report'; then
|
|
60
|
+
echo "FAIL: baseline does not begin with '# Authority Report' header." >&2
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Totals line must declare a non-negative surfaced-entries count.
|
|
65
|
+
if ! grep -qE '^[0-9]+ entries surfaced across [0-9]+ feeds\. [0-9]+ skipped\.$' "$BASELINE"; then
|
|
66
|
+
echo "FAIL: baseline is missing the 'N entries surfaced across M feeds. K skipped.' totals line." >&2
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# Classification sections — at least one of the four non-skip buckets must
|
|
71
|
+
# appear. (Empty buckets are omitted by D-21, but a well-shaped baseline
|
|
72
|
+
# always has at least one.)
|
|
73
|
+
SECTION_HITS=0
|
|
74
|
+
for heading in '^## spec-change' '^## heuristic-update' '^## pattern-guidance' '^## craft-tip'; do
|
|
75
|
+
if grep -qE "$heading" "$BASELINE"; then
|
|
76
|
+
SECTION_HITS=$((SECTION_HITS + 1))
|
|
77
|
+
fi
|
|
78
|
+
done
|
|
79
|
+
|
|
80
|
+
if [ "$SECTION_HITS" -lt 1 ]; then
|
|
81
|
+
echo "FAIL: baseline does not contain any classification heading (spec-change / heuristic-update / pattern-guidance / craft-tip)." >&2
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Count consistency check: the header's "N entries surfaced" must match the
|
|
86
|
+
# total bulleted entries across the four classification sections.
|
|
87
|
+
HEADER_COUNT=$(grep -oE '^[0-9]+ entries surfaced' "$BASELINE" | head -1 | grep -oE '^[0-9]+')
|
|
88
|
+
|
|
89
|
+
# Extract the body between the first "## spec-change|heuristic-update|pattern-guidance|craft-tip"
|
|
90
|
+
# heading and the closing "---" horizontal rule before the Skipped footer.
|
|
91
|
+
BULLET_COUNT=$(awk '
|
|
92
|
+
/^## (spec-change|heuristic-update|pattern-guidance|craft-tip)/ { in_section=1; next }
|
|
93
|
+
/^---$/ { in_section=0 }
|
|
94
|
+
in_section && /^- / { count++ }
|
|
95
|
+
END { print count+0 }
|
|
96
|
+
' "$BASELINE")
|
|
97
|
+
|
|
98
|
+
if [ "$HEADER_COUNT" != "$BULLET_COUNT" ]; then
|
|
99
|
+
echo "FAIL: baseline header declares $HEADER_COUNT entries but the classification sections contain $BULLET_COUNT bullets." >&2
|
|
100
|
+
echo " Counts MUST match — regenerate the baseline or fix the header." >&2
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# Footer: must have a Skipped line.
|
|
105
|
+
if ! grep -qE '^\*\*Skipped:\*\*' "$BASELINE"; then
|
|
106
|
+
echo "FAIL: baseline is missing the '**Skipped:**' footer line." >&2
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
echo "OK: authority-report baseline shape preserved (structural-only v1)."
|
|
111
|
+
echo " Fixtures: $FIXTURE_COUNT files under $FIXTURE_DIR"
|
|
112
|
+
echo " Entries: $HEADER_COUNT (header) = $BULLET_COUNT (bullets)"
|
|
113
|
+
exit 0
|
|
@@ -69,6 +69,14 @@ const PAIRS = [
|
|
|
69
69
|
data: null,
|
|
70
70
|
required: false,
|
|
71
71
|
},
|
|
72
|
+
{
|
|
73
|
+
name: 'authority-snapshot',
|
|
74
|
+
schema: 'reference/schemas/authority-snapshot.schema.json',
|
|
75
|
+
// .design/authority-snapshot.json is runtime-only (gitignored via .design/).
|
|
76
|
+
// Only schema-compile it; Phase 13.2-02's watcher emits the data file at runtime.
|
|
77
|
+
data: null,
|
|
78
|
+
required: false,
|
|
79
|
+
},
|
|
72
80
|
];
|
|
73
81
|
|
|
74
82
|
const USE_NPX = !process.argv.includes('--no-npx');
|
|
@@ -76,11 +84,20 @@ const USE_NPX = !process.argv.includes('--no-npx');
|
|
|
76
84
|
/**
|
|
77
85
|
* Try running ajv-cli via npx. Returns { ok, stdout, stderr, status, fetchFailed }.
|
|
78
86
|
* fetchFailed=true if npx couldn't fetch the package (offline); caller should fall back.
|
|
87
|
+
*
|
|
88
|
+
* We pass `-c ajv-formats` so schemas declaring `format: "date-time"` (etc.) are
|
|
89
|
+
* validated against the standard JSON Schema formats plugin rather than being
|
|
90
|
+
* rejected as unknown formats under ajv's strict mode. Schemas that declare no
|
|
91
|
+
* formats (e.g., plugin, marketplace) are unaffected.
|
|
79
92
|
*/
|
|
80
93
|
function runAjv(args) {
|
|
94
|
+
const injected = [...args];
|
|
95
|
+
// Inject `-c ajv-formats` after the sub-command (compile/validate) but before -s flags.
|
|
96
|
+
// Simpler: just append; ajv-cli accepts -c at any position.
|
|
97
|
+
injected.push('-c', 'ajv-formats');
|
|
81
98
|
const result = spawnSync(
|
|
82
99
|
'npx',
|
|
83
|
-
['--yes', 'ajv-cli@5', ...
|
|
100
|
+
['--yes', '-p', 'ajv-cli@5', '-p', 'ajv-formats@3', 'ajv', ...injected],
|
|
84
101
|
{ encoding: 'utf8', cwd: REPO_ROOT }
|
|
85
102
|
);
|
|
86
103
|
const combined = (result.stdout || '') + (result.stderr || '');
|
package/skills/audit/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: gdd-audit
|
|
3
3
|
description: "Run design audit — wraps design-verifier + design-auditor + design-reflector. --retroactive audits the full cycle scope."
|
|
4
4
|
argument-hint: "[--retroactive] [--quick] [--no-reflect]"
|
|
5
|
-
tools: Read, Write, Task, Glob
|
|
5
|
+
tools: Read, Write, Task, Glob, Bash
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# /gdd:audit
|
|
@@ -51,4 +51,14 @@ Run only `design-auditor` (skip `design-integration-checker`). Faster health che
|
|
|
51
51
|
- Do not modify source files.
|
|
52
52
|
- Do not rerun stages; this is a read-only audit.
|
|
53
53
|
|
|
54
|
+
## Step 7 — Update notice (post-closeout surface)
|
|
55
|
+
|
|
56
|
+
After the consolidated audit summary has been printed (and any reflection-proposal count appended), emit the plugin-update banner if one is present:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
[ -f .design/update-available.md ] && cat .design/update-available.md
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Written by `hooks/update-check.sh`; suppressed mid-pipeline and when the latest release is dismissed.
|
|
63
|
+
|
|
54
64
|
## AUDIT COMPLETE
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gdd-check-update
|
|
3
|
+
description: "Manual plugin-update check. Shows cached state by default; --refresh bypasses the 24h TTL; --dismiss hides the nudge until a newer release ships; --prompt spawns design-update-checker for a richer summary."
|
|
4
|
+
argument-hint: "[--refresh] [--dismiss] [--prompt]"
|
|
5
|
+
tools: Read, Write, Bash, Task
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /gdd:check-update
|
|
9
|
+
|
|
10
|
+
**Role:** Manual entry point for the plugin-update checker. The SessionStart hook (`hooks/update-check.sh`) already runs on its own 24h cadence and writes `.design/update-cache.json` + `.design/update-available.md`. This command lets the user inspect / force / dismiss / enrich that state on demand.
|
|
11
|
+
|
|
12
|
+
## Flags
|
|
13
|
+
|
|
14
|
+
| Flag | Effect |
|
|
15
|
+
|---|---|
|
|
16
|
+
| *(none)* | Print cached state (latest_tag, delta, is_newer). If the cache is older than 24h, trigger `--refresh` implicitly. |
|
|
17
|
+
| `--refresh` | Invoke `hooks/update-check.sh --refresh` — bypasses the 24h TTL and re-fetches immediately. |
|
|
18
|
+
| `--dismiss` | Write `update_dismissed: "<latest_tag>"` to `.design/config.json` atomically and delete `.design/update-available.md`. Sticky until a newer release ships. |
|
|
19
|
+
| `--prompt` | Spawn `design-update-checker` agent (Haiku) to produce a 3–5-line "what this release changes for you" summary. Does not alter the banner or cache. |
|
|
20
|
+
|
|
21
|
+
Flags can be combined: `--refresh --prompt` is valid (re-fetch, then enrich). `--dismiss` is the only flag that mutates `.design/config.json`.
|
|
22
|
+
|
|
23
|
+
## Steps
|
|
24
|
+
|
|
25
|
+
1. **Parse flags.** Detect `--refresh`, `--dismiss`, `--prompt` in `$ARGUMENTS`. Any unknown flag: print `Unknown flag: <flag>` and exit.
|
|
26
|
+
|
|
27
|
+
2. **--refresh path** (if `--refresh` in flags):
|
|
28
|
+
Run the hot-path hook with the refresh flag:
|
|
29
|
+
```bash
|
|
30
|
+
bash "${CLAUDE_PLUGIN_ROOT:-$(pwd)}/hooks/update-check.sh" --refresh
|
|
31
|
+
```
|
|
32
|
+
This re-fetches `/releases/latest`, rewrites `.design/update-cache.json`, and re-renders `.design/update-available.md` subject to the same state/dismissal gates.
|
|
33
|
+
|
|
34
|
+
3. **Read cache.** After any optional refresh, read `.design/update-cache.json`. If it does not exist:
|
|
35
|
+
- Print: `No cache. Network may be unreachable or the hook has not run yet. Try /gdd:check-update --refresh.`
|
|
36
|
+
- Exit.
|
|
37
|
+
|
|
38
|
+
<!-- markdownlint-disable MD025 -->
|
|
39
|
+
|
|
40
|
+
4. **Dismiss path** (if `--dismiss` in flags):
|
|
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.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
CONFIG_PATH=".design/config.json"
|
|
45
|
+
LATEST_TAG="$(grep -E '"latest_tag"' .design/update-cache.json | head -n1 | sed -E 's/.*"latest_tag"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')"
|
|
46
|
+
[ -n "$LATEST_TAG" ] || { echo 'No latest_tag in cache — nothing to dismiss.'; exit 0; }
|
|
47
|
+
|
|
48
|
+
mkdir -p .design
|
|
49
|
+
|
|
50
|
+
# Env-prefix form: CONFIG_PATH and LATEST_TAG are placed into the child process env,
|
|
51
|
+
# then python3 reads them via os.environ. Heredoc body is flat at column 0 (required —
|
|
52
|
+
# any leading indentation breaks Python parsing in a heredoc).
|
|
53
|
+
CONFIG_PATH="$CONFIG_PATH" LATEST_TAG="$LATEST_TAG" python3 <<'PY'
|
|
54
|
+
import json, os, sys, tempfile
|
|
55
|
+
|
|
56
|
+
config_path = os.environ['CONFIG_PATH']
|
|
57
|
+
latest_tag = os.environ['LATEST_TAG']
|
|
58
|
+
|
|
59
|
+
# Load existing config if present; otherwise start fresh. Preserve every unknown key.
|
|
60
|
+
try:
|
|
61
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
|
62
|
+
data = json.load(f)
|
|
63
|
+
if not isinstance(data, dict):
|
|
64
|
+
data = {}
|
|
65
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
66
|
+
data = {}
|
|
67
|
+
|
|
68
|
+
# Set ONLY the dismissal key — every other pre-existing key is preserved verbatim.
|
|
69
|
+
data['update_dismissed'] = latest_tag
|
|
70
|
+
|
|
71
|
+
# Atomic write: write to tmp on the SAME filesystem, then os.replace() (POSIX rename(2)).
|
|
72
|
+
target_dir = os.path.dirname(config_path) or '.'
|
|
73
|
+
fd, tmp_path = tempfile.mkstemp(prefix='config.', suffix='.tmp', dir=target_dir)
|
|
74
|
+
try:
|
|
75
|
+
with os.fdopen(fd, 'w', encoding='utf-8') as f:
|
|
76
|
+
json.dump(data, f, indent=2)
|
|
77
|
+
f.write('\n')
|
|
78
|
+
os.replace(tmp_path, config_path) # atomic on same filesystem
|
|
79
|
+
except Exception as exc:
|
|
80
|
+
try:
|
|
81
|
+
os.unlink(tmp_path)
|
|
82
|
+
except OSError:
|
|
83
|
+
pass
|
|
84
|
+
sys.exit(1)
|
|
85
|
+
PY
|
|
86
|
+
|
|
87
|
+
# Remove the rendered banner (user dismissed — stop showing it until a newer release ships).
|
|
88
|
+
rm -f .design/update-available.md
|
|
89
|
+
echo "Dismissed $LATEST_TAG. The nudge will return when a newer release ships."
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
D-14 atomic-write invariant: `os.replace()` (POSIX `rename(2)`) is atomic on the same filesystem — an interrupted write leaves the original config intact. The `json.load → set single key → json.dump` round-trip guarantees every unknown top-level key (e.g. `model_profile`, `parallelism`) is preserved verbatim.
|
|
93
|
+
|
|
94
|
+
5. **Print default state** (always, unless the skill exited early):
|
|
95
|
+
```
|
|
96
|
+
━━━ /gdd:check-update ━━━
|
|
97
|
+
Current: v<X.Y.Z>
|
|
98
|
+
Latest: v<A.B.C> (delta: <major|minor|patch|off-cadence|none>)
|
|
99
|
+
Newer: <true|false>
|
|
100
|
+
Checked: <ISO time of checked_at>
|
|
101
|
+
Dismissed: <tag or "no">
|
|
102
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
103
|
+
```
|
|
104
|
+
Parse these fields from `.design/update-cache.json` using `grep + sed` (no jq dependency). Read dismissal from `.design/config.json` via the same pattern as hooks/update-check.sh:
|
|
105
|
+
```bash
|
|
106
|
+
[ -f .design/config.json ] && grep -E '"update_dismissed"' .design/config.json | sed -E 's/.*"update_dismissed"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
6. **--prompt path** (if `--prompt` in flags):
|
|
110
|
+
Spawn the `design-update-checker` agent via the Task tool. Pass as context:
|
|
111
|
+
- `current_tag` — from `.design/update-cache.json`
|
|
112
|
+
- `latest_tag` — from `.design/update-cache.json`
|
|
113
|
+
- `delta` — from `.design/update-cache.json`
|
|
114
|
+
- `release_body` — the `changelog_excerpt` from the cache (may be pre-truncated to 500 chars; that's fine — the agent knows)
|
|
115
|
+
|
|
116
|
+
Display the agent's inline response verbatim below the state banner. The agent ends with `## UPDATE-CHECKER COMPLETE` — the skill's own completion marker (below) is the final line.
|
|
117
|
+
|
|
118
|
+
## Defaults
|
|
119
|
+
|
|
120
|
+
- No flags → behave as: `--refresh (only if cache >24h old) → print state`. Silent no-op if cache is fresh and there is no newer version.
|
|
121
|
+
|
|
122
|
+
## Do Not
|
|
123
|
+
|
|
124
|
+
- Do not fetch from GitHub in this skill directly — always go through `hooks/update-check.sh --refresh` so the caching + state-guard + dismissal logic stays in one place.
|
|
125
|
+
- Do not modify `.design/update-available.md` except to delete it on `--dismiss`. The hot-path hook is the only writer of that banner (D-06).
|
|
126
|
+
- Do not rewrite `.design/config.json` wholesale — the atomic python rewrite preserves every unknown key (D-14).
|
|
127
|
+
- Do not pass variables to the python heredoc via trailing `KEY=VALUE` argv — that assigns to `sys.argv`, not `os.environ`. Use env-prefix form only.
|
|
128
|
+
|
|
129
|
+
## Completion marker
|
|
130
|
+
|
|
131
|
+
Last line of output:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
## CHECK-UPDATE COMPLETE
|
|
135
|
+
```
|
|
@@ -30,4 +30,14 @@ Closes the current cycle: marks CYCLES.md entry complete, archives pipeline arti
|
|
|
30
30
|
- Do not delete source files in `src/` — only archive `.design/` artifacts.
|
|
31
31
|
- Do not auto-start a new cycle — user invokes `/gdd:new-cycle` explicitly.
|
|
32
32
|
|
|
33
|
+
## Step 6 — Update notice (post-closeout surface)
|
|
34
|
+
|
|
35
|
+
After the archive has been written and STATE.md has been cleared for the next cycle, emit the plugin-update banner if one is present:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
[ -f .design/update-available.md ] && cat .design/update-available.md
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Written by `hooks/update-check.sh`; suppressed mid-pipeline and when the latest release is dismissed.
|
|
42
|
+
|
|
33
43
|
## COMPLETE-CYCLE COMPLETE
|
package/skills/design/SKILL.md
CHANGED
|
@@ -41,7 +41,7 @@ Skip if `auto_mode=true`.
|
|
|
41
41
|
|
|
42
42
|
## Pre-execution — Project-local conventions
|
|
43
43
|
|
|
44
|
-
When spawning the executor, include any `./.claude/skills/design-*-conventions.md` files in `<required_reading>` so the executor sees project-local design conventions (typography, color, layout, motion, component, interaction decisions codified from prior sketch wrap-ups). Also include any `~/.claude/gdd/global-skills/*.md` files if the directory exists � global skills are cross-project conventions that inform but do not override project-local D-XX decisions.
|
|
44
|
+
When spawning the executor, include any `./.claude/skills/design-*-conventions.md` files in `<required_reading>` so the executor sees project-local design conventions (typography, color, layout, motion, component, interaction decisions codified from prior sketch wrap-ups). Also include any `~/.claude/gdd/global-skills/*.md` files if the directory exists � global skills are cross-project conventions that inform but do not override project-local D-XX decisions.
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
@@ -256,13 +256,13 @@ Next: /get-design-done:verify
|
|
|
256
256
|
|
|
257
257
|
After design-executor has finished and DESIGN-PLAN.md tasks are complete:
|
|
258
258
|
|
|
259
|
-
1. Read `
|
|
260
|
-
- If `
|
|
261
|
-
- If `
|
|
259
|
+
1. Read `figma:` status from `.design/STATE.md` `<connections>` (the unified remote MCP covers both reads and writes as of v1.0.7.1):
|
|
260
|
+
- If `figma: not_configured` or `figma: unavailable` or absent → skip this block entirely (no prompt, no output)
|
|
261
|
+
- If `figma: available` → proceed to step 2
|
|
262
262
|
|
|
263
263
|
2. Offer the user a prompt:
|
|
264
264
|
```
|
|
265
|
-
figma-
|
|
265
|
+
figma write-back is available — propagate design decisions back to Figma?
|
|
266
266
|
Modes: annotate (layer comments) | tokenize (variable bindings) | mappings (Code Connect)
|
|
267
267
|
Run figma-write? (y/N):
|
|
268
268
|
```
|
package/skills/discover/SKILL.md
CHANGED
|
@@ -22,9 +22,9 @@ user-invocable: true
|
|
|
22
22
|
**A — Figma probe:**
|
|
23
23
|
|
|
24
24
|
```
|
|
25
|
-
A1. ToolSearch({ query: "select:
|
|
25
|
+
A1. ToolSearch({ query: "select:mcp__figma__get_metadata", max_results: 1 })
|
|
26
26
|
A2. Empty result → figma: not_configured (skip all Figma paths)
|
|
27
|
-
Non-empty result → call
|
|
27
|
+
Non-empty result → call mcp__figma__get_metadata
|
|
28
28
|
Success → figma: available
|
|
29
29
|
Error → figma: unavailable
|
|
30
30
|
```
|
package/skills/explore/SKILL.md
CHANGED
|
@@ -19,9 +19,9 @@ Probe connection availability and update `.design/STATE.md` `<connections>`:
|
|
|
19
19
|
|
|
20
20
|
**A — Figma probe:**
|
|
21
21
|
```
|
|
22
|
-
ToolSearch({ query: "select:
|
|
22
|
+
ToolSearch({ query: "select:mcp__figma__get_metadata", max_results: 1 })
|
|
23
23
|
Empty → figma: not_configured
|
|
24
|
-
Non-empty → call
|
|
24
|
+
Non-empty → call mcp__figma__get_metadata; success → available; error → unavailable
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
**B — Refero probe:**
|
|
@@ -31,7 +31,59 @@ Empty → refero: not_configured
|
|
|
31
31
|
Non-empty → refero: available
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
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
|
|