@hegemonart/get-design-done 1.13.3 → 1.14.2
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 +66 -7
- package/README.md +135 -40
- package/agents/design-component-generator.md +221 -0
- package/agents/design-context-builder.md +80 -3
- package/agents/design-paper-writer.md +131 -0
- package/agents/design-pencil-writer.md +99 -0
- package/agents/design-research-synthesizer.md +45 -6
- package/agents/design-verifier.md +51 -0
- package/connections/21st-dev.md +98 -0
- package/connections/claude-design.md +106 -22
- package/connections/connections.md +41 -11
- package/connections/magic-patterns.md +105 -0
- package/connections/paper-design.md +137 -0
- package/connections/pencil-dev.md +88 -0
- package/hooks/budget-enforcer.js +13 -2
- package/hooks/gdd-read-injection-scanner.js +4 -9
- package/hooks/update-check.sh +13 -0
- package/package.json +4 -1
- package/reference/ai-native-tool-interface.md +102 -0
- 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-watcher-diff.sh +5 -1
- package/skills/check-update/SKILL.md +2 -0
- package/skills/explore/SKILL.md +53 -1
- package/reference/BRANCH-PROTECTION.md +0 -65
- package/scripts/apply-branch-protection.sh +0 -75
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/update-check.sh
CHANGED
|
@@ -165,6 +165,14 @@ if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
|
165
165
|
BODY_EXCERPT="$(printf '%s' "${RAW}" | extract_body)"
|
|
166
166
|
# Strip control chars defensively (T-13.3-03)
|
|
167
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
|
|
168
176
|
if [ -n "${LATEST_TAG}" ]; then
|
|
169
177
|
read -r DELTA_STATE DELTA_KIND <<EOF
|
|
170
178
|
$(classify_delta "${DISPLAY_CURRENT}" "${LATEST_TAG}")
|
|
@@ -195,6 +203,11 @@ EOF
|
|
|
195
203
|
|
|
196
204
|
C_LATEST="$(grep -E '"latest_tag"' "${CACHE}" 2>/dev/null | head -n1 | sed -E 's/.*"latest_tag"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')"
|
|
197
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
|
|
198
211
|
C_NEWER="$(grep -E '"is_newer"' "${CACHE}" 2>/dev/null | head -n1 | sed -E 's/.*"is_newer"[[:space:]]*:[[:space:]]*(true|false).*/\1/')"
|
|
199
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')"
|
|
200
213
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hegemonart/get-design-done",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.2",
|
|
4
4
|
"description": "A Claude Code plugin for systematic design improvement",
|
|
5
5
|
"author": "Hegemon",
|
|
6
6
|
"homepage": "https://github.com/hegemonart/get-design-done",
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"CHANGELOG.md",
|
|
26
26
|
"LICENSE"
|
|
27
27
|
],
|
|
28
|
+
"bin": {
|
|
29
|
+
"get-design-done": "./scripts/install.cjs"
|
|
30
|
+
},
|
|
28
31
|
"publishConfig": {
|
|
29
32
|
"access": "public",
|
|
30
33
|
"provenance": true
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# AI-Native Design Tool Interface — Capability Contract
|
|
2
|
+
|
|
3
|
+
This file defines the capability-based contract that AI-native design tools must implement to integrate with the get-design-done pipeline. Two sub-categories are defined: **canvas** and **component-generator**. Future tools implement one sub-category and plug in via the same probe/read/write or probe/generate/adopt surface.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Sub-Categories
|
|
8
|
+
|
|
9
|
+
### Canvas Tools
|
|
10
|
+
|
|
11
|
+
Canvas tools treat the design canvas as both source AND destination. They expose a bidirectional read+write surface.
|
|
12
|
+
|
|
13
|
+
**Contract:**
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
probe() → { available | unavailable | not_configured }
|
|
17
|
+
|
|
18
|
+
read(selection) → {
|
|
19
|
+
jsx: string, // React JSX of component tree
|
|
20
|
+
styles: object, // computed CSS styles
|
|
21
|
+
screenshot: base64_png, // visual snapshot
|
|
22
|
+
metadata: object // component name, bounds, id
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
write(proposal) → { confirmed | rejected }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Implementations:**
|
|
29
|
+
- `connections/paper-design.md` — MCP-based; 24-tool server; budget: 100 calls/week (free)
|
|
30
|
+
- `connections/pencil-dev.md` — file-based; `.pen` YAML spec files; git-tracked; no MCP
|
|
31
|
+
|
|
32
|
+
**Pipeline stages:** `explore` (read) + `verify` (screenshot) + `design` (write via writer agent)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
### Component Generators
|
|
37
|
+
|
|
38
|
+
Component generators produce UI component code from a natural-language description and an optional design-system target. They expose a generative one-way (or roundtrip) surface.
|
|
39
|
+
|
|
40
|
+
**Contract:**
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
probe() → { available | unavailable | not_configured }
|
|
44
|
+
|
|
45
|
+
generate(description: string, ds: "shadcn"|"tailwind"|"mantine"|"chakra") → {
|
|
46
|
+
code: string, // component source code
|
|
47
|
+
preview_url: string, // hosted preview URL
|
|
48
|
+
variants: array, // multiple generated variations
|
|
49
|
+
component_id: string // for adopt/annotate operations
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
adopt(variant: object) → { confirmed | rejected }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Implementations:**
|
|
56
|
+
- `connections/21st-dev.md` — Magic MCP; `npx @21st-dev/magic@latest init`; marketplace prior-art gate
|
|
57
|
+
- `connections/magic-patterns.md` — Claude connector (`mcp__magic_patterns*`) + API key fallback; DS-aware generation
|
|
58
|
+
|
|
59
|
+
**Pipeline stages:** `explore` (prior-art gate for 21st.dev) + `design` (generate + adopt)
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Shared Probe Pattern
|
|
64
|
+
|
|
65
|
+
All AI-native tools use the three-value status schema from `connections/connections.md`:
|
|
66
|
+
|
|
67
|
+
| Status | Meaning |
|
|
68
|
+
|--------|---------|
|
|
69
|
+
| `available` | Tool confirmed present and responsive |
|
|
70
|
+
| `unavailable` | Tool present but errored (rate-limited, auth failure) |
|
|
71
|
+
| `not_configured` | ToolSearch returned empty or no .pen files found |
|
|
72
|
+
|
|
73
|
+
STATE.md format: `<tool-name>: <status>` in the `<connections>` block.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Extending with Future Tools
|
|
78
|
+
|
|
79
|
+
To add a new AI-native design tool:
|
|
80
|
+
|
|
81
|
+
1. Determine sub-category: **canvas** (bidirectional design source) or **component-generator** (code generator).
|
|
82
|
+
2. Create `connections/<tool-name>.md` following the frozen template in `connections/figma.md`.
|
|
83
|
+
3. Implement `probe()` using ToolSearch or file-based check. Write status to STATE.md.
|
|
84
|
+
4. For **canvas**: expose `read()` and `write()` surfaces via the corresponding agent.
|
|
85
|
+
5. For **component-generator**: implement `generate()` and `adopt()` in `agents/design-component-generator.md` as a new `<!-- impl: <tool> -->` section.
|
|
86
|
+
6. Add a row to `connections/connections.md` capability matrix with `canvas` or `generator` column marked.
|
|
87
|
+
7. Append to `test-fixture/baselines/current/connection-list.txt` in sorted order.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Candidate Tools (Backlog)
|
|
92
|
+
|
|
93
|
+
| Tool | Sub-category | Priority | Notes |
|
|
94
|
+
|------|-------------|----------|-------|
|
|
95
|
+
| Subframe | canvas | high | MCP-based; production-ready components; check for `mcp__subframe*` |
|
|
96
|
+
| v0.dev | generator | high | Vercel product; generates shadcn/tailwind; check for `mcp__v0*` |
|
|
97
|
+
| Galileo AI | generator | medium | Enterprise DS generation; API key required |
|
|
98
|
+
| Builder.io Visual Copilot | canvas + generator | medium | Figma plugin + code export; check for `mcp__builder*` |
|
|
99
|
+
| Locofy | generator | low | Figma→React/Next.js; Figma plugin-based |
|
|
100
|
+
| Anima | canvas | low | Figma→React; Figma plugin-based |
|
|
101
|
+
| Plasmic | generator | medium | Headless CMS + visual builder; `mcp__plasmic*` |
|
|
102
|
+
| TeleportHQ | generator | low | Code export + collaboration |
|
|
@@ -25,6 +25,7 @@ const os = require('os');
|
|
|
25
25
|
const CWD = process.cwd();
|
|
26
26
|
const TELEMETRY_PATH = path.join(CWD, '.design', 'telemetry', 'costs.jsonl');
|
|
27
27
|
const METRICS_PATH = path.join(CWD, '.design', 'agent-metrics.json');
|
|
28
|
+
const PHASE_TOTALS_PATH = path.join(CWD, '.design', 'telemetry', 'phase-totals.json');
|
|
28
29
|
const AGENTS_DIR = path.join(CWD, 'agents');
|
|
29
30
|
|
|
30
31
|
// ---- frontmatter reader (no YAML dep) ----
|
|
@@ -124,6 +125,18 @@ function writeAtomic(filePath, content) {
|
|
|
124
125
|
fs.renameSync(tmp, filePath);
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
// ---- phase totals aggregator (WR-02: avoids full JSONL replay in budget enforcer) ----
|
|
129
|
+
function aggregateByPhase(rows) {
|
|
130
|
+
const byPhase = {};
|
|
131
|
+
for (const r of rows) {
|
|
132
|
+
const phase = r.phase || 'unknown';
|
|
133
|
+
byPhase[phase] = (byPhase[phase] || 0) + Number(r.est_cost_usd || 0);
|
|
134
|
+
}
|
|
135
|
+
// Round to 6dp to match per-agent precision
|
|
136
|
+
for (const k of Object.keys(byPhase)) byPhase[k] = Number(byPhase[k].toFixed(6));
|
|
137
|
+
return byPhase;
|
|
138
|
+
}
|
|
139
|
+
|
|
127
140
|
// ---- main ----
|
|
128
141
|
function main() {
|
|
129
142
|
const rows = readTelemetryRows();
|
|
@@ -133,6 +146,13 @@ function main() {
|
|
|
133
146
|
agents,
|
|
134
147
|
};
|
|
135
148
|
writeAtomic(METRICS_PATH, JSON.stringify(payload, null, 2) + '\n');
|
|
149
|
+
// Write lightweight phase-totals.json so budget-enforcer can read phase spend
|
|
150
|
+
// in O(1) without replaying the full JSONL on every agent spawn (WR-02).
|
|
151
|
+
const phaseTotals = {
|
|
152
|
+
generated_at: new Date().toISOString(),
|
|
153
|
+
totals: aggregateByPhase(rows),
|
|
154
|
+
};
|
|
155
|
+
writeAtomic(PHASE_TOTALS_PATH, JSON.stringify(phaseTotals, null, 2) + '\n');
|
|
136
156
|
}
|
|
137
157
|
|
|
138
158
|
try {
|
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;
|
|
@@ -35,7 +35,11 @@ fi
|
|
|
35
35
|
|
|
36
36
|
# Count actual fixture files (should be 4 frozen feeds + 1 README; we only
|
|
37
37
|
# care that at least one XML/JSON fixture is present).
|
|
38
|
-
|
|
38
|
+
# Use null-delimited find to handle filenames with spaces/newlines (WR-05).
|
|
39
|
+
FIXTURE_COUNT=0
|
|
40
|
+
while IFS= read -r -d '' _f; do
|
|
41
|
+
FIXTURE_COUNT=$((FIXTURE_COUNT + 1))
|
|
42
|
+
done < <(find "$FIXTURE_DIR" -maxdepth 1 -type f \( -name '*.atom' -o -name '*.rss' -o -name '*.json' \) -print0)
|
|
39
43
|
if [ "$FIXTURE_COUNT" -lt 1 ]; then
|
|
40
44
|
echo "FAIL: $FIXTURE_DIR contains no feed fixtures (.atom/.rss/.json)." >&2
|
|
41
45
|
exit 1
|
|
@@ -35,6 +35,8 @@ Flags can be combined: `--refresh --prompt` is valid (re-fetch, then enrich). `-
|
|
|
35
35
|
- Print: `No cache. Network may be unreachable or the hook has not run yet. Try /gdd:check-update --refresh.`
|
|
36
36
|
- Exit.
|
|
37
37
|
|
|
38
|
+
<!-- markdownlint-disable MD025 -->
|
|
39
|
+
|
|
38
40
|
4. **Dismiss path** (if `--dismiss` in flags):
|
|
39
41
|
Compute new config contents and write atomically. The python heredoc receives CONFIG_PATH and LATEST_TAG via the ENVIRONMENT (env-prefix form — `KEY=VALUE python3 <<PY`), NOT via trailing argv. Passing `python3 -c '...' KEY=VALUE` makes Python treat the assignments as `sys.argv`, which the old draft did incorrectly; env-prefix form is the portable fix.
|
|
40
42
|
|
package/skills/explore/SKILL.md
CHANGED
|
@@ -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
|
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# Branch Protection — Two-Phase Rollout
|
|
2
|
-
|
|
3
|
-
Per D-16 / D-17: branch protection on `main` is rolled out in two phases. The
|
|
4
|
-
repo admin applies each phase manually via `scripts/apply-branch-protection.sh`
|
|
5
|
-
(no CI automation — avoids leaking repo admin credentials).
|
|
6
|
-
|
|
7
|
-
## Phase A — Advisory (initial state)
|
|
8
|
-
|
|
9
|
-
Status checks **run** on every push, but they are **not required to merge**.
|
|
10
|
-
This is the default posture while CI stabilizes and baselines are established.
|
|
11
|
-
|
|
12
|
-
Apply:
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
bash scripts/apply-branch-protection.sh --advisory
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Effects:
|
|
19
|
-
|
|
20
|
-
- Required status checks: **none** (checks run but don't block)
|
|
21
|
-
- `main` accepts direct pushes (the solo maintainer's existing workflow)
|
|
22
|
-
- Force-push: allowed by admins
|
|
23
|
-
|
|
24
|
-
## Phase B — Enforcing (after first clean release)
|
|
25
|
-
|
|
26
|
-
After the first full release cycle ships clean (plan 13-06 / 13-07 smoke test
|
|
27
|
-
passes on a real tag + GitHub Release), tighten to enforcing.
|
|
28
|
-
|
|
29
|
-
Apply:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
bash scripts/apply-branch-protection.sh --enforcing
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Effects:
|
|
36
|
-
|
|
37
|
-
- Required status checks (all must pass to merge):
|
|
38
|
-
- `Lint (markdown + frontmatter + stale-refs)`
|
|
39
|
-
- `Validate (schemas + plugin + shellcheck)`
|
|
40
|
-
- `Test (Node 22 / ubuntu-latest)`
|
|
41
|
-
- `Test (Node 22 / macos-latest)`
|
|
42
|
-
- `Test (Node 22 / windows-latest)`
|
|
43
|
-
- `Test (Node 24 / ubuntu-latest)`
|
|
44
|
-
- `Test (Node 24 / macos-latest)`
|
|
45
|
-
- `Test (Node 24 / windows-latest)`
|
|
46
|
-
- `Security (secrets + injection scan)`
|
|
47
|
-
- `Size budget (blocking)`
|
|
48
|
-
- Require linear history (no merge commits)
|
|
49
|
-
- Disallow force-push
|
|
50
|
-
- Admins still bypass for emergency fixes (logged)
|
|
51
|
-
|
|
52
|
-
## When to promote Phase A → Phase B
|
|
53
|
-
|
|
54
|
-
- [ ] Wave A + B of Phase 13 merged to main
|
|
55
|
-
- [ ] `release.yml` (plan 13-06) merged and triggered at least once successfully
|
|
56
|
-
- [ ] Release smoke test (plan 13-07) passed on a real tag
|
|
57
|
-
- [ ] Baseline lock (plan 13-08) committed
|
|
58
|
-
|
|
59
|
-
When all four are true, run `apply-branch-protection.sh --enforcing`.
|
|
60
|
-
|
|
61
|
-
## Rollback
|
|
62
|
-
|
|
63
|
-
To revert either phase: `apply-branch-protection.sh --disable` removes all
|
|
64
|
-
protection rules. Use only if a protection misconfiguration is blocking a
|
|
65
|
-
legitimate merge.
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# apply-branch-protection.sh — manually apply branch protection to `main`.
|
|
3
|
-
# Per D-16: this script is run by the repo admin locally, NOT from CI.
|
|
4
|
-
# Usage:
|
|
5
|
-
# bash scripts/apply-branch-protection.sh --advisory
|
|
6
|
-
# bash scripts/apply-branch-protection.sh --enforcing
|
|
7
|
-
# bash scripts/apply-branch-protection.sh --disable
|
|
8
|
-
|
|
9
|
-
set -euo pipefail
|
|
10
|
-
|
|
11
|
-
MODE="${1:-}"
|
|
12
|
-
REPO="${GITHUB_REPOSITORY:-hegemonart/get-design-done}"
|
|
13
|
-
|
|
14
|
-
if ! command -v gh >/dev/null 2>&1; then
|
|
15
|
-
echo "ERROR: gh CLI not found. Install from https://cli.github.com/"
|
|
16
|
-
exit 1
|
|
17
|
-
fi
|
|
18
|
-
|
|
19
|
-
case "$MODE" in
|
|
20
|
-
--advisory)
|
|
21
|
-
echo "Applying ADVISORY branch protection to $REPO main..."
|
|
22
|
-
gh api -X PUT "repos/${REPO}/branches/main/protection" \
|
|
23
|
-
-H "Accept: application/vnd.github+json" \
|
|
24
|
-
-f "required_status_checks=null" \
|
|
25
|
-
-F "enforce_admins=false" \
|
|
26
|
-
-f "required_pull_request_reviews=null" \
|
|
27
|
-
-F "restrictions=null" \
|
|
28
|
-
-F "required_linear_history=false" \
|
|
29
|
-
-F "allow_force_pushes=true" \
|
|
30
|
-
-F "allow_deletions=false"
|
|
31
|
-
echo "Advisory mode applied. CI checks will run but not block merges."
|
|
32
|
-
;;
|
|
33
|
-
--enforcing)
|
|
34
|
-
echo "Applying ENFORCING branch protection to $REPO main..."
|
|
35
|
-
# Status check names must match the `name:` field of each job exactly.
|
|
36
|
-
# See reference/BRANCH-PROTECTION.md §Phase B for the authoritative list.
|
|
37
|
-
gh api -X PUT "repos/${REPO}/branches/main/protection" \
|
|
38
|
-
-H "Accept: application/vnd.github+json" \
|
|
39
|
-
--input - <<'JSON'
|
|
40
|
-
{
|
|
41
|
-
"required_status_checks": {
|
|
42
|
-
"strict": true,
|
|
43
|
-
"contexts": [
|
|
44
|
-
"Lint (markdown + frontmatter + stale-refs)",
|
|
45
|
-
"Validate (schemas + plugin + shellcheck)",
|
|
46
|
-
"Test (Node 22 / ubuntu-latest)",
|
|
47
|
-
"Test (Node 22 / macos-latest)",
|
|
48
|
-
"Test (Node 22 / windows-latest)",
|
|
49
|
-
"Test (Node 24 / ubuntu-latest)",
|
|
50
|
-
"Test (Node 24 / macos-latest)",
|
|
51
|
-
"Test (Node 24 / windows-latest)",
|
|
52
|
-
"Security (secrets + injection scan)",
|
|
53
|
-
"Size budget (blocking)"
|
|
54
|
-
]
|
|
55
|
-
},
|
|
56
|
-
"enforce_admins": false,
|
|
57
|
-
"required_pull_request_reviews": null,
|
|
58
|
-
"restrictions": null,
|
|
59
|
-
"required_linear_history": true,
|
|
60
|
-
"allow_force_pushes": false,
|
|
61
|
-
"allow_deletions": false
|
|
62
|
-
}
|
|
63
|
-
JSON
|
|
64
|
-
echo "Enforcing mode applied. CI must pass before merge; linear history required."
|
|
65
|
-
;;
|
|
66
|
-
--disable)
|
|
67
|
-
echo "Removing branch protection from $REPO main..."
|
|
68
|
-
gh api -X DELETE "repos/${REPO}/branches/main/protection" || true
|
|
69
|
-
echo "Protection removed."
|
|
70
|
-
;;
|
|
71
|
-
*)
|
|
72
|
-
echo "Usage: $0 --advisory | --enforcing | --disable"
|
|
73
|
-
exit 1
|
|
74
|
-
;;
|
|
75
|
-
esac
|