baldart 3.6.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/CHANGELOG.md +599 -0
- package/README.md +566 -0
- package/VERSION +1 -0
- package/bin/baldart.js +143 -0
- package/framework/.claude/agents/REGISTRY.md +169 -0
- package/framework/.claude/agents/api-perf-cost-auditor.md +291 -0
- package/framework/.claude/agents/code-reviewer.md +350 -0
- package/framework/.claude/agents/codebase-architect.md +391 -0
- package/framework/.claude/agents/coder.md +291 -0
- package/framework/.claude/agents/deep-human-insight.md +198 -0
- package/framework/.claude/agents/doc-reviewer.md +440 -0
- package/framework/.claude/agents/email-deliverability-architect.md +193 -0
- package/framework/.claude/agents/hybrid-ml-architect.md +285 -0
- package/framework/.claude/agents/hyper-gamification-designer.md +149 -0
- package/framework/.claude/agents/legal-counsel-gdpr.md +179 -0
- package/framework/.claude/agents/marketing-conversion-strategist.md +162 -0
- package/framework/.claude/agents/motion-expert.md +108 -0
- package/framework/.claude/agents/onboarding-architect-lead.md +230 -0
- package/framework/.claude/agents/plan-auditor.md +546 -0
- package/framework/.claude/agents/prd-card-writer.md +372 -0
- package/framework/.claude/agents/prd.md +744 -0
- package/framework/.claude/agents/qa-sentinel.md +305 -0
- package/framework/.claude/agents/remotion-animator-orchestrator.md +218 -0
- package/framework/.claude/agents/security-reviewer.md +276 -0
- package/framework/.claude/agents/senior-researcher.md +175 -0
- package/framework/.claude/agents/seo-analytics-strategist.md +156 -0
- package/framework/.claude/agents/skill-improver.md +61 -0
- package/framework/.claude/agents/ui-expert.md +191 -0
- package/framework/.claude/agents/visual-designer.md +190 -0
- package/framework/.claude/agents/website-orchestrator.md +118 -0
- package/framework/.claude/agents/wiki-curator.md +145 -0
- package/framework/.claude/commands/baldart-push.md +15 -0
- package/framework/.claude/commands/check.md +237 -0
- package/framework/.claude/commands/codexreview.md +203 -0
- package/framework/.claude/commands/design-review.md +11 -0
- package/framework/.claude/commands/issue-review.md +34 -0
- package/framework/.claude/commands/new.md +331 -0
- package/framework/.claude/commands/qa.md +257 -0
- package/framework/.claude/hooks/framework-edit-gate.js +208 -0
- package/framework/.claude/hooks/lint-before-commit.sh.template +66 -0
- package/framework/.claude/settings.local.json.example +32 -0
- package/framework/.claude/skills/api-design-principles/SKILL.md +567 -0
- package/framework/.claude/skills/api-design-principles/assets/api-design-checklist.md +155 -0
- package/framework/.claude/skills/api-design-principles/assets/rest-api-template.py +182 -0
- package/framework/.claude/skills/api-design-principles/references/graphql-schema-design.md +583 -0
- package/framework/.claude/skills/api-design-principles/references/rest-best-practices.md +408 -0
- package/framework/.claude/skills/baldart-push/SKILL.md +222 -0
- package/framework/.claude/skills/bug/SKILL.md +200 -0
- package/framework/.claude/skills/bug/references/logging-patterns.md +174 -0
- package/framework/.claude/skills/capture/SKILL.md +125 -0
- package/framework/.claude/skills/capture/references/synthesis-template.md +42 -0
- package/framework/.claude/skills/context-primer/SKILL.md +189 -0
- package/framework/.claude/skills/copywriting/SKILL.md +273 -0
- package/framework/.claude/skills/copywriting/references/copy-frameworks.md +338 -0
- package/framework/.claude/skills/copywriting/references/natural-transitions.md +252 -0
- package/framework/.claude/skills/doc-writing-for-rag/SKILL.md +119 -0
- package/framework/.claude/skills/doc-writing-for-rag/references/before-after-examples.md +291 -0
- package/framework/.claude/skills/doc-writing-for-rag/references/compact-templates.md +183 -0
- package/framework/.claude/skills/doc-writing-for-rag/references/frontmatter-minimal.md +112 -0
- package/framework/.claude/skills/doc-writing-for-rag/references/line-count-targets.md +110 -0
- package/framework/.claude/skills/doc-writing-for-rag/references/schemas-and-errors.md +129 -0
- package/framework/.claude/skills/find-skills/SKILL.md +133 -0
- package/framework/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/framework/.claude/skills/frontend-design/SKILL.md +84 -0
- package/framework/.claude/skills/gamification-design/SKILL.md +130 -0
- package/framework/.claude/skills/issue-review/SKILL.md +45 -0
- package/framework/.claude/skills/kie-ai/SKILL.md +262 -0
- package/framework/.claude/skills/kie-ai/references/models-catalog.md +272 -0
- package/framework/.claude/skills/kie-ai/scripts/kie_api.sh +209 -0
- package/framework/.claude/skills/kie-ai/scripts/remove_greenscreen.py +69 -0
- package/framework/.claude/skills/kie-ai/scripts/setup_api_key.sh +77 -0
- package/framework/.claude/skills/motion-design/LICENSE +21 -0
- package/framework/.claude/skills/motion-design/README.md +82 -0
- package/framework/.claude/skills/motion-design/SKILL.md +336 -0
- package/framework/.claude/skills/motion-design/director/choreography.md +93 -0
- package/framework/.claude/skills/motion-design/director/context-adaptation.md +83 -0
- package/framework/.claude/skills/motion-design/director/core-philosophy.md +53 -0
- package/framework/.claude/skills/motion-design/director/decision-framework.md +91 -0
- package/framework/.claude/skills/motion-design/director/disney-principles.md +102 -0
- package/framework/.claude/skills/motion-design/director/emotion-mapping.md +71 -0
- package/framework/.claude/skills/motion-design/director/motion-personality.md +89 -0
- package/framework/.claude/skills/motion-design/director/narrative-structure.md +62 -0
- package/framework/.claude/skills/motion-design/patterns/ambient-continuous.md +81 -0
- package/framework/.claude/skills/motion-design/patterns/entrance-exit.md +82 -0
- package/framework/.claude/skills/motion-design/patterns/multi-element.md +69 -0
- package/framework/.claude/skills/motion-design/patterns/state-feedback.md +96 -0
- package/framework/.claude/skills/motion-design/reference/property-selection.md +95 -0
- package/framework/.claude/skills/motion-design/reference/quality-checklist.md +67 -0
- package/framework/.claude/skills/motion-design/reference/timing-easing-tables.md +106 -0
- package/framework/.claude/skills/motion-design/reference/troubleshooting.md +73 -0
- package/framework/.claude/skills/new/SKILL.md +1687 -0
- package/framework/.claude/skills/playwright-skill/API_REFERENCE.md +652 -0
- package/framework/.claude/skills/playwright-skill/SKILL.md +157 -0
- package/framework/.claude/skills/playwright-skill/package.json +26 -0
- package/framework/.claude/skills/prd/SKILL.md +228 -0
- package/framework/.claude/skills/prd/assets/card-template.yml +232 -0
- package/framework/.claude/skills/prd/assets/epic-template.yml +190 -0
- package/framework/.claude/skills/prd/assets/prd-template.md +230 -0
- package/framework/.claude/skills/prd/assets/state-template.md +78 -0
- package/framework/.claude/skills/prd/references/api-perf-gate.md +152 -0
- package/framework/.claude/skills/prd/references/audit-phase.md +478 -0
- package/framework/.claude/skills/prd/references/backlog-phase.md +145 -0
- package/framework/.claude/skills/prd/references/discovery-phase.md +359 -0
- package/framework/.claude/skills/prd/references/impact-analysis.md +233 -0
- package/framework/.claude/skills/prd/references/prd-add-phase.md +214 -0
- package/framework/.claude/skills/prd/references/prd-writing-phase.md +145 -0
- package/framework/.claude/skills/prd/references/research-phase.md +216 -0
- package/framework/.claude/skills/prd/references/ui-design-phase.md +61 -0
- package/framework/.claude/skills/prd/references/validation-phase.md +72 -0
- package/framework/.claude/skills/prd-add/SKILL.md +222 -0
- package/framework/.claude/skills/prd-add/references/impact-analysis.md +233 -0
- package/framework/.claude/skills/remotion-best-practices/SKILL.md +48 -0
- package/framework/.claude/skills/remotion-best-practices/rules/3d.md +86 -0
- package/framework/.claude/skills/remotion-best-practices/rules/animations.md +29 -0
- package/framework/.claude/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
- package/framework/.claude/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
- package/framework/.claude/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
- package/framework/.claude/skills/remotion-best-practices/rules/assets.md +78 -0
- package/framework/.claude/skills/remotion-best-practices/rules/audio.md +169 -0
- package/framework/.claude/skills/remotion-best-practices/rules/calculate-metadata.md +104 -0
- package/framework/.claude/skills/remotion-best-practices/rules/can-decode.md +75 -0
- package/framework/.claude/skills/remotion-best-practices/rules/charts.md +58 -0
- package/framework/.claude/skills/remotion-best-practices/rules/compositions.md +141 -0
- package/framework/.claude/skills/remotion-best-practices/rules/display-captions.md +184 -0
- package/framework/.claude/skills/remotion-best-practices/rules/extract-frames.md +229 -0
- package/framework/.claude/skills/remotion-best-practices/rules/fonts.md +152 -0
- package/framework/.claude/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
- package/framework/.claude/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
- package/framework/.claude/skills/remotion-best-practices/rules/get-video-duration.md +58 -0
- package/framework/.claude/skills/remotion-best-practices/rules/gifs.md +141 -0
- package/framework/.claude/skills/remotion-best-practices/rules/images.md +130 -0
- package/framework/.claude/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
- package/framework/.claude/skills/remotion-best-practices/rules/light-leaks.md +73 -0
- package/framework/.claude/skills/remotion-best-practices/rules/lottie.md +67 -0
- package/framework/.claude/skills/remotion-best-practices/rules/maps.md +401 -0
- package/framework/.claude/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
- package/framework/.claude/skills/remotion-best-practices/rules/measuring-text.md +143 -0
- package/framework/.claude/skills/remotion-best-practices/rules/parameters.md +98 -0
- package/framework/.claude/skills/remotion-best-practices/rules/sequencing.md +118 -0
- package/framework/.claude/skills/remotion-best-practices/rules/subtitles.md +36 -0
- package/framework/.claude/skills/remotion-best-practices/rules/tailwind.md +11 -0
- package/framework/.claude/skills/remotion-best-practices/rules/text-animations.md +20 -0
- package/framework/.claude/skills/remotion-best-practices/rules/timing.md +179 -0
- package/framework/.claude/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
- package/framework/.claude/skills/remotion-best-practices/rules/transitions.md +197 -0
- package/framework/.claude/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
- package/framework/.claude/skills/remotion-best-practices/rules/trimming.md +52 -0
- package/framework/.claude/skills/remotion-best-practices/rules/videos.md +171 -0
- package/framework/.claude/skills/seo-audit/SKILL.md +394 -0
- package/framework/.claude/skills/seo-audit/references/aeo-geo-patterns.md +279 -0
- package/framework/.claude/skills/seo-audit/references/ai-writing-detection.md +190 -0
- package/framework/.claude/skills/simplify/SKILL.md +137 -0
- package/framework/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/framework/.claude/skills/skill-creator/SKILL.md +356 -0
- package/framework/.claude/skills/skill-creator/references/output-patterns.md +82 -0
- package/framework/.claude/skills/skill-creator/references/workflows.md +28 -0
- package/framework/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/framework/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/framework/.claude/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/framework/.claude/skills/ui-design/SKILL.md +199 -0
- package/framework/.claude/skills/ui-design/references/component-discovery.md +54 -0
- package/framework/.claude/skills/ui-design/references/evaluation.md +171 -0
- package/framework/.claude/skills/ui-design/references/generation.md +109 -0
- package/framework/.claude/skills/ui-design/references/inventory.md +59 -0
- package/framework/.claude/skills/webapp-testing/LICENSE.txt +202 -0
- package/framework/.claude/skills/webapp-testing/SKILL.md +123 -0
- package/framework/.claude/skills/webapp-testing/examples/console_logging.py +35 -0
- package/framework/.claude/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/framework/.claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/framework/.claude/skills/webapp-testing/scripts/with_server.py +106 -0
- package/framework/.claude/skills/worktree-manager/SKILL.md +680 -0
- package/framework/AGENTS.md +240 -0
- package/framework/agents/api-contracts.md +137 -0
- package/framework/agents/architecture.md +145 -0
- package/framework/agents/coding-standards.md +148 -0
- package/framework/agents/data-model.md +110 -0
- package/framework/agents/deployment-protocol.md +232 -0
- package/framework/agents/design-review.md +172 -0
- package/framework/agents/env-reference.md +171 -0
- package/framework/agents/github-issue-subagent.md +252 -0
- package/framework/agents/index.md +261 -0
- package/framework/agents/llm-wiki-methodology.md +216 -0
- package/framework/agents/maintenance-protocol.md +305 -0
- package/framework/agents/observability.md +162 -0
- package/framework/agents/performance.md +155 -0
- package/framework/agents/project-context.md +145 -0
- package/framework/agents/runbook.md +208 -0
- package/framework/agents/security.md +168 -0
- package/framework/agents/skills-mapping.md +286 -0
- package/framework/agents/testing.md +111 -0
- package/framework/agents/workflows.md +215 -0
- package/framework/docs/PROJECT-CONFIGURATION.md +336 -0
- package/framework/docs/references/brand-guidelines.md +170 -0
- package/framework/docs/references/ui-guidelines.template.md +182 -0
- package/framework/routines/code-review.routine.yml +46 -0
- package/framework/routines/doc-review.routine.yml +45 -0
- package/framework/routines/ds-drift.routine.yml +52 -0
- package/framework/routines/full-sweep.routine.yml +51 -0
- package/framework/routines/index.yml +70 -0
- package/framework/routines/skill-improve.routine.yml +50 -0
- package/framework/routines/wiki-review.routine.yml +45 -0
- package/framework/templates/baldart.config.template.yml +113 -0
- package/framework/templates/breaking-change-checklist.md +484 -0
- package/framework/templates/feature-card.template.yml +125 -0
- package/framework/templates/overlays/README.md +44 -0
- package/framework/templates/overlays/copywriting.fidelity-example.md +62 -0
- package/framework/templates/overlays/ui-design.fidelity-example.md +75 -0
- package/framework/templates/skill-project-context.snippet.md +19 -0
- package/framework/templates/spec.template.md +208 -0
- package/package.json +51 -0
- package/src/commands/add.js +229 -0
- package/src/commands/configure.js +385 -0
- package/src/commands/doctor.js +486 -0
- package/src/commands/migrate.js +185 -0
- package/src/commands/push.js +0 -0
- package/src/commands/routines.js +269 -0
- package/src/commands/status.js +130 -0
- package/src/commands/update.js +419 -0
- package/src/commands/version.js +88 -0
- package/src/utils/contamination.js +400 -0
- package/src/utils/git.js +181 -0
- package/src/utils/hooks.js +152 -0
- package/src/utils/routine-adapters/claude-code-cloud.js +78 -0
- package/src/utils/routine-adapters/cron.js +138 -0
- package/src/utils/routine-adapters/github-actions.js +141 -0
- package/src/utils/routine-adapters/index.js +21 -0
- package/src/utils/routines.js +166 -0
- package/src/utils/state.js +143 -0
- package/src/utils/symlinks.js +425 -0
- package/src/utils/ui.js +133 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contamination scanner & autofix.
|
|
3
|
+
*
|
|
4
|
+
* Detects project-specific tokens (hardcoded paths, brand identity, opinionated
|
|
5
|
+
* stack choices) that should NOT enter the upstream BALDART framework when a
|
|
6
|
+
* consumer pushes improvements. Patterns are the same ones used to clean the
|
|
7
|
+
* v3.0.0 refactor.
|
|
8
|
+
*
|
|
9
|
+
* Three severities:
|
|
10
|
+
*
|
|
11
|
+
* - 'autofixable' — safe textual substitution (hardcoded paths).
|
|
12
|
+
* autofix() applies these; UI confirms once.
|
|
13
|
+
* - 'requires-decision' — semantic tokens (brand identity, stack opinions).
|
|
14
|
+
* UI lists each line, user decides whether to (a) move
|
|
15
|
+
* to overlay, (b) keep as-is and force push, or (c) abort.
|
|
16
|
+
* - 'block' — secrets / credentials patterns; never auto-cleared.
|
|
17
|
+
*
|
|
18
|
+
* Public API:
|
|
19
|
+
*
|
|
20
|
+
* scan(content, opts?) → { findings: [{rule, line, lineNum, match, severity, suggestion?}] }
|
|
21
|
+
* autofix(content) → { content: <new>, applied: [{rule, count}] }
|
|
22
|
+
* explain(finding) → string (one-line human readable)
|
|
23
|
+
* isClean(findings) → boolean
|
|
24
|
+
* summarise(findings) → { autofixable: N, requiresDecision: N, blocking: N }
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
// -----------------------------------------------------------------------
|
|
28
|
+
// Rules
|
|
29
|
+
// -----------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Each rule:
|
|
33
|
+
* id — short slug
|
|
34
|
+
* severity — 'autofixable' | 'requires-decision' | 'block'
|
|
35
|
+
* pattern — RegExp (must be /g for autofix to work)
|
|
36
|
+
* replace — function(match) → string (only meaningful for autofixable)
|
|
37
|
+
* label — short human label
|
|
38
|
+
* advice — one-line guidance for the UI
|
|
39
|
+
*/
|
|
40
|
+
const RULES = [
|
|
41
|
+
// ---- Autofixable: hardcoded canonical paths ----
|
|
42
|
+
{
|
|
43
|
+
id: 'path-design-system',
|
|
44
|
+
severity: 'autofixable',
|
|
45
|
+
pattern: /\bdocs\/design-system\//g,
|
|
46
|
+
replace: () => '${paths.design_system}/',
|
|
47
|
+
label: 'docs/design-system/',
|
|
48
|
+
advice: 'replace with `${paths.design_system}/`',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'path-references-api',
|
|
52
|
+
severity: 'autofixable',
|
|
53
|
+
pattern: /\bdocs\/references\/api\//g,
|
|
54
|
+
replace: () => '${paths.references_dir}/api/',
|
|
55
|
+
label: 'docs/references/api/',
|
|
56
|
+
advice: 'replace with `${paths.references_dir}/api/`',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'path-references-ui',
|
|
60
|
+
severity: 'autofixable',
|
|
61
|
+
pattern: /\bdocs\/references\/ui\//g,
|
|
62
|
+
replace: () => '${paths.references_dir}/ui/',
|
|
63
|
+
label: 'docs/references/ui/',
|
|
64
|
+
advice: 'replace with `${paths.references_dir}/ui/`',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'path-references-other',
|
|
68
|
+
severity: 'autofixable',
|
|
69
|
+
// Catches docs/references/<anything> but NOT the api/, ui/ branches
|
|
70
|
+
// already covered, and NOT the bare `docs/references` directory token.
|
|
71
|
+
pattern: /\bdocs\/references\/(?!api\/|ui\/)([a-zA-Z0-9_\-\/.]+)/g,
|
|
72
|
+
replace: (_m, rest) => `\${paths.references_dir}/${rest}`,
|
|
73
|
+
label: 'docs/references/<x>',
|
|
74
|
+
advice: 'replace with `${paths.references_dir}/<x>`',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'path-prd',
|
|
78
|
+
severity: 'autofixable',
|
|
79
|
+
pattern: /\bdocs\/prd\//g,
|
|
80
|
+
replace: () => '${paths.prd_dir}/',
|
|
81
|
+
label: 'docs/prd/',
|
|
82
|
+
advice: 'replace with `${paths.prd_dir}/`',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'path-decisions',
|
|
86
|
+
severity: 'autofixable',
|
|
87
|
+
pattern: /\bdocs\/decisions\//g,
|
|
88
|
+
replace: () => '${paths.adrs_dir}/',
|
|
89
|
+
label: 'docs/decisions/',
|
|
90
|
+
advice: 'replace with `${paths.adrs_dir}/`',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'path-wiki',
|
|
94
|
+
severity: 'autofixable',
|
|
95
|
+
pattern: /\bdocs\/wiki\//g,
|
|
96
|
+
replace: () => '${paths.wiki_dir}/',
|
|
97
|
+
label: 'docs/wiki/',
|
|
98
|
+
advice: 'replace with `${paths.wiki_dir}/`',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'path-backlog',
|
|
102
|
+
severity: 'autofixable',
|
|
103
|
+
// Match `backlog/` or `/backlog/` when used as a path token. Don't match
|
|
104
|
+
// the word `backlog` inside prose. We require either: it's preceded by a
|
|
105
|
+
// path-like separator (slash, space, quote, paren, ``), AND followed by
|
|
106
|
+
// something path-like (slash, *, alphanumeric, dot).
|
|
107
|
+
pattern: /(^|[\s"'`(\[])\/?backlog\/(?=[a-zA-Z*.\/]|$|\s|["'`)\]])/g,
|
|
108
|
+
replace: (_m, lead) => `${lead}\${paths.backlog_dir}/`,
|
|
109
|
+
label: 'backlog/',
|
|
110
|
+
advice: 'replace with `${paths.backlog_dir}/`',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'path-components-primitives',
|
|
114
|
+
severity: 'autofixable',
|
|
115
|
+
pattern: /\bsrc\/components\/ui\//g,
|
|
116
|
+
replace: () => '${paths.components_primitives}/',
|
|
117
|
+
label: 'src/components/ui/',
|
|
118
|
+
advice: 'replace with `${paths.components_primitives}/`',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'path-components-root',
|
|
122
|
+
severity: 'autofixable',
|
|
123
|
+
pattern: /\bsrc\/components\/(?!ui\/)/g,
|
|
124
|
+
replace: () => '${paths.components_root}/',
|
|
125
|
+
label: 'src/components/',
|
|
126
|
+
advice: 'replace with `${paths.components_root}/`',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'path-global-styles',
|
|
130
|
+
severity: 'autofixable',
|
|
131
|
+
pattern: /\bsrc\/app\/globals\.css\b/g,
|
|
132
|
+
replace: () => '${paths.global_styles}',
|
|
133
|
+
label: 'src/app/globals.css',
|
|
134
|
+
advice: 'replace with `${paths.global_styles}`',
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
// ---- Requires decision: identity / brand tokens ----
|
|
138
|
+
{
|
|
139
|
+
id: 'identity-neo-brutalism',
|
|
140
|
+
severity: 'requires-decision',
|
|
141
|
+
pattern: /\bNeo-?Brutalism\b/g,
|
|
142
|
+
label: 'Neo-Brutalism',
|
|
143
|
+
advice: 'move to .baldart/overlays/<skill>.md or reference identity.design_philosophy',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'identity-italian-first',
|
|
147
|
+
severity: 'requires-decision',
|
|
148
|
+
pattern: /\bItalian-first\b/gi,
|
|
149
|
+
label: 'Italian-first',
|
|
150
|
+
advice: 'replace with reference to identity.language',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'identity-merchant',
|
|
154
|
+
severity: 'requires-decision',
|
|
155
|
+
pattern: /\b(?:merchant|merchants)\b/gi,
|
|
156
|
+
label: 'merchant',
|
|
157
|
+
advice: 'audience segment — move to overlay or reference identity.audience_segments',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 'identity-customer',
|
|
161
|
+
severity: 'requires-decision',
|
|
162
|
+
pattern: /\b(?:customer|customers)\b/gi,
|
|
163
|
+
label: 'customer',
|
|
164
|
+
advice: 'audience segment — move to overlay or reference identity.audience_segments',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: 'identity-fidelity',
|
|
168
|
+
severity: 'requires-decision',
|
|
169
|
+
pattern: /\bfidelity\b/gi,
|
|
170
|
+
label: 'fidelity',
|
|
171
|
+
advice: 'project name leak — generalise or move to overlay',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 'identity-brutal-wrapper',
|
|
175
|
+
severity: 'requires-decision',
|
|
176
|
+
pattern: /\bBrutal(?:Line|Bar|Donut|Heatmap|Area|Pie|Scatter)Chart\b/g,
|
|
177
|
+
label: 'Brutal*Chart',
|
|
178
|
+
advice: 'project chart wrapper — move to overlay or reference stack.charting.wrappers_root',
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
// ---- Requires decision: stack opinions ----
|
|
182
|
+
{
|
|
183
|
+
id: 'stack-recharts',
|
|
184
|
+
severity: 'requires-decision',
|
|
185
|
+
pattern: /\brecharts\b/gi,
|
|
186
|
+
label: 'recharts',
|
|
187
|
+
advice: 'opinionated charting choice — should live in stack.charting.canonical or overlay',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'stack-nivo',
|
|
191
|
+
severity: 'requires-decision',
|
|
192
|
+
pattern: /@nivo\/[a-z-]+/g,
|
|
193
|
+
label: '@nivo/*',
|
|
194
|
+
advice: 'opinionated charting choice — should live in stack.charting.canonical or overlay',
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: 'stack-forbidden-chart-libs',
|
|
198
|
+
severity: 'requires-decision',
|
|
199
|
+
pattern: /\b(?:chart\.js|echarts|victory|visx|observable-plot|tremor)\b/g,
|
|
200
|
+
label: '<chart-lib>',
|
|
201
|
+
advice: 'forbidden-list entry — should live in stack.charting.forbidden or overlay',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: 'stack-framer-motion',
|
|
205
|
+
severity: 'requires-decision',
|
|
206
|
+
pattern: /\bframer-motion\b/g,
|
|
207
|
+
label: 'framer-motion',
|
|
208
|
+
advice: 'opinionated animation choice — should live in stack.animation.canonical',
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
// ---- Block: secrets / credentials ----
|
|
212
|
+
// Conservative: we flag clearly-secret-like tokens. False positives are
|
|
213
|
+
// acceptable; false negatives are not.
|
|
214
|
+
{
|
|
215
|
+
id: 'secret-api-key',
|
|
216
|
+
severity: 'block',
|
|
217
|
+
// Quoted or unquoted — covers both `apiKey: "abc..."` (JSON/JS) and
|
|
218
|
+
// `API_KEY=abc...` (.env / shell). Conservative on length to avoid
|
|
219
|
+
// false positives on short config flags.
|
|
220
|
+
pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"]?[A-Za-z0-9_\-]{20,}['"]?/gi,
|
|
221
|
+
label: 'API key literal',
|
|
222
|
+
advice: 'looks like a hardcoded API key — remove before pushing',
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: 'secret-github-pat',
|
|
226
|
+
severity: 'block',
|
|
227
|
+
pattern: /\bgh[posu]_[A-Za-z0-9]{30,}\b/g,
|
|
228
|
+
label: 'GitHub PAT',
|
|
229
|
+
advice: 'GitHub personal access token pattern — remove before pushing',
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: 'secret-slack-token',
|
|
233
|
+
severity: 'block',
|
|
234
|
+
pattern: /\bxox[baprs]-[A-Za-z0-9-]{20,}\b/g,
|
|
235
|
+
label: 'Slack token',
|
|
236
|
+
advice: 'Slack token pattern — remove before pushing',
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: 'secret-jwt',
|
|
240
|
+
severity: 'block',
|
|
241
|
+
pattern: /\beyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\b/g,
|
|
242
|
+
label: 'JWT',
|
|
243
|
+
advice: 'JSON Web Token pattern — remove before pushing',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: 'secret-bearer',
|
|
247
|
+
severity: 'block',
|
|
248
|
+
pattern: /bearer\s+[A-Za-z0-9._\-]{20,}/gi,
|
|
249
|
+
label: 'Bearer token',
|
|
250
|
+
advice: 'looks like a hardcoded bearer token — remove before pushing',
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: 'secret-aws',
|
|
254
|
+
severity: 'block',
|
|
255
|
+
pattern: /AKIA[0-9A-Z]{16}/g,
|
|
256
|
+
label: 'AWS access key id',
|
|
257
|
+
advice: 'AWS access key pattern — remove before pushing',
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
id: 'secret-private-key',
|
|
261
|
+
severity: 'block',
|
|
262
|
+
pattern: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/g,
|
|
263
|
+
label: 'PEM private key header',
|
|
264
|
+
advice: 'private key block — remove before pushing',
|
|
265
|
+
},
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
// -----------------------------------------------------------------------
|
|
269
|
+
// Opt-out marker
|
|
270
|
+
// -----------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Some files legitimately quote project-default paths or identity tokens as
|
|
274
|
+
* *examples* (e.g. autodetection probe descriptions, migration guides,
|
|
275
|
+
* starter overlays). Autofixing them would destroy their pedagogical value.
|
|
276
|
+
*
|
|
277
|
+
* Such files declare opt-out with either:
|
|
278
|
+
* - an HTML comment `<!-- contamination-scan: skip -->` anywhere in the
|
|
279
|
+
* first 20 lines, OR
|
|
280
|
+
* - a YAML frontmatter key `contamination_scan: skip`.
|
|
281
|
+
*
|
|
282
|
+
* The scan / autofix entry points consult this first and short-circuit.
|
|
283
|
+
*/
|
|
284
|
+
function isOptedOut(content) {
|
|
285
|
+
if (typeof content !== 'string') return false;
|
|
286
|
+
const head = content.split('\n', 20).join('\n');
|
|
287
|
+
if (/<!--\s*contamination-scan:\s*skip[\s\S]*?-->/i.test(head)) return true;
|
|
288
|
+
if (/^contamination_scan:\s*skip\b/im.test(head)) return true;
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// -----------------------------------------------------------------------
|
|
293
|
+
// Public API
|
|
294
|
+
// -----------------------------------------------------------------------
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Scan a single file's text content. Returns findings grouped by rule.
|
|
298
|
+
* `opts.skipRules` is an optional array of rule ids to ignore (used when
|
|
299
|
+
* the user has already confirmed those tokens should pass).
|
|
300
|
+
*
|
|
301
|
+
* If the file declares the opt-out marker (see isOptedOut), returns an
|
|
302
|
+
* empty findings list — the file is explicitly exempt.
|
|
303
|
+
*/
|
|
304
|
+
function scan(content, opts = {}) {
|
|
305
|
+
if (typeof content === 'string' && isOptedOut(content)) {
|
|
306
|
+
return { findings: [], optedOut: true };
|
|
307
|
+
}
|
|
308
|
+
if (typeof content !== 'string') {
|
|
309
|
+
throw new TypeError('scan(content): expected string');
|
|
310
|
+
}
|
|
311
|
+
const skip = new Set(opts.skipRules || []);
|
|
312
|
+
const lines = content.split('\n');
|
|
313
|
+
const findings = [];
|
|
314
|
+
|
|
315
|
+
for (const rule of RULES) {
|
|
316
|
+
if (skip.has(rule.id)) continue;
|
|
317
|
+
// Reset lastIndex defensively — RULES patterns are /g.
|
|
318
|
+
rule.pattern.lastIndex = 0;
|
|
319
|
+
for (let i = 0; i < lines.length; i++) {
|
|
320
|
+
const line = lines[i];
|
|
321
|
+
// Use String.prototype.matchAll to get all hits per line.
|
|
322
|
+
const local = new RegExp(rule.pattern.source, rule.pattern.flags);
|
|
323
|
+
const hits = line.matchAll(local);
|
|
324
|
+
for (const m of hits) {
|
|
325
|
+
findings.push({
|
|
326
|
+
rule: rule.id,
|
|
327
|
+
label: rule.label,
|
|
328
|
+
severity: rule.severity,
|
|
329
|
+
line: line,
|
|
330
|
+
lineNum: i + 1,
|
|
331
|
+
match: m[0],
|
|
332
|
+
advice: rule.advice,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return { findings };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Apply autofix substitutions across content. Returns the new content plus a
|
|
342
|
+
* tally of which rules fired and how many times.
|
|
343
|
+
*
|
|
344
|
+
* Only rules with severity === 'autofixable' AND a `replace` function run.
|
|
345
|
+
*/
|
|
346
|
+
function autofix(content) {
|
|
347
|
+
if (typeof content !== 'string') {
|
|
348
|
+
throw new TypeError('autofix(content): expected string');
|
|
349
|
+
}
|
|
350
|
+
if (isOptedOut(content)) return { content, applied: [], optedOut: true };
|
|
351
|
+
let out = content;
|
|
352
|
+
const applied = [];
|
|
353
|
+
for (const rule of RULES) {
|
|
354
|
+
if (rule.severity !== 'autofixable' || typeof rule.replace !== 'function') continue;
|
|
355
|
+
const re = new RegExp(rule.pattern.source, rule.pattern.flags);
|
|
356
|
+
let count = 0;
|
|
357
|
+
out = out.replace(re, (...args) => {
|
|
358
|
+
count++;
|
|
359
|
+
return rule.replace(...args);
|
|
360
|
+
});
|
|
361
|
+
if (count > 0) applied.push({ rule: rule.id, label: rule.label, count });
|
|
362
|
+
}
|
|
363
|
+
return { content: out, applied };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function explain(finding) {
|
|
367
|
+
const sev = finding.severity === 'autofixable'
|
|
368
|
+
? 'AUTOFIX'
|
|
369
|
+
: finding.severity === 'requires-decision'
|
|
370
|
+
? 'REVIEW '
|
|
371
|
+
: 'BLOCK ';
|
|
372
|
+
return `${sev} L${finding.lineNum} ${finding.label} — ${finding.advice}`;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function isClean(findings) {
|
|
376
|
+
return !findings || findings.length === 0;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function summarise(findings) {
|
|
380
|
+
const out = { autofixable: 0, requiresDecision: 0, blocking: 0, total: 0 };
|
|
381
|
+
if (!findings) return out;
|
|
382
|
+
for (const f of findings) {
|
|
383
|
+
out.total++;
|
|
384
|
+
if (f.severity === 'autofixable') out.autofixable++;
|
|
385
|
+
else if (f.severity === 'requires-decision') out.requiresDecision++;
|
|
386
|
+
else if (f.severity === 'block') out.blocking++;
|
|
387
|
+
}
|
|
388
|
+
return out;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
module.exports = {
|
|
392
|
+
scan,
|
|
393
|
+
autofix,
|
|
394
|
+
explain,
|
|
395
|
+
isClean,
|
|
396
|
+
isOptedOut,
|
|
397
|
+
summarise,
|
|
398
|
+
// Exposed for tests / introspection
|
|
399
|
+
_rules: RULES,
|
|
400
|
+
};
|
package/src/utils/git.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
const simpleGit = require('simple-git');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const FRAMEWORK_DIR = '.framework';
|
|
6
|
+
|
|
7
|
+
class GitUtils {
|
|
8
|
+
constructor(cwd = process.cwd()) {
|
|
9
|
+
this.cwd = cwd;
|
|
10
|
+
this.git = simpleGit(cwd);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async isGitRepo() {
|
|
14
|
+
try {
|
|
15
|
+
await this.git.status();
|
|
16
|
+
return true;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async hasCleanWorkingTree() {
|
|
23
|
+
const status = await this.git.status();
|
|
24
|
+
return status.isClean();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async frameworkExists() {
|
|
28
|
+
const frameworkPath = path.join(this.cwd, FRAMEWORK_DIR);
|
|
29
|
+
return fs.existsSync(frameworkPath);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async addSubtree(repo, branch = 'main') {
|
|
33
|
+
const repoUrl = this.normalizeRepoUrl(repo);
|
|
34
|
+
|
|
35
|
+
await this.git.raw([
|
|
36
|
+
'subtree',
|
|
37
|
+
'add',
|
|
38
|
+
'--prefix',
|
|
39
|
+
FRAMEWORK_DIR,
|
|
40
|
+
repoUrl,
|
|
41
|
+
branch,
|
|
42
|
+
'--squash'
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async updateSubtree(repo, branch = 'main') {
|
|
47
|
+
const repoUrl = this.normalizeRepoUrl(repo);
|
|
48
|
+
|
|
49
|
+
await this.git.raw([
|
|
50
|
+
'subtree',
|
|
51
|
+
'pull',
|
|
52
|
+
'--prefix',
|
|
53
|
+
FRAMEWORK_DIR,
|
|
54
|
+
repoUrl,
|
|
55
|
+
branch,
|
|
56
|
+
'--squash'
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async pushSubtree(repo, branch = 'main') {
|
|
61
|
+
const repoUrl = this.normalizeRepoUrl(repo);
|
|
62
|
+
|
|
63
|
+
await this.git.raw([
|
|
64
|
+
'subtree',
|
|
65
|
+
'push',
|
|
66
|
+
'--prefix',
|
|
67
|
+
FRAMEWORK_DIR,
|
|
68
|
+
repoUrl,
|
|
69
|
+
branch
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async getFrameworkVersion() {
|
|
74
|
+
const versionFile = path.join(this.cwd, FRAMEWORK_DIR, 'VERSION');
|
|
75
|
+
if (fs.existsSync(versionFile)) {
|
|
76
|
+
return fs.readFileSync(versionFile, 'utf8').trim();
|
|
77
|
+
}
|
|
78
|
+
return 'unknown';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async createBackupTag() {
|
|
82
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
83
|
+
const tagName = `backup/${timestamp}`;
|
|
84
|
+
await this.git.addTag(tagName);
|
|
85
|
+
return tagName;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getRemoteVersion(repo, branch = 'main') {
|
|
89
|
+
const repoUrl = this.normalizeRepoUrl(repo);
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const versionContent = await this.git.raw([
|
|
93
|
+
'show',
|
|
94
|
+
`${repoUrl}/${branch}:VERSION`
|
|
95
|
+
]);
|
|
96
|
+
return versionContent.trim();
|
|
97
|
+
} catch (error) {
|
|
98
|
+
return 'unknown';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async hasChangesToPush() {
|
|
103
|
+
try {
|
|
104
|
+
const log = await this.git.raw([
|
|
105
|
+
'log',
|
|
106
|
+
'origin/main..HEAD',
|
|
107
|
+
'--oneline',
|
|
108
|
+
'--',
|
|
109
|
+
FRAMEWORK_DIR
|
|
110
|
+
]);
|
|
111
|
+
return log.trim().length > 0;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async getChangesSummary() {
|
|
118
|
+
try {
|
|
119
|
+
const log = await this.git.raw([
|
|
120
|
+
'log',
|
|
121
|
+
'origin/main..HEAD',
|
|
122
|
+
'--oneline',
|
|
123
|
+
'--',
|
|
124
|
+
FRAMEWORK_DIR
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
const stat = await this.git.raw([
|
|
128
|
+
'diff',
|
|
129
|
+
'origin/main..HEAD',
|
|
130
|
+
'--stat',
|
|
131
|
+
'--',
|
|
132
|
+
FRAMEWORK_DIR
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
return { log: log.trim(), stat: stat.trim() };
|
|
136
|
+
} catch (error) {
|
|
137
|
+
return { log: '', stat: '' };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
normalizeRepoUrl(repo) {
|
|
142
|
+
// Handle different formats:
|
|
143
|
+
// - "owner/repo" -> "https://github.com/owner/repo.git"
|
|
144
|
+
// - "https://github.com/owner/repo" -> "https://github.com/owner/repo.git"
|
|
145
|
+
// - "https://github.com/owner/repo.git" -> unchanged
|
|
146
|
+
|
|
147
|
+
if (repo.startsWith('http://') || repo.startsWith('https://')) {
|
|
148
|
+
return repo.endsWith('.git') ? repo : `${repo}.git`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Assume GitHub shorthand
|
|
152
|
+
return `https://github.com/${repo}.git`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async commitChanges(message) {
|
|
156
|
+
await this.git.add(FRAMEWORK_DIR);
|
|
157
|
+
await this.git.commit(message);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async fetch(repo, branch = 'main') {
|
|
161
|
+
const repoUrl = this.normalizeRepoUrl(repo);
|
|
162
|
+
await this.git.fetch(repoUrl, branch);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async diffWithRemote() {
|
|
166
|
+
try {
|
|
167
|
+
const diff = await this.git.raw([
|
|
168
|
+
'diff',
|
|
169
|
+
'HEAD',
|
|
170
|
+
'FETCH_HEAD',
|
|
171
|
+
'--',
|
|
172
|
+
FRAMEWORK_DIR
|
|
173
|
+
]);
|
|
174
|
+
return diff;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
return '';
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = GitUtils;
|