@ericrisco/rsc 1.0.33 → 1.1.0

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/README.md CHANGED
@@ -378,7 +378,7 @@ Three engines + engine-agnostic disciplines. Every engine skill pins the current
378
378
 
379
379
  ### 🎨 Design & content craft
380
380
 
381
- [design](skills/design/) · [presentations](skills/presentations/) · [course-storytelling](skills/course-storytelling/) · [course-builder](skills/course-builder/) · [technical-writing](skills/technical-writing/) · [translation-l10n](skills/translation-l10n/)
381
+ [design-loop](skills/design-loop/) · [design](skills/design/) · [design-dna](skills/design-dna/) · [presentations](skills/presentations/) · [course-storytelling](skills/course-storytelling/) · [course-builder](skills/course-builder/) · [technical-writing](skills/technical-writing/) · [translation-l10n](skills/translation-l10n/)
382
382
 
383
383
  ### 🧠 Knowledge & meta
384
384
 
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "1.0.33",
2
+ "version": "1.1.0",
3
3
  "counts": {
4
- "skills": 276
4
+ "skills": 278
5
5
  },
6
6
  "skills": [
7
7
  {
@@ -1404,6 +1404,29 @@
1404
1404
  "conversion"
1405
1405
  ],
1406
1406
  "recommends": [
1407
+ "design-loop",
1408
+ "design-dna",
1409
+ "nextjs",
1410
+ "marketing"
1411
+ ],
1412
+ "profiles": [
1413
+ "full"
1414
+ ]
1415
+ },
1416
+ {
1417
+ "id": "design-dna",
1418
+ "description": "Use when a design has won and its look must survive: codify it into a permanent style skill with measured ratios, bans and tests that can fail, then build new pieces in that exact look. Fires on 'turn this into a skill', 'keep the same style', 'my output drifted'. NOT finding the winner (that is `design-loop`), NOT the words (that is `marketing`).",
1419
+ "tags": [
1420
+ "design",
1421
+ "style",
1422
+ "dna",
1423
+ "codify",
1424
+ "skill-factory",
1425
+ "consistency"
1426
+ ],
1427
+ "recommends": [
1428
+ "design-loop",
1429
+ "design",
1407
1430
  "nextjs",
1408
1431
  "marketing"
1409
1432
  ],
@@ -1420,6 +1443,7 @@
1420
1443
  "craft"
1421
1444
  ],
1422
1445
  "recommends": [
1446
+ "design-loop",
1423
1447
  "animate",
1424
1448
  "review-animations",
1425
1449
  "apple-design",
@@ -1430,6 +1454,25 @@
1430
1454
  "full"
1431
1455
  ]
1432
1456
  },
1457
+ {
1458
+ "id": "design-loop",
1459
+ "description": "Use when something must be built well and there is a real reference it should beat: turn what makes the reference good into measured mechanisms, then run a builder and three fresh-context critics until all three pass. Fires on 'design loop', 'make it as good as <site>', 'it looks generic'. NOT keeping an already-won look (that is `design-dna`).",
1460
+ "tags": [
1461
+ "design",
1462
+ "critique",
1463
+ "loop",
1464
+ "reference",
1465
+ "quality",
1466
+ "convergence"
1467
+ ],
1468
+ "recommends": [
1469
+ "design",
1470
+ "design-dna",
1471
+ "marketing",
1472
+ "nextjs"
1473
+ ],
1474
+ "profiles": []
1475
+ },
1433
1476
  {
1434
1477
  "id": "digitalocean",
1435
1478
  "description": "Use when deploying or operating a workload on DigitalOcean — Droplet vs App Platform vs Functions, doctl, app spec YAML, Managed Postgres/MySQL/Valkey on the VPC, S3-compatible Spaces + CDN. NOT host-agnostic CI/CD or rollback strategy (that is `deployment`), NOT a bare Hetzner VPS (that is `hetzner`), NOT another managed PaaS (that is `railway`).",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ericrisco/rsc",
3
- "version": "1.0.33",
3
+ "version": "1.1.0",
4
4
  "description": "Eric Risco's agent-skills catalog as a granular, self-recommending CLI installer.",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/doctor.js CHANGED
@@ -7,6 +7,7 @@ import { loadManifest } from './lib/manifest.js';
7
7
  import { listBackups } from './lib/backups.js';
8
8
  import { SDD_GATE_TEXT } from '../targets/hook-once.mjs';
9
9
  import { isEnabled, checkSello, readSello, countFindings, readEffectiveConfig, validateRiskConfig } from '../targets/sello.mjs';
10
+ import { designIdentity } from './lib/design-identity.js';
10
11
  import { countGaps } from './lib/capabilities.js';
11
12
 
12
13
  // The sello's health, surfaced where the user already looks (spec: non-blocking
@@ -57,6 +58,10 @@ export function doctor({ target, home, cwd }) {
57
58
  },
58
59
  contextBudget: contextBudget({ target, home, cwd }),
59
60
  sello: selloStatus(root),
61
+ // Whether this harness has a design identity at all. `design` has always DECLARED that it
62
+ // stops without one; until lib/design-identity.js nothing checked it (P2). Reported here,
63
+ // never nagged about and never blocking: a missing identity is low risk (P7).
64
+ designIdentity: designIdentity(root),
60
65
  // An inert gate must never read as an armed one (the same rule the sello follows):
61
66
  // the gitmoji guard has a per-project kill switch, so say which state it is in.
62
67
  gitmojiGuard: existsSync(join(root, '.rsc', '.no-gitmoji')) ? 'opted-out' : 'armed',
@@ -0,0 +1,117 @@
1
+ // Does this harness have a design identity, and can we tell?
2
+ //
3
+ // `design` has declared for a long time that without a brand study it STOPS and does not design.
4
+ // Nothing checked it: `grep -rln "wiki/brand" scripts/` returned nothing before this file existed.
5
+ // That is P2 — a rule declared binding with no mechanism is a decorative gate. Spec:
6
+ // 02-DOCS/wiki/sdd/specs/design-area-cleanup.md
7
+ //
8
+ // Three states, and the third is the whole point. A checker that cannot look must never report
9
+ // green: that exact defect was found and fixed in the design-dna style checker two days earlier,
10
+ // where regex probes ran against empty text and returned vacuous passes. Absence of evidence is
11
+ // reported as absence of evidence.
12
+ //
13
+ // Read-only. It never writes: 02-DOCS is untracked (P9) and there is no undo there.
14
+ import { existsSync, lstatSync, readFileSync, readdirSync, statSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+
17
+ // Where the harness keeps durable design knowledge, and the section in the root instructions file
18
+ // that points at it. Both are the `design`/`harness` convention, not invented here.
19
+ export const BRAND_DIR = join('02-DOCS', 'wiki', 'brand');
20
+ export const BRAND_SECTION = '## Brand & voice';
21
+
22
+ /**
23
+ * @returns {{state: 'present'|'missing'|'inconclusive', linked: boolean, articles: string[],
24
+ * reason: string, fix?: string}}
25
+ * `present` — articles exist under the brand dir. `linked` says whether the root
26
+ * instructions file points at them, which is how other skills find it.
27
+ * `missing` — looked, found nothing. Carries the fix (P6: a denial carries its way out).
28
+ * `inconclusive` — could not look. Never conflated with `missing`, and never with green.
29
+ */
30
+ export function designIdentity(root = process.cwd()) {
31
+ const dir = join(root, BRAND_DIR);
32
+
33
+ let articles;
34
+ try {
35
+ if (!existsSync(dir)) {
36
+ // existsSync FOLLOWS symlinks, so a dangling one reports false and would be filed as "no
37
+ // identity here". It is not absence, it is unreadability — the same conflation this whole
38
+ // area was cleaning up. lstat sees the link itself and tells the two apart.
39
+ let danglingLink = false;
40
+ try { danglingLink = lstatSync(dir).isSymbolicLink(); } catch { /* genuinely absent */ }
41
+ if (danglingLink) {
42
+ return {
43
+ state: 'inconclusive',
44
+ linked: false,
45
+ articles: [],
46
+ reason: `${BRAND_DIR} is a symlink that does not resolve`,
47
+ };
48
+ }
49
+ return {
50
+ state: 'missing',
51
+ linked: false,
52
+ articles: [],
53
+ reason: `no ${BRAND_DIR}/ in this harness`,
54
+ fix: 'run `design-loop` to converge on a winner, then `design-dna` to write its record here',
55
+ };
56
+ }
57
+ // Not statSync: it follows symlinks, so a symlink here could report on a directory outside the
58
+ // declared zone. Same reason drift-check.js:167 uses dirents.
59
+ if (!statSync(dir).isDirectory()) {
60
+ return {
61
+ state: 'inconclusive',
62
+ linked: false,
63
+ articles: [],
64
+ reason: `${BRAND_DIR} exists but is not a directory`,
65
+ };
66
+ }
67
+ articles = readdirSync(dir).filter((f) => f.endsWith('.md') && f !== 'index.md');
68
+ } catch (e) {
69
+ // Unreadable for any reason (permissions, dangling symlink, ELOOP). We did not look, so we
70
+ // cannot say. Reporting `missing` here would invent a finding; reporting green would hide one.
71
+ return {
72
+ state: 'inconclusive',
73
+ linked: false,
74
+ articles: [],
75
+ reason: `cannot read ${BRAND_DIR}: ${e.code || e.message}`,
76
+ };
77
+ }
78
+
79
+ // The link matters on its own: five skills reach the identity through the root instructions file,
80
+ // so an unlinked identity is one they will not find.
81
+ let linked = false;
82
+ const claudeMd = join(root, 'CLAUDE.md');
83
+ if (existsSync(claudeMd)) {
84
+ try {
85
+ linked = readFileSync(claudeMd, 'utf8').includes(BRAND_SECTION);
86
+ } catch {
87
+ // The articles are what we were asked about and we did read them. A CLAUDE.md we could not
88
+ // read makes `linked` unknown, not the whole answer unknown — report it in the reason.
89
+ return {
90
+ state: articles.length ? 'present' : 'missing',
91
+ linked: false,
92
+ articles,
93
+ reason: `${articles.length} article(s), but CLAUDE.md could not be read to confirm the link`,
94
+ };
95
+ }
96
+ }
97
+
98
+ if (!articles.length) {
99
+ return {
100
+ state: 'missing',
101
+ linked,
102
+ articles: [],
103
+ reason: `${BRAND_DIR}/ exists but holds no identity article`,
104
+ fix: 'run `design-loop` to converge on a winner, then `design-dna` to write its record here',
105
+ };
106
+ }
107
+
108
+ return {
109
+ state: 'present',
110
+ linked,
111
+ articles,
112
+ reason: linked
113
+ ? `${articles.length} article(s), linked from CLAUDE.md`
114
+ : `${articles.length} article(s), but no "${BRAND_SECTION}" section in CLAUDE.md points at them`,
115
+ ...(linked ? {} : { fix: `add a "${BRAND_SECTION}" section to CLAUDE.md linking ${BRAND_DIR}/` }),
116
+ };
117
+ }
@@ -25,7 +25,7 @@ export const DOMAINS = [
25
25
  { title: 'Ship & operate — devops', ids: ['docker', 'github-actions', 'git-workflow', 'domains-dns', 'monitoring', 'email-deliverability', 'scaling', 'deployment', 'deprecation'] },
26
26
  { title: 'Ship & operate — quality & security', ids: ['code-review', 'security-scan', 'secure-coding', 'testing-py', 'testing-web', 'testing-go', 'e2e-testing', 'accessibility', 'performance', 'error-handling', 'observability'] },
27
27
  { title: 'Motion & interface craft', ids: ['design-eng', 'animate', 'animate-expo', 'review-animations', 'improve-animations', 'find-animation-opportunities', 'animation-vocabulary', 'apple-design', 'prototype'] },
28
- { title: 'Design & content craft', ids: ['design', 'presentations', 'course-storytelling', 'course-builder', 'technical-writing', 'translation-l10n'] },
28
+ { title: 'Design & content craft', ids: ['design-loop', 'design', 'design-dna', 'presentations', 'course-storytelling', 'course-builder', 'technical-writing', 'translation-l10n'] },
29
29
  { title: 'Knowledge & meta', ids: ['knowledge-ops', 'codebase-onboarding', 'research-ops', 'decision-records', 'continuous-learning', 'skill-scout', 'context-budget', 'roast-me', 'fable-operator'] },
30
30
  ];
31
31
 
@@ -11,6 +11,8 @@ const OUTCOMES = {
11
11
  go: 'Your Go backend service',
12
12
  postgresdb: 'Store your data reliably',
13
13
  design: 'Make it look good and convert',
14
+ 'design-loop': 'Make it as good as the site you admire',
15
+ 'design-dna': 'Keep every page looking like yours',
14
16
  marketing: 'Copy that sells',
15
17
  presentations: 'On-brand presentations',
16
18
  'course-storytelling': 'Teach so it actually lands',