create-issflow 1.9.1 → 1.10.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.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +2 -2
  3. package/package.json +1 -1
  4. package/template/.issflow/README.md +1 -1
  5. package/template/.issflow/commands/design.md +12 -6
  6. package/template/.issflow/commands/phase.md +2 -0
  7. package/template/.issflow/hooks/approval-policy.js +78 -0
  8. package/template/.issflow/hooks/plan-gate.js +49 -56
  9. package/template/.issflow/hooks/verify-approval.js +43 -0
  10. package/template/.issflow/istartsoft-flow/METHODOLOGY.md +53 -5
  11. package/template/.issflow/skills/ui-ux-pro-max/LICENSE.upstream +21 -0
  12. package/template/.issflow/skills/ui-ux-pro-max/SKILL.md +70 -0
  13. package/template/.issflow/skills/ui-ux-pro-max/UPSTREAM.md +4 -0
  14. package/template/.issflow/skills/ui-ux-pro-max/data/app-interface.csv +33 -0
  15. package/template/.issflow/skills/ui-ux-pro-max/data/catalog-summary.json +78 -0
  16. package/template/.issflow/skills/ui-ux-pro-max/data/charts.csv +26 -0
  17. package/template/.issflow/skills/ui-ux-pro-max/data/colors.csv +193 -0
  18. package/template/.issflow/skills/ui-ux-pro-max/data/data-provenance.json +1348 -0
  19. package/template/.issflow/skills/ui-ux-pro-max/data/google-fonts.csv +1935 -0
  20. package/template/.issflow/skills/ui-ux-pro-max/data/icons.csv +106 -0
  21. package/template/.issflow/skills/ui-ux-pro-max/data/landing.csv +35 -0
  22. package/template/.issflow/skills/ui-ux-pro-max/data/motion.csv +18 -0
  23. package/template/.issflow/skills/ui-ux-pro-max/data/products.csv +193 -0
  24. package/template/.issflow/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  25. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
  26. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  27. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/avalonia.csv +57 -0
  28. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  29. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +60 -0
  30. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/javafx.csv +76 -0
  31. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  32. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
  33. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/nextjs.csv +62 -0
  34. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +77 -0
  35. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +68 -0
  36. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  37. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/react.csv +67 -0
  38. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/shadcn.csv +69 -0
  39. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/svelte.csv +56 -0
  40. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  41. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
  42. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/uno.csv +60 -0
  43. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/uwp.csv +56 -0
  44. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  45. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/winui.csv +60 -0
  46. package/template/.issflow/skills/ui-ux-pro-max/data/stacks/wpf.csv +57 -0
  47. package/template/.issflow/skills/ui-ux-pro-max/data/styles.csv +89 -0
  48. package/template/.issflow/skills/ui-ux-pro-max/data/typography.csv +75 -0
  49. package/template/.issflow/skills/ui-ux-pro-max/data/ui-reasoning.csv +193 -0
  50. package/template/.issflow/skills/ui-ux-pro-max/data/ux-guidelines.csv +120 -0
  51. package/template/.issflow/skills/ui-ux-pro-max/references/pro-rules.md +117 -0
  52. package/template/.issflow/skills/ui-ux-pro-max/references/quick-reference.md +256 -0
  53. package/template/.issflow/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  54. package/template/.issflow/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
  55. package/template/.issflow/skills/ui-ux-pro-max/scripts/__pycache__/reasoning_contract.cpython-313.pyc +0 -0
  56. package/template/.issflow/skills/ui-ux-pro-max/scripts/core.py +993 -0
  57. package/template/.issflow/skills/ui-ux-pro-max/scripts/design_system.py +1668 -0
  58. package/template/.issflow/skills/ui-ux-pro-max/scripts/reasoning_contract.py +123 -0
  59. package/template/.issflow/skills/ui-ux-pro-max/scripts/search.py +177 -0
  60. package/template/.issflow/skills/ux-design/SKILL.md +3 -0
  61. package/template/.issflow/templates/PLAN-SCOPED.md +36 -0
  62. package/template/.issflow/templates/UX-PACK-template.md +1 -0
package/README.md CHANGED
@@ -32,7 +32,7 @@ The portable kit (every tool) in `<project>/.issflow/`:
32
32
 
33
33
  - `agents/` — planner · researcher · implementer · test-author · debugger · e2e-runner · synthesizer
34
34
  - `commands/` — `/overview` `/feature` `/goal` `/propose` `/phase` `/sprint` `/ui-audit` `/qa-audit` `/security-audit` `/release` `/uat` `/change-request` `/replan` `/quick` `/synthesize` `/runbook` `/store-wisdom` `/log-issue` `/log-decision` `/unstuck`
35
- - `skills/` — caveman · grill-me · karpathy-guidelines · ux-design · security · code-standards
35
+ - `skills/` — caveman · grill-me · karpathy-guidelines · ux-design · ui-ux-pro-max · security · code-standards
36
36
  - `hooks/` — session-start · context-guard · plan-gate (rule-13 enforcement) · pre-compact · subagent-stop · feature-gate (Stop gate for `/feature`, artifact-verified)
37
37
  - `istartsoft-flow/METHODOLOGY.md` — the full methodology (single source of truth)
38
38
 
package/bin/cli.js CHANGED
@@ -227,8 +227,8 @@ function agentsMd() {
227
227
  '`/change-request` · `/replan` · `/synthesize` · `/runbook` · `/store-wisdom` ·',
228
228
  '`/log-issue` · `/log-decision` · `/unstuck`', '',
229
229
  '## Skills — `.issflow/skills/` (loaded on demand)', '',
230
- 'caveman · grill-me · karpathy-guidelines · ux-design · security · code-standards ·',
231
- 'support-triage', '',
230
+ 'caveman · grill-me · karpathy-guidelines · ux-design · ui-ux-pro-max · security ·',
231
+ 'code-standards · support-triage', '',
232
232
  '## Autonomy', '',
233
233
  'Planning (`/overview` grill + plan approval) always asks — that input is cheap.',
234
234
  '**AUTO (default)** governs the DEV loop: follow the plan, decide + log + continue,',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-issflow",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "description": "Scaffold the iStartSoftFlow AI-coding workflow into a project. Stack-agnostic, tool-agnostic (Claude Code, Codex, Cursor, Gemini, Aider), non-destructive.",
5
5
  "bin": {
6
6
  "create-issflow": "bin/cli.js"
@@ -14,7 +14,7 @@ This directory is the **portable workflow kit** (tool-agnostic). Every
14
14
  |------|------|
15
15
  | `agents/` | Fresh-context workers (planner, researcher, implementer, …) |
16
16
  | `commands/` | Procedures — Default: `/overview` `/feature` `/quick`; Advanced: `/propose` `/design` `/support` … |
17
- | `skills/` | On-demand cookbooks (ux-design, security, support-triage, …) |
17
+ | `skills/` | On-demand cookbooks (ux-design, ui-ux-pro-max, security, support-triage, …) |
18
18
  | `hooks/` | Lifecycle enforcement scripts |
19
19
  | `istartsoft-flow/` | METHODOLOGY.md — every hard rule lives here once |
20
20
  | `templates/` | Proposal / UX pack / architecture / CR / FEATURE / automation |
@@ -24,13 +24,19 @@ and STOP. Write nothing.
24
24
  Target: `$ARGUMENTS` → `ux` | `arch` | `both` (default **both**).
25
25
 
26
26
  ## STEP 1 — UX PACK (if ux|both)
27
- 1. Apply `ux-design` skill (authoring mode): wireframe baseline + cookbook direction.
28
- 2. Scaffold or update `docs/UX-PACK.md` from `.issflow/templates/UX-PACK-template.md`.
29
- 3. Fill flows, screen inventory, tokens/frame, a11y notes from OVERVIEW + PLAN.
30
- 4. SHOW me UX-PACK.md. **STOP for approval.**
31
- 5. On approve: stamp `> Approval: approved <date> v<n>`; set `design: approved <date>`
27
+ 1. Apply `ui-ux-pro-max` (visual direction): run the kit engine
28
+ `.issflow/skills/ui-ux-pro-max/scripts/search.py --design-system` from
29
+ OVERVIEW product + industry. Fold into UX-PACK tokens — do not create a
30
+ parallel `design-system/` SoT. If Python 3 is missing, ask me to install it
31
+ (do not install tools yourself).
32
+ 2. Apply `ux-design` skill (authoring mode): wireframe baseline + cookbook direction.
33
+ Catalogue output must still pass the cookbook (tokens, a11y, no emoji icons).
34
+ 3. Scaffold or update `docs/UX-PACK.md` from `.issflow/templates/UX-PACK-template.md`.
35
+ 4. Fill flows, screen inventory, tokens/frame, a11y notes from OVERVIEW + PLAN.
36
+ 5. SHOW me UX-PACK.md. **STOP for approval.**
37
+ 6. On approve: stamp `> Approval: approved <date> v<n>`; set `design: approved <date>`
32
38
  in STATE.md; HISTORY line `design v<n> approved`.
33
- 6. On change request: revise and re-show; header stays PENDING.
39
+ 7. On change request: revise and re-show; header stays PENDING.
34
40
 
35
41
  ## STEP 2 — ARCHITECTURE (if arch|both)
36
42
  1. Dispatch `researcher` (DESIGN) only if open unknowns remain; else use OVERVIEW.
@@ -119,6 +119,8 @@ for the slice using the declared E2E runner (it brings the test stack up/down
119
119
  itself). Then run the `ux-design` cookbook + wireframe check on the new UI
120
120
  (hard rule 9): the UI must match the wireframe frame and pass the cookbook
121
121
  (tokens, a11y/WCAG AA, states, breakpoints). A `ux-design` BLOCK is a GATE FAIL.
122
+ Do not re-run `ui-ux-pro-max` `--design-system` here — the look is locked in the
123
+ approved UX pack.
122
124
  Non-frontend phase -> skip §3c.
123
125
 
124
126
  PHASE GATE (rule 5) -> go to §4/§5/§6 (see GATE below).
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ function fail(message) {
8
+ return { ok: false, reason: message };
9
+ }
10
+
11
+ function within(root, relative) {
12
+ if (!relative || path.isAbsolute(relative)) return false;
13
+ const full = path.resolve(root, relative);
14
+ return full.startsWith(path.resolve(root) + path.sep);
15
+ }
16
+
17
+ function read(root, relative) {
18
+ if (!within(root, relative)) throw new Error('Path escapes repository');
19
+ return fs.readFileSync(path.join(root, relative), 'utf8');
20
+ }
21
+
22
+ function gitBlobSha(content) {
23
+ const bytes = Buffer.from(content, 'utf8');
24
+ return crypto.createHash('sha1').update(`blob ${bytes.length}\0`).update(bytes).digest('hex');
25
+ }
26
+
27
+ function activePlan(root, explicit) {
28
+ if (explicit) return explicit.replaceAll('\\', '/');
29
+ const pointer = path.join(root, 'docs', 'ACTIVE-PLAN');
30
+ if (fs.existsSync(pointer)) return fs.readFileSync(pointer, 'utf8').trim();
31
+ const plansDir = path.join(root, 'docs', 'plans');
32
+ if (fs.existsSync(plansDir) && fs.readdirSync(plansDir).some((name) => /^PLAN-.*\.md$/i.test(name))) {
33
+ throw new Error('docs/ACTIVE-PLAN is required when scoped plans exist');
34
+ }
35
+ return 'docs/PLAN.md'; // Legacy projects remain supported.
36
+ }
37
+
38
+ function matchesPath(pattern, relative) {
39
+ if (pattern.endsWith('/**')) return relative.startsWith(pattern.slice(0, -2));
40
+ return relative === pattern;
41
+ }
42
+
43
+ function validate(root, { planFile, changedFiles = [] } = {}) {
44
+ try {
45
+ const relativePlan = activePlan(root, planFile);
46
+ if (!within(root, relativePlan)) return fail('Invalid plan path');
47
+ const plan = read(root, relativePlan);
48
+ if (relativePlan === 'docs/PLAN.md') {
49
+ const match = plan.match(/^>\s*Approval:\s*(.+)$/mi);
50
+ if (!match || !/^approved\b/i.test(match[1].trim())) return fail('Legacy PLAN.md is not approved');
51
+ return { ok: true, mode: 'legacy', planFile: relativePlan, warning: 'Legacy approval is not revision-scoped; migrate to docs/plans/' };
52
+ }
53
+
54
+ const id = plan.match(/^#\s+(PLAN-[A-Za-z0-9-]+)\b/m)?.[1];
55
+ const revision = Number(plan.match(/^>\s*Revision:\s*(\d+)\s*$/mi)?.[1]);
56
+ if (!id || !Number.isSafeInteger(revision) || revision < 1) return fail('Plan ID or revision missing');
57
+ const approvalFile = `docs/approvals/${id}.json`;
58
+ const approval = JSON.parse(read(root, approvalFile));
59
+ if (approval.planId !== id || approval.revision !== revision) return fail('Approval is for a different plan ID or revision');
60
+ if (approval.planBlobSha !== gitBlobSha(plan)) return fail('Plan changed after approval; request a new approval');
61
+ if (!approval.approvedBy || !approval.approvedAt || !approval.approvalSource) return fail('Approval provenance is incomplete');
62
+ if (!Array.isArray(approval.allowedPaths) || approval.allowedPaths.length === 0 ||
63
+ !approval.allowedPaths.every((p) => typeof p === 'string' && p.length > 0 && !p.includes('..') && !p.startsWith('/'))) {
64
+ return fail('Approved path boundaries are missing or invalid');
65
+ }
66
+ for (const file of changedFiles) {
67
+ const normalized = file.replaceAll('\\', '/');
68
+ if (!within(root, normalized) || !approval.allowedPaths.some((p) => matchesPath(p, normalized))) {
69
+ return fail(`File outside approved plan scope: ${file}`);
70
+ }
71
+ }
72
+ return { ok: true, mode: 'scoped', planFile: relativePlan, planId: id, revision, approval };
73
+ } catch (error) {
74
+ return fail(`Approval verification failed: ${error.message}`);
75
+ }
76
+ }
77
+
78
+ module.exports = { validate, gitBlobSha, activePlan, matchesPath };
@@ -1,64 +1,57 @@
1
1
  #!/usr/bin/env node
2
- // PreToolUse plan gate — hard rule 13 as code. While docs/PLAN.md carries
3
- // `> Approval: PENDING`, no SOURCE file may be created or edited: the plan
4
- // needs a human sign-off before build work starts. Docs, kit config, and
5
- // planning artifacts stay writable (planning is exactly what PENDING is for).
6
- // An active feature lane is exempt — its doc approval is its own rule-13-scoped
7
- // gate and it never touches the main PLAN. Fail-OPEN on any error: a hook bug
8
- // must never wedge the tool loop. Pure Node, cross-platform, zero deps.
9
2
  'use strict';
10
- const fs = require('fs');
11
- const path = require('path');
12
3
 
13
- const silent = () => process.exit(0);
14
- const deny = (reason) => {
15
- process.stdout.write(JSON.stringify({
16
- hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason },
17
- }));
18
- process.exit(0);
19
- };
20
-
21
- let input = '';
22
- process.stdin.setEncoding('utf8');
23
- process.stdin.on('data', (d) => (input += d));
24
- process.stdin.on('end', () => {
25
- let evt;
26
- try { evt = JSON.parse(input); } catch (_) { return silent(); }
27
- try { run(evt); } catch (_) { silent(); }
28
- });
29
-
30
- function run(evt) {
31
- const MUTATORS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
32
- if (!MUTATORS.has(evt.tool_name || '')) return silent();
33
-
34
- const projectDir = process.env.CLAUDE_PROJECT_DIR || evt.cwd || '.';
35
- const read = (f) => { try { return fs.readFileSync(path.join(projectDir, f), 'utf8'); } catch (_) { return null; } };
36
-
37
- const plan = read(path.join('docs', 'PLAN.md'));
38
- if (!plan) return silent(); // no plan world (e.g. /quick-only repo)
39
- const approval = plan.match(/^>\s*Approval:\s*(.+)$/m);
40
- if (!approval) return silent(); // not our PLAN format — don't guess
41
- if (!/pending/i.test(approval[1])) return silent(); // signed off — build away
4
+ // Claude PreToolUse guard. Shell commands and other hosts require the CLI/CI verifier.
5
+ const path = require('node:path');
6
+ const { validate } = require('./approval-policy');
42
7
 
43
- // the feature lane carries its own scoped approval and never edits the main PLAN.
44
- const state = read(path.join('docs', 'STATE.md')) || '';
45
- if (/^\s*feature:\s*\S+\s*\(active\)/m.test(state)) return silent();
8
+ const MUTATORS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
46
9
 
47
- const file = String((evt.tool_input || {}).file_path || (evt.tool_input || {}).notebook_path || '');
48
- if (!file) return silent();
49
- const rel = path.relative(projectDir, path.resolve(projectDir, file)).split(path.sep).join('/');
10
+ function deny(reason) {
11
+ process.stdout.write(JSON.stringify({
12
+ hookSpecificOutput: {
13
+ hookEventName: 'PreToolUse',
14
+ permissionDecision: 'deny',
15
+ permissionDecisionReason: reason,
16
+ },
17
+ }) + '\n');
18
+ }
50
19
 
51
- // writable while PENDING: planning + docs + kit + repo meta — never product source.
52
- const ALLOW = [
53
- /^docs\//, /^\.issflow\//, /^\.claude\//, /^\.cursor\//, /^\.github\//, /^\.aider/, /^\.git/,
54
- /^(README|AGENTS|CLAUDE|GEMINI|CHANGELOG|LICENSE)[^/]*$/i, /^[^/]+\.md$/,
55
- ];
56
- if (ALLOW.some((re) => re.test(rel))) return silent();
20
+ function safePlanningPath(relative) {
21
+ return /^docs\/plans\/PLAN-[\w-]+\.md$/.test(relative) ||
22
+ /^docs\/features\/[\w.-]+\/(PLAN|FEATURE)\.md$/.test(relative) ||
23
+ relative === 'docs/ACTIVE-PLAN';
24
+ }
57
25
 
58
- deny(
59
- `PLAN-APPROVAL gate (hard rule 13): docs/PLAN.md still reads "> Approval: PENDING", ` +
60
- `so source changes are blocked (attempted: ${rel}). Get the plan signed off first — ` +
61
- `run /overview's PLAN-APPROVAL step (or /replan, then re-approve) and stamp the header ` +
62
- `"> Approval: approved <date> v<n>". Planning artifacts under docs/ stay writable.`
63
- );
26
+ function run(event) {
27
+ if (!MUTATORS.has(event.tool_name)) return;
28
+ const root = path.resolve(process.env.CLAUDE_PROJECT_DIR || event.cwd || process.cwd());
29
+ const input = event.tool_input || {};
30
+ const paths = event.tool_name === 'MultiEdit'
31
+ ? [input.file_path]
32
+ : [input.file_path || input.notebook_path];
33
+ if (paths.some((p) => typeof p !== 'string' || !p.trim())) {
34
+ return deny('Approval gate cannot determine target path');
35
+ }
36
+ const relative = paths.map((p) => path.relative(root, path.resolve(root, p)).split(path.sep).join('/'));
37
+ if (relative.some((p) => p.startsWith('..') || path.isAbsolute(p))) {
38
+ return deny('Cannot edit outside repository');
39
+ }
40
+ if (relative.every(safePlanningPath)) return;
41
+ if (relative.some((p) => /^docs\/approvals\//.test(p))) {
42
+ return deny('Approval records require a separate owner-controlled action');
43
+ }
44
+ const result = validate(root, { changedFiles: relative });
45
+ if (!result.ok) deny(`Plan approval required: ${result.reason}`);
64
46
  }
47
+
48
+ let raw = '';
49
+ process.stdin.setEncoding('utf8');
50
+ process.stdin.on('data', (chunk) => { raw += chunk; });
51
+ process.stdin.on('end', () => {
52
+ try {
53
+ run(JSON.parse(raw));
54
+ } catch (error) {
55
+ deny(`Approval gate error: ${error.message}`);
56
+ }
57
+ });
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { execFileSync } = require('node:child_process');
5
+ const path = require('node:path');
6
+ const { validate } = require('./approval-policy');
7
+
8
+ function main(args) {
9
+ const root = process.cwd();
10
+ const baseIndex = args.indexOf('--base');
11
+ const filesIndex = args.indexOf('--files');
12
+ const planIndex = args.indexOf('--plan');
13
+ if (baseIndex !== -1 && filesIndex !== -1) throw new Error('Choose --base or --files');
14
+ const planFile = planIndex < 0 ? undefined : args[planIndex + 1];
15
+ let changedFiles = [];
16
+ if (baseIndex >= 0) {
17
+ const base = args[baseIndex + 1];
18
+ if (!base || base.startsWith('--')) throw new Error('Missing --base value');
19
+ const mergeBase = execFileSync('git', ['merge-base', base, 'HEAD'], { cwd: root, encoding: 'utf8' }).trim();
20
+ const diff = execFileSync('git', ['diff', '--name-only', '--diff-filter=ACMRT', mergeBase, 'HEAD'], { cwd: root, encoding: 'utf8' });
21
+ changedFiles = diff.split(/\r?\n/).filter(Boolean);
22
+ } else if (filesIndex >= 0) {
23
+ const files = args[filesIndex + 1];
24
+ if (!files || files.startsWith('--')) throw new Error('Missing --files value');
25
+ changedFiles = files.split(',').map((f) => f.trim()).filter(Boolean);
26
+ }
27
+ const result = validate(root, { planFile, changedFiles });
28
+ if (!result.ok) {
29
+ console.error(`BLOCKED: ${result.reason}`);
30
+ process.exitCode = 1;
31
+ return;
32
+ }
33
+ console.log(`Approval verified: ${result.planId || 'legacy'}${result.revision ? ` revision ${result.revision}` : ''}`);
34
+ if (result.warning) console.warn(result.warning);
35
+ console.log(`Checked ${changedFiles.length} changed file(s)`);
36
+ }
37
+
38
+ try {
39
+ main(process.argv.slice(2));
40
+ } catch (error) {
41
+ console.error(`BLOCKED: ${error.message}`);
42
+ process.exitCode = 1;
43
+ }
@@ -34,10 +34,56 @@
34
34
 
35
35
  Caveman ULTRA mode always on. Apply the `karpathy-guidelines` skill (engineering
36
36
  discipline) on every coding and debugging task. Apply the `ux-design` skill (the UX
37
- cookbook + wireframe baseline) on every UI-facing task. Apply the `security` skill
38
- (the Secure SDLC cookbook) at design (threat model), while coding (secure coding),
39
- and before any deploy. Apply the `code-standards` skill (naming per the language's
40
- own idiom + the declared architecture) on every coding task.
37
+ cookbook + wireframe baseline) on every UI-facing task. When **authoring** a new
38
+ visual direction (`/design`, or no approved UX-PACK yet), apply `ui-ux-pro-max`
39
+ first and fold the look into the UX pack — do not re-roll styles at phase CLOSE.
40
+ Apply the `security` skill (the Secure SDLC cookbook) at design (threat model),
41
+ while coding (secure coding), and before any deploy. Apply the `code-standards`
42
+ skill (naming per the language's own idiom + the declared architecture) on every
43
+ coding task.
44
+
45
+ -----
46
+
47
+ ## Plan-scoped approvals (new work)
48
+
49
+ For each new task, create one `docs/plans/PLAN-<id>.md` with a unique ID, revision,
50
+ scope, changed-file boundaries, acceptance criteria, tests, and risks. Set
51
+ `docs/ACTIVE-PLAN` to its relative path. One human approval authorizes exactly
52
+ one plan revision, not other plans or future edits to that plan.
53
+
54
+ Approval is recorded separately in `docs/approvals/PLAN-<id>.json` with
55
+ `planId`, `revision`, `planBlobSha` (Git blob SHA-1 of the exact Markdown),
56
+ `approvedBy`, `approvedAt`, `approvalSource`, and `allowedPaths`.
57
+ The approval record is created only after explicit human consent. Revisions,
58
+ including seemingly small edits, invalidate the old digest and require new approval.
59
+ Do not let an agent self-approve a plan.
60
+
61
+ Use `node .issflow/hooks/verify-approval.js --base origin/main` to check the
62
+ active plan and changed paths. Run `node --test tests/approval-policy.test.js`
63
+ when those tests are installed. For an existing project with only `docs/PLAN.md`,
64
+ the verifier retains legacy `> Approval: approved ...` compatibility and warns
65
+ that it is not revision-scoped. Migrate intentionally, not by silently rewriting
66
+ existing plans.
67
+
68
+ **Enforcement boundary:** The Claude PreToolUse hook protects supported file-edit
69
+ tools; it does not intercept shell commands, GitHub API writes, or every Codex
70
+ execution mode. The verifier checks the local checkout/PR diff, but a writable
71
+ approval JSON is not cryptographic proof of the human approver. Require protected
72
+ branches, required CI checks, and an actual human GitHub PR review for merge.
73
+ No tool can treat a self-edited approval file as owner authorization.
74
+
75
+ **Recovery:** If approval metadata is missing or malformed, stop implementation.
76
+ A human should inspect the plan, restore the correct pointer and approval record,
77
+ then rerun verification. Never bypass the gate by editing `STATE.md` or disabling
78
+ checks. A scoped feature lane does not inherit approval from another plan.
79
+
80
+ **Engineering:** Prefer feature-based modules and vertical slices. Keep route
81
+ adapters thin; put business rules near their feature. Use idiomatic names,
82
+ cohesive files, explicit contracts, and minimal abstractions. Comments explain
83
+ non-obvious intent, not line-by-line mechanics. Use concise Conventional Commit
84
+ subjects (`feat(scope): change`, `fix(scope): change`); avoid tool signatures
85
+ and promotional boilerplate while preserving truthful Git author/committer
86
+ metadata and any required disclosures.
41
87
 
42
88
  -----
43
89
 
@@ -155,7 +201,7 @@ those gates, never the plan, the money, or the design/arch frame.
155
201
  | House role | Kit surface |
156
202
  |------------|-------------|
157
203
  | BA / PM | `/overview` grill · `grill-me` · PLAN / PROPOSAL / CR gates |
158
- | UX / UI | `/design` (UX pack) · `ux-design` skill · `/ui-audit` · rule 9 CLOSE |
204
+ | UX / UI | `/design` (UX pack) · `ui-ux-pro-max` (look) · `ux-design` (frame + gate) · `/ui-audit` · rule 9 CLOSE |
159
205
  | Architect | `/design` (ARCHITECTURE) · `researcher` DESIGN · `code-standards` · `/log-decision` |
160
206
  | Developer | `implementer` · `/phase` · `/feature` · `/quick` |
161
207
  | QA | `test-author` · `e2e-runner` · `/qa-audit` · `/uat` |
@@ -691,6 +737,8 @@ the KB. The kit works normally without a KB.
691
737
  `scripts/regression.sh` (default mock; `--real` runs the real corpus).
692
738
  - `.issflow/skills/ux-design/` — the UX cookbook + wireframe baseline (read on
693
739
  demand for any UI work).
740
+ - `.issflow/skills/ui-ux-pro-max/` — visual direction for `/design` (vendored
741
+ search engine + catalogue; MIT upstream).
694
742
  - `.issflow/skills/security/` — the Secure SDLC cookbook + threat-modeling /
695
743
  secure-coding / pentest / standards references (read on demand for security work).
696
744
  - `.issflow/skills/code-standards/` — naming-per-language + architecture cookbook
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Next Level Builder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: ui-ux-pro-max
3
+ description: >
4
+ Design intelligence for NEW visual direction — styles, palettes, type, landing
5
+ patterns. Use when authoring `/design` UX-PACK, setting OVERVIEW UX direction,
6
+ or the project has no approved UX pack yet. Triggers: "/design", "UX pack",
7
+ "visual direction", "design system", "palette", "typography", "landing pattern",
8
+ "ทิศทางดีไซน์", "โทนสี". Skip for backend, and skip re-rolling styles when an
9
+ approved UX-PACK already exists (follow that frame; validate with ux-design).
10
+ ---
11
+
12
+ # ui-ux-pro-max — visual direction (authoring)
13
+
14
+ Vendored from [ui-ux-pro-max-skill](https://github.com/nextlevelbuilder/ui-ux-pro-max-skill)
15
+ (MIT — see `LICENSE.upstream` + `UPSTREAM.md`). Engine lives **in this folder**.
16
+
17
+ `ux-design` remains the **gate** (hard rule 9 — wireframe + cookbook). This skill
18
+ picks the **look**. Do not let a catalogue restyle override an approved UX pack.
19
+
20
+ ## When
21
+
22
+ - `/design` STEP 1 (author the UX pack) — **required** when UI is in scope.
23
+ - First visual direction (OVERVIEW UX / no approved `docs/UX-PACK.md`).
24
+ - Human explicitly asks to restyle — then re-run and wait for UX-PACK re-approve.
25
+
26
+ **Not** at `/phase` CLOSE. CLOSE = `ux-design` cookbook against the approved pack.
27
+ Do not run `--design-system` again per slice.
28
+
29
+ ## Engine
30
+
31
+ ```
32
+ python3 .issflow/skills/ui-ux-pro-max/scripts/search.py
33
+ ```
34
+
35
+ If this project's kit was mirrored for Claude Code, the same files exist under
36
+ `.claude/skills/ui-ux-pro-max/` — use whichever path is present.
37
+
38
+ Try `python3`, then `python`, then `py -3`. Stdlib only. If Python 3 is missing,
39
+ **ask the human** to install it (python.org / Homebrew / apt). Do not install
40
+ Python or npm yourself. Do not tell them to run `uipro init` — the catalogue
41
+ is already in the kit.
42
+
43
+ On-demand detail: `references/quick-reference.md`, `references/pro-rules.md`.
44
+
45
+ ## Authoring workflow
46
+
47
+ 1. Product / industry / keywords from OVERVIEW + PLAN (2–5 terms). Detect stack
48
+ from the project; never assume HTML+Tailwind.
49
+ 2. Run:
50
+
51
+ ```
52
+ python3 .issflow/skills/ui-ux-pro-max/scripts/search.py "<product> <industry> <keywords>" --design-system -p "<project>" -f markdown
53
+ ```
54
+
55
+ Retry once if empty. Do not persist unverified output. Do **not** pass
56
+ `--persist` / do **not** write a parallel `design-system/` tree — fold into
57
+ `docs/UX-PACK.md` → Frame & tokens (style, palette, type, effects,
58
+ anti-patterns). Kit SoT stays UX-PACK.
59
+ 3. Optional domain search only for a named gap:
60
+
61
+ ```
62
+ python3 .issflow/skills/ui-ux-pro-max/scripts/search.py "<query>" --domain ux|icons|chart|typography|style|color|landing
63
+ python3 .issflow/skills/ui-ux-pro-max/scripts/search.py "<query>" --stack <detected>
64
+ ```
65
+
66
+ 4. Then apply `ux-design` authoring (wireframe + cookbook). Catalogue must still
67
+ pass: no emoji icons, tokens not raw hex in components, WCAG AA.
68
+
69
+ If UX-PACK is already **approved**, read it and stop — unless the human asked to
70
+ change the look.
@@ -0,0 +1,4 @@
1
+ upstream: https://github.com/nextlevelbuilder/ui-ux-pro-max-skill
2
+ commit: 623a9a9
3
+ license: MIT (LICENSE.upstream)
4
+ omitted: scripts/tests, validate_data.py, phosphor-icons-upstream.json, google-font-licenses.json
@@ -0,0 +1,33 @@
1
+ No,Category,Issue,Keywords,Platform,Description,Do,Don't,Code Example Good,Code Example Bad,Severity
2
+ 1,Accessibility,Icon Button Labels,icon button accessibilityLabel,iOS/Android/React Native,Icon-only buttons must expose an accessible label,Set accessibilityLabel or label prop on icon buttons,Icon buttons without accessible names,"<Pressable accessibilityLabel=""Close""><XIcon /></Pressable>",<Pressable><XIcon /></Pressable>,Critical
3
+ 2,Accessibility,Form Control Labels,form input label accessibilityLabel,iOS/Android/React Native,All inputs must have a visible label and an accessibility label,Pair Text label with input and set accessibilityLabel,Inputs with placeholder only,"<View><Text>Email</Text><TextInput accessibilityLabel=""Email address"" /></View>","<TextInput placeholder=""Email"" /></View>",Critical
4
+ 3,Accessibility,Role & Traits,accessibilityRole accessibilityTraits,iOS/Android/React Native,Interactive elements must expose correct roles/traits,Use accessibilityRole/button/link/checkbox etc.,Rely on generic views with no roles,"<Pressable accessibilityRole=""button"">Submit</Pressable>",<View onTouchStart={submit}>Submit</View>,High
5
+ 4,Accessibility,Dynamic Updates,accessibilityLiveRegion announce,iOS/Android/React Native,Async status updates should be announced to screen readers,Use accessibilityLiveRegion or announceForAccessibility,Update text silently with no announcement,"<Text accessibilityLiveRegion=""polite"">{status}</Text>",<Text>{status}</Text>,Medium
6
+ 5,Accessibility,Decorative Icons,accessible={false} importantForAccessibility,iOS/Android/React Native,Decorative icons should be hidden from screen readers,Mark decorative icons as not accessible,Have screen reader read every icon,"<Icon accessible={false} importantForAccessibility=""no"" />",<Icon />,Medium
7
+ 6,Touch,Touch Target Size,touch target size platform runtime arbitration iOS 44pt Android 48dp web 24 CSS px React Native hitSlop,iOS/Android/React Native,Native targets use 44pt on iOS and 48dp on Android; web WCAG 2.2 has a separate 24 by 24 CSS px minimum with exceptions,Select 44pt for iOS and 48dp for Android at runtime; evaluate web targets separately against WCAG 2.5.8 and its exceptions,"Collapse iOS 44pt, Android 48dp, and web 24 CSS px into one cross-platform number","Platform.select({ ios: 44, android: 48 }); // web: evaluate 24 CSS px + WCAG exceptions",<Pressable><Icon size={16} /></Pressable>,Critical
8
+ 7,Touch,Touch Spacing,touch spacing gap 8px,iOS/Android/React Native,Adjacent touch targets need enough spacing,Keep at least 8dp spacing between touchables,Cluster many buttons with no gap,<View style={{ gap: 8 }}><Button ... /><Button ... /></View>,<View><Button ... /><Button ... /></View>,Medium
9
+ 8,Touch,Gesture Conflicts,scroll swipe back gesture,iOS/Android/React Native,Custom gestures must not break system scroll/back,Reserve horizontal swipes for carousels,Full-screen custom swipe conflicting with back,HorizontalPager inside vertical ScrollView,PanResponder on full screen blocking back,High
10
+ 9,Navigation,Back Behavior,back handler navigation stack,iOS/Android/React Native,Back navigation should be predictable and preserve state,Use navigation.goBack and keep screen state,Reset stack or exit app unexpectedly,onPress={() => navigation.goBack()},BackHandler.exitApp() on first press,Critical
11
+ 10,Navigation,Bottom Tabs,tab bar max items,iOS/Android/React Native,Bottom tab bar should have at most 5 primary items,Use 3–5 tabs and move extras to More/Settings,Overloaded tab bar with many icons,Home/Explore/Profile/Settings,Home/Explore/Shop/Cart/Profile/Settings/More,Medium
12
+ 11,Navigation,Modal Escape,modal dismiss close affordance,iOS/Android/React Native,Modals/sheets must have clear close actions,Provide close button and swipe-down where platform expects,Trapping users in modal with no obvious exit,"<Modal><Button title=""Close"" onPress={onClose} /></Modal>",<Modal><View>{children}</View></Modal>,High
13
+ 12,State,Preserve Screen State,navigation preserve state,iOS/Android/React Native,Returning to a screen should restore its scroll and form state,Keep components mounted or persist state,Reset list scroll and form inputs on every visit,<Tab.Navigator screenOptions={{ unmountOnBlur: false }}>,<Tab.Screen options={{ unmountOnBlur: true }} />,Medium
14
+ 13,Feedback,Loading Indicators,activity indicator skeleton,iOS/Android/React Native,Show visible feedback during network operations,Use ActivityIndicator or skeleton for >300ms operations,Leave button and screen frozen,"{loading ? <ActivityIndicator /> : <Button title=""Save"" />}"," ""<Button title=""""Save"""" onPress={submit} /> // no loading""",High
15
+ 14,Feedback,Success Feedback,toast checkmark banner,iOS/Android/React Native,Confirm successful actions with brief feedback,Show toast/checkmark or banner,Complete actions silently with no confirmation,showToast('Saved successfully'),// silently update state only,Medium
16
+ 15,Feedback,Error Feedback,inline error banner,iOS/Android/React Native,Show clear error messages near the problem,input-level error + summary banner,Only change border color with no explanation,<TextInput ... /><Text style={{color:'red'}}>{error}</Text>,<TextInput style={{borderColor:'red'}} />,High
17
+ 16,Forms,Inline Validation,onBlur validation,iOS/Android/React Native,Validate inputs on blur or submit with clear messaging,Validate onBlur and onSubmit,Validate on every keystroke causing jank,onBlur={() => validateEmail(value)},onChangeText={v => validateEmail(v)} // every char,Medium
18
+ 17,Forms,Keyboard Type,keyboardType returnKeyType,iOS/Android/React Native,Use appropriate keyboardType and returnKeyType,Match email/tel/number/search types,Use default keyboard for all inputs,"<TextInput keyboardType=""email-address"" />","<TextInput keyboardType=""default"" />",Medium
19
+ 18,Forms,Auto Focus & Next,autoFocus blurOnSubmit onSubmitEditing,iOS/Android/React Native,Guide users through form fields with Next/Done flows,Use onSubmitEditing to focus next input,Force users to tap each field manually,onSubmitEditing={() => nextRef.current?.focus()},"// no onSubmitEditing, manual tap only",Low
20
+ 19,Forms,Password Visibility,secureTextEntry toggle,iOS/Android/React Native,Allow toggling password visibility securely,Provide Show/Hide icon toggling secureTextEntry,Force users to type blind with no option,<TextInput secureTextEntry={secure} /><Icon onPress={toggle} />,<TextInput secureTextEntry /> // no toggle,Medium
21
+ 20,Performance,Virtualize Long Lists,FlatList SectionList virtualization,iOS/Android/React Native,Use FlatList/SectionList for lists over ~50 items,Use keyExtractor and initialNumToRender appropriately,Render hundreds of items with ScrollView,<FlatList data={items} renderItem={...} />,<ScrollView>{items.map(renderItem)}</ScrollView>,High
22
+ 21,Performance,Image Size & Cache,Image resize cache,iOS/Android/React Native,Use correctly sized and cached images,Use Image component with proper resizeMode and caching,Load full-resolution images everywhere,"<Image source={{uri}} resizeMode=""cover"" />",<Image source={require('4k.png')} /> // small avatar,Medium
23
+ 22,Performance,Debounce High-Freq Events,debounce scroll search,iOS/Android/React Native,Debounce scroll/search callbacks to avoid jank,Wrap handlers with debounce/throttle,Run heavy logic on every event,onScroll={debouncedHandleScroll},onScroll={handleScrollHeavy},Medium
24
+ 23,Animation,Duration & Easing,animation duration easing,iOS/Android/React Native,Micro-interactions should be 150–300ms with native-like easing,Use ease-out for enter/ease-in for exit,Use long or linear animations for core UI,"Animated.timing(..., { duration: 200, easing: Easing.out(Easing.quad) })","Animated.timing(..., { duration: 800, easing: Easing.linear })",Medium
25
+ 24,Animation,Respect Reduced Motion,reduced motion accessibility,iOS/Android/React Native,Respect OS reduced-motion accessibility setting,Check reduceMotionEnabled and simplify animations,Ignore user motion preferences,if (reduceMotionEnabled) skipAnimation(),Always run complex parallax animations,Critical
26
+ 25,Animation,Limited Continuous Motion,loop animation loader,iOS/Android/React Native,Reserve infinite animations for loaders and live data,Use looping only where necessary,Keep decorative elements looping forever,Animated.loop(loaderAnim) for ActivityIndicator,Animated.loop(bounceAnim) on background icons,Medium
27
+ 26,Typography,Base Font Size,fontScale dynamic type,iOS/Android/React Native,Body text must be readable and support Dynamic Type,Use platform fontScale and at least 14–16pt base,Render critical text below 12pt,<Text style={{ fontSize: 16 }}>Body</Text>,<Text style={{ fontSize: 10 }}>Body</Text>,High
28
+ 27,Typography,Dynamic Type Support,allowFontScaling adjustsFontSizeToFit,iOS/Android/React Native,Support system text scaling without breaking layout,Set allowFontScaling and test large text,Disable scaling on all text globally,<Text allowFontScaling>{label}</Text>,<Text allowFontScaling={false}>{label}</Text>,High
29
+ 28,Safe Areas,Safe Area Insets,safe area insets notch gesture,iOS/Android/React Native,Content must not overlap notches/gesture bars,Wrap screens in SafeAreaView or apply insets,Place tappable content under system bars,<SafeAreaView style={{ flex: 1 }}><Screen /></SafeAreaView>,<View style={{ flex: 1 }}><Screen /></View>,High
30
+ 29,Theming,Light/Dark Contrast,dark mode contrast tokens,iOS/Android/React Native,Ensure sufficient contrast in both light and dark themes,Use semantic tokens and test both themes,Reuse light-theme grays directly in dark mode,colors.textPrimaryDark = '#F9FAFB',colors.textPrimaryDark = '#9CA3AF' on '#111827',High
31
+ 30,Anti-Pattern,No Gesture-Only Actions,gesture only hidden controls,iOS/Android/React Native,Don't rely solely on hidden gestures for core actions,Provide visible buttons in addition to gestures,Rely on swipe/shake only with no UI affordance,Swipe to delete + visible Delete button,Only shake device to undo with no UI,Critical
32
+ 31,Accessibility,Dragging Alternatives,accessible drag interaction drag single pointer alternative keyboard drag alternative reorder resize move buttons React Native native runtime platform router arbitration iOS Android web,iOS/Android/React Native,React Native drag and reorder operations need a non-drag path selected for the active native runtime,Provide named Move up/down buttons or a position menu beside drag handles; route iOS and Android behavior through the runtime platform adapter,"Make drag, swipe, or a web-only pointer handler the only way to reorder native content","<Button title=""Move up"" onPress={() => moveItem(index, index - 1)} />",<DragHandle /> only,High
33
+ 32,Forms,Authentication Reuse,password manager passkey paste redundant entry,iOS/Android/React Native,Authentication and multi-step flows should reuse prior values,Support password managers passkeys paste and prefilled confirmed values,Force users to retype credentials or the same data in one flow,"textContentType=""password"" autoComplete=""current-password""",onPaste disabled,Critical
@@ -0,0 +1,78 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "verifiedAt": "2026-08-26",
4
+ "counts": {
5
+ "styles": {
6
+ "total": 88,
7
+ "searchable": 79,
8
+ "active": 50,
9
+ "supplemental": 29,
10
+ "deprecated": 9
11
+ },
12
+ "products": 192,
13
+ "palettes": 192,
14
+ "reasoningProfiles": 192,
15
+ "fontPairings": 74,
16
+ "googleFonts": 1934,
17
+ "curatedIcons": 105,
18
+ "upstreamPhosphorIcons": 1512,
19
+ "uxGuidelines": 119,
20
+ "motionPresets": 17,
21
+ "chartTypes": 25,
22
+ "stacks": 22,
23
+ "stackGuidelines": 1271
24
+ },
25
+ "snapshots": {
26
+ "google-fonts.csv": {
27
+ "sha256": "1c8c3b2ea1faf6a1012da463756def8b3889db33f2226f0343bb4daa80307d03"
28
+ },
29
+ "google-font-licenses.json": {
30
+ "sha256": "35688523f2955795caa1a47c53b83099e60c1708461476f9cc3a050cf3b0148a"
31
+ },
32
+ "icons.csv": {
33
+ "sha256": "50816c6012030178195a16ee481ebf58b47bd985d70e8ec58886cc83f6eddafc"
34
+ },
35
+ "phosphor-icons-upstream.json": {
36
+ "sha256": "2399325233b277b5c97a80e6a5e8941154f5d057beee4e7613db87c87d700236"
37
+ }
38
+ },
39
+ "promotionPolicy": {
40
+ "changedFamilySetRequiresExplicitApproval": true,
41
+ "relevanceGateRequired": true,
42
+ "unlicensedFamiliesExcluded": true
43
+ },
44
+ "pendingCandidates": [
45
+ {
46
+ "family": "Edu NSW ACT Cursive",
47
+ "reason": "No exact-family METADATA.pb entry in the official google/fonts repository snapshot"
48
+ },
49
+ {
50
+ "family": "Edu NSW ACT Hand Pre",
51
+ "reason": "No exact-family METADATA.pb entry in the official google/fonts repository snapshot"
52
+ },
53
+ {
54
+ "family": "Edu QLD Hand",
55
+ "reason": "No exact-family METADATA.pb entry in the official google/fonts repository snapshot"
56
+ },
57
+ {
58
+ "family": "Edu SA Hand",
59
+ "reason": "No exact-family METADATA.pb entry in the official google/fonts repository snapshot"
60
+ },
61
+ {
62
+ "family": "Edu VIC WA NT Hand",
63
+ "reason": "No exact-family METADATA.pb entry in the official google/fonts repository snapshot"
64
+ },
65
+ {
66
+ "family": "Edu VIC WA NT Hand Pre",
67
+ "reason": "No exact-family METADATA.pb entry in the official google/fonts repository snapshot"
68
+ },
69
+ {
70
+ "family": "Google Sans",
71
+ "reason": "No exact-family METADATA.pb entry in the official google/fonts repository snapshot"
72
+ },
73
+ {
74
+ "family": "Google Sans Flex",
75
+ "reason": "No exact-family METADATA.pb entry in the official google/fonts repository snapshot"
76
+ }
77
+ ]
78
+ }